diff options
-rw-r--r-- | doc/reference/spice-gtk-docs.xml | 1 | ||||
-rw-r--r-- | doc/reference/spice-gtk-sections.txt | 21 | ||||
-rw-r--r-- | doc/reference/spice-gtk.types | 1 | ||||
-rw-r--r-- | gtk/Makefile.am | 3 | ||||
-rw-r--r-- | gtk/channel-port.c | 425 | ||||
-rw-r--r-- | gtk/channel-port.h | 76 | ||||
-rw-r--r-- | gtk/map-file | 4 | ||||
-rw-r--r-- | gtk/spice-channel.c | 4 | ||||
-rw-r--r-- | gtk/spice-client.h | 1 |
9 files changed, 536 insertions, 0 deletions
diff --git a/doc/reference/spice-gtk-docs.xml b/doc/reference/spice-gtk-docs.xml index 82cdce8..4a9a3cf 100644 --- a/doc/reference/spice-gtk-docs.xml +++ b/doc/reference/spice-gtk-docs.xml @@ -36,6 +36,7 @@ <xi:include href="xml/channel-record.xml"/> <xi:include href="xml/channel-smartcard.xml"/> <xi:include href="xml/channel-usbredir.xml"/> + <xi:include href="xml/channel-port.xml"/> </chapter> <chapter> diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt index 87d4225..d82a886 100644 --- a/doc/reference/spice-gtk-sections.txt +++ b/doc/reference/spice-gtk-sections.txt @@ -403,3 +403,24 @@ SPICE_DEPRECATED_FOR spice_g_signal_connect_object </SECTION> +<SECTION> +<FILE>channel-port</FILE> +<TITLE>SpicePortChannel</TITLE> +SpicePortChannel +SpicePortChannelClass +<SUBSECTION> +spice_port_event +spice_port_write_async +spice_port_write_finish +<SUBSECTION Standard> +SPICE_PORT_CHANNEL +SPICE_IS_PORT_CHANNEL +SPICE_TYPE_PORT_CHANNEL +spice_port_channel_get_type +SPICE_PORT_CHANNEL_CLASS +SPICE_IS_PORT_CHANNEL_CLASS +SPICE_PORT_CHANNEL_GET_CLASS +<SUBSECTION Private> +SpicePortChannelPrivate +</SECTION> + diff --git a/doc/reference/spice-gtk.types b/doc/reference/spice-gtk.types index ff80277..2f52845 100644 --- a/doc/reference/spice-gtk.types +++ b/doc/reference/spice-gtk.types @@ -42,3 +42,4 @@ spice_usbredir_channel_get_type spice_usb_device_get_type spice_usb_device_manager_get_type spice_usb_device_widget_get_type +spice_port_channel_get_type
\ No newline at end of file diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 06cc6e1..d38ba7a 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -236,6 +236,7 @@ libspice_client_glib_2_0_la_SOURCES = \ channel-inputs.c \ channel-main.c \ channel-playback.c \ + channel-port.c \ channel-record.c \ channel-smartcard.c \ channel-usbredir.c \ @@ -277,6 +278,7 @@ libspice_client_glibinclude_HEADERS = \ channel-inputs.h \ channel-main.h \ channel-playback.h \ + channel-port.h \ channel-record.h \ channel-smartcard.h \ channel-usbredir.h \ @@ -589,6 +591,7 @@ glib_introspection_files = \ channel-inputs.c \ channel-main.c \ channel-playback.c \ + channel-port.c \ channel-record.c \ channel-smartcard.c \ channel-usbredir.c \ diff --git a/gtk/channel-port.c b/gtk/channel-port.c new file mode 100644 index 0000000..e1f61d2 --- /dev/null +++ b/gtk/channel-port.c @@ -0,0 +1,425 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2012 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 "spice-client.h" +#include "spice-common.h" +#include "spice-channel-priv.h" +#include "spice-marshal.h" + +/** + * SECTION:channel-port + * @short_description: private communication channel + * @title: Port Channel + * @section_id: + * @see_also: #SpiceChannel + * @stability: Stable + * @include: channel-port.h + * + * A Spice port channel carry arbitrary data between the Spice client + * and the Spice server. It may be used to provide additional + * services on top of a Spice connection. For example, a channel can + * be associated with the qemu monitor for the client to interact + * with it, just like any qemu chardev. Or it may be used with + * various protocols, such as the Spice Controller. + * + * A port kind is identified simply by a fqdn, such as + * org.qemu.monitor, org.spice.spicy.test or org.ovirt.controller... + * + * Once connected and initialized, the client may read the name of the + * port via SpicePortChannel:port-name. + + * When the other end of the port is ready, + * SpicePortChannel:port-opened is set to %TRUE and you can start + * receiving data via the signal SpicePortChannel::port-data, or + * sending data via spice_port_write_async(). + * + * Since: 0.15 + */ + +#define SPICE_PORT_CHANNEL_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelPrivate)) + +struct _SpicePortChannelPrivate { + gchar *name; + gboolean opened; +}; + +G_DEFINE_TYPE(SpicePortChannel, spice_port_channel, SPICE_TYPE_CHANNEL) + +/* Properties */ +enum { + PROP_0, + PROP_PORT_NAME, + PROP_PORT_OPENED, +}; + +/* Signals */ +enum { + SPICE_PORT_DATA, + SPICE_PORT_EVENT, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL]; + +static void spice_port_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg); + +static void spice_port_channel_init(SpicePortChannel *channel) +{ + channel->priv = SPICE_PORT_CHANNEL_GET_PRIVATE(channel); +} + +static void spice_port_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(object)->priv; + + switch (prop_id) { + case PROP_PORT_NAME: + g_value_set_string(value, c->name); + break; + case PROP_PORT_OPENED: + g_value_set_boolean(value, c->opened); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void spice_port_channel_finalize(GObject *object) +{ + SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(object)->priv; + + g_free(c->name); + + if (G_OBJECT_CLASS(spice_port_channel_parent_class)->finalize) + G_OBJECT_CLASS(spice_port_channel_parent_class)->finalize(object); +} + +static void spice_port_channel_reset(SpiceChannel *channel, gboolean migrating) +{ + SpicePortChannelPrivate *c = SPICE_PORT_CHANNEL(channel)->priv; + + g_clear_pointer(&c->name, g_free); + c->opened = FALSE; + + SPICE_CHANNEL_CLASS(spice_port_channel_parent_class)->channel_reset(channel, migrating); +} + +static void spice_port_channel_class_init(SpicePortChannelClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass); + + gobject_class->finalize = spice_port_channel_finalize; + gobject_class->get_property = spice_port_get_property; + channel_class->handle_msg = spice_port_handle_msg; + channel_class->channel_reset = spice_port_channel_reset; + + g_object_class_install_property + (gobject_class, PROP_PORT_NAME, + g_param_spec_string("port-name", + "Port name", + "Port name", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property + (gobject_class, PROP_PORT_OPENED, + g_param_spec_boolean("port-opened", + "Port opened", + "Port opened", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * SpicePort::port-data: + * @channel: the channel that emitted the signal + * @data: the data received + * @size: number of bytes read + * + * The #SpicePortChannel::port-data signal is emitted when new + * port data is received. + * Since: 0.15 + **/ + signals[SPICE_PORT_DATA] = + g_signal_new("port-data", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_user_marshal_VOID__POINTER_INT, + G_TYPE_NONE, + 2, + G_TYPE_POINTER, G_TYPE_INT); + + + /** + * SpicePort::port-event: + * @channel: the channel that emitted the signal + * @event: the event received + * @size: number of bytes read + * + * The #SpicePortChannel::port-event signal is emitted when new + * port event is received. + * Since: 0.15 + **/ + signals[SPICE_PORT_EVENT] = + g_signal_new("port-event", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + g_type_class_add_private(klass, sizeof(SpicePortChannelPrivate)); +} + +/* signal trampoline---------------------------------------------------------- */ + +struct SPICE_PORT_DATA { + uint8_t *data; + gsize data_size; +}; + +struct SPICE_PORT_EVENT { + int event; +}; + +/* main context */ +static void do_emit_main_context(GObject *object, int signum, gpointer params) +{ + switch (signum) { + case SPICE_PORT_DATA: { + struct SPICE_PORT_DATA *p = params; + g_signal_emit(object, signals[signum], 0, p->data, p->data_size); + break; + } + case SPICE_PORT_EVENT: { + struct SPICE_PORT_EVENT *p = params; + g_signal_emit(object, signals[signum], 0, p->event); + break; + } + default: + g_warn_if_reached(); + } +} + +/* coroutine context */ +static void port_set_opened(SpicePortChannel *self, gboolean opened) +{ + SpicePortChannelPrivate *c = self->priv; + + if (c->opened == opened) + return; + + c->opened = opened; + g_object_notify_main_context(G_OBJECT(self), "port-opened"); +} + +/* coroutine context */ +static void port_handle_init(SpiceChannel *channel, SpiceMsgIn *in) +{ + SpicePortChannel *self = SPICE_PORT_CHANNEL(channel); + SpicePortChannelPrivate *c = self->priv; + SpiceMsgPortInit *init = spice_msg_in_parsed(in); + + CHANNEL_DEBUG(channel, "init: %s %d", init->name, init->opened); + g_return_if_fail(init->name != NULL && *init->name != '\0'); + g_return_if_fail(c->name == NULL); + + c->name = g_strdup((gchar*)init->name); + g_object_notify(G_OBJECT(channel), "port-name"); + + port_set_opened(self, init->opened); +} + +/* coroutine context */ +static void port_handle_event(SpiceChannel *channel, SpiceMsgIn *in) +{ + SpicePortChannel *self = SPICE_PORT_CHANNEL(channel); + SpiceMsgPortEvent *event = spice_msg_in_parsed(in); + + CHANNEL_DEBUG(channel, "port event: %d", event->event); + switch (event->event) { + case SPICE_PORT_EVENT_OPENED: + port_set_opened(self, true); + break; + case SPICE_PORT_EVENT_CLOSED: + port_set_opened(self, false); + break; + } + + emit_main_context(channel, SPICE_PORT_EVENT, event->event); +} + +/* coroutine context */ +static void port_handle_msg(SpiceChannel *channel, SpiceMsgIn *in) +{ + SpicePortChannel *self = SPICE_PORT_CHANNEL(channel); + int size; + uint8_t *buf; + + buf = spice_msg_in_raw(in, &size); + CHANNEL_DEBUG(channel, "port %p got %d %p", channel, size, buf); + port_set_opened(self, true); + emit_main_context(channel, SPICE_PORT_DATA, buf, size); +} + +static void port_write_free_cb(uint8_t *data, void *user_data) +{ + GSimpleAsyncResult *result = user_data; + + g_simple_async_result_complete(result); + g_object_unref(result); +} + +/** + * spice_port_write_async: + * @port: A #SpicePortChannel + * @buffer: (array length=count) (element-type guint8): the buffer + * containing the data to write + * @count: the number of bytes to write + * @cancellable: (allow-none): optional GCancellable object, NULL to ignore + * @callback: (scope async): callback to call when the request is satisfied + * @user_data: (closure): the data to pass to callback function + * + * Request an asynchronous write of count bytes from @buffer into the + * @port. When the operation is finished @callback will be called. You + * can then call spice_port_write_finish() to get the result of + * the operation. + * + * Since: 0.15 + **/ +void spice_port_write_async(SpicePortChannel *self, + const void *buffer, gsize count, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + SpicePortChannelPrivate *c; + SpiceMsgOut *msg; + + g_return_if_fail(SPICE_IS_PORT_CHANNEL(self)); + g_return_if_fail(buffer != NULL); + c = self->priv; + + if (!c->opened) { + g_simple_async_report_error_in_idle(G_OBJECT(self), callback, user_data, + SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED, + "The port is not opened"); + return; + } + + simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data, + spice_port_write_async); + g_simple_async_result_set_op_res_gssize(simple, count); + + msg = spice_msg_out_new(SPICE_CHANNEL(self), SPICE_MSGC_SPICEVMC_DATA); + spice_marshaller_add_ref_full(msg->marshaller, (uint8_t*)buffer, count, + port_write_free_cb, simple); + spice_msg_out_send(msg); +} + +/** + * spice_port_write_finish: + * @port: a #SpicePortChannel + * @result: a #GAsyncResult + * @error: a #GError location to store the error occurring, or %NULL + * to ignore + * + * Finishes a port write operation. + * + * Returns: a #gssize containing the number of bytes written to the stream. + * Since: 0.15 + **/ +gssize spice_port_write_finish(SpicePortChannel *self, + GAsyncResult *result, GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail(SPICE_IS_PORT_CHANNEL(self), -1); + g_return_val_if_fail(result != NULL, -1); + + simple = (GSimpleAsyncResult *)result; + + if (g_simple_async_result_propagate_error(simple, error)) + return -1; + + g_return_val_if_fail(g_simple_async_result_is_valid(result, G_OBJECT(self), + spice_port_write_async), -1); + + return g_simple_async_result_get_op_res_gssize(simple); +} + +/** + * spice_port_event: + * @port: a #SpicePortChannel + * @event: a SPICE_PORT_EVENT value + * + * Send an event to the port. + * + * Note: The values SPICE_PORT_EVENT_CLOSED and + * SPICE_PORT_EVENT_OPENED are managed by the channel connection + * state. + * + * Since: 0.15 + **/ +void spice_port_event(SpicePortChannel *self, guint8 event) +{ + SpiceMsgcPortEvent e; + SpiceMsgOut *msg; + + g_return_if_fail(SPICE_IS_PORT_CHANNEL(self)); + g_return_if_fail(event > SPICE_PORT_EVENT_CLOSED); + + msg = spice_msg_out_new(SPICE_CHANNEL(self), SPICE_MSGC_PORT_EVENT); + e.event = event; + msg->marshallers->msgc_port_event(msg->marshaller, &e); + spice_msg_out_send(msg); +} + +static const spice_msg_handler port_handlers[] = { + [ SPICE_MSG_PORT_INIT ] = port_handle_init, + [ SPICE_MSG_PORT_EVENT ] = port_handle_event, + [ SPICE_MSG_SPICEVMC_DATA ] = port_handle_msg, +}; + +/* coroutine context */ +static void spice_port_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg) +{ + int type = spice_msg_in_type(msg); + SpiceChannelClass *parent_class; + + g_return_if_fail(type < SPICE_N_ELEMENTS(port_handlers)); + + parent_class = SPICE_CHANNEL_CLASS(spice_port_channel_parent_class); + + if (port_handlers[type] != NULL) + port_handlers[type](channel, msg); + else if (parent_class->handle_msg) + parent_class->handle_msg(channel, msg); + else + g_return_if_reached(); +} diff --git a/gtk/channel-port.h b/gtk/channel-port.h new file mode 100644 index 0000000..84d512d --- /dev/null +++ b/gtk/channel-port.h @@ -0,0 +1,76 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2012 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 __SPICE_CLIENT_PORT_CHANNEL_H__ +#define __SPICE_CLIENT_PORT_CHANNEL_H__ + +#include <gio/gio.h> +#include "spice-client.h" + +G_BEGIN_DECLS + +#define SPICE_TYPE_PORT_CHANNEL (spice_port_channel_get_type()) +#define SPICE_PORT_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannel)) +#define SPICE_PORT_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelClass)) +#define SPICE_IS_PORT_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PORT_CHANNEL)) +#define SPICE_IS_PORT_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PORT_CHANNEL)) +#define SPICE_PORT_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PORT_CHANNEL, SpicePortChannelClass)) + +typedef struct _SpicePortChannel SpicePortChannel; +typedef struct _SpicePortChannelClass SpicePortChannelClass; +typedef struct _SpicePortChannelPrivate SpicePortChannelPrivate; + +/** + * SpicePortChannel: + * + * The #SpicePortChannel struct is opaque and should not be accessed directly. + */ +struct _SpicePortChannel { + SpiceChannel parent; + + /*< private >*/ + SpicePortChannelPrivate *priv; + /* Do not add fields to this struct */ +}; + +/** + * SpicePortChannelClass: + * @parent_class: Parent class. + * + * Class structure for #SpicePortChannel. + */ +struct _SpicePortChannelClass { + SpiceChannelClass parent_class; + + /*< private >*/ + /* Do not add fields to this struct */ +}; + +GType spice_port_channel_get_type(void); + +void spice_port_write_async(SpicePortChannel *port, + const void *buffer, gsize count, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gssize spice_port_write_finish(SpicePortChannel *port, + GAsyncResult *result, GError **error); +void spice_port_event(SpicePortChannel *port, guint8 event); + +G_END_DECLS + +#endif /* __SPICE_CLIENT_PORT_CHANNEL_H__ */ diff --git a/gtk/map-file b/gtk/map-file index 79ad9d2..516764c 100644 --- a/gtk/map-file +++ b/gtk/map-file @@ -69,6 +69,10 @@ spice_main_set_display; spice_main_set_display_enabled; spice_playback_channel_get_type; spice_playback_channel_set_delay; +spice_port_channel_get_type; +spice_port_event; +spice_port_write_async; +spice_port_write_finish; spice_record_channel_get_type; spice_record_send_data; spice_session_connect; diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c index c9c4639..d374204 100644 --- a/gtk/spice-channel.c +++ b/gtk/spice-channel.c @@ -1872,6 +1872,7 @@ const gchar* spice_channel_type_to_string(gint type) [ SPICE_CHANNEL_TUNNEL ] = "tunnel", [ SPICE_CHANNEL_SMARTCARD ] = "smartcard", [ SPICE_CHANNEL_USBREDIR ] = "usbredir", + [ SPICE_CHANNEL_PORT ] = "port", }; const char *str = NULL; @@ -1942,6 +1943,9 @@ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id) break; } #endif + case SPICE_CHANNEL_PORT: + gtype = SPICE_TYPE_PORT_CHANNEL; + break; default: g_debug("unsupported channel kind: %s: %d", spice_channel_type_to_string(type), type); diff --git a/gtk/spice-client.h b/gtk/spice-client.h index 5c05ebb..730d11a 100644 --- a/gtk/spice-client.h +++ b/gtk/spice-client.h @@ -40,6 +40,7 @@ #include "channel-record.h" #include "channel-smartcard.h" #include "channel-usbredir.h" +#include "channel-port.h" #include "smartcard-manager.h" #include "usb-device-manager.h" |