ui: add support for -display spice-app

ui: gtk+sdl bugfixes.
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJcb6ppAAoJEEy22O7T6HE4aeAQALeT8KprIaGqAqem/8xQftGE
 UfZxGIVC9GfVaMG91nPxfCxAEDe9Iyndpmau/l27aA7QrqY+lGsD2Zk1gykCHTd8
 1ElRcn2LXQduFdlTA+r6ibbRkx4LMHCo7KVTvjx+5+SETV/FyDteNWKMKj0K58R8
 f8pkyffOG94LbxukEtmN+IJwtK7xpQhUDGCRNSeBxgFejJVlgquGjII6UcFIBHUT
 xDLgrpfKsVGYCUymIviTGhRC9Ep1fsgtHsv6IHRB+zbUHGncFAjLkBPmCYEc0NFX
 xQNBNQwN12IGbD/BPUyjt/J3BXNUUeBckdnbcLNoA0J+6CgXL6QLaA3RGMidiIwf
 TzY+/464CG/t9YIW6Voh8xyQeqKRiKARkfHuR68avCzuXCMrsOJKlmnszHqmtXTO
 +nVk9vjzCH8LUA0tv8fGrdbu2Ai5Jr0GVknex3eiOPoJSCTIBFyzDO8vOqaBYu8d
 zxBdVXZOpvn4XpoSwtcO9pY+HozeThA3Kv4bTTvnJBwBwjM4vUWHXhnZCFuy+MjU
 tKeAna7nPfztvUQFBD0sBZ0Lj5q5GKQdLwDzTtD4coNoJfaZGRrjpHG/v8uR/bs3
 p3lTO2O9JWj2AMXpEuK7py3IuyNMLV0uFz6rZSKuw+67URdtRb8+/aM34ows892x
 UqlZxU0nUnfVCclaVo6t
 =tArC
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kraxel/tags/ui-20190222-pull-request' into staging

ui: add support for -display spice-app
ui: gtk+sdl bugfixes.

# gpg: Signature made Fri 22 Feb 2019 07:53:13 GMT
# gpg:                using RSA key 4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full]
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>" [full]
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full]
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* remotes/kraxel/tags/ui-20190222-pull-request:
  display: add -display spice-app launching a Spice client
  spice: use a default name for the server
  qapi: document DisplayType enum
  build-sys: add gio-2.0 check
  char: register spice ports after spice started
  char: move SpiceChardev and open_spice_port() to spice.h header
  spice: do not stop spice if VM is paused
  spice: merge options lists
  spice: avoid spice runtime assert
  char/spice: discard write() if backend is disconnected
  char/spice: trigger HUP event
  ui/gtk: Fix the license information
  sdl2: drop qemu_input_event_send_key_qcode call
  spice: set device address and device display ID in QXL interface
  kbd-state: don't block auto-repeat events

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2019-02-25 09:05:41 +00:00
commit 8a4c08b161
15 changed files with 433 additions and 54 deletions

View File

