[gtk-vnc-devel] [PATCH] VNC tunnel support



Hi,

The following patch implements VNC tunnel support. This patch is large but a lot of it is the gvncviewer support. The main vision I have of tunnels is to allow existing protocols to be tunneled through the VNC session.

In particular, I envision tunneling sound, NBD, and text through this interface. I've also included a QEMU patch that implements support for this. Tunnels are disabled by default and an interface is added to enable it. The reason for this is that I intend to make QEMU tunnel all vc's by default when tunnel support is present.

I'll check this patch in after people have had a chance to look at it.

Regards,

Anthony Liguori
diff --git a/console.h b/console.h
index 1ac74fa..8d58983 100644
--- a/console.h
+++ b/console.h
@@ -123,6 +123,7 @@ void vnc_display_close(DisplayState *ds);
 int vnc_display_open(DisplayState *ds, const char *display);
 int vnc_display_password(DisplayState *ds, const char *password);
 void do_info_vnc(void);
+CharDriverState *qemu_chr_open_vnc(const char *name);
 
 /* x_keymap.c */
 extern uint8_t _translate_keycode(const int key);
diff --git a/vl.c b/vl.c
index deff149..d76a40d 100644
--- a/vl.c
+++ b/vl.c
@@ -3402,6 +3402,9 @@ CharDriverState *qemu_chr_open(const char *filename)
         printf("Unable to open driver: %s\n", p);
         return 0;
     } else
