[Spice-devel] [PATCH spice-gtk v3] glib: add SpiceQmpPort helper
Victor Toso
victortoso at redhat.com
Fri Sep 21 18:55:47 UTC 2018
On Fri, Sep 21, 2018 at 02:34:58PM +0400, marcandre.lureau at redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau at redhat.com>
>
> Add a few helper functions to deal with a QMP port channel, in order
> to ease json handling, and wrapping a few commands.
>
> (by convention, the port should have the name
> "org.qemu.monitor.qmp.0", but it's not strictly required)
>
> This helper is put into use in the virt-viewer "Add QEMU-like UI: VT
> console & basic VM status" series.
>
> Note: this adds a strong dependency on json-glib for
> spice-client-glib, a widely available and fairly small
> library.
>
> QMP specification is:
> https://git.qemu.org/?p=qemu.git;a=blob;f=docs/interop/qmp-spec.txt
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau at redhat.com>
> ---
>
> v3:
> - change QMP greeting handling
> - fix a refcount/bug leak
Acked-by: Victor Toso <victortoso at redhat.com>
>
>
> configure.ac | 2 +
> doc/reference/spice-gtk-docs.xml | 1 +
> doc/reference/spice-gtk-sections.txt | 27 ++
> meson.build | 2 +
> src/Makefile.am | 6 +
> src/map-file | 9 +
> src/meson.build | 4 +
> src/qmp-port.c | 579 +++++++++++++++++++++++++++
> src/qmp-port.h | 122 ++++++
> src/spice-client.h | 1 +
> src/spice-glib-sym-file | 9 +
> 11 files changed, 762 insertions(+)
> create mode 100644 src/qmp-port.c
> create mode 100644 src/qmp-port.h
>
> diff --git a/configure.ac b/configure.ac
> index 7b32e29..e686d76 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -173,6 +173,8 @@ PKG_CHECK_MODULES(CAIRO, cairo >= 1.2.0)
>
> PKG_CHECK_MODULES(GTHREAD, gthread-2.0)
>
> +PKG_CHECK_MODULES(JSON, json-glib-1.0)
> +
> AC_ARG_ENABLE([webdav],
> AS_HELP_STRING([--enable-webdav=@<:@auto/yes/no@:>@],
> [Enable webdav support @<:@default=auto@:>@]),
> diff --git a/doc/reference/spice-gtk-docs.xml b/doc/reference/spice-gtk-docs.xml
> index db5dd3d..696c375 100644
> --- a/doc/reference/spice-gtk-docs.xml
> +++ b/doc/reference/spice-gtk-docs.xml
> @@ -52,6 +52,7 @@
> <xi:include href="xml/spice-audio.xml"/>
> <xi:include href="xml/smartcard-manager.xml"/>
> <xi:include href="xml/usb-device-manager.xml"/>
> + <xi:include href="xml/qmp-port.xml"/>
> <xi:include href="xml/spice-util.xml"/>
> <xi:include href="xml/spice-version.xml"/>
> <xi:include href="xml/spice-uri.xml"/>
> diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
> index a85df7a..a0336aa 100644
> --- a/doc/reference/spice-gtk-sections.txt
> +++ b/doc/reference/spice-gtk-sections.txt
> @@ -494,6 +494,33 @@ SPICE_PORT_CHANNEL_GET_CLASS
> SpicePortChannelPrivate
> </SECTION>
>
> +<SECTION>
> +<FILE>qmp-port</FILE>
> +<TITLE>SpiceQmpPort</TITLE>
> +
> +<SUBSECTION>
> +SpiceQmpPort
> +SpiceQmpPortVmAction
> +SpiceQmpStatus
> +<SUBSECTION>
> +spice_qmp_port_get
> +spice_qmp_port_vm_action_async
> +spice_qmp_port_vm_action_finish
> +spice_qmp_port_query_status_async
> +spice_qmp_port_query_status_finish
> +spice_qmp_status_ref
> +spice_qmp_status_unref
> +<SUBSECTION Standard>
> +SPICE_IS_QMP_PORT
> +SPICE_IS_QMP_PORT_CLASS
> +SPICE_QMP_PORT
> +SPICE_QMP_PORT_CLASS
> +SPICE_QMP_PORT_GET_CLASS
> +SPICE_TYPE_QMP_PORT
> +spice_qmp_port_get_type
> +spice_qmp_status_get_type
> +</SECTION>
> +
> <SECTION>
> <FILE>spice-uri</FILE>
> spice_uri_get_scheme
> diff --git a/meson.build b/meson.build
> index 3c20401..875e07f 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -98,6 +98,8 @@ foreach dep, version : deps
> spice_gtk_deps += dependency(dep, version : version)
> endforeach
>
> +spice_gtk_deps += dependency('json-glib-1.0')
> +
> # TODO: specify minimum version for cairo, jpeg and zlib?
> deps = ['cairo', 'libjpeg', 'zlib']
> if spice_gtk_host_system == 'windows'
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 4dd657d..1bb6f9b 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -78,6 +78,7 @@ SPICE_COMMON_CPPFLAGS = \
> $(GLIB2_CFLAGS) \
> $(GIO_CFLAGS) \
> $(GOBJECT2_CFLAGS) \
> + $(JSON_CFLAGS) \
> $(SSL_CFLAGS) \
> $(SASL_CFLAGS) \
> $(GSTAUDIO_CFLAGS) \
> @@ -182,6 +183,7 @@ libspice_client_glib_2_0_la_LIBADD = \
> $(GIO_LIBS) \
> $(GOBJECT2_LIBS) \
> $(JPEG_LIBS) \
> + $(JSON_LIBS) \
> $(Z_LIBS) \
> $(LZ4_LIBS) \
> $(PIXMAN_LIBS) \
> @@ -243,6 +245,8 @@ libspice_client_glib_2_0_la_SOURCES = \
> channel-smartcard.c \
> channel-usbredir.c \
> channel-usbredir-priv.h \
> + qmp-port.c \
> + qmp-port.h \
> smartcard-manager.c \
> smartcard-manager-priv.h \
> spice-uri.c \
> @@ -293,6 +297,7 @@ libspice_client_glibinclude_HEADERS = \
> channel-smartcard.h \
> channel-usbredir.h \
> channel-webdav.h \
> + qmp-port.h \
> usb-device-manager.h \
> smartcard-manager.h \
> spice-file-transfer-task.h \
> @@ -526,6 +531,7 @@ glib_introspection_files = \
> channel-record.c \
> channel-smartcard.c \
> channel-usbredir.c \
> + qmp-port.c \
> smartcard-manager.c \
> usb-device-manager.c \
> $(NULL)
> diff --git a/src/map-file b/src/map-file
> index cdb81c3..500683c 100644
> --- a/src/map-file
> +++ b/src/map-file
> @@ -117,6 +117,15 @@ spice_port_channel_write_finish;
> spice_port_event;
> spice_port_write_async;
> spice_port_write_finish;
> +spice_qmp_port_get;
> +spice_qmp_port_get_type;
> +spice_qmp_port_query_status_async;
> +spice_qmp_port_query_status_finish;
> +spice_qmp_port_vm_action_async;
> +spice_qmp_port_vm_action_finish;
> +spice_qmp_status_get_type;
> +spice_qmp_status_ref;
> +spice_qmp_status_unref;
> spice_record_channel_get_type;
> spice_record_channel_send_data;
> spice_record_send_data;
> diff --git a/src/meson.build b/src/meson.build
> index 8c9199e..80e777d 100644
> --- a/src/meson.build
> +++ b/src/meson.build
> @@ -32,6 +32,7 @@ spice_client_glib_headers = [
> 'channel-smartcard.h',
> 'channel-usbredir.h',
> 'channel-webdav.h',
> + 'qmp-port.h',
> 'smartcard-manager.h',
> 'spice-audio.h',
> 'spice-channel.h',
> @@ -70,6 +71,7 @@ spice_client_glib_introspection_sources = [
> 'channel-smartcard.c',
> 'channel-usbredir.c',
> 'channel-webdav.c',
> + 'qmp-port.c',
> 'smartcard-manager.c',
> 'spice-audio.c',
> 'spice-channel.c',
> @@ -98,6 +100,8 @@ spice_client_glib_sources = [
> 'decode-zlib.c',
> 'gio-coroutine.c',
> 'gio-coroutine.h',
> + 'qmp-port.c',
> + 'qmp-port.h',
> 'smartcard-manager-priv.h',
> 'spice-audio-priv.h',
> 'spice-channel-cache.h',
> diff --git a/src/qmp-port.c b/src/qmp-port.c
> new file mode 100644
> index 0000000..c32bdc4
> --- /dev/null
> +++ b/src/qmp-port.c
> @@ -0,0 +1,579 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2018 Red Hat, Inc.
> +
> + 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.1 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, see <http://www.gnu.org/licenses/>.
> +*/
> +#include "config.h"
> +
> +#define _GNU_SOURCE
> +#include <string.h>
> +#include <json-glib/json-glib.h>
> +#include "spice-client.h"
> +
> +/**
> + * SECTION:qmp-port
> + * @short_description: QMP port helper
> + * @title: QMP port channel helper
> + * @section_id:
> + * @see_also: #SpicePortChannel
> + * @stability: Stable
> + * @include: spice-client.h
> + *
> + * A helper to handle QMP messages over a %SpicePortChannel.
> + *
> + * Since: 0.36
> + */
> +
> +typedef struct _SpiceQmpPortPrivate SpiceQmpPortPrivate;
> +
> +struct _SpiceQmpPortPrivate
> +{
> + SpicePortChannel *channel;
> + gboolean ready;
> +
> + gint id_counter;
> + GString *qmp_data;
> + JsonParser *qmp_parser;
> + GHashTable *qmp_tasks;
> +};
> +
> +struct _SpiceQmpPort
> +{
> + GObject parent;
> +
> + SpiceQmpPortPrivate *priv;
> +};
> +
> +struct _SpiceQmpPortClass
> +{
> + GObjectClass parent_class;
> +};
> +
> +enum {
> + PROP_CHANNEL = 1,
> + PROP_READY,
> +
> + PROP_LAST,
> +};
> +
> +enum {
> + SIGNAL_EVENT,
> +
> + SIGNAL_LAST,
> +};
> +
> +static guint signals[SIGNAL_LAST];
> +static GParamSpec *props[PROP_LAST] = { NULL, };
> +
> +G_DEFINE_TYPE_WITH_PRIVATE(SpiceQmpPort, spice_qmp_port, G_TYPE_OBJECT)
> +
> +G_DEFINE_BOXED_TYPE(SpiceQmpStatus, spice_qmp_status, spice_qmp_status_ref, spice_qmp_status_unref)
> +
> +typedef void (QMPCb)(GTask *task, JsonNode *node);
> +
> +static void
> +qmp_error_return(GTask *task, const gchar *desc)
> +{
> + g_task_return_new_error(task, SPICE_CLIENT_ERROR,
> + SPICE_CLIENT_ERROR_FAILED, "%s", desc);
> + g_object_unref(task);
> +}
> +
> +static gboolean
> +spice_qmp_dispatch_message(SpiceQmpPort *self)
> +{
> + JsonObject *obj = json_node_get_object(json_parser_get_root(self->priv->qmp_parser));
> + JsonNode *node;
> + GTask *task;
> + const gchar *event;
> +
> + if (json_object_get_member(obj, "QMP")) {
> + g_warn_if_fail(!self->priv->ready);
> + g_debug("QMP greeting received");
> + return TRUE;
> + }
> +
> + if ((node = json_object_get_member(obj, "error"))) {
> + gint id = json_object_get_int_member(obj, "id");
> + const gchar *desc = json_object_get_string_member(obj, "desc");
> +
> + g_debug("QMP return error: %s, id:%d", desc, id);
> + task = g_hash_table_lookup(self->priv->qmp_tasks, GINT_TO_POINTER(id));
> + g_return_val_if_fail(task != NULL, TRUE);
> + g_hash_table_steal(self->priv->qmp_tasks, GINT_TO_POINTER(id));
> + qmp_error_return(task, desc);
> + } 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 (!self->priv->ready && id == 0) {
> + self->priv->ready = TRUE;
> + g_object_notify(G_OBJECT(self), "ready");
> + }
> +
> + g_warn_if_fail(self->priv->ready);
> + task = g_hash_table_lookup(self->priv->qmp_tasks, GINT_TO_POINTER(id));
> + g_return_val_if_fail(task != NULL, TRUE);
> + cb = g_task_get_task_data(task);
> + g_hash_table_steal(self->priv->qmp_tasks, GINT_TO_POINTER(id));
> + cb(task, node);
> + } else if ((event = json_object_get_string_member(obj, "event"))) {
> + g_debug("QMP event %s", event);
> + g_signal_emit(G_OBJECT(self), signals[SIGNAL_EVENT], 0, event,
> + json_object_get_member(obj, "data"));
> + } else {
> + return FALSE;
> + }
> +
> + return TRUE;
> +}
> +
> +#define QMP_MAX_RESPONSE (10 * 1024 * 1024)
> +
> +static void
> +spice_qmp_handle_port_data(SpiceQmpPort *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);
> + if (qmp->len > QMP_MAX_RESPONSE) {
> + g_warning("QMP response is too large, over %d bytes, truncating",
> + QMP_MAX_RESPONSE);
> + g_string_set_size(qmp, 0);
> + return;
> + }
> +
> + 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 {
> + if (!spice_qmp_dispatch_message(self))
> + g_warning("Failed to dispatch: %s", str);
> + }
> + str = crlf + 2;
> + }
> +
> + g_string_erase(qmp, 0, str - qmp->str);
> +}
> +
> +static void qmp_task_disposed_cb(gpointer data)
> +{
> + qmp_error_return(G_TASK(data), "Task got disposed");
> +}
> +
> +static void spice_qmp_port_init(SpiceQmpPort *self)
> +{
> + self->priv = spice_qmp_port_get_instance_private(self);
> + self->priv->qmp_data = g_string_sized_new(256);
> + self->priv->qmp_parser = json_parser_new();
> + self->priv->qmp_tasks = g_hash_table_new_full(g_direct_hash, g_direct_equal,
> + NULL, qmp_task_disposed_cb);
> +}
> +
> +static void spice_qmp_port_dispose(GObject *gobject)
> +{
> + SpiceQmpPort *self = SPICE_QMP_PORT(gobject);
> +
> + g_string_free(self->priv->qmp_data, TRUE);
> + g_object_unref(self->priv->qmp_parser);
> + g_hash_table_unref(self->priv->qmp_tasks);
> +
> + g_object_set_data(G_OBJECT(self->priv->channel),
> + "spice-qmp-port", NULL);
> +
> + if (G_OBJECT_CLASS(spice_qmp_port_parent_class)->dispose)
> + G_OBJECT_CLASS(spice_qmp_port_parent_class)->dispose(gobject);
> +}
> +
> +static void spice_qmp_port_constructed(GObject *gobject)
> +{
> + SpiceQmpPort *self = SPICE_QMP_PORT(gobject);
> +
> + g_object_set_data(G_OBJECT(self->priv->channel),
> + "spice-qmp-port", self);
> +
> + spice_g_signal_connect_object(self->priv->channel,
> + "port-data", G_CALLBACK(spice_qmp_handle_port_data),
> + self, G_CONNECT_SWAPPED);
> +
> + if (G_OBJECT_CLASS(spice_qmp_port_parent_class)->constructed)
> + G_OBJECT_CLASS(spice_qmp_port_parent_class)->constructed(gobject);
> +}
> +
> +static void
> +spice_qmp_port_set_property(GObject *object,
> + guint property_id,
> + const GValue *value,
> + GParamSpec *pspec)
> +{
> + SpiceQmpPort *self = SPICE_QMP_PORT(object);
> +
> + switch (property_id) {
> + case PROP_CHANNEL:
> + g_clear_object(&self->priv->channel);
> + self->priv->channel = g_value_dup_object(value);
> + break;
> +
> + default:
> + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
> + break;
> + }
> +}
> +
> +static void
> +spice_qmp_port_get_property(GObject *object,
> + guint property_id,
> + GValue *value,
> + GParamSpec *pspec)
> +{
> + SpiceQmpPort *self = SPICE_QMP_PORT(object);
> +
> + switch (property_id) {
> + case PROP_CHANNEL:
> + g_value_set_object(value, self->priv->channel);
> + break;
> +
> + case PROP_READY:
> + g_value_set_boolean(value, self->priv->ready);
> + break;
> +
> + default:
> + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
> + break;
> + }
> +}
> +
> +static void spice_qmp_port_class_init(SpiceQmpPortClass *klass)
> +{
> + GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
> +
> + gobject_class->dispose = spice_qmp_port_dispose;
> + gobject_class->get_property = spice_qmp_port_get_property;
> + gobject_class->set_property = spice_qmp_port_set_property;
> + gobject_class->constructed = spice_qmp_port_constructed;
> +
> + /**
> + * SpiceQmpPort::event:
> + * @self: the #SpiceQmpPort that emitted the signal
> + * @name: the QMP event name
> + * @node: the event data json-node, or NULL
> + *
> + * Event emitted whenever a QMP event is received.
> + *
> + * Since: 0.36
> + */
> + signals[SIGNAL_EVENT] =
> + g_signal_new("event",
> + G_OBJECT_CLASS_TYPE(gobject_class),
> + G_SIGNAL_RUN_FIRST,
> + 0,
> + NULL, NULL, NULL,
> + G_TYPE_NONE,
> + 2, G_TYPE_STRING, G_TYPE_POINTER);
> +
> + props[PROP_CHANNEL] =
> + g_param_spec_object("channel",
> + "Channel",
> + "Associated port channel",
> + SPICE_TYPE_PORT_CHANNEL,
> + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
> +
> + props[PROP_READY] =
> + g_param_spec_boolean("ready",
> + "Ready",
> + "Whether the QMP port is ready",
> + FALSE,
> + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
> +
> + g_object_class_install_properties(gobject_class, PROP_LAST, props);
> + }
> +
> +static void
> +qmp_empty_return_cb(GTask *task, G_GNUC_UNUSED JsonNode *node)
> +{
> + g_task_return_boolean(task, TRUE);
> + g_object_unref(task);
> +}
> +
> +static void
> +spice_qmp_port_write_finished(GObject *source_object,
> + GAsyncResult *res,
> + gpointer t)
> +{
> + SpicePortChannel *port = SPICE_PORT_CHANNEL(source_object);
> + GTask *task = G_TASK(t);
> + SpiceQmpPort *self = g_task_get_source_object(task);
> + gint id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(task), "qmp-id"));
> + GError *err = NULL;
> +
> + spice_port_channel_write_finish(port, res, &err);
> + if (err) {
> + g_hash_table_steal(self->priv->qmp_tasks, GINT_TO_POINTER(id));
> + qmp_error_return(task, err->message);
> + g_error_free(err);
> + }
> +}
> +
> +static void
> +qmp(SpiceQmpPort *self, GTask *task,
> + const char *cmd, const gchar *args)
> +{
> + GString *str = g_string_sized_new(256);
> + gsize len;
> + gchar *data;
> + gint id = self->priv->id_counter;
> +
> + 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, " }");
> +
> + g_hash_table_insert(self->priv->qmp_tasks, GINT_TO_POINTER(id), task);
> +
> + len = str->len;
> + data = g_string_free(str, FALSE);
> + spice_port_channel_write_async(self->priv->channel, data, len,
> + g_task_get_cancellable(task),
> + spice_qmp_port_write_finished, task);
> + g_object_set_data_full(G_OBJECT(task), "qmp-data", data, g_free);
> + g_object_set_data(G_OBJECT(task), "qmp-id", GINT_TO_POINTER(id));
> +
> + self->priv->id_counter++;
> +}
> +
> +/**
> + * spice_qmp_port_vm_action_finish:
> + * @self: a qmp port helper
> + * @result: The async #GAsyncResult result
> + * @error: a #GError pointer, or %NULL
> + *
> + * Finishes asynchronous VM action and returns the result.
> + *
> + * Since: 0.36
> + **/
> +gboolean spice_qmp_port_vm_action_finish(SpiceQmpPort *self,
> + GAsyncResult *result,
> + GError **error)
> +{
> + g_return_val_if_fail(SPICE_IS_QMP_PORT(self), FALSE);
> + g_return_val_if_fail(g_task_is_valid(result, self), FALSE);
> +
> + return g_task_propagate_boolean(G_TASK(result), error);
> +}
> +
> +/**
> + * spice_qmp_port_vm_action_async:
> + * @self: a qmp port helper
> + * @action: a VM action
> + * @cancellable: a #GCancellable, or %NULL
> + * @callback: callback to call when the action is complete
> + * @user_data: the data to pass to the callback function
> + *
> + * Request the VM to perform an action.
> + *
> + * Since: 0.36
> + **/
> +void spice_qmp_port_vm_action_async(SpiceQmpPort *self,
> + SpiceQmpPortVmAction action,
> + GCancellable *cancellable,
> + GAsyncReadyCallback callback,
> + gpointer user_data)
> +{
> + GTask *task;
> + const gchar *cmd;
> +
> + g_return_if_fail(SPICE_IS_QMP_PORT(self));
> + g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable));
> + g_return_if_fail(self->priv->ready);
> + g_return_if_fail(action >= 0 && action < SPICE_QMP_PORT_VM_ACTION_LAST);
> +
> + task = g_task_new(self, cancellable, callback, user_data);
> + g_task_set_task_data(task, qmp_empty_return_cb, NULL);
> +
> + switch (action) {
> + case SPICE_QMP_PORT_VM_ACTION_QUIT:
> + cmd = "quit";
> + break;
> + case SPICE_QMP_PORT_VM_ACTION_RESET:
> + cmd = "system_reset";
> + break;
> + case SPICE_QMP_PORT_VM_ACTION_POWER_DOWN:
> + cmd = "system_powerdown";
> + break;
> + case SPICE_QMP_PORT_VM_ACTION_PAUSE:
> + cmd = "stop";
> + break;
> + case SPICE_QMP_PORT_VM_ACTION_CONTINUE:
> + cmd = "cont";
> + break;
> + default:
> + g_return_if_reached();
> + }
> +
> + qmp(self, task, cmd, NULL);
> +}
> +
> +static void
> +qmp_capabilities_cb(GTask *task, JsonNode *node)
> +{
> + g_task_return_boolean(task, TRUE);
> + g_object_unref(task);
> +}
> +
> +/**
> + * spice_qmp_port_get:
> + * @channel: the QMP port channel
> + *
> + * Associate a QMP port helper to the given port channel. If there is
> + * already a helper associated with the channel, it is simply returned.
> + *
> + * Returns: (transfer none): a weak reference to the associated SpiceQmpPort
> + *
> + * Since: 0.36
> + **/
> +SpiceQmpPort *spice_qmp_port_get(SpicePortChannel *channel)
> +{
> + GObject *self;
> +
> + g_return_val_if_fail(SPICE_IS_PORT_CHANNEL(channel), NULL);
> +
> + self = g_object_get_data(G_OBJECT(channel), "spice-qmp-port");
> +
> + if (self == NULL) {
> + GTask *task;
> +
> + self = g_object_new(SPICE_TYPE_QMP_PORT, "channel", channel, NULL);
> + task = g_task_new(self, NULL, NULL, NULL);
> + g_task_set_task_data(task, qmp_capabilities_cb, NULL);
> + qmp(SPICE_QMP_PORT(self), task, "qmp_capabilities", NULL);
> + }
> +
> + return SPICE_QMP_PORT(self);
> +}
> +
> +/**
> + * spice_qmp_status_ref:
> + * @status: a #SpiceQmpStatus
> + *
> + * References a @status.
> + *
> + * Returns: The same @status
> + *
> + * Since: 0.36
> + **/
> +SpiceQmpStatus *
> +spice_qmp_status_ref(SpiceQmpStatus *status)
> +{
> + g_return_val_if_fail(status != NULL, NULL);
> +
> + status->ref++;
> +
> + return status;
> +}
> +
> +/**
> + * spice_qmp_status_unref:
> + * @status: a #SpiceQmpStatus
> + *
> + * Removes a reference from the given @status.
> + *
> + * Since: 0.36
> + **/
> +void spice_qmp_status_unref(SpiceQmpStatus *status)
> +{
> + if (status && --status->ref == 0) {
> + g_free(status->status);
> + g_free(status);
> + }
> +}
> +
> +static void
> +qmp_query_status_return_cb(GTask *task, JsonNode *node)
> +{
> + SpiceQmpStatus *status = g_new0(SpiceQmpStatus, 1);
> + JsonObject *obj = json_node_get_object(node);
> +
> + status->version = 1;
> + status->ref = 1;
> + status->running = json_object_get_boolean_member(obj, "running");
> + status->singlestep = json_object_get_boolean_member(obj, "singlestep");
> + status->status = g_strdup(json_object_get_string_member(obj, "status"));
> +
> + g_task_return_pointer(task, status, (GDestroyNotify)spice_qmp_status_unref);
> + g_object_unref(task);
> +}
> +
> +/**
> + * spice_qmp_port_query_status_async:
> + * @self: A #SpiceQmpPort
> + * @cancellable: A #GCancellable
> + * @callback: The async callback.
> + * @user_data: The async callback user data.
> + *
> + * Query the run status of all VCPUs.
> + *
> + * Since: 0.36
> + **/
> +void spice_qmp_port_query_status_async(SpiceQmpPort *self,
> + GCancellable *cancellable,
> + GAsyncReadyCallback callback,
> + gpointer user_data)
> +{
> + GTask *task;
> +
> + g_return_if_fail(SPICE_IS_QMP_PORT(self));
> + g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable));
> + g_return_if_fail(self->priv->ready);
> +
> + task = g_task_new(self, cancellable, callback, user_data);
> + g_task_set_task_data(task, qmp_query_status_return_cb, NULL);
> +
> + qmp(self, task, "query-status", NULL);
> +}
> +
> +/**
> + * spice_qmp_port_query_status_finish:
> + * @self: A #SpiceQmpPort
> + * @result: The async #GAsyncResult result
> + * @error: a #GError pointer, or %NULL
> + *
> + * Finish the asynchronous status query.
> + *
> + * Returns: The #SpiceQmpStatus result or %NULL, in which case @error
> + * will be set.
> + *
> + * Since: 0.36
> + **/
> +SpiceQmpStatus *
> +spice_qmp_port_query_status_finish(SpiceQmpPort *self,
> + GAsyncResult *result,
> + GError **error)
> +{
> + g_return_val_if_fail(SPICE_IS_QMP_PORT(self), NULL);
> + g_return_val_if_fail(g_task_is_valid(result, self), NULL);
> +
> + return g_task_propagate_pointer(G_TASK(result), error);
> +}
> diff --git a/src/qmp-port.h b/src/qmp-port.h
> new file mode 100644
> index 0000000..a8a4e30
> --- /dev/null
> +++ b/src/qmp-port.h
> @@ -0,0 +1,122 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> + Copyright (C) 2018 Red Hat, Inc.
> +
> + 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.1 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, see <http://www.gnu.org/licenses/>.
> +*/
> +#ifndef QMP_PORT_H_
> +#define QMP_PORT_H_
> +
> +#if !defined(__SPICE_CLIENT_H_INSIDE__) && !defined(SPICE_COMPILATION)
> +#warning "Only <spice-client.h> can be included directly"
> +#endif
> +
> +#include <glib-object.h>
> +#include "channel-port.h"
> +
> +G_BEGIN_DECLS
> +
> +#define SPICE_TYPE_QMP_PORT (spice_qmp_port_get_type ())
> +#define SPICE_QMP_PORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_QMP_PORT, SpiceQmpPort))
> +#define SPICE_QMP_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_QMP_PORT, SpiceQmpPortClass))
> +#define SPICE_IS_QMP_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_QMP_PORT))
> +#define SPICE_IS_QMP_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_QMP_PORT))
> +#define SPICE_QMP_PORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_QMP_PORT, SpiceQmpPortClass))
> +
> +/**
> + * SpiceQmpPort:
> + *
> + * Opaque data structure.
> + * Since: 0.36
> + */
> +typedef struct _SpiceQmpPort SpiceQmpPort;
> +typedef struct _SpiceQmpPortClass SpiceQmpPortClass;
> +
> +/**
> + * SpiceQmpPortVmAction:
> + * @SPICE_QMP_PORT_VM_ACTION_QUIT: This command will cause the VM process to exit gracefully.
> + * @SPICE_QMP_PORT_VM_ACTION_RESET: Performs a hard reset of the VM.
> + * @SPICE_QMP_PORT_VM_ACTION_POWER_DOWN: Performs a power down operation.
> + * @SPICE_QMP_PORT_VM_ACTION_PAUSE: Stop all VCPU execution.
> + * @SPICE_QMP_PORT_VM_ACTION_CONTINUE: Resume all VCPU execution.
> + * @SPICE_QMP_PORT_VM_ACTION_LAST: the last enum value.
> + *
> + * An action to perform on the VM.
> + *
> + * Since: 0.36
> + **/
> +typedef enum SpiceQmpPortVmAction {
> + SPICE_QMP_PORT_VM_ACTION_QUIT,
> + SPICE_QMP_PORT_VM_ACTION_RESET,
> + SPICE_QMP_PORT_VM_ACTION_POWER_DOWN,
> + SPICE_QMP_PORT_VM_ACTION_PAUSE,
> + SPICE_QMP_PORT_VM_ACTION_CONTINUE,
> +
> + SPICE_QMP_PORT_VM_ACTION_LAST,
> +} SpiceQmpPortVmAction;
> +
> +/**
> + * SpiceQmpStatus:
> + * @version: the structure version
> + * @running: true if all VCPUs are runnable, false if not runnable
> + * @singlestep: true if VCPUs are in single-step mode
> + * @status: the virtual machine run state
> + *
> + * Information about VCPU run state.
> + *
> + * Since: 0.36
> + **/
> +typedef struct _SpiceQmpStatus {
> + /*< private >*/
> + gint ref;
> +
> + /*< public >*/
> + gint version;
> +
> + gboolean running;
> + gboolean singlestep;
> + gchar *status;
> +} SpiceQmpStatus;
> +
> +GType spice_qmp_port_get_type(void);
> +
> +SpiceQmpPort *spice_qmp_port_get(SpicePortChannel *channel);
> +
> +void spice_qmp_port_vm_action_async(SpiceQmpPort *self,
> + SpiceQmpPortVmAction action,
> + GCancellable *cancellable,
> + GAsyncReadyCallback callback,
> + gpointer user_data);
> +
> +gboolean spice_qmp_port_vm_action_finish(SpiceQmpPort *self,
> + GAsyncResult *result,
> + GError **error);
> +
> +GType spice_qmp_status_get_type(void);
> +
> +SpiceQmpStatus *spice_qmp_status_ref(SpiceQmpStatus *status);
> +void spice_qmp_status_unref(SpiceQmpStatus *status);
> +
> +void spice_qmp_port_query_status_async(SpiceQmpPort *self,
> + GCancellable *cancellable,
> + GAsyncReadyCallback callback,
> + gpointer user_data);
> +
> +SpiceQmpStatus *spice_qmp_port_query_status_finish(SpiceQmpPort *self,
> + GAsyncResult *result,
> + GError **error);
> +
> +G_END_DECLS
> +
> +#endif /* QMP_PORT_H_ */
> diff --git a/src/spice-client.h b/src/spice-client.h
> index 32b79ea..77362b9 100644
> --- a/src/spice-client.h
> +++ b/src/spice-client.h
> @@ -51,6 +51,7 @@
> #include "usb-device-manager.h"
> #include "spice-audio.h"
> #include "spice-file-transfer-task.h"
> +#include "qmp-port.h"
>
> G_BEGIN_DECLS
>
> diff --git a/src/spice-glib-sym-file b/src/spice-glib-sym-file
> index b19844c..2df1cc0 100644
> --- a/src/spice-glib-sym-file
> +++ b/src/spice-glib-sym-file
> @@ -96,6 +96,15 @@ spice_port_channel_write_finish
> spice_port_event
> spice_port_write_async
> spice_port_write_finish
> +spice_qmp_port_get
> +spice_qmp_port_get_type
> +spice_qmp_port_query_status_async
> +spice_qmp_port_query_status_finish
> +spice_qmp_port_vm_action_async
> +spice_qmp_port_vm_action_finish
> +spice_qmp_status_get_type
> +spice_qmp_status_ref
> +spice_qmp_status_unref
> spice_record_channel_get_type
> spice_record_channel_send_data
> spice_record_send_data
> --
> 2.19.0
>
> _______________________________________________
> Spice-devel mailing list
> Spice-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/spice-devel
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/spice-devel/attachments/20180921/a3c420ae/attachment-0001.sig>
More information about the Spice-devel
mailing list