@ -2,30 +2,12 @@
#include "trace.h" #include "trace.h"
#include "ui/qemu-spice.h" #include "ui/qemu-spice.h"
#include "chardev/char.h" #include "chardev/char.h"
#include "chardev/spice.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "qemu/error-report.h" #include "qemu/error-report.h"
#include "qemu/option.h" #include "qemu/option.h"
#include <spice.h>
#include <spice/protocol.h> #include <spice/protocol.h>
typedef struct SpiceChardev {
Chardev parent;
SpiceCharDeviceInstance sin;
bool active;
bool blocked;
const uint8_t *datapos;
int datalen;
QLIST_ENTRY(SpiceChardev) next;
} SpiceChardev;
#define TYPE_CHARDEV_SPICE "chardev-spice"
#define TYPE_CHARDEV_SPICEVMC "chardev-spicevmc"
#define TYPE_CHARDEV_SPICEPORT "chardev-spiceport"
#define SPICE_CHARDEV(obj) OBJECT_CHECK(SpiceChardev, (obj), TYPE_CHARDEV_SPICE)
typedef struct SpiceCharSource { typedef struct SpiceCharSource {
GSource source; GSource source;
SpiceChardev *scd; SpiceChardev *scd;
@ -148,15 +130,25 @@ static void vmc_unregister_interface(SpiceChardev *scd)
static gboolean spice_char_source_prepare(GSource *source, gint *timeout) static gboolean spice_char_source_prepare(GSource *source, gint *timeout)
{ {
SpiceCharSource *src = (SpiceCharSource *)source; SpiceCharSource *src = (SpiceCharSource *)source;
Chardev *chr = CHARDEV(src->scd);
*timeout = -1; *timeout = -1;
if (!chr->be_open) {
return true;
}
return !src->scd->blocked; return !src->scd->blocked;
} }
static gboolean spice_char_source_check(GSource *source) static gboolean spice_char_source_check(GSource *source)
{ {
SpiceCharSource *src = (SpiceCharSource *)source; SpiceCharSource *src = (SpiceCharSource *)source;
Chardev *chr = CHARDEV(src->scd);
if (!chr->be_open) {
return true;
}
return !src->scd->blocked; return !src->scd->blocked;
} }
@ -164,9 +156,12 @@ static gboolean spice_char_source_check(GSource *source)
static gboolean spice_char_source_dispatch(GSource *source, static gboolean spice_char_source_dispatch(GSource *source,
GSourceFunc callback, gpointer user_data) GSourceFunc callback, gpointer user_data)
{ {
SpiceCharSource *src = (SpiceCharSource *)source;
Chardev *chr = CHARDEV(src->scd);
GIOFunc func = (GIOFunc)callback; GIOFunc func = (GIOFunc)callback;
GIOCondition cond = chr->be_open ? G_IO_OUT : G_IO_HUP;
return func(NULL, G_IO_OUT, user_data); return func(NULL, cond, user_data);
} }
static GSourceFuncs SpiceCharSourceFuncs = { static GSourceFuncs SpiceCharSourceFuncs = {
@ -195,6 +190,12 @@ static int spice_chr_write(Chardev *chr, const uint8_t *buf, int len)
int read_bytes; int read_bytes;
assert(s->datalen == 0); assert(s->datalen == 0);
if (!chr->be_open) {
trace_spice_chr_discard_write(len);
return len;
}
s->datapos = buf; s->datapos = buf;
s->datalen = len; s->datalen = len;
spice_server_char_device_wakeup(&s->sin); spice_server_char_device_wakeup(&s->sin);
@ -287,13 +288,19 @@ static void qemu_chr_open_spice_vmc(Chardev *chr,
} }
*be_opened = false; *be_opened = false;
#if SPICE_SERVER_VERSION < 0x000e02
/* Spice < 0.14.2 doesn't explicitly open smartcard chardev */
if (strcmp(type, "smartcard") == 0) {
*be_opened = true;
}
#endif
chr_open(chr, type); chr_open(chr, type);
} }
static void qemu_chr_open_spice_port(Chardev *chr, void qemu_chr_open_spice_port(Chardev *chr,
ChardevBackend *backend, ChardevBackend *backend,
bool *be_opened, bool *be_opened,
Error **errp) Error **errp)
{ {
ChardevSpicePort *spiceport = backend->u.spiceport.data; ChardevSpicePort *spiceport = backend->u.spiceport.data;
const char *name = spiceport->fqdn; const char *name = spiceport->fqdn;
@ -309,6 +316,11 @@ static void qemu_chr_open_spice_port(Chardev *chr,
*be_opened = false; *be_opened = false;
s = SPICE_CHARDEV(chr); s = SPICE_CHARDEV(chr);
s->sin.portname = g_strdup(name); s->sin.portname = g_strdup(name);
if (using_spice) {
/* spice server already created */
vmc_register_interface(s);
}
} }
void qemu_spice_register_ports(void) void qemu_spice_register_ports(void)

View File

@ -10,6 +10,7 @@ wct_cmd_other(const char *cmd) "%s"
wct_speed(int speed) "%d" wct_speed(int speed) "%d"
# chardev/spice.c # chardev/spice.c
spice_chr_discard_write(int len) "spice chr write discarded %d"
spice_vmc_write(ssize_t out, int len) "spice wrote %zd of requested %d" spice_vmc_write(ssize_t out, int len) "spice wrote %zd of requested %d"
spice_vmc_read(int bytes, int len) "spice read %d of requested %d" spice_vmc_read(int bytes, int len) "spice read %d of requested %d"
spice_vmc_register_interface(void *scd) "spice vmc registered interface %p" spice_vmc_register_interface(void *scd) "spice vmc registered interface %p"

13
configure vendored
View File

@ -3503,6 +3503,14 @@ for i in $glib_modules; do
fi fi
done done
if $pkg_config --atleast-version=$glib_req_ver gio-2.0; then
gio=yes
gio_cflags=$($pkg_config --cflags gio-2.0)
gio_libs=$($pkg_config --libs gio-2.0)
else
gio=no
fi
# Sanity check that the current size_t matches the # Sanity check that the current size_t matches the
# size that glib thinks it should be. This catches # size that glib thinks it should be. This catches
# problems on multi-arch where people try to build # problems on multi-arch where people try to build
@ -6520,6 +6528,11 @@ if test "$gtk" = "yes" ; then
echo "CONFIG_GTK_GL=y" >> $config_host_mak echo "CONFIG_GTK_GL=y" >> $config_host_mak
fi fi
fi fi
if test "$gio" = "yes" ; then
echo "CONFIG_GIO=y" >> $config_host_mak
echo "GIO_CFLAGS=$gio_cflags" >> $config_host_mak
echo "GIO_LIBS=$gio_libs" >> $config_host_mak
fi
echo "CONFIG_TLS_PRIORITY=\"$tls_priority\"" >> $config_host_mak echo "CONFIG_TLS_PRIORITY=\"$tls_priority\"" >> $config_host_mak
if test "$gnutls" = "yes" ; then if test "$gnutls" = "yes" ; then
echo "CONFIG_GNUTLS=y" >> $config_host_mak echo "CONFIG_GNUTLS=y" >> $config_host_mak

View File

@ -276,7 +276,8 @@ static void qxl_spice_monitors_config_async(PCIQXLDevice *qxl, int replay)
QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG, QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG,
0)); 0));
} else { } else {
#if SPICE_SERVER_VERSION >= 0x000c06 /* release 0.12.6 */ /* >= release 0.12.6, < release 0.14.2 */
#if SPICE_SERVER_VERSION >= 0x000c06 && SPICE_SERVER_VERSION < 0x000e02
if (qxl->max_outputs) { if (qxl->max_outputs) {
spice_qxl_set_max_monitors(&qxl->ssd.qxl, qxl->max_outputs); spice_qxl_set_max_monitors(&qxl->ssd.qxl, qxl->max_outputs);
} }
@ -2188,6 +2189,17 @@ static void qxl_realize_common(PCIQXLDevice *qxl, Error **errp)
SPICE_INTERFACE_QXL_MAJOR, SPICE_INTERFACE_QXL_MINOR); SPICE_INTERFACE_QXL_MAJOR, SPICE_INTERFACE_QXL_MINOR);
return; return;
} }
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
char device_address[256] = "";
if (qemu_spice_fill_device_address(qxl->vga.con, device_address, 256)) {
spice_qxl_set_device_info(&qxl->ssd.qxl,
device_address,
0,
qxl->max_outputs);
}
#endif
qemu_add_vm_change_state_handler(qxl_vm_change_state_handler, qxl); qemu_add_vm_change_state_handler(qxl_vm_change_state_handler, qxl);
qxl->update_irq = qemu_bh_new(qxl_update_irq_bh, qxl); qxl->update_irq = qemu_bh_new(qxl_update_irq_bh, qxl);