+    if (strstart(filename, "vnc:", &p)) {
+	return qemu_chr_open_vnc(p);
+    } else
 #ifndef _WIN32
     if (strstart(filename, "unix:", &p)) {
 	return qemu_chr_open_tcp(p, 0, 1);
diff --git a/vnc.c b/vnc.c
index 2e1ca34..ad751b8 100644
--- a/vnc.c
+++ b/vnc.c
@@ -125,6 +125,18 @@ enum {
 
 #endif /* CONFIG_VNC_TLS */
 
+typedef struct VNCTunnelState VNCTunnelState;
+
+struct VNCTunnelState
+{
+    CharDriverState chr;
+    VncState *vs;
+    char *name;
+    int16_t id;
+    int connected;
+    VNCTunnelState *next;
+};
+
 struct VncState
 {
     QEMUTimer *timer;
@@ -140,6 +152,7 @@ struct VncState
     int has_resize;
     int has_hextile;
     int has_pointer_type_change;
+    int has_tunnels;
     int absolute;
     int last_x;
     int last_y;
@@ -186,6 +199,8 @@ struct VncState
     pid_t script_pid;
     int script_fd;
 #endif
+
+    VNCTunnelState *tunnel_list;
 };
 
 static VncState *vnc_state; /* needed for info vnc */
@@ -222,6 +237,128 @@ static void vnc_flush(VncState *vs);
 static void vnc_update_client(void *opaque);
 static void vnc_client_read(void *opaque);
 
+/* VNC Tunnel functions */
+
+static void tunnel_find_rsp(VncState *vs, int16_t id);
+static void tunnel_write(VncState *vs, int16_t id, const void *data, size_t size);
+
+static int vnc_tunnel_write(CharDriverState *chr, const uint8_t *buf, int len)
+{
+    VNCTunnelState *s = chr->opaque;
+
+    if (s->vs->csock != -1 && s->vs->has_tunnels) {
+	tunnel_write(s->vs, s->id, buf, len);
+	return len;
+    }
+
+    return 0;
+}
+
+CharDriverState *qemu_chr_open_vnc(const char *name)
+{
+    VNCTunnelState *s;
+    static int16_t max_tunnel_id;
+
+    s = qemu_mallocz(sizeof(VNCTunnelState));
+    if (!s)
+	return NULL;
+
+    s->name = strdup(name);
+    if (s->name == NULL) {
+	qemu_free(s);
+	return NULL;
+    }
+
+    s->vs = vnc_state;
+    s->id = max_tunnel_id++;
+    s->chr.opaque = s;
+    s->chr.chr_write = vnc_tunnel_write;
+    qemu_chr_reset(&s->chr);
+
+    s->next = s->vs->tunnel_list;
+    s->vs->tunnel_list = s;
+
+    return &s->chr;
+}
+
+static VNCTunnelState *find_tunnel_by_tid(VncState *vs, uint16_t tid)
+{
+    VNCTunnelState *s;
+
+    for (s = vs->tunnel_list; s; s = s->next) {
+	if (s->id == tid)
+	    return s;
+    }
+
+    return NULL;
+}
+
+static void vnc_tunnel_open_request(VncState *vs,
+				    const char *name, size_t size)
+{
+    VNCTunnelState *s;
+
+    for (s = vs->tunnel_list; s; s = s->next) {
+	if (strlen(s->name) == size && memcmp(s->name, name, size) == 0)
+	    break;
+    }
+
+    if (s == NULL || s->connected) {
+	tunnel_find_rsp(vs, -1);
+    } else {
+	s->connected = 1;
+	tunnel_find_rsp(vs, s->id);
+	qemu_chr_reset(&s->chr);
+    }
+}
+
+static void vnc_server_tunnel_shutdown(VncState *vs, VNCTunnelState *s)
+{
+    vnc_write_u8(vs, 255);
+    vnc_write_u8(vs, 1);
+    vnc_write_u16(vs, s->id);
+    vnc_flush(vs);
+}
+
+static void vnc_tunnel_client_shutdown(VncState *vs, uint16_t tid)
+{
+    VNCTunnelState *s;
+
+    s = find_tunnel_by_tid(vs, tid);
+    if (s) {
+	s->connected = 0;
+	vnc_server_tunnel_shutdown(vs, s);
+    }
+}
+
+static void vnc_tunnel_client_send(VncState *vs, int16_t tid,
+				   const void *data, size_t size)
+{
+    VNCTunnelState *s;
+    CharDriverState *chr;
+
+    s = find_tunnel_by_tid(vs, tid);
+    if (s == NULL || !s->connected)
+	return;
+
+    chr = (CharDriverState *)s;
+
+    if (qemu_chr_can_read(chr))
+	qemu_chr_read(chr, (char *)data, size);
+}
+
+static void vnc_close_tunnels(VncState *vs)
+{
+    VNCTunnelState *s;
+
+    for (s = vs->tunnel_list; s; s = s->next) {
+	if (s->connected) {
+	    /* FIXME signal disconnection */
+	    s->connected = 0;
+	}
+    }
+}
+
 static inline void vnc_set_bit(uint32_t *d, int k)
 {
     d[k >> 5] |= 1 << (k & 0x1f);
@@ -655,6 +792,7 @@ static int vnc_client_io_error(VncState *vs, int ret, int last_errno)
 	buffer_reset(&vs->input);
 	buffer_reset(&vs->output);
 	vs->need_update = 0;
+	vnc_close_tunnels(vs);
 #if CONFIG_VNC_TLS
 	if (vs->tls_session) {
 	    gnutls_deinit(vs->tls_session);
@@ -1088,6 +1226,33 @@ static void send_ext_key_event_ack(VncState *vs)
     vnc_flush(vs);
 }
 
+static void send_tunnels_ack(VncState *vs)
+{
+    vnc_write_u8(vs, 0);
+    vnc_write_u8(vs, 0);
+    vnc_write_u16(vs, 1);
+    vnc_framebuffer_update(vs, 0, 0, vs->ds->width, vs->ds->height, -259);
+    vnc_flush(vs);
+}
+
+static void tunnel_find_rsp(VncState *vs, int16_t id)
+{
+    vnc_write_u8(vs, 255);
+    vnc_write_u8(vs, 0); /* find response */
+    vnc_write_u16(vs, id);
+    vnc_flush(vs);
+}
+
+static void tunnel_write(VncState *vs, int16_t id, const void *data, size_t size)
+{
+    vnc_write_u8(vs, 255);
+    vnc_write_u8(vs, 2); /* tunnel server send */
+    vnc_write_u16(vs, id);
+    vnc_write_u32(vs, size);
+    vnc_write(vs, data, size);
+    vnc_flush(vs);
+}
+
 static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
 {
     int i;
@@ -1095,6 +1260,7 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
     vs->has_hextile = 0;
     vs->has_resize = 0;
     vs->has_pointer_type_change = 0;
+    vs->has_tunnels = 0;
     vs->absolute = -1;
     vs->ds->dpy_copy = NULL;
 
@@ -1118,6 +1284,9 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
 	case -258:
 	    send_ext_key_event_ack(vs);
 	    break;
+	case -259:
+	    vs->has_tunnels = 1;
+	    send_tunnels_ack(vs);
 	default:
 	    break;
 	}
@@ -1283,6 +1452,31 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
 	    ext_key_event(vs, read_u16(data, 2),
 			  read_u32(data, 4), read_u32(data, 8));
 	    break;
+	case 1:
+	    if (len < 4)
+		return 4;
+
+	    if (len == 4)
+		return 4 + read_u16(data, 2);
+
+	    vnc_tunnel_open_request(vs, data + 4, read_u16(data, 2));
+	    break;
+	case 2:
+	    if (len < 4)
+		return 4;
+
+	    vnc_tunnel_client_shutdown(vs, read_u16(data, 2));
+	    break;
+	case 3:
+	    if (len < 8)
+		return 8;
+
+	    if (len == 8)
+		return 8 + read_u32(data, 4);
+
+	    vnc_tunnel_client_send(vs, read_u16(data, 2),
+				   data + 8, read_u32(data, 4));
+	    break;
 	default:
 	    printf("Msg: %d\n", read_u16(data, 0));
 	    vnc_client_error(vs);
@@ -2117,6 +2311,7 @@ void vnc_display_close(DisplayState *ds)
 	vs->script_pid = -1;
     }
 #endif
+    vnc_close_tunnels(vs);
     vs->auth = VNC_AUTH_INVALID;
 #if CONFIG_VNC_TLS
     vs->subauth = VNC_AUTH_INVALID;
diff -r 1e6a5272ef00 configure.ac
--- a/configure.ac	Wed Feb 06 22:20:39 2008 -0600
+++ b/configure.ac	Thu Feb 07 19:51:08 2008 -0600
@@ -13,6 +13,7 @@ PYGTK_REQUIRED=2.0.0
 PYGTK_REQUIRED=2.0.0
 GTKGLEXT_REQUIRED=1.2.0
 VIEW_REQUIRED=0.6.0
+VTE_REQUIRED=0.16.0
 GTHREAD_REQUIRED=2.0.0
 PYTHON_REQUIRED=2.4
 
@@ -118,6 +119,23 @@ AC_DEFINE_UNQUOTED(WITH_LIBVIEW,[$WITH_L
 AC_DEFINE_UNQUOTED(WITH_LIBVIEW,[$WITH_LIBVIEW], [Whether to use libview])
 AC_SUBST(VIEW_CFLAGS)
 AC_SUBST(VIEW_LIBS)
+
+AC_ARG_WITH(libvte,
+[  --with-libvte           enable libvte support in gvncviewer],
+[case "${withval}" in
+   yes|no) ;;
+   *)	   AC_MSG_ERROR([bad value ${withval} for libvte option]) ;;
+ esac],[withval=yes])
+
+WITH_LIBVTE=0
+if test "${withval}" = "yes"; then
+  PKG_CHECK_MODULES(VTE, vte >= $VTE_REQUIRED,
+		   [WITH_LIBVTE=1], [WITH_LIBVTE=0])
+  VTE_LIBS="$VTE_LIBS -lutil"
+fi
+AC_DEFINE_UNQUOTED(WITH_LIBVTE,[$WITH_LIBVTE], [Whether to use libvte])
+AC_SUBST(VTE_CFLAGS)
+AC_SUBST(VTE_LIBS)
 
 PKG_CHECK_MODULES(GNUTLS, gnutls >= $GNUTLS_REQUIRED)
 AC_SUBST(GNUTLS_CFLAGS)
diff -r 1e6a5272ef00 examples/Makefile.am
--- a/examples/Makefile.am	Wed Feb 06 22:20:39 2008 -0600
+++ b/examples/Makefile.am	Thu Feb 07 19:51:08 2008 -0600
@@ -7,8 +7,8 @@ endif
 
 gvncviewer_SOURCES = gvncviewer.c
 gvncviewer_LDADD = ../src/libgtk-vnc-1.0.la @GTK_CFLAGS@ @GTKGLEXT_LIBS@ \
-		   @VIEW_LIBS@
+		   @VIEW_LIBS@ @VTE_LIBS@
 gvncviewer_CFLAGS = @GTK_CFLAGS@ @GTKGLEXT_CFLAGS@ @WARNING_CFLAGS@ \
-		    @VIEW_CFLAGS@ -I$(top_srcdir)/src/
+		    @VIEW_CFLAGS@ @VTE_CFLAGS@ -I$(top_srcdir)/src/
 
 EXTRA_DIST = gvncviewer.py
diff -r 1e6a5272ef00 examples/gvncviewer.c
--- a/examples/gvncviewer.c	Wed Feb 06 22:20:39 2008 -0600
+++ b/examples/gvncviewer.c	Thu Feb 07 19:51:08 2008 -0600
@@ -14,6 +14,27 @@
 #if WITH_LIBVIEW
 #include <libview/autoDrawer.h>
 #endif
+
+#if WITH_LIBVTE
+#include <vte/vte.h>
+#endif
+
+#include <pty.h>
+#include <termios.h>
+#include <unistd.h>
+#include <errno.h>
+
+static GtkWidget *notebook;
+
+struct tunnel_state
+{
+	VncTunnel *tunnel;
+	GtkWidget *notebook;
+	GtkWidget *terminal;
+	int slave;
+	gchar *name;
+	int io_watch;
+};
 
 #if WITH_GTKGLEXT
 #include <gtk/gtkgl.h>
@@ -257,6 +278,187 @@ static gboolean window_state_event(GtkWi
 }
 #endif
 
+static void do_tunnel_init(GtkWidget *widget G_GNUC_UNUSED, GtkWidget *tunnel)
+{
+	gtk_widget_set_sensitive(tunnel, TRUE);
+}
+
+static void do_close_tab(GtkButton *button, struct tunnel_state *s)
+{
+	gint page_no;
+
+	g_object_unref(s->tunnel);
+	s->tunnel = NULL;
+
+	g_source_remove(s->io_watch);
+	s->io_watch = -1;
+
+	page_no = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook),
+					s->terminal);
+	gtk_notebook_remove_page(GTK_NOTEBOOK(s->notebook), page_no);
+
+	g_free(s->name);
+	g_free(s);
+}
+
+static gboolean do_slave_input(GIOChannel *channel, GIOCondition cond, gpointer data)
+{
+	struct tunnel_state *s = data;
+	char buffer[1024];
+	ssize_t len;
+
+	do {
+		len = read(s->slave, buffer, sizeof(buffer));
+	} while (len == -1 && errno == EINTR);
+
+	if (len > 0) {
+		vnc_tunnel_send(s->tunnel, buffer, len);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void do_tunnel_connected(VncTunnel *tunnel, struct tunnel_state *s)
+{
+	GtkWidget *label, *hbox, *button, *image;
+	GtkWidget *terminal;
+	int master, slave, err;
+	struct termios term;
+	GIOChannel *chan;
+
+	hbox = gtk_hbox_new(FALSE, 0);
+	label = gtk_label_new(s->name);
+	button = gtk_button_new();
+	image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+#if WITH_LIBVTE
+	terminal = vte_terminal_new();
+#else
+	terminal = NULL;
+#endif
+
+	err = openpty(&master, &slave, NULL, NULL, NULL);
+	g_assert(err != -1);
+
+	err = tcgetattr(slave, &term);
+	g_assert(err != -1);
+	cfmakeraw(&term);
+	err = tcsetattr(slave, 0, &term);
+	g_assert(err != -1);
+
+	err = tcgetattr(master, &term);
+	g_assert(err != -1);
+	cfmakeraw(&term);
+	err = tcsetattr(master, 0, &term);
+	g_assert(err != -1);
+
+#if WITH_LIBVTE
+	vte_terminal_set_pty(VTE_TERMINAL(terminal), master);
+	vte_terminal_set_size(VTE_TERMINAL(terminal), 1, 1);
+#endif
+
+	gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+	gtk_button_set_image(GTK_BUTTON(button), image);
+	gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
+
+	gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
+
+	gtk_widget_show_all(hbox);
+	gtk_widget_show(terminal);
+
+	gtk_signal_connect(GTK_OBJECT(button), "clicked",
+			   GTK_SIGNAL_FUNC(do_close_tab), s);
+
+	gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), terminal, hbox);
+	s->terminal = terminal;
+	s->slave = slave;
+
+	chan = g_io_channel_unix_new(slave);
+	s->io_watch = g_io_add_watch(chan, G_IO_IN, do_slave_input, s);
+}
+
+static void do_tunnel_io_watch(VncTunnel *tunnel, struct tunnel_state *s)
+{
+	guchar *data;
+	guint size;
+
+	data = vnc_tunnel_recv(tunnel, &size);
+	if (data) {
+		ssize_t ret;
+		ret = write(s->slave, data, size);
+		g_assert(ret == size);
+	}
+
+	g_free(data);
+}
+
+static void do_tunnel_shutdown(VncTunnel *tunnel, struct tunnel_state *s)
+{
+	g_free(s->name);
+	g_free(s);
+}
+
+static void do_open_tunnel(GtkWidget *menu, VncDisplay *vnc)
+{
+	GtkWidget *label, *entry, *vbox, *dialog;
+	int response;
+
+	dialog = gtk_dialog_new_with_buttons("Enter tunnel name",
+					     NULL,
+					     0,
+					     GTK_STOCK_CANCEL,
+					     GTK_RESPONSE_CANCEL,
+					     GTK_STOCK_OK,
+					     GTK_RESPONSE_OK,
+					     NULL);
+	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+	label = gtk_label_new("Tunnel Name:");
+	entry = gtk_entry_new();
+	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+
+	vbox = gtk_bin_get_child(GTK_BIN(dialog));
+
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), entry, TRUE, TRUE, 0);
+
+	gtk_widget_show_all(dialog);
+	response = gtk_dialog_run(GTK_DIALOG(dialog));
+	gtk_widget_hide(GTK_WIDGET(dialog));
+
+	if (response == GTK_RESPONSE_OK) {
+		struct tunnel_state *s;
+		const gchar *name;
+
+		name = gtk_entry_get_text(GTK_ENTRY(entry));
+
+		s = g_new0(struct tunnel_state, 1);
+		s->name = g_strdup(name);
+		s->notebook = notebook;
+
+		s->tunnel = vnc_display_tunnel_open(vnc, name);
+		g_signal_connect(G_OBJECT(s->tunnel), "vnc-tunnel-connected",
+				 G_CALLBACK(do_tunnel_connected), s);
+		g_signal_connect(G_OBJECT(s->tunnel), "vnc-tunnel-io-watch",
+				 G_CALLBACK(do_tunnel_io_watch), s);
+		g_signal_connect(G_OBJECT(s->tunnel), "vnc-tunnel-shutdown",
+				 G_CALLBACK(do_tunnel_shutdown), s);
+		
+	}
+
+	gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static void do_show_tabs(GtkNotebook *notebook, GtkWidget *child,
+			 guint page_num, gpointer data)
+{
+	if (gtk_notebook_get_n_pages(notebook) == 1)
+		gtk_notebook_set_show_tabs(notebook, FALSE);
+	else
+		gtk_notebook_set_show_tabs(notebook, TRUE);
+}
+
 int main(int argc, char **argv)
 {
 	char port[1024], hostname[1024];
@@ -272,6 +474,9 @@ int main(int argc, char **argv)
 	GtkWidget *cab;
 	GtkWidget *fullscreen;
 	GtkWidget *scaling;
+	GtkWidget *sep;
+	GtkWidget *tunnel;
+	GtkWidget *label;
 
 	if (argc != 2 && argc != 3) {
 		fprintf(stderr, "Usage: %s hostname[:display] [password]\n",
@@ -292,6 +497,7 @@ int main(int argc, char **argv)
 	layout = gtk_vbox_new(FALSE, 0);
 #endif
 	menubar = gtk_menu_bar_new();
+	notebook = gtk_notebook_new();
 
 	gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
 
@@ -319,19 +525,31 @@ int main(int argc, char **argv)
 
 	fullscreen = gtk_check_menu_item_new_with_mnemonic("_Full Screen");
 	scaling = gtk_check_menu_item_new_with_mnemonic("OpenGL _Scaling");
+	sep = gtk_separator_menu_item_new();
+	tunnel = gtk_menu_item_new_with_mnemonic("Open _Tunnel..");
 
 	gtk_menu_append(GTK_MENU(submenu), fullscreen);
 	gtk_menu_append(GTK_MENU(submenu), scaling);
+	gtk_menu_append(GTK_MENU(submenu), sep);
+	gtk_menu_append(GTK_MENU(submenu), tunnel);
 
 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(view), submenu);
+
+	gtk_widget_set_sensitive(tunnel, FALSE);
+
+	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
+	gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
+
+	label = gtk_label_new("Display");
+	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vnc, label);
 
 #if WITH_LIBVIEW
 	ViewAutoDrawer_SetActive(VIEW_AUTODRAWER(layout), FALSE);
 	ViewOvBox_SetOver(VIEW_OV_BOX(layout), menubar);
-	ViewOvBox_SetUnder(VIEW_OV_BOX(layout), vnc);
+	ViewOvBox_SetUnder(VIEW_OV_BOX(layout), notebook);
 #else
 	gtk_box_pack_start(GTK_BOX(layout), menubar, FALSE, TRUE, 0);
-	gtk_box_pack_start(GTK_BOX(layout), vnc, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(layout), notebook, TRUE, TRUE, 0);
 #endif
 	gtk_container_add(GTK_CONTAINER(window), layout);
 	gtk_widget_realize(vnc);
@@ -352,6 +570,7 @@ int main(int argc, char **argv)
 	vnc_display_set_keyboard_grab(VNC_DISPLAY(vnc), TRUE);
 	vnc_display_set_pointer_grab(VNC_DISPLAY(vnc), TRUE);
 	//vnc_display_set_pointer_local(VNC_DISPLAY(vnc), TRUE);
+	vnc_display_set_tunnels(VNC_DISPLAY(vnc), TRUE);
 
 	gtk_signal_connect(GTK_OBJECT(window), "delete-event",
 			   GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
@@ -393,6 +612,16 @@ int main(int argc, char **argv)
 	gtk_signal_connect(GTK_OBJECT(window), "window-state-event",
 			   GTK_SIGNAL_FUNC(window_state_event), layout);
 #endif
+	gtk_signal_connect(GTK_OBJECT(tunnel), "activate",
+			   GTK_SIGNAL_FUNC(do_open_tunnel), vnc);
+#if WITH_LIBVTE
+	gtk_signal_connect(GTK_OBJECT(vnc), "vnc-tunnel-init",
+			   GTK_SIGNAL_FUNC(do_tunnel_init), tunnel);
+#endif
+	gtk_signal_connect(GTK_OBJECT(notebook), "page-added",
+			   GTK_SIGNAL_FUNC(do_show_tabs), NULL);
+	gtk_signal_connect(GTK_OBJECT(notebook), "page-removed",
+			   GTK_SIGNAL_FUNC(do_show_tabs), NULL);
 
 	gtk_main();
 
diff -r 1e6a5272ef00 src/Makefile.am
--- a/src/Makefile.am	Wed Feb 06 22:20:39 2008 -0600
+++ b/src/Makefile.am	Thu Feb 07 19:51:08 2008 -0600
@@ -13,7 +13,7 @@ libgtk_vnc_1_0_la_LDFLAGS = -Wl,--versio
                             -version-info 0:1:0
 
 gtk_vnc_includedir = $(includedir)/gtk-vnc-1.0/
-gtk_vnc_include_HEADERS = vncdisplay.h
+gtk_vnc_include_HEADERS = vncdisplay.h vnctunnel.h
 
 libgtk_vnc_1_0_la_SOURCES = blt.h blt1.h \
 	coroutine.h \
@@ -21,6 +21,7 @@ libgtk_vnc_1_0_la_SOURCES = blt.h blt1.h
 	gvnc.h gvnc.c \
 	vncdisplay.h vncdisplay.c \
         vncmarshal.h vncmarshal.c \
+	vnctunnel.h vnctunnel.c \
 	x_keymap.h x_keymap.c vnc_keycodes.h \
 	utils.h
 
@@ -56,8 +57,8 @@ CODEGENDIR = $(shell pkg-config --variab
 CODEGENDIR = $(shell pkg-config --variable=codegendir pygtk-2.0)
 DEFSDIR = $(shell pkg-config --variable=defsdir pygtk-2.0)
 
-vnc.defs: vncdisplay.h
-	$(PYTHON) $(CODEGENDIR)/h2def.py $< > $@
+vnc.defs: vncdisplay.h vnctunnel.h
+	$(PYTHON) $(CODEGENDIR)/h2def.py $^ > $@
 
 vncmodule.defs.c: vnc.override vnc.defs
 	pygtk-codegen-2.0 --prefix gtkvnc \
diff -r 1e6a5272ef00 src/gvnc.c
--- a/src/gvnc.c	Wed Feb 06 22:20:39 2008 -0600
+++ b/src/gvnc.c	Thu Feb 07 19:51:08 2008 -0600
@@ -42,6 +42,7 @@
 #include <gdk/gdkkeysyms.h>
 
 #include "vnc_keycodes.h"
+#include "vnctunnel.h"
 
 struct wait_queue
 {
@@ -166,6 +167,10 @@ struct gvnc
 	int zrle_pi_bits;
 
 	gboolean has_ext_key_event;
+
+	gboolean has_tunnels;
+	GList *pending_tunnels;
+	GList *open_tunnels;
 };
 
 #define nibhi(a) (((a) >> 4) & 0x0F)
@@ -926,6 +931,56 @@ gboolean gvnc_pointer_event(struct gvnc 
 	return !gvnc_has_error(gvnc);	
 }
 
+gboolean gvnc_tunnel_open_request(struct gvnc *gvnc, const char *name)
+{
+	size_t size = strlen(name);
+
+	if (!gvnc->has_tunnels) {
+		printf("open request when tunnel support is not available\n");
+		gvnc->has_error = TRUE;
+		return FALSE;
+	}
+
+	gvnc_buffered_write_u8(gvnc, 255);
+	gvnc_buffered_write_u8(gvnc, 1);
+	gvnc_buffered_write_u16(gvnc, size);
+	gvnc_buffered_write(gvnc, name, size);
+	gvnc_buffered_flush(gvnc);
+	return !gvnc_has_error(gvnc);
+}
+
+gboolean gvnc_tunnel_client_shutdown(struct gvnc *gvnc, uint16_t id)
+{
+	if (!gvnc->has_tunnels) {
+		printf("shutdown request when tunnel support is not available\n");
+		gvnc->has_error = TRUE;
+		return FALSE;
+	}
+
+	gvnc_buffered_write_u8(gvnc, 255);
+	gvnc_buffered_write_u8(gvnc, 2);
+	gvnc_buffered_write_u16(gvnc, id);
+	return !gvnc_has_error(gvnc);
+}
+
+gboolean gvnc_tunnel_client_send(struct gvnc *gvnc, uint16_t id,
+				 const void *data, size_t length)
+{
+	if (!gvnc->has_tunnels) {
+		printf("send request when tunnel support is not available\n");
+		gvnc->has_error = TRUE;
+		return FALSE;
+	}
+
+	gvnc_buffered_write_u8(gvnc, 255);
+	gvnc_buffered_write_u8(gvnc, 3);
+	gvnc_buffered_write_u16(gvnc, id);
+	gvnc_buffered_write_u32(gvnc, length);
+	gvnc_buffered_write(gvnc, data, length);
+	gvnc_buffered_flush(gvnc);
+	return !gvnc_has_error(gvnc);
+}
+ 
 gboolean gvnc_client_cut_text(struct gvnc *gvnc,
 			      const void *data, size_t length)
 {
@@ -1761,6 +1816,114 @@ static void gvnc_pointer_type_change(str
 		gvnc->has_error = TRUE;
 }
 
+static void gvnc_tunnels_available(struct gvnc *gvnc)
+{
+	if (gvnc->has_error || !gvnc->ops.tunnels_available)
+		return;
+
+	if (!gvnc->ops.tunnels_available(gvnc->ops_data))
+		gvnc->has_error = TRUE;
+}
+
+VncTunnel *gvnc_tunnel_open(struct gvnc *gvnc, const char *name)
+{
+	VncTunnel *tunnel;
+
+	if (!gvnc_tunnel_open_request(gvnc, name))
+		return NULL;
+
+	tunnel = vnc_tunnel_new(gvnc);
+	gvnc->pending_tunnels = g_list_append(gvnc->pending_tunnels, tunnel);
+
+	return tunnel;
+}
+
+void gvnc_tunnel_remove(struct gvnc *gvnc, VncTunnel *tunnel)
+{
+	gvnc->pending_tunnels = g_list_remove(gvnc->pending_tunnels, tunnel);
+	gvnc->open_tunnels = g_list_remove(gvnc->open_tunnels, tunnel);
+}
+
+static gint tunnel_cmp(gconstpointer a, gconstpointer b)
+{
+	VncTunnel *lhs = VNC_TUNNEL(a);
+	uint16_t *ptid = (void *)b;
+	return vnc_tunnel_get_tid(lhs) - *ptid;
+}
+
+static VncTunnel *find_tunnel_by_tid(struct gvnc *gvnc, uint16_t tid)
+{
+	GList *obj;
+	obj = g_list_find_custom(gvnc->open_tunnels, &tid, tunnel_cmp);
+	if (obj)
+		return VNC_TUNNEL(obj->data);
+	return NULL;
+}
+
+static void gvnc_tunnel_open_response(struct gvnc *gvnc, uint16_t id)
+{
+	VncTunnel *tunnel;
+
+	if (gvnc->has_error)
+		return;
+
+	GVNC_DEBUG("Tunnel open response\n");
+
+	if (!gvnc->pending_tunnels) {
+		printf("open response with no pending tunnels\n");
+		return;
+	}
+
+	tunnel = gvnc->pending_tunnels->data;
+	gvnc->pending_tunnels = g_list_remove(gvnc->pending_tunnels, tunnel);
+	if ((int16_t)id == -1)
+		vnc_tunnel_feed_shutdown(tunnel);
+	else {
+		vnc_tunnel_set_tid(tunnel, id);
+		gvnc->open_tunnels = g_list_prepend(gvnc->open_tunnels,
+						    tunnel);
+	}
+}
+
+static void gvnc_tunnel_server_shutdown(struct gvnc *gvnc, uint16_t id)
+{
+	VncTunnel *tunnel;
+
+	if (gvnc->has_error)
+		return;
+
+	GVNC_DEBUG("Tunnel server shutdown\n");
+
+	tunnel = find_tunnel_by_tid(gvnc, id);
+	if (!tunnel) {
+		printf("shutdown request with no open tunnel\n");
+		return;
+	}
+
+	vnc_tunnel_feed_shutdown(tunnel);
+	gvnc->open_tunnels = g_list_remove(gvnc->open_tunnels, tunnel);
+}
+
+static void gvnc_tunnel_server_send(struct gvnc *gvnc, uint16_t id,
+				    const void *data, size_t len)
+{
+	VncTunnel *tunnel;
+
+	if (gvnc->has_error)
+		return;
+
+	GVNC_DEBUG("Tunnel server send\n");
+
+	tunnel = find_tunnel_by_tid(gvnc, id);
+	if (!tunnel) {
+		GVNC_DEBUG("Unable to find tunnel %d\n", id);
+		printf("unable to find tunnel for send\n");
+		return;
+	}
+
+	vnc_tunnel_feed_recv(tunnel, data, len);
+}
+
 static void gvnc_rich_cursor_blt(struct gvnc *gvnc, uint8_t *pixbuf,
 				 uint8_t *image, uint8_t *mask,
 				 int pitch, uint16_t width, uint16_t height)
@@ -1910,6 +2073,10 @@ static void gvnc_framebuffer_update(stru
 		break;
 	case GVNC_ENCODING_EXT_KEY_EVENT:
 		gvnc->has_ext_key_event = TRUE;
+		break;
+	case GVNC_ENCODING_TUNNEL_IO:
+		gvnc->has_tunnels = TRUE;
+		gvnc_tunnels_available(gvnc);
 		break;
 	default:
 		GVNC_DEBUG("Received an unknown encoding type: %d\n", etype);
@@ -2011,6 +2178,42 @@ gboolean gvnc_server_message(struct gvnc
 		gvnc_server_cut_text(gvnc, data, n_text);
 		free(data);
 	}	break;
+	case 255: /* ExtServerMessage */
+		switch (gvnc_read_u8(gvnc)) {
+		case 0: /* TunnelOpenResponse */
+			gvnc_tunnel_open_response(gvnc, gvnc_read_u16(gvnc));
+			break;
+		case 1: /* TunnelServerShutdown */
+			gvnc_tunnel_server_shutdown(gvnc, gvnc_read_u16(gvnc));
+			break;
+		case 2: { /* TunnelServerSend */
+			uint8_t *data;
+			uint16_t id;
+			size_t size;
+
+			id = gvnc_read_u16(gvnc);
+			size = gvnc_read_u32(gvnc);
+			if ((size + 1) < size || (size + 1) < 1) {
+				gvnc->has_error = TRUE;
+				break;
+			}
+
+			data = malloc(size + 1);
+			if (data == NULL) {
+				gvnc->has_error = TRUE;
+				break;
+			}
+			gvnc_read(gvnc, data, size);
+			data[size] = 0;
+
+			gvnc_tunnel_server_send(gvnc, id, data, size);
+			free(data);
+		}	break;
+		default:
+			gvnc->has_error = TRUE;
+			break;
+		}
+		break;
 	default:
 		GVNC_DEBUG("Received an unknown message: %u\n", msg);
 		gvnc->has_error = TRUE;
diff -r 1e6a5272ef00 src/gvnc.h
--- a/src/gvnc.h	Wed Feb 06 22:20:39 2008 -0600
+++ b/src/gvnc.h	Thu Feb 07 19:51:08 2008 -0600
@@ -3,6 +3,8 @@
 
 #include <glib.h>
 #include <stdint.h>
+
+#include "vnctunnel.h"
 
 struct gvnc;
 
@@ -24,6 +26,7 @@ struct gvnc_ops
 	gboolean (*auth_unsupported)(void *, unsigned int);
 	gboolean (*render_jpeg)(void *, rgb24_render_func *render, void *,
 				int, int, int, int, uint8_t *, int);
+	gboolean (*tunnels_available)(void *);
 };
 
 struct gvnc_pixel_format
@@ -90,6 +93,7 @@ typedef enum {
 
 	GVNC_ENCODING_POINTER_CHANGE = -257,
 	GVNC_ENCODING_EXT_KEY_EVENT = -258,
+	GVNC_ENCODING_TUNNEL_IO = -259,
 } gvnc_encoding;
 
 typedef enum {
@@ -153,6 +157,13 @@ gboolean gvnc_key_event(struct gvnc *gvn
 gboolean gvnc_key_event(struct gvnc *gvnc, uint8_t down_flag,
 			uint32_t key, uint16_t scancode);
 
+gboolean gvnc_tunnel_open_request(struct gvnc *gvnc, const char *name);
+
+gboolean gvnc_tunnel_client_shutdown(struct gvnc *gvnc, uint16_t id);
+
+gboolean gvnc_tunnel_client_send(struct gvnc *gvnc, uint16_t id,
+				 const void *data, size_t length);
+
 gboolean gvnc_framebuffer_update_request(struct gvnc *gvnc,
 					 uint8_t incremental,
 					 uint16_t x, uint16_t y,
@@ -176,6 +187,10 @@ int gvnc_get_height(struct gvnc *gvnc);
 /* HACK this is temporary */
 gboolean gvnc_using_raw_keycodes(struct gvnc *gvnc);
 
+VncTunnel *gvnc_tunnel_open(struct gvnc *gvnc, const char *name);
+
+void gvnc_tunnel_remove(struct gvnc *gvnc, VncTunnel *tunnel);
+
 #endif
 /*
  * Local variables:
diff -r 1e6a5272ef00 src/libgtk-vnc_sym.version
--- a/src/libgtk-vnc_sym.version	Wed Feb 06 22:20:39 2008 -0600
+++ b/src/libgtk-vnc_sym.version	Thu Feb 07 19:51:08 2008 -0600
@@ -33,6 +33,14 @@
     vnc_display_set_scaling;
     vnc_display_force_grab;
 
+    vnc_display_tunnel_open;
+    vnc_display_set_tunnels;
+
+    vnc_tunnel_get_type;
+    vnc_tunnel_recv;
+    vnc_tunnel_send;
+    vnc_tunnel_new;
+
   local:
       *;
 };
diff -r 1e6a5272ef00 src/vnc.override
--- a/src/vnc.override	Wed Feb 06 22:20:39 2008 -0600
+++ b/src/vnc.override	Thu Feb 07 19:51:08 2008 -0600
@@ -3,14 +3,20 @@ headers
 #include <Python.h>
 #include "pygobject.h"
 #include "vncdisplay.h"
+#include "vnctunnel.h"
 %%
 modulename gtkvnc
 %%
 import gtk.DrawingArea as PyGtkDrawingArea_Type
+import gobject.GObject as PyGObject_Type
 %%
 ignore-glob
   *_get_type
   vnc_display_send_keys_ex
+  vnc_tunnel_get_tid
+  vnc_tunnel_set_tid
+  vnc_tunnel_feed_recv
+  vnc_tunnel_feed_shutdown
 %%
 override vnc_display_send_keys kwargs 
 static PyObject*
@@ -51,3 +57,45 @@ _wrap_vnc_display_send_keys(PyGObject *s
     Py_INCREF(Py_None);
     return Py_None;
 }
+
+%%
+override vnc_tunnel_recv noargs
+static PyObject *
+_wrap_vnc_tunnel_recv(PyGObject *self)
+{
+    gchar *str;
+    guint size;
+    PyObject *ret;
+
+    str = vnc_tunnel_recv(VNC_TUNNEL(self->obj), &size);
+    if (str == NULL) {
+	Py_INCREF(Py_None);
+	ret = Py_None;
+    } else {
+	ret = PyString_FromStringAndSize(str, size);
+	g_free(str);
+    }
+
+    return ret;
+}
+
+%%
+override vnc_tunnel_send onearg
+static PyObject *
+_wrap_vnc_tunnel_send(PyGObject *self, PyObject *arg)
+{
+    GString *str;
+    char *data;
+    Py_ssize_t length;
+    PyObject *ret;
+    int err;
+
+    err = PyString_AsStringAndSize(arg, &data, &length);
+    if (err == -1)
+	return NULL;
+
+    vnc_tunnel_send(VNC_TUNNEL(self->obj), data, length);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
diff -r 1e6a5272ef00 src/vncdisplay.c
--- a/src/vncdisplay.c	Wed Feb 06 22:20:39 2008 -0600
+++ b/src/vncdisplay.c	Thu Feb 07 19:51:08 2008 -0600
@@ -81,6 +81,7 @@ struct _VncDisplayPrivate
 	gboolean read_only;
 	gboolean allow_lossy;
 	gboolean allow_scaling;
+	gboolean allow_tunnels;
 };
 
 /* Delayed signal emmission.
@@ -128,12 +129,12 @@ typedef enum
 	VNC_SERVER_CUT_TEXT,
 	VNC_BELL,
 
+	VNC_TUNNEL_INIT,
+
 	LAST_SIGNAL
 } vnc_display_signals;
 
-static guint signals[LAST_SIGNAL] = { 0, 0, 0, 0,
-				      0, 0, 0, 0,
-				      0, 0, 0, 0, 0,};
+static guint signals[LAST_SIGNAL] = { 0 };
 static GParamSpec *signalCredParam;
 
 GtkWidget *vnc_display_new(void)
@@ -1293,6 +1294,17 @@ static gboolean on_render_jpeg(void *opa
 	return TRUE;
 }
 
+static gboolean on_tunnels_available(void *opaque)
+{
+	VncDisplay *obj = VNC_DISPLAY(opaque);
+
+	g_signal_emit (G_OBJECT (obj),
+		       signals[VNC_TUNNEL_INIT],
+		       0);
+
+	return TRUE;
+}
+
 static const struct gvnc_ops vnc_display_ops = {
 	.auth_cred = on_auth_cred,
 	.auth_type = on_auth_type,
@@ -1306,6 +1318,7 @@ static const struct gvnc_ops vnc_display
 	.server_cut_text = on_server_cut_text,
 	.bell = on_bell,
 	.render_jpeg = on_render_jpeg,
+	.tunnels_available = on_tunnels_available,
 };
 
 /* we use an idle function to allow the coroutine to exit before we actually
@@ -1325,19 +1338,7 @@ static void *vnc_coroutine(void *opaque)
 	VncDisplayPrivate *priv = obj->priv;
 
 	/* this order is extremely important! */
-	int32_t encodings[] = {	GVNC_ENCODING_TIGHT_JPEG5,
-				GVNC_ENCODING_TIGHT,
-				GVNC_ENCODING_EXT_KEY_EVENT,
-				GVNC_ENCODING_DESKTOP_RESIZE,
-				GVNC_ENCODING_RICH_CURSOR,
-				GVNC_ENCODING_XCURSOR,
-				GVNC_ENCODING_POINTER_CHANGE,
-				GVNC_ENCODING_ZRLE,
-				GVNC_ENCODING_HEXTILE,
-				GVNC_ENCODING_RRE,
-				GVNC_ENCODING_COPY_RECT,
-				GVNC_ENCODING_RAW };
-	int32_t *encodingsp;
+	int32_t encodings[32];
 	int n_encodings;
 	int ret;
 	struct signal_data s;
@@ -1364,20 +1365,28 @@ static void *vnc_coroutine(void *opaque)
 
 	emit_signal_delayed(obj, VNC_INITIALIZED, &s);
 
-	encodingsp = encodings;
-	n_encodings = ARRAY_SIZE(encodings);
-
+	n_encodings = 0;
 	if (check_pixbuf_support("jpeg")) {
-		if (!priv->allow_lossy) {
-			encodingsp++;
-			n_encodings--;
-		}
-	} else {
-		encodingsp += 2;
-		n_encodings -= 2;
+		if (priv->allow_lossy)
+			encodings[n_encodings++] = GVNC_ENCODING_TIGHT_JPEG5;
+		encodings[n_encodings++] = GVNC_ENCODING_TIGHT;
 	}
 
-	if (!gvnc_set_encodings(priv->gvnc, n_encodings, encodingsp))
+	if (priv->allow_tunnels)
+		encodings[n_encodings++] = GVNC_ENCODING_TUNNEL_IO;
+
+	encodings[n_encodings++] = GVNC_ENCODING_EXT_KEY_EVENT;
+	encodings[n_encodings++] = GVNC_ENCODING_DESKTOP_RESIZE;
+	encodings[n_encodings++] = GVNC_ENCODING_RICH_CURSOR;
+	encodings[n_encodings++] = GVNC_ENCODING_XCURSOR;
+	encodings[n_encodings++] = GVNC_ENCODING_POINTER_CHANGE;
+	encodings[n_encodings++] = GVNC_ENCODING_ZRLE;
+	encodings[n_encodings++] = GVNC_ENCODING_HEXTILE;
+	encodings[n_encodings++] = GVNC_ENCODING_RRE;
+	encodings[n_encodings++] = GVNC_ENCODING_COPY_RECT;
+	encodings[n_encodings++] = GVNC_ENCODING_RAW;
+
+	if (!gvnc_set_encodings(priv->gvnc, n_encodings, encodings))
 			goto cleanup;
 
 	if (!gvnc_framebuffer_update_request(priv->gvnc, 0, 0, 0, priv->fb.width, priv->fb.height))
@@ -1747,6 +1756,16 @@ static void vnc_display_class_init(VncDi
 			     g_cclosure_marshal_VOID__VOID,
 			     G_TYPE_NONE,
 			     0);
+
+	signals[VNC_TUNNEL_INIT] =
+		g_signal_new ("vnc-tunnel-init",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (VncDisplayClass, vnc_tunnel_init),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
 
 	g_type_class_add_private(klass, sizeof(VncDisplayPrivate));
 }
@@ -2048,6 +2067,18 @@ gboolean vnc_display_set_scaling(VncDisp
 }
 #endif
 
+VncTunnel *vnc_display_tunnel_open(VncDisplay *obj, const gchar *name)
+{
+	g_return_val_if_fail(VNC_IS_DISPLAY(obj), NULL);
+
+	return gvnc_tunnel_open(obj->priv->gvnc, name);
+}
+
+gboolean vnc_display_set_tunnels(VncDisplay *obj, gboolean enable)
+{
+	obj->priv->allow_tunnels = enable;
+}
+
 /*
  * Local variables:
  *  c-indent-level: 8
diff -r 1e6a5272ef00 src/vncdisplay.h
--- a/src/vncdisplay.h	Wed Feb 06 22:20:39 2008 -0600
+++ b/src/vncdisplay.h	Thu Feb 07 19:51:08 2008 -0600
@@ -17,6 +17,8 @@ typedef struct _VncDisplayPrivate VncDis
 
 #include <gtk/gtkdrawingarea.h>
 #include <glib.h>
+
+#include "vnctunnel.h"
 
 #define VNC_TYPE_DISPLAY (vnc_display_get_type())
 #define VNC_TYPE_DISPLAY_CREDENTIAL (vnc_display_credential_get_type())
@@ -53,6 +55,7 @@ struct _VncDisplayClass
 	void		(* vnc_initialized)	(VncDisplay *display);
 	void		(* vnc_disconnected)	(VncDisplay *display);
 	void		(* vnc_auth_credential)	(VncDisplay *display, GValueArray *credList);
+	void		(* vnc_tunnel_init)	(VncDisplay *display);
 };
 
 typedef enum
@@ -109,6 +112,10 @@ gboolean	vnc_display_set_scaling(VncDisp
 
 void		vnc_display_force_grab(VncDisplay *obj, gboolean enable);
 
+VncTunnel *	vnc_display_tunnel_open(VncDisplay *obj, const gchar *name);
+
+gboolean	vnc_display_set_tunnels(VncDisplay *obj, gboolean enable);
+
 G_END_DECLS
 
 #endif
diff -r 1e6a5272ef00 src/vnctunnel.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/vnctunnel.c	Thu Feb 07 19:51:08 2008 -0600
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2007  Anthony Liguori <anthony codemonkey ws>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  GTK VNC Widget
+ */
+
+#include "vnctunnel.h"
+#include "gvnc.h"
+
+#include <string.h>
+#include <stdint.h>
+
+#define VNC_TUNNEL_GET_PRIVATE(obj) \
+      (G_TYPE_INSTANCE_GET_PRIVATE((obj), VNC_TYPE_TUNNEL, VncTunnelPrivate))
+
+struct _VncTunnelPrivate
+{
+	GList *send_queue;
+	GList *recv_queue;
+	gboolean connected;
+	struct gvnc *gvnc;
+	uint16_t tid;
+};
+
+enum
+{
+	VNC_TUNNEL_CONNECTED,
+	VNC_TUNNEL_IO_WATCH,
+	VNC_TUNNEL_SHUTDOWN,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+VncTunnel *vnc_tunnel_new(struct gvnc *gvnc)
+{
+	VncTunnel *obj;
+
+	obj = VNC_TUNNEL(g_object_new(VNC_TYPE_TUNNEL, NULL));
+	if (obj) {
+		VncTunnelPrivate *priv = obj->priv;
+
+		priv->send_queue = NULL;
+		priv->recv_queue = NULL;
+		priv->connected = FALSE;
+		priv->gvnc = gvnc;
+		priv->tid = -1;
+	}
+
+	return obj;
+}
+
+static void vnc_tunnel_finalize(GObject *gobj)
+{
+	VncTunnel *obj = VNC_TUNNEL(gobj);
+	VncTunnelPrivate *priv = obj->priv;
+
+	if (priv->connected) {
+		gvnc_tunnel_client_shutdown(priv->gvnc, priv->tid);
+		priv->connected = FALSE;
+	}
+
+	while (priv->send_queue) {
+		GString *str = priv->send_queue->data;
+		priv->send_queue = g_list_remove(priv->send_queue, str);
+		g_string_free(str, TRUE);
+	}
+
+	while (priv->recv_queue) {
+		GString *str = priv->recv_queue->data;
+		priv->recv_queue = g_list_remove(priv->recv_queue, str);
+		g_string_free(str, TRUE);
+	}
+
+	gvnc_tunnel_remove(priv->gvnc, obj);
+}
+
+static void vnc_tunnel_class_init(VncTunnelClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+	object_class->finalize = vnc_tunnel_finalize;
+
+	signals[VNC_TUNNEL_CONNECTED] =
+		g_signal_new ("vnc-tunnel-connected",
+			      G_OBJECT_CLASS_TYPE(object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET(VncTunnelClass, vnc_tunnel_connected),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+	signals[VNC_TUNNEL_IO_WATCH] =
+		g_signal_new ("vnc-tunnel-io-watch",
+			      G_OBJECT_CLASS_TYPE(object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET(VncTunnelClass, vnc_tunnel_io_watch),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+	signals[VNC_TUNNEL_SHUTDOWN] =
+		g_signal_new ("vnc-tunnel-shutdown",
+			      G_OBJECT_CLASS_TYPE(object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET(VncTunnelClass, vnc_tunnel_shutdown),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+	g_type_class_add_private(klass, sizeof(VncTunnelPrivate));
+}
+
+static void vnc_tunnel_init(GTypeInstance *instance,
+			    gpointer klass G_GNUC_UNUSED)
+{
+	VncTunnel *obj = VNC_TUNNEL(instance);
+	obj->priv = VNC_TUNNEL_GET_PRIVATE(obj);
+	memset(obj->priv, 0, sizeof(VncTunnelPrivate));
+}
+
+GType vnc_tunnel_get_type(void)
+{
+	static GType type;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(VncTunnelClass),
+			NULL,
+			NULL,
+			(GClassInitFunc)vnc_tunnel_class_init,
+			NULL,
+			NULL,
+			sizeof(VncTunnel),
+			0,
+			vnc_tunnel_init,
+			NULL
+		};
+
+		type = g_type_register_static(G_TYPE_OBJECT,
+					      "VncTunnel",
+					      &info,
+					      0);
+	}
+
+	return type;
+}
+
+void vnc_tunnel_send(VncTunnel *obj, const guchar *data, guint size)
+{
+	VncTunnelPrivate *priv = obj->priv;
+
+	if (priv->connected)
+		gvnc_tunnel_client_send(priv->gvnc, priv->tid,
+					data, size);
+	else {
+		GString *copy = g_string_new_len(data, size);
+		priv->send_queue = g_list_append(priv->send_queue, copy);
+	}
+}
+
+guchar *vnc_tunnel_recv(VncTunnel *obj, guint *size)
+{
+	VncTunnelPrivate *priv = obj->priv;
+	GString *str;
+
+	if (!priv->connected || !priv->recv_queue)
+		return NULL;
+
+	str = priv->recv_queue->data;
+	priv->recv_queue = g_list_remove(priv->recv_queue, str);
+	*size = str->len;
+	return g_string_free(str, FALSE);
+}
+
+void vnc_tunnel_set_tid(VncTunnel *obj, guint tid)
+{
+	VncTunnelPrivate *priv = obj->priv;
+
+	priv->tid = tid;
+	priv->connected = TRUE;
+
+	while (priv->send_queue) {
+		GString *str = priv->send_queue->data;
+		priv->send_queue = g_list_remove(priv->send_queue, str);
+		vnc_tunnel_send(obj, str->str, str->len);
+		g_string_free(str, TRUE);
+	}
+
+	g_signal_emit(obj, signals[VNC_TUNNEL_CONNECTED], 0);
+}
+
+guint vnc_tunnel_get_tid(VncTunnel *obj)
+{
+	return obj->priv->tid;
+}
+
+void vnc_tunnel_feed_recv(VncTunnel *obj, const guchar *data, guint size)
+{
+	VncTunnelPrivate *priv = obj->priv;
+	priv->recv_queue = g_list_append(priv->recv_queue,
+					 g_string_new_len(data, size));
+	g_signal_emit(obj, signals[VNC_TUNNEL_IO_WATCH], 0);
+}
+
+void vnc_tunnel_feed_shutdown(VncTunnel *obj)
+{
+	obj->priv->connected = FALSE;
+	g_signal_emit(obj, signals[VNC_TUNNEL_SHUTDOWN], 0);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ *  tab-width: 8
+ * End:
+ */
diff -r 1e6a5272ef00 src/vnctunnel.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/vnctunnel.h	Thu Feb 07 19:51:08 2008 -0600
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007  Anthony Liguori <anthony codemonkey ws>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  GTK VNC Widget
+ */
+
+#ifndef _VNC_TUNNEL_H_
+#define _VNC_TUNNEL_H_
+
+typedef struct _VncTunnel VncTunnel;
+typedef struct _VncTunnelClass VncTunnelClass;
+typedef struct _VncTunnelPrivate VncTunnelPrivate;
+
+#include <glib-object.h>
+
+#define VNC_TYPE_TUNNEL (vnc_tunnel_get_type())
+
+#define VNC_TUNNEL(obj) \
+        (G_TYPE_CHECK_INSTANCE_CAST((obj), VNC_TYPE_TUNNEL, VncTunnel))
+
+#define VNC_TUNNEL_CLASS(klass) \
+        (G_TYPE_CHECK_CLASS_CAST((klass), VNC_TYPE_TUNNEL, VncTunnelClass))
+
+#define VNC_IS_TUNNEL(obj) \
+        (G_TYPE_CHECK_INSTANCE_TYPE((obj), VNC_TYPE_TUNNEL))
+
+#define VNC_IS_TUNNEL_CLASS(klass) \
+        (G_TYPE_CHECK_CLASS_TYPE((klass), VNC_TYPE_TUNNEL))
+
+#define VNC_TUNNEL_GET_CLASS(obj) \
+        (G_TYPE_INSTANCE_GET_CLASS((obj), VNC_TYPE_TUNNEL, VncTunnelClass))
+
+struct _VncTunnel
+{
+	GObject parent;
+
+	VncTunnelPrivate *priv;
+};
+
+struct _VncTunnelClass
+{
+	GObjectClass parent;
+
+	/* Signals */
+	void	(* vnc_tunnel_connected)	(VncTunnel *obj);
+	void	(* vnc_tunnel_io_watch)		(VncTunnel *obj);
+	void	(* vnc_tunnel_shutdown)		(VncTunnel *obj);
+};
+
+struct gvnc;
+
+G_BEGIN_DECLS
+
+GType		vnc_tunnel_get_type(void);
+
+VncTunnel *	vnc_tunnel_new(struct gvnc *gvnc);
+
+guchar *	vnc_tunnel_recv(VncTunnel *obj, guint *size);
+
+void		vnc_tunnel_send(VncTunnel *obj, const guchar *data, guint size);
+
+guint		vnc_tunnel_get_tid(VncTunnel *obj);
+
+void		vnc_tunnel_set_tid(VncTunnel *obj, guint tid);
+
+void		vnc_tunnel_feed_recv(VncTunnel *obj, const guchar *data, guint size);
+
+void		vnc_tunnel_feed_shutdown(VncTunnel *obj);
+
+G_END_DECLS
+
+#endif


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]