summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/reference/spice-gtk-docs.xml1
-rw-r--r--doc/reference/spice-gtk-sections.txt21
-rw-r--r--doc/reference/spice-gtk.types1
-rw-r--r--gtk/Makefile.am3
-rw-r--r--gtk/channel-port.c425
-rw-r--r--gtk/channel-port.h76
-rw-r--r--gtk/map-file4
-rw-r--r--gtk/spice-channel.c4
-rw-r--r--gtk/spice-client.h1
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"