27
include/chardev/spice.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef CHARDEV_SPICE_H_
#define CHARDEV_SPICE_H_
#include <spice.h>
#include "chardev/char-fe.h"
typedef struct SpiceChardev {
Chardev parent;
SpiceCharDeviceInstance sin;
bool active;
bool blocked;
const uint8_t *datapos;
int datalen;
QLIST_ENTRY(SpiceChardev) next;
} SpiceChardev;
#define TYPE_CHARDEV_SPICE "chardev-spice"
#define TYPE_CHARDEV_SPICEVMC "chardev-spicevmc"
#define TYPE_CHARDEV_SPICEPORT "chardev-spiceport"
#define SPICE_CHARDEV(obj) OBJECT_CHECK(SpiceChardev, (obj), TYPE_CHARDEV_SPICE)
void qemu_chr_open_spice_port(Chardev *chr, ChardevBackend *backend,
bool *be_opened, Error **errp);
#endif

View File

@ -179,3 +179,7 @@ void qemu_spice_wakeup(SimpleSpiceDisplay *ssd);
void qemu_spice_display_start(void); void qemu_spice_display_start(void);
void qemu_spice_display_stop(void); void qemu_spice_display_stop(void);
int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd); int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd);
bool qemu_spice_fill_device_address(QemuConsole *con,
char *device_address,
size_t size);

