[Spice-commits] 5 commits - configure.ac doc/reference gtk/channel-port.c gtk/channel-port.h gtk/Makefile.am gtk/map-file gtk/spice-channel.c gtk/spice-channel.h gtk/spice-channel-priv.h gtk/spice-client.h gtk/spicy.c spice-common

Marc-André Lureau elmarco at kemper.freedesktop.org
Wed Dec 5 02:45:19 PST 2012


 configure.ac                         |    1 
 doc/reference/spice-gtk-docs.xml     |    1 
 doc/reference/spice-gtk-sections.txt |   23 +
 doc/reference/spice-gtk.types        |    1 
 gtk/Makefile.am                      |    3 
 gtk/channel-port.c                   |  425 +++++++++++++++++++++++++++++++++++
 gtk/channel-port.h                   |   76 ++++++
 gtk/map-file                         |    6 
 gtk/spice-channel-priv.h             |    4 
 gtk/spice-channel.c                  |  138 +++++++++--
 gtk/spice-channel.h                  |    4 
 gtk/spice-client.h                   |    1 
 gtk/spicy.c                          |  149 ++++++++++++
 spice-common                         |    2 
 14 files changed, 804 insertions(+), 30 deletions(-)

New commits:
commit fcbbc248a8f885f9a9a6e7c47d7aae0c1ab3cd1b
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Fri Nov 30 21:00:08 2012 +0100

    channel: rely on couroutine instead of channel state
    
    We can simplify the channel state callback and simplify a little
    the code by relying on coroutine state instead.

diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index 41a1b34..5e6ec1e 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -68,9 +68,6 @@ struct _SpiceMsgIn {
 enum spice_channel_state {
     SPICE_CHANNEL_STATE_UNCONNECTED = 0,
     SPICE_CHANNEL_STATE_CONNECTING,
-    SPICE_CHANNEL_STATE_LINK_HDR,
-    SPICE_CHANNEL_STATE_LINK_MSG,
-    SPICE_CHANNEL_STATE_AUTH,
     SPICE_CHANNEL_STATE_READY,
     SPICE_CHANNEL_STATE_SWITCHING,
     SPICE_CHANNEL_STATE_MIGRATING,
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index d374204..36fe21e 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -1202,7 +1202,6 @@ static void spice_channel_recv_link_hdr(SpiceChannel *channel)
         goto error;
     }
 
-    c->state = SPICE_CHANNEL_STATE_LINK_MSG;
     return;
 
 error:
@@ -1695,7 +1694,6 @@ static void spice_channel_recv_link_msg(SpiceChannel *channel)
         CHANNEL_DEBUG(channel, "got channel caps %u:0x%X", i, *caps);
     }
 
