Hi On Mon, Sep 3, 2018 at 8:53 AM, Victor Toso <victortoso@xxxxxxxxxx> wrote: > Hi, > > On Tue, Jul 31, 2018 at 03:41:25PM +0200, marcandre.lureau@xxxxxxxxxx wrote: >> From: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> >> >> If the "org.qemu.monitor.qmp.0" port is available: >> - enable the VM UI >> - get and follow the VM state >> - send the requested VM actions >> >> This adds a json-glib dependency on spice-gtk display. If necessary, >> we could make it optional. > > Having some trouble playing with this. Considering that the > patches that I'm looking at [0] and this series diverge a bit, > would you mind sending another version with some info on the > cover letter in order to have this properly configured to test? > > [0] https://pagure.io/fork/elmarco/virt-viewer/branch/qemu-ui > > I've configured my fedora 28 vm running on QEMU Session to > consider org.qemu.console.serial.0 and org.qemu.monitor.qmp.0 as > spice ports (like webdav) eg: > > <channel type='spiceport'> > <source channel='org.qemu.monitor.qmp.0'/> > <target type='virtio' name='org.qemu.monitor.qmp.0'/> > <address type='virtio-serial' controller='0' bus='0' port='4'/> > </channel> > > I see the debug of those channels in spice-gtk but nothing on > virt-viewer. I'm thinking that some other configuration or > initialization steps are needed to interact with monitor/serial. > Try with my qemu branch https://github.com/elmarco/qemu spice If you start with -display app (with remote-viewer installed in your prefix, and mime db correctly updated to deal with spice+unix://), it will do the serial & monitor setup for you. > Let me know if you need more info from my side. > > Also, feel free to push the acked patches as they don't overlap > with these, I think. > > Cheers, > >> Signed-off-by: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> >> --- >> configure.ac | 8 +- >> src/Makefile.am | 2 + >> src/virt-viewer-session-spice.c | 166 ++++++++++++++++++++++++++++++++ >> 3 files changed, 175 insertions(+), 1 deletion(-) >> >> diff --git a/configure.ac b/configure.ac >> index 3c7522d..c5aa0dc 100644 >> --- a/configure.ac >> +++ b/configure.ac >> @@ -27,6 +27,7 @@ GTK_VNC_REQUIRED="0.4.0" >> SPICE_GTK_REQUIRED="0.35" >> SPICE_PROTOCOL_REQUIRED="0.12.7" >> GOVIRT_REQUIRED="0.3.2" >> +JSON_GLIB_REQUIRED="1.0" >> >> AC_SUBST([GLIB2_REQUIRED]) >> AC_SUBST([LIBXML2_REQUIRED]) >> @@ -37,6 +38,7 @@ AC_SUBST([GTK_VNC_REQUIRED]) >> AC_SUBST([SPICE_GTK_REQUIRED]) >> AC_SUBST([SPICE_PROTOCOL_REQUIRED]) >> AC_SUBST([GOVIRT_REQUIRED]) >> +AC_SUBST([JSON_GLIB_REQUIRED]) >> >> AC_MSG_CHECKING([for native Win32]) >> case "$host_os" in >> @@ -159,13 +161,15 @@ AC_ARG_WITH([spice-gtk], >> AS_IF([test "x$with_spice_gtk" != "xno" && test "x$with_spice_gtk" != "xyes"], >> [PKG_CHECK_EXISTS([spice-client-gtk-3.0 >= $SPICE_GTK_REQUIRED >> spice-client-glib-2.0 >= $SPICE_GTK_REQUIRED >> - spice-protocol >= $SPICE_PROTOCOL_REQUIRED], >> + spice-protocol >= $SPICE_PROTOCOL_REQUIRED >> + json-glib-1.0 >= $JSON_GLIB_REQUIRED], >> [with_spice_gtk=yes], [with_spice_gtk=no])]) >> >> AS_IF([test "x$with_spice_gtk" = "xyes"], >> [PKG_CHECK_MODULES(SPICE_GTK, [spice-client-gtk-3.0 >= $SPICE_GTK_REQUIRED >> spice-client-glib-2.0 >= $SPICE_GTK_REQUIRED])] >> [PKG_CHECK_MODULES(SPICE_PROTOCOL, [spice-protocol >= $SPICE_PROTOCOL_REQUIRED])] >> + [PKG_CHECK_MODULES(JSON_GLIB, [json-glib-1.0 >= JSON_GLIB_REQUIRED])] >> [AC_DEFINE([HAVE_SPICE_GTK], 1, [Have spice-gtk?])] >> ) >> AM_CONDITIONAL([HAVE_SPICE_GTK], [test "x$with_spice_gtk" = "xyes"]) >> @@ -294,3 +298,5 @@ AC_MSG_NOTICE([ LIBVIRT: $LIBVIRT_CFLAGS $LIBVIRT_LIBS]) >> AC_MSG_NOTICE([]) >> AC_MSG_NOTICE([ OVIRT: $OVIRT_CFLAGS $OVIRT_LIBS]) >> AC_MSG_NOTICE([]) >> +AC_MSG_NOTICE([ JSON_GLIB: $JSON_GLIB_CFLAGS $JSON_GLIB_LIBS]) >> +AC_MSG_NOTICE([]) >> diff --git a/src/Makefile.am b/src/Makefile.am >> index 3a5d90d..b2dc786 100644 >> --- a/src/Makefile.am >> +++ b/src/Makefile.am >> @@ -114,6 +114,7 @@ COMMON_LIBS = \ >> $(GTK_VNC_LIBS) \ >> $(VTE_LIBS) \ >> $(SPICE_GTK_LIBS) \ >> + $(JSON_GLIB_LIBS) \ >> $(LIBXML2_LIBS) \ >> $(OVIRT_LIBS) \ >> $(NULL) >> @@ -125,6 +126,7 @@ COMMON_CFLAGS = \ >> $(GTK_CFLAGS) \ >> $(GTK_VNC_CFLAGS) \ >> $(VTE_CFLAGS) \ >> + $(JSON_GLIB_CFLAGS) \ >> $(SPICE_GTK_CFLAGS) \ >> $(LIBXML2_CFLAGS) \ >> $(OVIRT_CFLAGS) \ >> diff --git a/src/virt-viewer-session-spice.c b/src/virt-viewer-session-spice.c >> index 1c622e6..39aae1d 100644 >> --- a/src/virt-viewer-session-spice.c >> +++ b/src/virt-viewer-session-spice.c >> @@ -27,6 +27,7 @@ >> #include <glib/gi18n.h> >> >> #include <spice-client-gtk.h> >> +#include <json-glib/json-glib.h> >> >> #include <usb-device-widget.h> >> #include "virt-viewer-file.h" >> @@ -39,6 +40,8 @@ >> >> G_DEFINE_TYPE (VirtViewerSessionSpice, virt_viewer_session_spice, VIRT_VIEWER_TYPE_SESSION) >> >> +typedef void (QMPCb)(VirtViewerSessionSpice *self, JsonNode *node); >> + >> struct _VirtViewerSessionSpicePrivate { >> GtkWindow *main_window; >> SpiceSession *session; >> @@ -51,7 +54,11 @@ struct _VirtViewerSessionSpicePrivate { >> guint pass_try; >> gboolean did_auto_conf; >> VirtViewerFileTransferDialog *file_transfer_dialog; >> + SpicePortChannel *qmp; >> >> + GString *qmp_data; >> + JsonParser *qmp_parser; >> + GHashTable *qmp_cb; >> }; >> >> #define VIRT_VIEWER_SESSION_SPICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), VIRT_VIEWER_TYPE_SESSION_SPICE, VirtViewerSessionSpicePrivate)) >> @@ -80,6 +87,7 @@ static void virt_viewer_session_spice_smartcard_insert(VirtViewerSession *sessio >> static void virt_viewer_session_spice_smartcard_remove(VirtViewerSession *session); >> static gboolean virt_viewer_session_spice_fullscreen_auto_conf(VirtViewerSessionSpice *self); >> static void virt_viewer_session_spice_apply_monitor_geometry(VirtViewerSession *self, GHashTable *monitors); >> +static void virt_viewer_session_spice_vm_action(VirtViewerSession *self, gint action); >> >> static void virt_viewer_session_spice_clear_displays(VirtViewerSessionSpice *self) >> { >> @@ -236,6 +244,16 @@ virt_viewer_session_spice_constructed(GObject *obj) >> G_OBJECT_CLASS(virt_viewer_session_spice_parent_class)->constructed(obj); >> } >> >> +static void >> +virt_viewer_session_spice_finalize(GObject *obj) >> +{ >> + VirtViewerSessionSpice *self = VIRT_VIEWER_SESSION_SPICE(obj); >> + >> + g_string_free(self->priv->qmp_data, TRUE); >> + g_object_unref(self->priv->qmp_parser); >> + g_hash_table_unref(self->priv->qmp_cb); >> +} >> + >> static void >> virt_viewer_session_spice_class_init(VirtViewerSessionSpiceClass *klass) >> { >> @@ -246,6 +264,7 @@ virt_viewer_session_spice_class_init(VirtViewerSessionSpiceClass *klass) >> oclass->set_property = virt_viewer_session_spice_set_property; >> oclass->dispose = virt_viewer_session_spice_dispose; >> oclass->constructed = virt_viewer_session_spice_constructed; >> + oclass->finalize = virt_viewer_session_spice_finalize; >> >> dclass->close = virt_viewer_session_spice_close; >> dclass->open_fd = virt_viewer_session_spice_open_fd; >> @@ -259,6 +278,7 @@ virt_viewer_session_spice_class_init(VirtViewerSessionSpiceClass *klass) >> dclass->apply_monitor_geometry = virt_viewer_session_spice_apply_monitor_geometry; >> dclass->can_share_folder = virt_viewer_session_spice_can_share_folder; >> dclass->can_retry_auth = virt_viewer_session_spice_can_retry_auth; >> + dclass->vm_action = virt_viewer_session_spice_vm_action; >> >> g_type_class_add_private(klass, sizeof(VirtViewerSessionSpicePrivate)); >> >> @@ -287,6 +307,9 @@ static void >> virt_viewer_session_spice_init(VirtViewerSessionSpice *self G_GNUC_UNUSED) >> { >> self->priv = VIRT_VIEWER_SESSION_SPICE_GET_PRIVATE(self); >> + self->priv->qmp_data = g_string_sized_new(256); >> + self->priv->qmp_parser = json_parser_new(); >> + self->priv->qmp_cb = g_hash_table_new(g_direct_hash, g_direct_equal); >> } >> >> static void >> @@ -998,6 +1021,134 @@ port_name_to_vte_name(const char *name) >> return NULL; >> } >> >> +static void >> +spice_qmp_data(VirtViewerSessionSpice *self, gpointer data, >> + int size G_GNUC_UNUSED, >> + SpicePortChannel *port G_GNUC_UNUSED) >> +{ >> + GString *qmp = self->priv->qmp_data; >> + gchar *str, *crlf; >> + >> + g_string_append_len(qmp, data, size); >> + >> + str = qmp->str; >> + while ((crlf = memmem(str, qmp->len - (str - qmp->str), "\r\n", 2))) { >> + GError *err = NULL; >> + >> + *crlf = '\0'; >> + json_parser_load_from_data(self->priv->qmp_parser, str, crlf - str, &err); >> + if (err) { >> + g_warning("JSON parsing error: %s", err->message); >> + g_error_free(err); >> + } else { >> + JsonObject *obj = json_node_get_object(json_parser_get_root(self->priv->qmp_parser)); >> + JsonNode *node; >> + const gchar *event; >> + >> + if (json_object_get_member(obj, "QMP")) { >> + g_debug("QMP greeting received"); >> + } else if ((node = json_object_get_member(obj, "return"))) { >> + gint id = json_object_get_int_member(obj, "id"); >> + QMPCb *cb; >> + >> + g_debug("QMP return id:%d", id); >> + if ((cb = g_hash_table_lookup(self->priv->qmp_cb, GINT_TO_POINTER(id)))) { >> + g_hash_table_remove(self->priv->qmp_cb, GINT_TO_POINTER(id)); >> + cb(self, node); >> + } >> + } else if ((event = json_object_get_string_member(obj, "event"))) { >> + g_debug("%s", event); >> + if (g_str_equal(event, "STOP")) { >> + g_object_set(virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self)), >> + "vm-running", FALSE, NULL); >> + } else if (g_str_equal(event, "RESUME")) { >> + g_object_set(virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self)), >> + "vm-running", TRUE, NULL); >> + } else { >> + g_debug("unhandled QMP event: %s", event); >> + } >> + } else { >> + g_debug("unhandled JSON %s", str); >> + } >> + } >> + str = crlf + 2; >> + } >> + >> + g_string_erase(qmp, 0, str - qmp->str); >> +} >> + >> +static void >> +qmp_query_status_cb(VirtViewerSessionSpice *self, JsonNode *node) >> +{ >> + const char *status = json_object_get_string_member(json_node_get_object(node), "status"); >> + gboolean running = TRUE; >> + >> + if (g_str_equal(status, "paused")) { >> + running = FALSE; >> + } >> + >> + g_object_set(virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self)), >> + "vm-running", running, NULL); >> +} >> + >> +static void >> +qmp(VirtViewerSessionSpice *self, >> + const char *cmd, const gchar *args, QMPCb *cb) >> +{ >> + GString *str = g_string_sized_new(256); >> + gsize len; >> + gchar *data; >> + static gint id = 0; >> + >> + if (!self->priv->qmp) >> + return; >> + >> + g_string_append_printf(str, "{ 'execute': '%s'", cmd); >> + if (args) >> + g_string_append_printf(str, ", 'arguments': { %s }", args); >> + g_string_append_printf(str, ", 'id': %d", id); >> + g_string_append(str, " }"); >> + >> + if (cb) >> + g_hash_table_insert(self->priv->qmp_cb, GINT_TO_POINTER(id), cb); >> + >> + id++; >> + len = str->len; >> + data = g_string_free(str, FALSE); >> + spice_port_channel_write_async(self->priv->qmp, data, len, >> + NULL, spice_port_write_finished, >> + data); >> +} >> + >> +static void >> +virt_viewer_session_spice_vm_action(VirtViewerSession *sess, gint action) >> +{ >> + VirtViewerSessionSpice *self = VIRT_VIEWER_SESSION_SPICE(sess); >> + const gchar *cmd; >> + >> + switch (action) { >> + case VIRT_VIEWER_SESSION_VM_ACTION_QUIT: >> + cmd = "quit"; >> + break; >> + case VIRT_VIEWER_SESSION_VM_ACTION_RESET: >> + cmd = "system_reset"; >> + break; >> + case VIRT_VIEWER_SESSION_VM_ACTION_POWER_DOWN: >> + cmd = "system_powerdown"; >> + break; >> + case VIRT_VIEWER_SESSION_VM_ACTION_PAUSE: >> + cmd = "stop"; >> + break; >> + case VIRT_VIEWER_SESSION_VM_ACTION_CONTINUE: >> + cmd = "cont"; >> + break; >> + default: >> + g_return_if_reached(); >> + } >> + >> + qmp(self, cmd, NULL, NULL); >> +} >> + >> static void >> spice_port_opened(SpiceChannel *channel, GParamSpec *pspec G_GNUC_UNUSED, >> VirtViewerSessionSpice *self) >> @@ -1018,6 +1169,21 @@ spice_port_opened(SpiceChannel *channel, GParamSpec *pspec G_GNUC_UNUSED, >> g_return_if_fail(name != NULL); >> g_debug("port#%d %s: %s", id, name, opened ? "opened" : "closed"); >> >> + if (g_str_equal(name, "org.qemu.monitor.qmp.0") && opened) { >> + g_return_if_fail(!self->priv->qmp); >> + >> + g_object_set(virt_viewer_session_get_app(VIRT_VIEWER_SESSION(self)), >> + "vm-ui", TRUE, NULL); >> + >> + self->priv->qmp = port; >> + qmp(self, "qmp_capabilities", NULL, NULL); >> + qmp(self, "query-status", NULL, qmp_query_status_cb); >> + >> + virt_viewer_signal_connect_object(port, "port-data", >> + G_CALLBACK(spice_qmp_data), self, G_CONNECT_SWAPPED); >> + goto end; >> + } >> + >> vte = g_object_get_data(G_OBJECT(port), "virt-viewer-vte"); >> if (vte) { >> if (opened) >> -- >> 2.18.0.321.gffc6fa0e39 >> >> _______________________________________________ >> virt-tools-list mailing list >> virt-tools-list@xxxxxxxxxx >> https://www.redhat.com/mailman/listinfo/virt-tools-list _______________________________________________ virt-tools-list mailing list virt-tools-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/virt-tools-list