View File

@ -1085,12 +1085,42 @@
# #
# Display (user interface) type. # Display (user interface) type.
# #
# @default: The default user interface, selecting from the first available
# of gtk, sdl, cocoa, and vnc.
#
# @none: No user interface or video output display. The guest will
# still see an emulated graphics card, but its output will not
# be displayed to the QEMU user.
#
# @gtk: The GTK user interface.
#
# @sdl: The SDL user interface.
#
# @egl-headless: No user interface, offload GL operations to a local
# DRI device. Graphical display need to be paired with
# VNC or Spice. (Since 3.1)
#
# @curses: Display video output via curses. For graphics device
# models which support a text mode, QEMU can display this
# output using a curses/ncurses interface. Nothing is
# displayed when the graphics device is in graphical mode or
# if the graphics device does not support a text
# mode. Generally only the VGA device models support text
# mode.
#
# @cocoa: The Cocoa user interface.
#
# @spice-app: Set up a Spice server and run the default associated
# application to connect to it. The server will redirect
# the serial console and QEMU monitors. (Since 4.0)
#
# Since: 2.12 # Since: 2.12
# #
## ##
{ 'enum' : 'DisplayType', { 'enum' : 'DisplayType',
'data' : [ 'default', 'none', 'gtk', 'sdl', 'data' : [ 'default', 'none', 'gtk', 'sdl',
'egl-headless', 'curses', 'cocoa' ] } 'egl-headless', 'curses', 'cocoa',
'spice-app'] }
## ##
# @DisplayOptions: # @DisplayOptions:

View File

@ -1211,6 +1211,7 @@ STEXI
ETEXI ETEXI
DEF("display", HAS_ARG, QEMU_OPTION_display, DEF("display", HAS_ARG, QEMU_OPTION_display,
"-display spice-app[,gl=on|off]\n"
"-display sdl[,frame=on|off][,alt_grab=on|off][,ctrl_grab=on|off]\n" "-display sdl[,frame=on|off][,alt_grab=on|off][,ctrl_grab=on|off]\n"
" [,window_close=on|off][,gl=on|core|es|off]\n" " [,window_close=on|off][,gl=on|core|es|off]\n"
"-display gtk[,grab_on_hover=on|off][,gl=on|off]|\n" "-display gtk[,grab_on_hover=on|off][,gl=on|off]|\n"
@ -1262,6 +1263,10 @@ Start a VNC server on display <arg>
@item egl-headless @item egl-headless
Offload all OpenGL operations to a local DRI device. For any graphical display, Offload all OpenGL operations to a local DRI device. For any graphical display,
this display needs to be paired with either VNC or SPICE displays. this display needs to be paired with either VNC or SPICE displays.
@item spice-app
Start QEMU as a Spice server and launch the default Spice client
application. The Spice server will redirect the serial consoles and
QEMU monitors. (Since 4.0)
@end table @end table
ETEXI ETEXI

View File

@ -49,6 +49,11 @@ curses.mo-objs := curses.o
curses.mo-cflags := $(CURSES_CFLAGS) curses.mo-cflags := $(CURSES_CFLAGS)
curses.mo-libs := $(CURSES_LIBS) curses.mo-libs := $(CURSES_LIBS)
common-obj-$(call land,$(CONFIG_SPICE),$(CONFIG_GIO)) += spice-app.mo
spice-app.mo-objs := spice-app.o
spice-app.mo-cflags := $(GIO_CFLAGS)
spice-app.mo-libs := $(GIO_LIBS)
common-obj-$(CONFIG_OPENGL) += shader.o common-obj-$(CONFIG_OPENGL) += shader.o
common-obj-$(CONFIG_OPENGL) += console-gl.o common-obj-$(CONFIG_OPENGL) += console-gl.o
common-obj-$(CONFIG_OPENGL) += egl-helpers.o common-obj-$(CONFIG_OPENGL) += egl-helpers.o

View File

@ -6,29 +6,25 @@
* Authors: * Authors:
* Anthony Liguori <aliguori@us.ibm.com> * Anthony Liguori <aliguori@us.ibm.com>
* *
* This work is licensed under the terms of the GNU GPL, version 2 or later. * This program is free software; you can redistribute it and/or modify
* See the COPYING file in the top-level directory. * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* *
* Portions from gtk-vnc: * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* Portions from gtk-vnc (originally licensed under the LGPL v2+):
* *
* GTK VNC Widget * GTK VNC Widget
* *
* Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
* Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com> * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#define GETTEXT_PACKAGE "qemu" #define GETTEXT_PACKAGE "qemu"

View File

@ -42,14 +42,18 @@ void qkbd_state_key_event(QKbdState *kbd, QKeyCode qcode, bool down)
{ {
bool state = test_bit(qcode, kbd->keys); bool state = test_bit(qcode, kbd->keys);
if (state == down) { if (down == false /* got key-up event */ &&
state == false /* key is not pressed */) {
/* /*
* Filter out events which don't change the keyboard state. * Filter out suspicious key-up events.
* *
* Most notably this allows to simply send along all key-up * This allows simply sending along all key-up events, and
* events, and this function will filter out everything where * this function will filter out everything where the
* the corresponding key-down event wasn't send to the guest, * corresponding key-down event wasn't sent to the guest, for
* for example due to being a host hotkey. * example due to being a host hotkey.
*
* Note that key-down events on already pressed keys are *not*
* suspicious, those are keyboard autorepeat events.
*/ */
return; return;
} }

View File

@ -54,8 +54,5 @@ void sdl2_process_key(struct sdl2_console *scon,
break; break;
} }
} }
} else {
qemu_input_event_send_key_qcode(con, qcode,
ev->type == SDL_KEYDOWN);
} }
} }

