From: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> 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. Signed-off-by: Marc-André Lureau <marcandre.lureau@xxxxxxxxxx> --- configure.ac | 2 + doc/reference/spice-gtk-docs.xml | 1 + doc/reference/spice-gtk-sections.txt | 27 ++ src/Makefile.am | 6 + src/map-file | 9 + src/qmp-port.c | 571 +++++++++++++++++++++++++++ src/qmp-port.h | 122 ++++++ src/spice-client.h | 1 + src/spice-glib-sym-file | 9 + 9 files changed, 748 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/src/Makefile.am b/src/Makefile.am index afad922..8f8ccdf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -77,6 +77,7 @@ SPICE_COMMON_CPPFLAGS = \ $(GLIB2_CFLAGS) \ $(GIO_CFLAGS) \ $(GOBJECT2_CFLAGS) \ + $(JSON_CFLAGS) \ $(SSL_CFLAGS) \ $(SASL_CFLAGS) \ $(GSTAUDIO_CFLAGS) \ @@ -181,6 +182,7 @@ libspice_client_glib_2_0_la_LIBADD = \ $(GIO_LIBS) \ $(GOBJECT2_LIBS) \ $(JPEG_LIBS) \ + $(JSON_LIBS) \ $(Z_LIBS) \ $(LZ4_LIBS) \ $(PIXMAN_LIBS) \ @@ -242,6 +244,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 \ @@ -292,6 +296,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 \ @@ -525,6 +530,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/qmp-port.c b/src/qmp-port.c new file mode 100644 index 0000000..04ebc98 --- /dev/null +++ b/src/qmp-port.c @@ -0,0 +1,571 @@ +/* -*- 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 void +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 (!self->priv->ready && json_object_get_member(obj, "QMP")) { + g_debug("QMP greeting received"); + } else 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); + if ((task = g_hash_table_lookup(self->priv->qmp_tasks, GINT_TO_POINTER(id)))) { + 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"); + + g_debug("QMP return id:%d", id); + if (!self->priv->ready && id == 0) { + self->priv->ready = TRUE; + g_object_notify(G_OBJECT(self), "ready"); + } + + if (!self->priv->ready) { + g_warning("Invalid QMP return, not ready"); + } else if ((task = g_hash_table_lookup(self->priv->qmp_tasks, GINT_TO_POINTER(id)))) { + QMPCb *cb = g_task_get_task_data(task); + g_hash_table_steal(self->priv->qmp_tasks, GINT_TO_POINTER(id)); + cb(task, node); + } else { + g_debug("No task associated with return"); + } + } 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 { + g_debug("unhandled JSON %s", self->priv->qmp_data->str); + } +} + +#define QMP_MAX_RESPONSE (10 * 1024 * 1024) + +static void +spice_qmp_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 { + spice_qmp_dispatch_message(self); + } + str = crlf + 2; + } + + g_string_erase(qmp, 0, str - qmp->str); +} + +static void qmp_task_cancelled_cb(gpointer data) +{ + qmp_error_return(G_TASK(data), "Task got cancelled"); +} + +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_cancelled_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); + + 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_full(G_OBJECT(self->priv->channel), + "spice-qmp-port", self, g_object_unref); + + spice_g_signal_connect_object(self->priv->channel, + "port-data", G_CALLBACK(spice_qmp_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, 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.18.0.547.g1d89318c48 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel