summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc-André Lureau <marcandre.lureau@redhat.com>2010-12-02 18:59:24 +0100
committerMarc-André Lureau <marcandre.lureau@redhat.com>2010-12-05 18:15:22 +0100
commite0e77b800e5ac18f7e970b4ff70dd910972eba71 (patch)
treea327865ccbdea1da4072320b19dcf4e9a253f82a
parenta08d433772c1df26fa159be56cb880c5402ed792 (diff)
gtk: WIP use coroutines and GSocket to connect
-rw-r--r--configure.ac6
-rw-r--r--gtk/Makefile.am4
-rw-r--r--gtk/gio-coroutine.c144
-rw-r--r--gtk/gio-coroutine.h61
-rw-r--r--gtk/spice-channel-priv.h4
-rw-r--r--gtk/spice-channel.c35
-rw-r--r--gtk/spice-session-priv.h4
-rw-r--r--gtk/spice-session.c73
8 files changed, 330 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index 1a5ff6f..2cc32c9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -127,7 +127,7 @@ PKG_CHECK_MODULES(GTK2, gtk+-2.0)
AC_SUBST(GTK2_CFLAGS)
AC_SUBST(GTK2_LIBS)
-PKG_CHECK_MODULES(GLIB2, glib-2.0)
+PKG_CHECK_MODULES(GLIB2, glib-2.0 >= 2.22)
AC_SUBST(GLIB2_CFLAGS)
AC_SUBST(GLIB2_LIBS)
@@ -135,6 +135,10 @@ PKG_CHECK_MODULES(GOBJECT2, gobject-2.0)
AC_SUBST(GOBJECT2_CFLAGS)
AC_SUBST(GOBJECT2_LIBS)
+PKG_CHECK_MODULES(GIO, gio-2.0 >= 2.10.0)
+AC_SUBST(GIO_CFLAGS)
+AC_SUBST(GIO_LIBS)
+
PKG_CHECK_MODULES(PULSE, libpulse libpulse-mainloop-glib)
AC_SUBST(PULSE_CFLAGS)
AC_SUBST(PULSE_LIBS)
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 444465b..9741450 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -39,6 +39,7 @@ SPICE_COMMON_CPPFLAGS = \
$(PULSE_CFLAGS) \
$(GTK2_CFLAGS) \
$(GLIB2_CFLAGS) \
+ $(GIO_CFLAGS) \
$(GOBJECT2_CFLAGS) \
$(SSL_CFLAGS) \
$(NULL)
@@ -82,6 +83,7 @@ libspice_client_glib_la_LDFLAGS = \
libspice_client_glib_la_LIBADD = \
$(GLIB2_LIBS) \
+ $(GIO_LIBS) \
$(GOBJECT2_LIBS) \
$(CELT051_LIBS) \
$(JPEG_LIBS) \
@@ -119,6 +121,8 @@ libspice_client_glib_la_SOURCES = \
generated_marshallers.c \
generated_marshallers1.c \
coroutine.h \
+ gio-coroutine.c \
+ gio-coroutine.h \
\
channel-base.c \
channel-main.h \
diff --git a/gtk/gio-coroutine.c b/gtk/gio-coroutine.c
new file mode 100644
index 0000000..cc90cca
--- /dev/null
+++ b/gtk/gio-coroutine.c
@@ -0,0 +1,144 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2010 Red Hat, Inc.
+ Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+
+ 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.0 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "gio-coroutine.h"
+
+/* Main loop helper functions */
+static gboolean g_io_wait_helper(GSocket *sock G_GNUC_UNUSED,
+ GIOCondition cond,
+ gpointer data)
+{
+ struct coroutine *to = data;
+ coroutine_yieldto(to, &cond);
+ return FALSE;
+}
+
+GIOCondition g_io_wait(GSocket *sock, GIOCondition cond)
+{
+ GIOCondition *ret;
+ GSource *src = g_socket_create_source(sock,
+ cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ NULL);
+ g_source_set_callback(src, (GSourceFunc)g_io_wait_helper, coroutine_self(), NULL);
+ g_source_attach(src, NULL);
+ ret = coroutine_yield(NULL);
+ return *ret;
+}
+
+
+GIOCondition g_io_wait_interruptable(struct wait_queue *wait,
+ GSocket *sock,
+ GIOCondition cond)
+{
+ GIOCondition *ret;
+ gint id;
+
+ wait->context = coroutine_self();
+ GSource *src = g_socket_create_source(sock,
+ cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ NULL);
+ g_source_set_callback(src, (GSourceFunc)g_io_wait_helper,
+ wait->context, NULL);
+ id = g_source_attach(src, NULL);
+ wait->waiting = TRUE;
+ ret = coroutine_yield(NULL);
+ wait->waiting = FALSE;
+
+ if (ret == NULL) {
+ g_source_remove(id);
+ return 0;
+ } else
+ return *ret;
+}
+
+void g_io_wakeup(struct wait_queue *wait)
+{
+ if (wait->waiting)
+ coroutine_yieldto(wait->context, NULL);
+}
+
+
+/*
+ * Call immediately before the main loop does an iteration. Returns
+ * true if the condition we're checking is ready for dispatch
+ */
+static gboolean g_condition_wait_prepare(GSource *src,
+ int *timeout) {
+ struct g_condition_wait_source *vsrc = (struct g_condition_wait_source *)src;
+ *timeout = -1;
+ return vsrc->func(vsrc->data);
+}
+
+/*
+ * Call immediately after the main loop does an iteration. Returns
+ * true if the condition we're checking is ready for dispatch
+ */
+static gboolean g_condition_wait_check(GSource *src)
+{
+ struct g_condition_wait_source *vsrc = (struct g_condition_wait_source *)src;
+ return vsrc->func(vsrc->data);
+}
+
+static gboolean g_condition_wait_dispatch(GSource *src G_GNUC_UNUSED,
+ GSourceFunc cb,
+ gpointer data) {
+ return cb(data);
+}
+
+GSourceFuncs waitFuncs = {
+ .prepare = g_condition_wait_prepare,
+ .check = g_condition_wait_check,
+ .dispatch = g_condition_wait_dispatch,
+};
+
+static gboolean g_condition_wait_helper(gpointer data)
+{
+ struct coroutine *co = (struct coroutine *)data;
+ coroutine_yieldto(co, NULL);
+ return FALSE;
+}
+
+gboolean g_condition_wait(g_condition_wait_func func, gpointer data)
+{
+ GSource *src;
+ struct g_condition_wait_source *vsrc;
+
+ /* Short-circuit check in case we've got it ahead of time */
+ if (func(data)) {
+ return TRUE;
+ }
+
+ /*
+ * Don't have it, so yield to the main loop, checking the condition
+ * on each iteration of the main loop
+ */
+ src = g_source_new(&waitFuncs, sizeof(struct g_condition_wait_source));
+ vsrc = (struct g_condition_wait_source *)src;
+
+ vsrc->func = func;
+ vsrc->data = data;
+ vsrc->co = coroutine_self();
+
+ g_source_attach(src, NULL);
+ g_source_set_callback(src, g_condition_wait_helper, coroutine_self(), NULL);
+ coroutine_yield(NULL);
+ return TRUE;
+}
diff --git a/gtk/gio-coroutine.h b/gtk/gio-coroutine.h
new file mode 100644
index 0000000..ec9faa9
--- /dev/null
+++ b/gtk/gio-coroutine.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2010 Red Hat, Inc.
+ Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+
+ 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.0 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#ifndef __GIO_COROUTINE_H__
+#define __GIO_COROUTINE_H__
+
+#include <gio/gio.h>
+#include "coroutine.h"
+
+G_BEGIN_DECLS
+
+struct wait_queue
+{
+ gboolean waiting;
+ struct coroutine *context;
+};
+
+/*
+ * A special GSource impl which allows us to wait on a certain
+ * condition to be satisfied. This is effectively a boolean test
+ * run on each iteration of the main loop. So whenever a file has
+ * new I/O, or a timer occurs, etc we'll do the check. This is
+ * pretty efficient compared to a normal GLib Idle func which has
+ * to busy wait on a timeout, since our condition is only checked
+ * when some other source's state changes
+ */
+typedef gboolean (*g_condition_wait_func)(gpointer);
+
+struct g_condition_wait_source
+{
+ GSource src;
+ struct coroutine *co;
+ g_condition_wait_func func;
+ gpointer data;
+};
+
+GIOCondition g_io_wait (GSocket *sock, GIOCondition cond);
+gboolean g_condition_wait (g_condition_wait_func func, gpointer data);
+void g_io_wakeup (struct wait_queue *wait);
+GIOCondition g_io_wait_interruptable(struct wait_queue *wait, GSocket *sock, GIOCondition cond);
+
+G_END_DECLS
+
+#endif /* __GIO_COROUTINE_H__ */
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index 09a6138..498b677 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -19,6 +19,7 @@
#define __SPICE_CLIENT_CHANNEL_PRIV_H__
#include <openssl/ssl.h>
+#include "coroutine.h"
/* common/ */
#include "marshallers.h"
@@ -57,6 +58,9 @@ enum spice_channel_state {
struct spice_channel {
SpiceSession *session;
+ struct coroutine coroutine;
+ guint open_id;
+
char name[16];
enum spice_channel_state state;
int socket;
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index e69937e..5fa25a4 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -30,6 +30,8 @@
#include <sys/socket.h>
+#include "gio-coroutine.h"
+
static void spice_channel_send_msg(SpiceChannel *channel, spice_msg_out *out);
static void spice_channel_send_link(SpiceChannel *channel);
@@ -937,10 +939,31 @@ static int tls_verify(int preverify_ok, X509_STORE_CTX *ctx)
return preverify_ok;
}
+static void *spice_channel_coroutine(void *data)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(data);
+ GSocket *socket;
+ /* int ret; */
+ /* struct signal_data s; */
+
+ SPICE_DEBUG("Started background coroutine");
+ socket = spice_session_channel_open_host(c->session, c->tls);
+ if (socket == NULL) /* FIXME: reconnect with tls? */
+ goto cleanup;
+
+ cleanup:
+ SPICE_DEBUG("Doing final channel cleanup");
+ /* vnc_connection_close(conn); */
+ /* vnc_connection_emit_main_context(conn, VNC_DISCONNECTED, &s); */
+ /* g_idle_add(spice_connection_delayed_unref, conn); */
+ return NULL;
+}
+
static gboolean channel_connect(SpiceChannel *channel)
{
spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
int rc, err;
+ struct coroutine *co;
g_return_val_if_fail(c != NULL, FALSE);
@@ -954,6 +977,18 @@ static gboolean channel_connect(SpiceChannel *channel)
}
reconnect:
+ SPICE_DEBUG("Open coroutine starting");
+ c->open_id = 0;
+
+ co = &c->coroutine;
+
+ co->stack_size = 16 << 20;
+ co->entry = spice_channel_coroutine;
+ co->release = NULL;
+
+ coroutine_init(co);
+ coroutine_yieldto(co, channel);
+
if (spice_session_get_client_provided_socket(c->session)) {
if (c->socket == -1) {
g_signal_emit(channel, signals[SPICE_CHANNEL_OPEN_FD], 0, c->tls);
diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h
index 86d0bf4..e43fb6d 100644
--- a/gtk/spice-session-priv.h
+++ b/gtk/spice-session-priv.h
@@ -18,12 +18,16 @@
#ifndef __SPICE_CLIENT_SESSION_PRIV_H__
#define __SPICE_CLIENT_SESSION_PRIV_H__
+#include <glib.h>
+#include <gio/gio.h>
+
G_BEGIN_DECLS
void spice_session_set_connection_id(SpiceSession *session, int id);
int spice_session_get_connection_id(SpiceSession *session);
gboolean spice_session_get_client_provided_socket(SpiceSession *session);
+GSocket* spice_session_channel_open_host(SpiceSession *session, gboolean use_tls);
int spice_session_channel_connect(SpiceSession *session, bool use_tls);
void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel);
void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel);
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
index 71445a7..a60921a 100644
--- a/gtk/spice-session.c
+++ b/gtk/spice-session.c
@@ -15,6 +15,7 @@
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 <gio/gio.h>
#include "spice-client.h"
#include "spice-common.h"
@@ -24,6 +25,8 @@
/* spice/common */
#include "ring.h"
+#include "gio-coroutine.h"
+
struct channel {
SpiceChannel *channel;
RingItem link;
@@ -492,6 +495,76 @@ int spice_session_channel_connect(SpiceSession *session, bool use_tls)
return tcp_connect(&s->ai, NULL, NULL, s->host, port);
}
+static GSocket *channel_connect_socket(GSocketAddress *sockaddr,
+ GError **error)
+{
+ GSocket *sock = g_socket_new(g_socket_address_get_family(sockaddr),
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ error);
+
+ if (!sock)
+ return NULL;
+
+ g_socket_set_blocking(sock, FALSE);
+ if (!g_socket_connect(sock, sockaddr, NULL, error)) {
+ if (*error && (*error)->code == G_IO_ERROR_PENDING) {
+ g_error_free(*error);
+ *error = NULL;
+ SPICE_DEBUG("Socket pending");
+ g_io_wait(sock, G_IO_OUT|G_IO_ERR|G_IO_HUP);
+
+ if (!g_socket_check_connect_result(sock, error)) {
+ SPICE_DEBUG("Failed to connect %s", (*error)->message);
+ g_object_unref(sock);
+ return NULL;
+ }
+ } else {
+ SPICE_DEBUG("Socket error: %s", *error ? (*error)->message : "unknown");
+ g_object_unref(sock);
+ return NULL;
+ }
+ }
+
+ SPICE_DEBUG("Finally connected");
+
+ return sock;
+}
+
+GSocket* spice_session_channel_open_host(SpiceSession *session, gboolean use_tls)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+ GSocketConnectable *addr;
+ GSocketAddressEnumerator *enumerator;
+ GSocketAddress *sockaddr;
+ GError *conn_error = NULL;
+ GSocket *sock = NULL;
+ int port = atoi(use_tls ? s->tls_port : s->port);
+
+ SPICE_DEBUG("Resolving host %s %d", s->host, port);
+
+ addr = g_network_address_new(s->host, port);
+
+ enumerator = g_socket_connectable_enumerate (addr);
+ g_object_unref (addr);
+
+ /* Try each sockaddr until we succeed. Record the first
+ * connection error, but not any further ones (since they'll probably
+ * be basically the same as the first).
+ */
+ while (!sock &&
+ (sockaddr = g_socket_address_enumerator_next(enumerator, NULL, &conn_error))) {
+ SPICE_DEBUG("Trying one socket");
+ g_clear_error(&conn_error);
+ sock = channel_connect_socket(sockaddr, &conn_error);
+ g_object_unref(sockaddr);
+ }
+ g_object_unref(enumerator);
+ g_clear_error(&conn_error);
+ return sock;
+}
+
+
void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel)
{
spice_session *s = SPICE_SESSION_GET_PRIVATE(session);