202
ui/spice-app.c Normal file
View File

@ -0,0 +1,202 @@
/*
* QEMU external Spice client display driver
*
* Copyright (c) 2018 Marc-André Lureau <marcandre.lureau@redhat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include <gio/gio.h>
#include "qemu-common.h"
#include "ui/console.h"
#include "qemu/config-file.h"
#include "qemu/option.h"
#include "qemu/cutils.h"
#include "qapi/error.h"
#include "io/channel-command.h"
#include "chardev/spice.h"
#include "sysemu/sysemu.h"
static const char *tmp_dir;
static char *app_dir;
static char *sock_path;
typedef struct VCChardev {
SpiceChardev parent;
} VCChardev;
#define TYPE_CHARDEV_VC "chardev-vc"
#define VC_CHARDEV(obj) OBJECT_CHECK(VCChardev, (obj), TYPE_CHARDEV_VC)
static ChardevBackend *
chr_spice_backend_new(void)
{
ChardevBackend *be = g_new0(ChardevBackend, 1);
be->type = CHARDEV_BACKEND_KIND_SPICEPORT;
be->u.spiceport.data = g_new0(ChardevSpicePort, 1);
return be;
}
static void vc_chr_open(Chardev *chr,
ChardevBackend *backend,
bool *be_opened,
Error **errp)
{
ChardevBackend *be;
const char *fqdn = NULL;
if (strstart(chr->label, "serial", NULL)) {
fqdn = "org.qemu.console.serial.0";
} else if (strstart(chr->label, "parallel", NULL)) {
fqdn = "org.qemu.console.parallel.0";
} else if (strstart(chr->label, "compat_monitor", NULL)) {
fqdn = "org.qemu.monitor.hmp.0";
}
be = chr_spice_backend_new();
be->u.spiceport.data->fqdn = fqdn ?
g_strdup(fqdn) : g_strdup_printf("org.qemu.console.%s", chr->label);
qemu_chr_open_spice_port(chr, be, be_opened, errp);
qapi_free_ChardevBackend(be);
}
static void vc_chr_set_echo(Chardev *chr, bool echo)
{
/* TODO: set echo for frontends QMP and qtest */
}
static void char_vc_class_init(ObjectClass *oc, void *data)
{
ChardevClass *cc = CHARDEV_CLASS(oc);
cc->parse = qemu_chr_parse_vc;
cc->open = vc_chr_open;
cc->chr_set_echo = vc_chr_set_echo;
}
static const TypeInfo char_vc_type_info = {
.name = TYPE_CHARDEV_VC,
.parent = TYPE_CHARDEV_SPICEPORT,
.instance_size = sizeof(VCChardev),
.class_init = char_vc_class_init,
};
static void spice_app_atexit(void)
{
if (sock_path) {
unlink(sock_path);
}
if (tmp_dir) {
rmdir(tmp_dir);
}
g_free(sock_path);
g_free(app_dir);
}
static void spice_app_display_early_init(DisplayOptions *opts)
{
QemuOpts *qopts;
ChardevBackend *be = chr_spice_backend_new();
GError *err = NULL;
if (opts->has_full_screen) {
error_report("spice-app full-screen isn't supported yet.");
exit(1);
}
if (opts->has_window_close) {
error_report("spice-app window-close isn't supported yet.");
exit(1);
}
atexit(spice_app_atexit);
if (qemu_name) {
app_dir = g_build_filename(g_get_user_runtime_dir(),
"qemu", qemu_name, NULL);
if (g_mkdir_with_parents(app_dir, S_IRWXU) < -1) {
error_report("Failed to create directory %s: %s",
app_dir, strerror(errno));
exit(1);
}
} else {
app_dir = g_dir_make_tmp(NULL, &err);
tmp_dir = app_dir;
if (err) {
error_report("Failed to create temporary directory: %s",
err->message);
exit(1);
}
}
type_register(&char_vc_type_info);
sock_path = g_strjoin("", app_dir, "/", "spice.sock", NULL);
qopts = qemu_opts_create(qemu_find_opts("spice"), NULL, 0, &error_abort);
qemu_opt_set(qopts, "disable-ticketing", "on", &error_abort);
qemu_opt_set(qopts, "unix", "on", &error_abort);
qemu_opt_set(qopts, "addr", sock_path, &error_abort);
qemu_opt_set(qopts, "image-compression", "off", &error_abort);
qemu_opt_set(qopts, "streaming-video", "off", &error_abort);
qemu_opt_set(qopts, "gl", opts->has_gl ? "on" : "off", &error_abort);
display_opengl = opts->has_gl;
be->u.spiceport.data->fqdn = g_strdup("org.qemu.monitor.qmp.0");
qemu_chardev_new("org.qemu.monitor.qmp", TYPE_CHARDEV_SPICEPORT,
be, NULL, &error_abort);
qopts = qemu_opts_create(qemu_find_opts("mon"),
NULL, 0, &error_fatal);
qemu_opt_set(qopts, "chardev", "org.qemu.monitor.qmp", &error_abort);
qemu_opt_set(qopts, "mode", "control", &error_abort);
qapi_free_ChardevBackend(be);
}
static void spice_app_display_init(DisplayState *ds, DisplayOptions *opts)
{
GError *err = NULL;
gchar *uri;
uri = g_strjoin("", "spice+unix://", app_dir, "/", "spice.sock", NULL);
info_report("Launching display with URI: %s", uri);
g_app_info_launch_default_for_uri(uri, NULL, &err);
if (err) {
error_report("Failed to launch %s URI: %s", uri, err->message);
error_report("You need a capable Spice client, "
"such as virt-viewer 8.0");
exit(1);
}
g_free(uri);
}
static QemuDisplay qemu_display_spice_app = {
.type = DISPLAY_TYPE_SPICE_APP,
.early_init = spice_app_display_early_init,
.init = spice_app_display_init,
};
static void register_spice_app(void)
{
qemu_display_register(&qemu_display_spice_app);
}
type_init(register_spice_app);