-    c->state = SPICE_CHANNEL_STATE_AUTH;
     if (!spice_channel_test_common_capability(channel,
             SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION)) {
         CHANNEL_DEBUG(channel, "Server supports spice ticket auth only");
@@ -2012,28 +2010,10 @@ static void spice_channel_iterate_write(SpiceChannel *channel)
 static void spice_channel_iterate_read(SpiceChannel *channel)
 {
     SpiceChannelPrivate *c = channel->priv;
+    g_return_if_fail(c->state != SPICE_CHANNEL_STATE_MIGRATING);
 
-    /* TODO: get rid of state, and use coroutine state */
-    switch (c->state) {
-    case SPICE_CHANNEL_STATE_LINK_HDR:
-        spice_channel_recv_link_hdr(channel);
-        break;
-    case SPICE_CHANNEL_STATE_LINK_MSG:
-        spice_channel_recv_link_msg(channel);
-        break;
-    case SPICE_CHANNEL_STATE_AUTH:
-        spice_channel_recv_auth(channel);
-        break;
-    case SPICE_CHANNEL_STATE_READY:
-    case SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE:
-        spice_channel_recv_msg(channel,
-            (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
-        break;
-    case SPICE_CHANNEL_STATE_MIGRATING:
-        return;
-    default:
-        g_critical("unknown state %d", c->state);
-    }
+    spice_channel_recv_msg(channel,
+        (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
 }
 
 static gboolean wait_migration(gpointer data)
@@ -2066,7 +2046,9 @@ static gboolean spice_channel_iterate(SpiceChannel *channel)
         }
 
         SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
-        ret = g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_IN);
+
+        ret = g_coroutine_socket_wait(&c->coroutine, c->sock,
+            c->state != SPICE_CHANNEL_STATE_MIGRATING ? G_IO_IN : 0);
 
 #ifdef WIN32
         /* FIXME: windows gsocket is buggy, it doesn't return correct condition... */
@@ -2296,8 +2278,10 @@ connected:
                   strerror(errno));
     }
 
-    c->state = SPICE_CHANNEL_STATE_LINK_HDR;
     spice_channel_send_link(channel);
+    spice_channel_recv_link_hdr(channel);
+    spice_channel_recv_link_msg(channel);
+    spice_channel_recv_auth(channel);
 
     while (spice_channel_iterate(channel))
         ;
commit cc045e87ea524b7bfaccd966af929e96e696b036
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Fri Nov 30 01:02:04 2012 +0100

    spicy: demo SpicePort usage
    
    spicy has been modified to recognized 2 different port types to play
    with:
    
    * org.spice.spicy: will connect the port to the current stdin/stdout,
      and can be used as a chardev for the qemu monitor
    
    * org.spice.spicy.break: will send a break event on connect and
      disconnect immediately (exercice the port event and flush)

diff --git a/configure.ac b/configure.ac
index 7c59575..629fd5a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,7 @@ AM_CONDITIONAL([OS_WIN32],[test "$os_win32" = "yes"])
 
 AC_CHECK_HEADERS([sys/ipc.h sys/shm.h])
 AC_CHECK_HEADERS([sys/socket.h netinet/in.h arpa/inet.h])
+AC_CHECK_HEADERS([termios.h])
 
 AC_CHECK_LIBM
 AC_SUBST(LIBM)
diff --git a/gtk/spicy.c b/gtk/spicy.c
index 5758cc4..dd8c970 100644
--- a/gtk/spicy.c
+++ b/gtk/spicy.c
@@ -22,6 +22,9 @@
 #include <glib/gi18n.h>
 
 #include <sys/stat.h>
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
 
 #define GNOME_DESKTOP_USE_UNSTABLE_API 2
 #include "display/gnome-rr.h"
@@ -128,6 +131,7 @@ static char *spicy_title = NULL;
 static GMainLoop     *mainloop = NULL;
 static int           connections = 0;
 static GKeyFile      *keyfile = NULL;
+static SpicePortChannel*stdin_port = NULL;
 static GnomeRRScreen *rrscreen = NULL;
 static GnomeRRConfig *rrsaved = NULL;
 static GnomeRRConfig *rrcurrent = NULL;
@@ -1612,6 +1616,101 @@ static void display_monitors(SpiceChannel *display, GParamSpec *pspec,
     g_clear_pointer(&monitors, g_array_unref);
 }
 
+static void port_write_cb(GObject *source_object,
+                          GAsyncResult *res,
+                          gpointer user_data)
+{
+    SpicePortChannel *port = SPICE_PORT_CHANNEL(source_object);
+    GError *error = NULL;
+
+    spice_port_write_finish(port, res, &error);
+    if (error != NULL)
+        g_warning("%s", error->message);
+    g_clear_error(&error);
+}
+
+static void port_flushed_cb(GObject *source_object,
+                            GAsyncResult *res,
+                            gpointer user_data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(source_object);
+    GError *error = NULL;
+
+    spice_channel_flush_finish(channel, res, &error);
+    if (error != NULL)
+        g_warning("%s", error->message);
+    g_clear_error(&error);
+
+    spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
+}
+
+static gboolean input_cb(GIOChannel *gin, GIOCondition condition, gpointer data)
+{
+    char buf[4096];
+    gsize bytes_read;
+    GIOStatus status;
+
+    if (!(condition & G_IO_IN))
+        return FALSE;
+
+    status = g_io_channel_read_chars(gin, buf, sizeof(buf), &bytes_read, NULL);
+    if (status != G_IO_STATUS_NORMAL)
+        return FALSE;
+
+    if (stdin_port != NULL)
+        spice_port_write_async(stdin_port, buf, bytes_read, NULL, port_write_cb, NULL);
+
+    return TRUE;
+}
+
+static void port_opened(SpiceChannel *channel, GParamSpec *pspec,
+                        spice_connection *conn)
+{
+    SpicePortChannel *port = SPICE_PORT_CHANNEL(channel);
+    gchar *name = NULL;
+    gboolean opened = FALSE;
+
+    g_object_get(channel,
+                 "port-name", &name,
+                 "port-opened", &opened,
+                 NULL);
+
+    g_printerr("port %p %s: %s\n", channel, name, opened ? "opened" : "closed");
+
+    if (opened) {
+        /* only send a break event and disconnect */
+        if (g_strcmp0(name, "org.spice.spicy.break") == 0) {
+            spice_port_event(port, SPICE_PORT_EVENT_BREAK);
+        }
+
+        /* handle the first spicy port and connect it to stdin/out */
+        if (g_strcmp0(name, "org.spice.spicy") == 0 && stdin_port == NULL) {
+            stdin_port = port;
+            goto end;
+        }
+
+        if (port == stdin_port)
+            goto end;
+
+        spice_channel_flush_async(channel, NULL, port_flushed_cb, conn);
+    } else {
+        if (port == stdin_port)
+            stdin_port = NULL;
+    }
+
+end:
+    g_free(name);
+}
+
+static void port_data(SpicePortChannel *port,
+                      gpointer data, int size, spice_connection *conn)
+{
+    if (port != stdin_port)
+        return;
+
+    write(fileno(stdout), data, size);
+}
+
 static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
 {
     spice_connection *conn = data;
@@ -1659,6 +1758,14 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
     if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
         update_auto_usbredir_sensitive(conn);
     }
+
+    if (SPICE_IS_PORT_CHANNEL(channel)) {
+        g_signal_connect(channel, "notify::port-opened",
+                         G_CALLBACK(port_opened), conn);
+        g_signal_connect(channel, "port-data",
+                         G_CALLBACK(port_data), conn);
+        spice_channel_connect(channel);
+    }
 }
 
 static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer data)
@@ -1687,6 +1794,11 @@ static void channel_destroy(SpiceSession *s, SpiceChannel *channel, gpointer dat
         update_auto_usbredir_sensitive(conn);
     }
 
+    if (SPICE_IS_PORT_CHANNEL(channel)) {
+        if (SPICE_PORT_CHANNEL(channel) == stdin_port)
+            stdin_port = NULL;
+    }
+
     conn->channels--;
     if (conn->channels > 0) {
         return;
@@ -1839,6 +1951,40 @@ static void usb_connect_failed(GObject               *object,
     gtk_widget_destroy(dialog);
 }
 
+static void setup_terminal(gboolean reset)
+{
+    int stdinfd = fileno(stdin);
+
+    if (!isatty(stdinfd))
+        return;
+
+#ifdef HAVE_TERMIOS_H
+    static struct termios saved_tios;
+    struct termios tios;
+
+    if (reset)
+        tios = saved_tios;
+    else {
+        tcgetattr(stdinfd, &tios);
+        saved_tios = tios;
+        tios.c_lflag &= ~(ICANON | ECHO);
+    }
+
+    tcsetattr(stdinfd, TCSANOW, &tios);
+#endif
+}
+
+static void watch_stdin(void)
+{
+    int stdinfd = fileno(stdin);
+    GIOChannel *gin;
+
+    setup_terminal(false);
+    gin = g_io_channel_unix_new(stdinfd);
+    g_io_channel_set_flags(gin, G_IO_FLAG_NONBLOCK, NULL);
+    g_io_add_watch(gin, G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, NULL);
+}
+
 int main(int argc, char *argv[])
 {
     GError *error = NULL;
@@ -1929,6 +2075,8 @@ int main(int argc, char *argv[])
     g_free(port);
     g_free(tls_port);
 
+    watch_stdin();
+
     connection_connect(conn);
     if (connections > 0)
         g_main_loop_run(mainloop);
@@ -1950,5 +2098,6 @@ int main(int argc, char *argv[])
 
     g_free(spicy_title);
 
+    setup_terminal(true);
     return 0;
 }
commit a95e44560671356730d2c14064cd2965da9d782e
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Fri Nov 30 01:10:33 2012 +0100

    Add a port channel
    
    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...

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"
commit a1beca2fa13a82c762bc19b25d62f60270fa846d
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Fri Nov 30 00:54:24 2012 +0100

    channel: add flush_async()
    
    Add spice_channel_flush_async() that asynchronously will write all the
    pending channel data.

diff --git a/doc/reference/spice-gtk-sections.txt b/doc/reference/spice-gtk-sections.txt
index 60e287a..87d4225 100644
--- a/doc/reference/spice-gtk-sections.txt
+++ b/doc/reference/spice-gtk-sections.txt
@@ -99,6 +99,8 @@ spice_channel_test_capability
 spice_channel_test_common_capability
 spice_channel_type_to_string
 spice_channel_set_capability
+spice_channel_flush_async
+spice_channel_flush_finish
 <SUBSECTION Standard>
 SPICE_TYPE_CHANNEL_EVENT
 spice_channel_event_get_type
diff --git a/gtk/map-file b/gtk/map-file
index ed2c07f..79ad9d2 100644
--- a/gtk/map-file
+++ b/gtk/map-file
@@ -14,6 +14,8 @@ spice_channel_set_capability;
 spice_channel_test_capability;
 spice_channel_test_common_capability;
 spice_channel_type_to_string;
+spice_channel_flush_async;
+spice_channel_flush_finish;
 spice_client_error_quark;
 spice_cursor_channel_get_type;
 spice_display_channel_get_type;
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index a769ef8..41a1b34 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -134,6 +134,7 @@ struct _SpiceChannelPrivate {
 
     gsize                       total_read_bytes;
     uint64_t                    last_message_serial;
+    GSList                      *flushing;
 };
 
 SpiceMsgIn *spice_msg_in_new(SpiceChannel *channel);
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index 06667d4..c9c4639 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -1971,6 +1971,22 @@ void spice_channel_destroy(SpiceChannel *channel)
     g_object_unref(channel);
 }
 
+/* any context */
+static void spice_channel_flushed(SpiceChannel *channel, gboolean success)
+{
+    SpiceChannelPrivate *c = channel->priv;
+    GSList *l;
+
+    for (l = c->flushing; l != NULL; l = l->next) {
+        GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(l->data);
+        g_simple_async_result_set_op_res_gboolean(result, success);
+        g_simple_async_result_complete_in_idle(result);
+    }
+
+    g_slist_free_full(c->flushing, g_object_unref);
+    c->flushing = NULL;
+}
+
 /* coroutine context */
 static void spice_channel_iterate_write(SpiceChannel *channel)
 {
@@ -1984,6 +2000,8 @@ static void spice_channel_iterate_write(SpiceChannel *channel)
         if (out)
             spice_channel_write_msg(channel, out);
     } while (out);
+
+    spice_channel_flushed(channel, TRUE);
 }
 
 /* coroutine context */
@@ -2444,6 +2462,7 @@ static void channel_reset(SpiceChannel *channel, gboolean migrating)
 
     STATIC_MUTEX_LOCK(c->xmit_queue_lock);
     c->xmit_queue_blocked = TRUE; /* Disallow queuing new messages */
+    gboolean was_empty = g_queue_is_empty(&c->xmit_queue);
     g_queue_foreach(&c->xmit_queue, (GFunc)spice_msg_out_unref, NULL);
     g_queue_clear(&c->xmit_queue);
     if (c->xmit_queue_wakeup_id) {
@@ -2451,6 +2470,7 @@ static void channel_reset(SpiceChannel *channel, gboolean migrating)
         c->xmit_queue_wakeup_id = 0;
     }
     STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
+    spice_channel_flushed(channel, was_empty);
 
     g_array_set_size(c->remote_common_caps, 0);
     g_array_set_size(c->remote_caps, 0);
@@ -2722,3 +2742,83 @@ static void spice_channel_send_migration_handshake(SpiceChannel *channel)
         c->state = SPICE_CHANNEL_STATE_MIGRATING;
     }
 }