View File

@ -34,6 +34,7 @@
#include "qemu/option.h" #include "qemu/option.h"
#include "migration/misc.h" #include "migration/misc.h"
#include "hw/hw.h" #include "hw/hw.h"
#include "hw/pci/pci_bus.h"
#include "ui/spice-display.h" #include "ui/spice-display.h"
/* core bits */ /* core bits */
@ -397,6 +398,7 @@ static SpiceChannelList *qmp_query_spice_channels(void)
static QemuOptsList qemu_spice_opts = { static QemuOptsList qemu_spice_opts = {
.name = "spice", .name = "spice",
.head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head), .head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head),
.merge_lists = true,
.desc = { .desc = {
{ {
.name = "port", .name = "port",
@ -626,7 +628,7 @@ static void vm_change_state_handler(void *opaque, int running,
{ {
if (running) { if (running) {
qemu_spice_display_start(); qemu_spice_display_start();
} else { } else if (state != RUN_STATE_PAUSED) {
qemu_spice_display_stop(); qemu_spice_display_stop();
} }
} }
@ -783,7 +785,7 @@ void qemu_spice_init(void)
qemu_opt_foreach(opts, add_channel, &tls_port, &error_fatal); qemu_opt_foreach(opts, add_channel, &tls_port, &error_fatal);
spice_server_set_name(spice_server, qemu_name); spice_server_set_name(spice_server, qemu_name ?: "QEMU " QEMU_VERSION);
spice_server_set_uuid(spice_server, (unsigned char *)&qemu_uuid); spice_server_set_uuid(spice_server, (unsigned char *)&qemu_uuid);
seamless_migration = qemu_opt_get_bool(opts, "seamless-migration", 0); seamless_migration = qemu_opt_get_bool(opts, "seamless-migration", 0);
@ -863,6 +865,56 @@ bool qemu_spice_have_display_interface(QemuConsole *con)
return false; return false;
} }
/*
* Recursively (in reverse order) appends addresses of PCI devices as it moves
* up in the PCI hierarchy.
*
* @returns true on success, false when the buffer wasn't large enough
*/
static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
{
PCIBus *bus = pci_get_bus(pci);
/*
* equivalent to if (!pci_bus_is_root(bus)), but the function is not built
* with PCI_CONFIG=n, avoid using an #ifdef by checking directly
*/
if (bus->parent_dev != NULL) {
append_pci_address(buf, buf_size, bus->parent_dev);
}
size_t len = strlen(buf);
ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
return written > 0 && written < buf_size - len;
}
bool qemu_spice_fill_device_address(QemuConsole *con,
char *device_address,
size_t size)
{
DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
"device",
&error_abort));
PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
TYPE_PCI_DEVICE);
if (pci == NULL) {
warn_report("Setting device address of a display device to SPICE: "
"Not a PCI device.");
return false;
}
strncpy(device_address, "pci/0000", size);
if (!append_pci_address(device_address, size, pci)) {
warn_report("Setting device address of a display device to SPICE: "
"Too many PCI devices in the chain.");
return false;
}
return true;
}
int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con) int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con)
{ {
if (g_slist_find(spice_consoles, con)) { if (g_slist_find(spice_consoles, con)) {
@ -921,12 +973,20 @@ int qemu_spice_display_add_client(int csock, int skipauth, int tls)
void qemu_spice_display_start(void) void qemu_spice_display_start(void)
{ {
if (spice_display_is_running) {
return;
}
spice_display_is_running = true; spice_display_is_running = true;
spice_server_vm_start(spice_server); spice_server_vm_start(spice_server);
} }
void qemu_spice_display_stop(void) void qemu_spice_display_stop(void)
{ {
if (!spice_display_is_running) {
return;
}
spice_server_vm_stop(spice_server); spice_server_vm_stop(spice_server);
spice_display_is_running = false; spice_display_is_running = false;
} }

View File

@ -1147,6 +1147,17 @@ static void qemu_spice_display_init_one(QemuConsole *con)
ssd->qxl.base.sif = &dpy_interface.base; ssd->qxl.base.sif = &dpy_interface.base;
qemu_spice_add_display_interface(&ssd->qxl, con); qemu_spice_add_display_interface(&ssd->qxl, con);
#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
char device_address[256] = "";
if (qemu_spice_fill_device_address(con, device_address, 256)) {
spice_qxl_set_device_info(&ssd->qxl,
device_address,
qemu_console_get_head(con),
1);
}
#endif
qemu_spice_create_host_memslot(ssd); qemu_spice_create_host_memslot(ssd);
register_displaychangelistener(&ssd->dcl); register_displaychangelistener(&ssd->dcl);