+
+/**
+ * spice_channel_flush_async:
+ * @channel: a #SpiceChannel
+ * @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
+ *
+ * Forces an asynchronous write of all user-space buffered data for
+ * the given channel.
+ *
+ * When the operation is finished callback will be called. You can
+ * then call spice_channel_flush_finish() to get the result of the
+ * operation.
+ *
+ * Since: 0.15
+ **/
+void spice_channel_flush_async(SpiceChannel *self, GCancellable *cancellable,
+                               GAsyncReadyCallback callback, gpointer user_data)
+{
+    GSimpleAsyncResult *simple;
+    SpiceChannelPrivate *c;
+    gboolean was_empty;
+
+    g_return_if_fail(SPICE_IS_CHANNEL(self));
+    c = self->priv;
+
+    if (!c->state == SPICE_CHANNEL_STATE_READY) {
+        g_simple_async_report_error_in_idle(G_OBJECT(self), callback, user_data,
+            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+            "The channel is not ready yet");
+        return;
+    }
+
+    simple = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
+                                       spice_channel_flush_async);
+
+    STATIC_MUTEX_LOCK(c->xmit_queue_lock);
+    was_empty = g_queue_is_empty(&c->xmit_queue);
+    STATIC_MUTEX_UNLOCK(c->xmit_queue_lock);
+    if (was_empty) {
+        g_simple_async_result_set_op_res_gboolean(simple, TRUE);
+        g_simple_async_result_complete_in_idle(simple);
+        return;
+    }
+
+    c->flushing = g_slist_append(c->flushing, simple);
+}
+
+/**
+ * spice_channel_flush_finish:
+ * @channel: a #SpiceChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError location to store the error occurring, or %NULL
+ * to ignore.
+ *
+ * Finishes flushing a channel.
+ *
+ * Returns: %TRUE if flush operation succeeded, %FALSE otherwise.
+ * Since: 0.15
+ **/
+gboolean spice_channel_flush_finish(SpiceChannel *self, GAsyncResult *result,
+                                    GError **error)
+{
+    GSimpleAsyncResult *simple;
+
+    g_return_val_if_fail(SPICE_IS_CHANNEL(self), FALSE);
+    g_return_val_if_fail(result != NULL, FALSE);
+
+    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_channel_flush_async), FALSE);
+
+    CHANNEL_DEBUG(self, "flushed finished!");
+    return g_simple_async_result_get_op_res_gboolean(simple);
+}
diff --git a/gtk/spice-channel.h b/gtk/spice-channel.h
index d40b844..52ecd97 100644
--- a/gtk/spice-channel.h
+++ b/gtk/spice-channel.h
@@ -20,6 +20,7 @@
 
 G_BEGIN_DECLS
 
+#include <gio/gio.h>
 #include "spice-types.h"
 #include "spice-glib-enums.h"
 #include "spice-util.h"
@@ -112,7 +113,8 @@ gboolean spice_channel_open_fd(SpiceChannel *channel, int fd);
 void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason);
 gboolean spice_channel_test_capability(SpiceChannel *channel, guint32 cap);
 gboolean spice_channel_test_common_capability(SpiceChannel *channel, guint32 cap);
-
+void spice_channel_flush_async(SpiceChannel *channel, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+gboolean spice_channel_flush_finish(SpiceChannel *channel, GAsyncResult *result, GError **error);
 #ifndef SPICE_DISABLE_DEPRECATED
 SPICE_DEPRECATED
 void spice_channel_set_capability(SpiceChannel *channel, guint32 cap);
commit 857fe180816b2ca6cfce5d1a05bd4e796e0af128
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Wed Dec 5 11:26:10 2012 +0100

    update spice-common

diff --git a/spice-common b/spice-common
index 8543d04..b46d36b 160000
--- a/spice-common
+++ b/spice-common
@@ -1 +1 @@
-Subproject commit 8543d04cd238638ac54912f29a0990915ff51b6d
+Subproject commit b46d36bc1c01ca17a64262e157022fd21ad1e795


More information about the Spice-commits mailing list