[Ovirt-devel] [PATCH viewer] project cleanup, move sources in a src/ subdir and remove currently unused files

Mohammed Morsi mmorsi at redhat.com
Thu Jul 23 23:01:45 UTC 2009


---
 INSTALL           |  237 -----------
 Makefile.am       |   11 +-
 README            |    6 +-
 configure.ac      |    4 +-
 internal.h        |  220 ----------
 main.c            | 1148 -----------------------------------------------------
 ovirt-viewer.spec |    2 +-
 src/Makefile.am   |    5 +
 src/internal.h    |  220 ++++++++++
 src/main.c        | 1148 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/tunnel.c      |  327 +++++++++++++++
 src/wui_thread.c  | 1106 +++++++++++++++++++++++++++++++++++++++++++++++++++
 tunnel.c          |  327 ---------------
 wui_thread.c      | 1106 ---------------------------------------------------
 14 files changed, 2817 insertions(+), 3050 deletions(-)
 delete mode 100644 AUTHORS
 delete mode 100644 ChangeLog
 delete mode 100644 INSTALL
 delete mode 100644 NEWS
 delete mode 100644 internal.h
 delete mode 100644 main.c
 create mode 100644 src/Makefile.am
 create mode 100644 src/internal.h
 create mode 100644 src/main.c
 create mode 100644 src/tunnel.c
 create mode 100644 src/wui_thread.c
 delete mode 100644 tunnel.c
 delete mode 100644 wui_thread.c

diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index e69de29..0000000
diff --git a/ChangeLog b/ChangeLog
deleted file mode 100644
index e69de29..0000000
diff --git a/INSTALL b/INSTALL
deleted file mode 100644
index d3c5b40..0000000
--- a/INSTALL
+++ /dev/null
@@ -1,237 +0,0 @@
-Installation Instructions
-*************************
-
-Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
-2006, 2007 Free Software Foundation, Inc.
-
-This file is free documentation; the Free Software Foundation gives
-unlimited permission to copy, distribute and modify it.
-
-Basic Installation
-==================
-
-Briefly, the shell commands `./configure; make; make install' should
-configure, build, and install this package.  The following
-more-detailed instructions are generic; see the `README' file for
-instructions specific to this package.
-
-   The `configure' shell script attempts to guess correct values for
-various system-dependent variables used during compilation.  It uses
-those values to create a `Makefile' in each directory of the package.
-It may also create one or more `.h' files containing system-dependent
-definitions.  Finally, it creates a shell script `config.status' that
-you can run in the future to recreate the current configuration, and a
-file `config.log' containing compiler output (useful mainly for
-debugging `configure').
-
-   It can also use an optional file (typically called `config.cache'
-and enabled with `--cache-file=config.cache' or simply `-C') that saves
-the results of its tests to speed up reconfiguring.  Caching is
-disabled by default to prevent problems with accidental use of stale
-cache files.
-
-   If you need to do unusual things to compile the package, please try
-to figure out how `configure' could check whether to do them, and mail
-diffs or instructions to the address given in the `README' so they can
-be considered for the next release.  If you are using the cache, and at
-some point `config.cache' contains results you don't want to keep, you
-may remove or edit it.
-
-   The file `configure.ac' (or `configure.in') is used to create
-`configure' by a program called `autoconf'.  You need `configure.ac' if
-you want to change it or regenerate `configure' using a newer version
-of `autoconf'.
-
-The simplest way to compile this package is:
-
-  1. `cd' to the directory containing the package's source code and type
-     `./configure' to configure the package for your system.
-
-     Running `configure' might take a while.  While running, it prints
-     some messages telling which features it is checking for.
-
-  2. Type `make' to compile the package.
-
-  3. Optionally, type `make check' to run any self-tests that come with
-     the package.
-
-  4. Type `make install' to install the programs and any data files and
-     documentation.
-
-  5. You can remove the program binaries and object files from the
-     source code directory by typing `make clean'.  To also remove the
-     files that `configure' created (so you can compile the package for
-     a different kind of computer), type `make distclean'.  There is
-     also a `make maintainer-clean' target, but that is intended mainly
-     for the package's developers.  If you use it, you may have to get
-     all sorts of other programs in order to regenerate files that came
-     with the distribution.
-
-  6. Often, you can also type `make uninstall' to remove the installed
-     files again.
-
-Compilers and Options
-=====================
-
-Some systems require unusual options for compilation or linking that the
-`configure' script does not know about.  Run `./configure --help' for
-details on some of the pertinent environment variables.
-
-   You can give `configure' initial values for configuration parameters
-by setting variables in the command line or in the environment.  Here
-is an example:
-
-     ./configure CC=c99 CFLAGS=-g LIBS=-lposix
-
-   *Note Defining Variables::, for more details.
-
-Compiling For Multiple Architectures
-====================================
-
-You can compile the package for more than one kind of computer at the
-same time, by placing the object files for each architecture in their
-own directory.  To do this, you can use GNU `make'.  `cd' to the
-directory where you want the object files and executables to go and run
-the `configure' script.  `configure' automatically checks for the
-source code in the directory that `configure' is in and in `..'.
-
-   With a non-GNU `make', it is safer to compile the package for one
-architecture at a time in the source code directory.  After you have
-installed the package for one architecture, use `make distclean' before
-reconfiguring for another architecture.
-
-Installation Names
-==================
-
-By default, `make install' installs the package's commands under
-`/usr/local/bin', include files under `/usr/local/include', etc.  You
-can specify an installation prefix other than `/usr/local' by giving
-`configure' the option `--prefix=PREFIX'.
-
-   You can specify separate installation prefixes for
-architecture-specific files and architecture-independent files.  If you
-pass the option `--exec-prefix=PREFIX' to `configure', the package uses
-PREFIX as the prefix for installing programs and libraries.
-Documentation and other data files still use the regular prefix.
-
-   In addition, if you use an unusual directory layout you can give
-options like `--bindir=DIR' to specify different values for particular
-kinds of files.  Run `configure --help' for a list of the directories
-you can set and what kinds of files go in them.
-
-   If the package supports it, you can cause programs to be installed
-with an extra prefix or suffix on their names by giving `configure' the
-option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
-
-Optional Features
-=================
-
-Some packages pay attention to `--enable-FEATURE' options to
-`configure', where FEATURE indicates an optional part of the package.
-They may also pay attention to `--with-PACKAGE' options, where PACKAGE
-is something like `gnu-as' or `x' (for the X Window System).  The
-`README' should mention any `--enable-' and `--with-' options that the
-package recognizes.
-
-   For packages that use the X Window System, `configure' can usually
-find the X include and library files automatically, but if it doesn't,
-you can use the `configure' options `--x-includes=DIR' and
-`--x-libraries=DIR' to specify their locations.
-
-Specifying the System Type
-==========================
-
-There may be some features `configure' cannot figure out automatically,
-but needs to determine by the type of machine the package will run on.
-Usually, assuming the package is built to be run on the _same_
-architectures, `configure' can figure that out, but if it prints a
-message saying it cannot guess the machine type, give it the
-`--build=TYPE' option.  TYPE can either be a short name for the system
-type, such as `sun4', or a canonical name which has the form:
-
-     CPU-COMPANY-SYSTEM
-
-where SYSTEM can have one of these forms:
-
-     OS KERNEL-OS
-
-   See the file `config.sub' for the possible values of each field.  If
-`config.sub' isn't included in this package, then this package doesn't
-need to know the machine type.
-
-   If you are _building_ compiler tools for cross-compiling, you should
-use the option `--target=TYPE' to select the type of system they will
-produce code for.
-
-   If you want to _use_ a cross compiler, that generates code for a
-platform different from the build platform, you should specify the
-"host" platform (i.e., that on which the generated programs will
-eventually be run) with `--host=TYPE'.
-
-Sharing Defaults
-================
-
-If you want to set default values for `configure' scripts to share, you
-can create a site shell script called `config.site' that gives default
-values for variables like `CC', `cache_file', and `prefix'.
-`configure' looks for `PREFIX/share/config.site' if it exists, then
-`PREFIX/etc/config.site' if it exists.  Or, you can set the
-`CONFIG_SITE' environment variable to the location of the site script.
-A warning: not all `configure' scripts look for a site script.
-
-Defining Variables
-==================
-
-Variables not defined in a site shell script can be set in the
-environment passed to `configure'.  However, some packages may run
-configure again during the build, and the customized values of these
-variables may be lost.  In order to avoid this problem, you should set
-them in the `configure' command line, using `VAR=value'.  For example:
-
-     ./configure CC=/usr/local2/bin/gcc
-
-causes the specified `gcc' to be used as the C compiler (unless it is
-overridden in the site shell script).
-
-Unfortunately, this technique does not work for `CONFIG_SHELL' due to
-an Autoconf bug.  Until the bug is fixed you can use this workaround:
-
-     CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
-
-`configure' Invocation
-======================
-
-`configure' recognizes the following options to control how it operates.
-
-`--help'
-`-h'
-     Print a summary of the options to `configure', and exit.
-
-`--version'
-`-V'
-     Print the version of Autoconf used to generate the `configure'
-     script, and exit.
-
-`--cache-file=FILE'
-     Enable the cache: use and save the results of the tests in FILE,
-     traditionally `config.cache'.  FILE defaults to `/dev/null' to
-     disable caching.
-
-`--config-cache'
-`-C'
-     Alias for `--cache-file=config.cache'.
-
-`--quiet'
-`--silent'
-`-q'
-     Do not print messages saying which checks are being made.  To
-     suppress all normal output, redirect it to `/dev/null' (any error
-     messages will still be shown).
-
-`--srcdir=DIR'
-     Look for the package's source code in directory DIR.  Usually
-     `configure' can determine that directory automatically.
-
-`configure' also accepts some other, not widely useful, options.  Run
-`configure --help' for more details.
-
diff --git a/Makefile.am b/Makefile.am
index b5a3e01..50d85c2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
 # ovirt viewer console application
 # Copyright (C) 2008 Red Hat Inc.
-# Written by Richard W.M. Jones <rjones at redhat.com>
+# Written by Richard W.M. Jones <rjones at redhat.com>, Mohammed Morsi <mmorsi at redhat.com>
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -16,15 +16,10 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-bin_PROGRAMS = ovirt-viewer
+AUTOMAKE_OPTIONS = foreign
 
-ovirt_viewer_SOURCES = main.c wui_thread.c tunnel.c internal.h
-ovirt_viewer_CFLAGS = $(OVIRT_VIEWER_CFLAGS) -DCAINFO='"$(CAINFO)"'
-ovirt_viewer_LDADD = $(OVIRT_VIEWER_LIBS)
+SUBDIRS = src
 
 EXTRA_DIST = $(PACKAGE).spec
 
 DISTCLEAN_FILES = $(PACKAGE).spec
-
-valgrind:
-	valgrind --leak-check=full ./ovirt-viewer$(EXEEXT) --debug
diff --git a/NEWS b/NEWS
deleted file mode 100644
index e69de29..0000000
diff --git a/README b/README
index cff4b1f..3b98bc7 100644
--- a/README
+++ b/README
@@ -13,8 +13,12 @@ consoles from Linux, Unix or Windows-based desktops.
 
 Installation
 ═════════════════════════════════════════════════════════════════════
-Read INSTALL for complete instructions, or briefly:
   1. ./autogen.sh
   2. ./configure
   3. make
   4. ./ovirt-viewer
+
+Authors
+═════════════════════════════════════════════════════════════════════
+Richard W.M. Jones <rjones at redhat.com>
+Mohammed Morsi     <mmorsi at redhat.com>
diff --git a/configure.ac b/configure.ac
index 13276ea..95b8b8b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,7 +16,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
-AC_INIT(ovirt-viewer, 0.1)
+AC_INIT(ovirt-viewer, 1.0.0)
 AM_INIT_AUTOMAKE
 
 dnl Basic C compiler environment.
@@ -57,5 +57,5 @@ fi
 
 dnl Output.
 AC_CONFIG_HEADERS([config.h])
-AC_CONFIG_FILES([Makefile])
+AC_CONFIG_FILES([Makefile src/Makefile])
 AC_OUTPUT
diff --git a/internal.h b/internal.h
deleted file mode 100644
index bd65922..0000000
--- a/internal.h
+++ /dev/null
@@ -1,220 +0,0 @@
-/* ovirt viewer console application
- * Copyright (C) 2008 Red Hat Inc.
- * Written by Richard W.M. Jones <rjones at redhat.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#ifndef OVIRT_VIEWER_INTERNAL_H
-#define OVIRT_VIEWER_INTERNAL_H
-
-#ifndef G_THREADS_ENABLED
-#error "This program requires GLib threads, and cannot be compiled without."
-#endif
-
-/* Debugging messages are always compiled in, but have to
- * be turned on using the --debug command line switch.
- */
-extern gboolean debug;
-
-#define DEBUG(fs,...)							\
-  do {									\
-    if (debug) {							\
-      fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__,	\
-	       g_thread_self ());					\
-      fprintf (stderr, (fs), ## __VA_ARGS__);				\
-      fprintf (stderr, "\n");						\
-    }									\
-  } while (0)
-
-/* Verbose messages are always compiled in, but have to
- * be turned on using the --verbose command line switch.
- */
-extern gboolean verbose;
-
-#define VERBOSE(fs,...)							\
-  do {									        \
-    if (verbose) {							\
-      fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__,	\
-	       g_thread_self ());					\
-      fprintf (stderr, (fs), ## __VA_ARGS__);				\
-      fprintf (stderr, "\n");						\
-    }									\
-  } while (0)
-
-/* String equality tests, suggested by Jim Meyering. */
-#define STREQ(a,b) (strcmp((a),(b)) == 0)
-#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
-#define STRNEQ(a,b) (strcmp((a),(b)) != 0)
-#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
-#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
-#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
-#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
-#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
-#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
-
-extern const char *cainfo;
-extern gboolean check_cert;
-
-/* server we're connecting to */
-extern const char* hostname;
-
-/* port which to connect to the server via vnc */
-extern int ovirt_server_vnc_port;
-
-/* vm currently in focus */
-extern struct vm* vm_in_focus;
-
-/* Communications between the main thread and the WUI thread.  For
- * an explanation of the threading model, please see the comment in
- * main().
- */
-
-extern void start_wui_thread (void);
-extern void stop_wui_thread (void);
-
-extern void assert_is_main_thread (const char *, int);
-extern void assert_is_wui_thread (const char *, int);
-
-#define ASSERT_IS_MAIN_THREAD() assert_is_main_thread(__FILE__,__LINE__)
-#define ASSERT_IS_WUI_THREAD() assert_is_wui_thread(__FILE__,__LINE__)
-
-/* These are messages (instructions) which can be sent from the main
- * thread to the WUI thread.
- */
-
-/* Start connecting to WUI, and set the base URI. */
-extern void wui_thread_send_connect (const char *uri);
-
-/* Disconnect, forget URI, credentials, VMs etc. */
-extern void wui_thread_send_disconnect (void);
-
-/* Set the username and password and tell the WUI to try to log in. */
-extern void wui_thread_send_login (const char *username, const char *password);
-
-/* Tell the WUI thread to refresh the VM list.  Note that the WUI
- * thread does this automatically anyway after a successful login, and
- * it also periodically updates the list.  This call just tells it to
- * do so right away.
- */
-extern void wui_thread_send_refresh_vm_list (void);
-
-/* Retrieve the list of VMs.
- *
- * wui_thread_get_vmlist returns TRUE if there was a valid list of
- * VMs (even if it is empty), or FALSE if we don't have a valid list
- * of VMs to return.
- *
- * NB: Caller must call free_vmlist once it is done with the list.
- *
- */
-extern gboolean wui_thread_get_vmlist (GSList **ret);
-extern void free_vmlist (GSList *vmlist);
-
-struct vm {
-  char *description;
-  int hostid;
-  int id;
-  int vnc_port;
-  int forward_vnc_port;
-  char *uuid;			/* Printable UUID. */
-  char *state;
-
-  /* Only the fields above this point are required.  The remainder may
-   * be NULL / -1 to indicate they were missing in the data we got
-   * back from the WUI.
-   */
-
-  long mem_allocated;		/* Kbytes */
-  long mem_used;		/* Kbytes */
-  int vcpus_allocated;
-  int vcpus_used;
-  char *mac_addr;		/* Printable MAC addr. */
-};
-
-/* Returns true if the WUI thread thinks it is connected to a remote
- * WUI.  REST is connectionless so really this means that we
- * successfully made an HTTP/HTTPS request "recently", and we haven't
- * seen any errors above a certain threshold.
- */
-extern gboolean wui_thread_is_connected (void);
-
-/* Returns true if we successfully logged in with the username
- * and password supplied in a recent request, and we haven't
- * received any authorization failures since.
- */
-extern gboolean wui_thread_is_logged_in (void);
-
-/* Returns true if we have a valid list of VMs.  Note that because
- * of race conditions, this doesn't guarantee that wui_thread_get_vmlist
- * will work.
- */
-extern gboolean wui_thread_has_valid_vmlist (void);
-
-/* Returns true if the WUI thread is busy performing a request
- * at the moment.
- */
-extern gboolean wui_thread_is_busy (void);
-
-
-/* Communications between the main thread and the tunnel thread.*/
-extern void start_tunnel (void);
-extern void stop_tunnel  (void);
-
-/* port which local tunnel is listening on */
-extern int tunnel_port;
-
-
-/* Returns true if the main vm list contains a
- * running vm w/ the same name as specified one
- */
-extern gboolean main_vmlist_has_running_vm(struct vm*);
-
-/* Callbacks from the WUI thread to the main thread.  The WUI thread
- * adds these to the Glib main loop using g_idle_add, which means they
- * actually get executed in the context of the main thread.
- */
-
-/* The WUI thread has changed its state to connected. */
-extern gboolean main_connected (gpointer);
-
-/* The WUI thread has changed its state to disconnected. */
-extern gboolean main_disconnected (gpointer);
-
-/* The WUI thread has changed its state to logged in. */
-extern gboolean main_logged_in (gpointer);
-
-/* The WUI thread has changed its state to logged out. */
-extern gboolean main_logged_out (gpointer);
-
-/* The WUI thread has changed its state to busy. */
-extern gboolean main_busy (gpointer);
-
-/* The WUI thread has changed its state to idle. */
-extern gboolean main_idle (gpointer);
-
-/* The WUI thread had a connection problem. */
-extern gboolean main_connection_error (gpointer str);
-
-/* The WUI thread had a login problem. */
-extern gboolean main_login_error (gpointer str);
-
-/* The WUI thread reports a general status error. */
-extern gboolean main_status_error (gpointer str);
-
-/* The WUI thread has updated the vm list. */
-extern gboolean main_vmlist_updated (gpointer);
-
-#endif /* OVIRT_VIEWER_INTERNAL_H */
diff --git a/main.c b/main.c
deleted file mode 100644
index dd26c93..0000000
--- a/main.c
+++ /dev/null
@@ -1,1148 +0,0 @@
-/* ovirt viewer console application
- * Copyright (C) 2008 Red Hat Inc.
- * Written by Richard W.M. Jones <rjones at redhat.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#include <config.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <glib.h>
-#include <gtk/gtk.h>
-#include <gdk/gdkkeysyms.h>
-#include <vncdisplay.h>
-
-#ifdef HAVE_NETDB_H
-#include <netdb.h>
-#endif
-
-#ifdef HAVE_NETINET_IN_H
-#include <netinet/in.h>
-#endif
-
-#include <arpa/inet.h>
-
-#ifdef HAVE_SYS_SOCKET_H
-#include <sys/socket.h>
-#endif
-
-#ifdef HAVE_SYS_UN_H
-#include <sys/un.h>
-#endif
-
-#ifdef HAVE_WINDOWS_H
-#include <windows.h>
-#endif
-
-#include "internal.h"
-
-/*#define HTTPS "https"*/
-#define HTTPS "http"
-
-gboolean debug = 0;
-
-gboolean verbose = 0;
-
-/* Usually /etc/pki/tls/certs/ca-bundle.crt unless overridden during
- * configure or on the command line.
- */
-const char *cainfo = CAINFO;
-gboolean check_cert = FALSE; // do we want this enabled by default ?
-                      // would require a CA by default (self-signed wont work)
-                      // (don't set to true, change var/flag to no_check_cert)
-
-/* The WUI thread has updated the vm list.  Here in the main thread
-* we keep our own copy of the vmlist.
-*/
-static GSList *vmlist = NULL;
-
-/*  internal.h shared constructs */
-const char* hostname;
-struct vm* vm_in_focus;
-int ovirt_server_vnc_port = 5900;
-
-/* Private functions. */
-static void start_ui (void);
-static GtkWidget *menu_item_new (int which_menu);
-static void refresh_menu_vm_list (GtkWidget *, gpointer);
-static void connect_to_wui_on_enter (GtkWidget *, gpointer);
-static void connect_to_wui (GtkWidget *, gpointer);
-static void send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef);
-static void login_to_wui_on_enter (GtkWidget *, gpointer);
-static void login_to_wui (GtkWidget *, gpointer);
-static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data);
-static void destroy (GtkWidget *widget, gpointer data);
-static void clear_connectmenu (void);
-static void help_about (GtkWidget *menu);
-static void viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc);
-#if 0
-static void viewer_quit (GtkWidget *src, GtkWidget *vnc);
-#endif
-static void viewer_connected (GtkWidget *vnc);
-static void viewer_initialized (GtkWidget *vnc, GtkWidget *data);
-static void viewer_disconnected (GtkWidget *vnc);
-static void viewer_credential (GtkWidget *vnc, GValueArray *credList);
-static int viewer_open_vnc_socket (const char *vnchost, int vncport);
-static void add_vm_to_connectmenu (gpointer _vm, gpointer data);
-
-/* For any widgets accessed from multiple functions. */
-static GtkWidget *window;
-static GtkWidget *connectitem;
-static GtkWidget *connectmenu;
-static GtkWidget *no_connections;
-static GtkWidget *refresh_vmlist;
-static GtkWidget *refresh_vmlist_separator;
-static GtkWidget *connection_area;
-static GtkWidget *ca_hostname;
-static GtkWidget *ca_button;
-static GtkWidget *ca_error;
-static GtkWidget *login_area;
-static GtkWidget *la_username;
-static GtkWidget *la_password;
-static GtkWidget *la_button;
-static GtkWidget *la_error;
-static GtkWidget *notebook;
-static GtkWidget *statusbar;
-static guint statusbar_ctx;
-static GdkCursor *busy_cursor;
-
-/* Menus. */
-enum menuNums {
-  CONNECT_MENU,
-  VIEW_MENU,
-  SEND_KEY_MENU,
-  WINDOW_MENU,
-  HELP_MENU,
-};
-
-struct menuItem {
-  guint menu;
-  GtkWidget *label;
-  const char *ungrabbed_text;
-  const char *grabbed_text;
-};
-
-static struct menuItem menuItems[] = {
-  { CONNECT_MENU, NULL, "_Connect", "Connect" },
-  { VIEW_MENU, NULL, "_View", "View" },
-  { SEND_KEY_MENU, NULL, "_Send Key", "Send Key" },
-  { WINDOW_MENU, NULL, "_Window", "Window" },
-  { HELP_MENU, NULL, "_Help", "Help" }
-};
-
-#define MAX_KEY_COMBO 3
- struct keyComboDef {
- guint keys[MAX_KEY_COMBO];
- guint nkeys;
- const char *label;
- };
-
-#define NUM_KEY_COMBOS 17
-static struct keyComboDef keyCombos[] = {
- { { GDK_Control_L, GDK_Alt_L, GDK_Delete }, 3, "Ctrl+Alt+Del"},
- { { GDK_Control_L, GDK_Alt_L, GDK_BackSpace }, 3, "Ctrl+Alt+Backspace"},
- { {}, 0, "" },
- { { GDK_Control_L, GDK_Alt_L, GDK_F1 }, 3, "Ctrl+Alt+F1"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F2 }, 3, "Ctrl+Alt+F2"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F3 }, 3, "Ctrl+Alt+F3"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F4 }, 3, "Ctrl+Alt+F4"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F5 }, 3, "Ctrl+Alt+F5"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F6 }, 3, "Ctrl+Alt+F6"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F7 }, 3, "Ctrl+Alt+F7"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F8 }, 3, "Ctrl+Alt+F8"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F5 }, 3, "Ctrl+Alt+F9"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F6 }, 3, "Ctrl+Alt+F10"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F7 }, 3, "Ctrl+Alt+F11"},
- { { GDK_Control_L, GDK_Alt_L, GDK_F8 }, 3, "Ctrl+Alt+F12"},
- { {}, 0, "" },
- { { GDK_Print }, 1, "PrintScreen"},
-};
-
-/* Window title. */
-static const char *title = "oVirt Viewer";
-
-// when running vm
-// 47 chars
-static const char *title_vm =
-   "oVirt Viewer: (ctrl+alt to grab/release mouse) ";
-
-/* Gtk widget styles.  Avoid installation hassles by keeping this
- * inside the binary.  It can still be overridden by the user (who
- * will do that?)
- */
-static const char *styles =
-  "style \"ovirt-viewer-yellow-box\"\n"
-  "{\n"
-  "  bg[NORMAL] = shade (1.5, \"yellow\")\n"
-  "}\n"
-  "widget \"*.ovirt-viewer-connection-area\" style \"ovirt-viewer-yellow-box\"\n"
-  ;
-
-/* Command-line arguments. */
-static int print_version = 0;
-
-static const char *help_msg =
-  "Use '" PACKAGE " --help' to see a list of available command line options";
-
-static const GOptionEntry options[] = {
-  { "port", 'p', 0, G_OPTION_ARG_INT, &ovirt_server_vnc_port,
-    "set port which to connect to server via vnc", NULL },
-  { "cainfo", 0, 0, G_OPTION_ARG_STRING, &cainfo,
-    "set the path of the CA certificate bundle", NULL },
-  { "check-certificate", 0, 0, G_OPTION_ARG_NONE, &check_cert,
-    "check the SSL certificate of the server", NULL },
-  { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug,
-    "turn on debugging messages", NULL },
-  { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
-    "turn on verbose messages", NULL },
-  { "version", 'V', 0, G_OPTION_ARG_NONE, &print_version,
-    "display version and exit", NULL },
-  { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
-};
-
-int
-main (int argc, char *argv[])
-{
-  GOptionContext *context;
-  GError *error = NULL;
-
-  /* Initialize GLib threads before anything else.
-   *
-   * There is one main thread, which is used for all Gtk interactions
-   * and to keep the UI responsive, and one WUI thread.  The WUI
-   * thread is used to connect to the WUI, log in, and maintain the list
-   * of virtual machines.  The WUI thread is the only thread allowed
-   * to use the CURL library.
-   *
-   * The main thread sends instructions to the WUI thread (like "connect",
-   * "disconnect", etc.) using a simple message-passing protocol and
-   * a GAsyncQueue.
-   *
-   * The WUI thread keeps the UI updated by adding idle events which are
-   * processed in the main thread - see:
-   * http://mail.gnome.org/archives/gtk-app-devel-list/2007-March/msg00232.html
-   *
-   * A tunnel thread is also started to locally listen for vnc packets
-   * and make them proxyable, adding the vm name, before forwarding onto
-   * the server
-   *
-   * Note that under Win32 you must confine all Gtk/Gdk interactions
-   * to a single thread - see:
-   * http://developer.gimp.org/api/2.0/gdk/gdk-Threads.html
-   */
-  if (!g_thread_supported ()) {
-    g_thread_init (NULL);
-#ifndef WIN32
-    gdk_threads_init ();
-#endif
-  } else {
-    fprintf (stderr, "GLib threads not supported or not working.");
-    exit (1);
-  }
-
-  gtk_init (&argc, &argv);
-
-  /* Parse command-line options. */
-  context = g_option_context_new ("oVirt viewer");
-  g_option_context_add_main_entries (context, options, NULL);
-  g_option_context_add_group (context, gtk_get_option_group (TRUE));
-  g_option_context_add_group (context, vnc_display_get_option_group ());
-  g_option_context_parse (context, &argc, &argv, &error);
-
-  if (error) {
-    g_print ("%s\n%s\n", error->message, help_msg);
-    g_error_free (error);
-    exit (1);
-  }
-
-  if (print_version) {
-    printf ("%s %s\n", PACKAGE, VERSION);
-    exit (0);
-  }
-
-  start_wui_thread ();
-  start_ui ();
-
-  DEBUG ("entering the Gtk main loop");
-
-  gtk_main ();
-
-  stop_wui_thread ();
-  stop_tunnel();
-
-  exit (0);
-}
-
-/* Create the viewer window, menus. */
-static void
-start_ui (void)
-{
-  int i;
-  GtkWidget *vbox;
-  GtkWidget *menubar;
-  GtkWidget *view;
-  GtkWidget *viewmenu;
-  GtkWidget *sendkey;
-  GtkWidget *sendkeymenu;
-  GtkWidget *sendkeymenuitem;
-  GtkWidget *wind;
-  GtkWidget *windmenu;
-  GtkWidget *help;
-  GtkWidget *helpmenu;
-  GtkWidget *about;
-  GtkWidget *ca_vbox;
-  GtkWidget *ca_hbox;
-  GtkWidget *ca_label;
-  GtkWidget *la_vbox;
-  GtkWidget *la_hbox;
-  GtkWidget *la_label;
-
-  DEBUG ("creating viewer windows and menus");
-
-  /* Parse styles. */
-  gtk_rc_parse_string (styles);
-
-  /* Busy cursor, used by main_busy() function.
-   * XXX This cursor is crap - how can we use the Bluecurve/theme cursor?
-   */
-  busy_cursor = gdk_cursor_new (GDK_WATCH);
-
-  /* Window. */
-  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-  gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
-  gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
-  gtk_window_set_title (GTK_WINDOW (window), title);
-
-  g_signal_connect (G_OBJECT (window), "delete_event",
-		    G_CALLBACK (delete_event), NULL);
-  g_signal_connect (G_OBJECT (window), "destroy",
-		    G_CALLBACK (destroy), NULL);
-
-  /* VBox for layout within the window. */
-  vbox = gtk_vbox_new (FALSE, 0);
-
-  /* Menubar. */
-  menubar = gtk_menu_bar_new ();
-  connectitem = menu_item_new (CONNECT_MENU);
-  connectmenu = gtk_menu_new ();
-  gtk_menu_item_set_submenu (GTK_MENU_ITEM (connectitem), connectmenu);
-
-  no_connections = gtk_menu_item_new_with_label ("Not connected");
-  gtk_menu_append (GTK_MENU (connectmenu), no_connections);
-  gtk_widget_set_sensitive (no_connections, FALSE);
-
-  /* This is not added to the menu yet, but will be when we are
-   * connected.
-   */
-  refresh_vmlist =
-    gtk_menu_item_new_with_label ("Refresh list of virtual machines");
-  g_object_ref (refresh_vmlist);
-  refresh_vmlist_separator = gtk_separator_menu_item_new ();
-  g_object_ref (refresh_vmlist_separator);
-
-  g_signal_connect (G_OBJECT (refresh_vmlist), "activate",
-		    G_CALLBACK (refresh_menu_vm_list), NULL);
-
-#if 0
-  screenshot = gtk_menu_item_new_with_mnemonic ("_Screenshot");
-  gtk_menu_append (GTK_MENU (filemenu), screenshot);
-  g_signal_connect (screenshot, "activate",
-		    GTK_SIGNAL_FUNC (take_screenshot), NULL);
-#endif
-
-  view = menu_item_new (VIEW_MENU);
-  viewmenu = gtk_menu_new ();
-  gtk_menu_item_set_submenu (GTK_MENU_ITEM (view), viewmenu);
-
-  sendkey = menu_item_new (SEND_KEY_MENU);
-  sendkeymenu = gtk_menu_new ();
-  gtk_menu_item_set_submenu (GTK_MENU_ITEM (sendkey), sendkeymenu);
-
-  for(i = 0; i < NUM_KEY_COMBOS; ++i){
-    sendkeymenuitem = gtk_menu_item_new_with_label (keyCombos[i].label);
-    gtk_menu_append (GTK_MENU (sendkeymenu), sendkeymenuitem);
-    g_signal_connect (G_OBJECT (sendkeymenuitem), "activate",
-                      G_CALLBACK (send_key_to_vm), &(keyCombos[i]));
-  }
-
-  wind = menu_item_new (WINDOW_MENU);
-  windmenu = gtk_menu_new ();
-  gtk_menu_item_set_submenu (GTK_MENU_ITEM (wind), windmenu);
-
-  help = menu_item_new (HELP_MENU);
-  helpmenu = gtk_menu_new ();
-  gtk_menu_item_set_submenu (GTK_MENU_ITEM (help), helpmenu);
-
-  about = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL);
-  gtk_menu_append(GTK_MENU(helpmenu), about);
-  g_signal_connect(about, "activate", GTK_SIGNAL_FUNC (help_about), NULL);
-
-  gtk_menu_bar_append (GTK_MENU_BAR (menubar), connectitem);
-  gtk_menu_bar_append (GTK_MENU_BAR (menubar), view);
-  gtk_menu_bar_append (GTK_MENU_BAR (menubar), sendkey);
-  gtk_menu_bar_append (GTK_MENU_BAR (menubar), wind);
-  gtk_menu_bar_append (GTK_MENU_BAR (menubar), help);
-
-  /* For login dialogs, etc., usually invisible. */
-  connection_area = gtk_event_box_new ();
-  ca_vbox = gtk_vbox_new (FALSE, 0);
-  ca_label = gtk_label_new ("Give the name of the oVirt management server:");
-  ca_hbox = gtk_hbox_new (FALSE, 0);
-  ca_hostname = gtk_entry_new ();
-  gtk_entry_set_width_chars (GTK_ENTRY (ca_hostname), 24);
-  ca_button = gtk_button_new_with_label ("Connect");
-  ca_error = gtk_label_new (NULL);
-  gtk_box_pack_start (GTK_BOX (ca_hbox), ca_hostname, FALSE, FALSE, 0);
-  gtk_box_pack_start (GTK_BOX (ca_hbox), ca_button,   FALSE, FALSE, 0);
-  gtk_box_pack_start (GTK_BOX (ca_vbox), ca_label,    FALSE, FALSE, 4);
-  gtk_box_pack_start (GTK_BOX (ca_vbox), ca_hbox,     TRUE,  FALSE, 4);
-  gtk_box_pack_start (GTK_BOX (ca_vbox), ca_error,    TRUE,  FALSE, 4);
-  gtk_container_add (GTK_CONTAINER (connection_area), ca_vbox);
-
-  gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area");
-
-  g_signal_connect (G_OBJECT (ca_hostname), "key-release-event",
-		    G_CALLBACK (connect_to_wui_on_enter), NULL);
-  g_signal_connect (G_OBJECT (ca_button), "clicked",
-		    G_CALLBACK (connect_to_wui), NULL);
-
-  login_area = gtk_event_box_new ();
-  la_vbox = gtk_vbox_new (FALSE, 0);
-  la_hbox = gtk_hbox_new (FALSE, 0);
-  la_label = gtk_label_new ("Username / password:");
-  la_username = gtk_entry_new ();
-  gtk_entry_set_width_chars (GTK_ENTRY (la_username), 12);
-  la_password = gtk_entry_new ();
-  gtk_entry_set_width_chars (GTK_ENTRY (la_password), 12);
-  gtk_entry_set_visibility (GTK_ENTRY (la_password), FALSE);
-  la_button = gtk_button_new_with_label ("Login");
-  la_error = gtk_label_new (NULL);
-  gtk_container_add (GTK_CONTAINER (la_hbox), la_username);
-  gtk_container_add (GTK_CONTAINER (la_hbox), la_password);
-  gtk_container_add (GTK_CONTAINER (la_hbox), la_button);
-  gtk_box_pack_start (GTK_BOX (la_vbox), la_label,     TRUE,  FALSE, 4);
-  gtk_box_pack_start (GTK_BOX (la_vbox), la_hbox,     TRUE,  FALSE, 4);
-  gtk_box_pack_start (GTK_BOX (la_vbox), la_error,    TRUE,  FALSE, 4);
-  gtk_container_add (GTK_CONTAINER (login_area), la_vbox);
-
-  gtk_widget_set_name (login_area, "ovirt-viewer-login-area");
-
-  g_signal_connect (G_OBJECT (la_username), "key-release-event",
-		    G_CALLBACK (login_to_wui_on_enter), NULL);
-  g_signal_connect (G_OBJECT (la_password), "key-release-event",
-		    G_CALLBACK (login_to_wui_on_enter), NULL);
-  g_signal_connect (G_OBJECT (la_button), "clicked",
-		    G_CALLBACK (login_to_wui), NULL);
-
-  /* Tabbed notebook. */
-  notebook = gtk_notebook_new ();
-  /*gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);*/
-  gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), TRUE);
-  gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
-
-  /* Status bar. */
-  statusbar = gtk_statusbar_new ();
-  statusbar_ctx = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar),
-						"context");
-  gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, "");
-
-  /* Packing. */
-  gtk_container_add (GTK_CONTAINER (window), vbox);
-  gtk_container_add_with_properties (GTK_CONTAINER (vbox), menubar,
-				     "expand", FALSE, NULL);
-  gtk_container_add_with_properties (GTK_CONTAINER (vbox), connection_area,
-				     "expand", FALSE, "fill", TRUE, NULL);
-  gtk_container_add_with_properties (GTK_CONTAINER (vbox), login_area,
-				     "expand", FALSE, "fill", TRUE, NULL);
-  gtk_container_add_with_properties (GTK_CONTAINER (vbox), notebook,
-				     "expand", TRUE, NULL);
-  gtk_container_add_with_properties (GTK_CONTAINER (vbox), statusbar,
-				     "expand", FALSE, NULL);
-
-  /* Show widgets. */
-  gtk_widget_show_all (window);
-
-  if (wui_thread_is_connected ())
-    gtk_widget_hide (connection_area);
-  if (!wui_thread_is_connected () || wui_thread_is_logged_in ())
-    gtk_widget_hide (login_area);
-}
-
-static GtkWidget *
-menu_item_new (int which_menu)
-{
-  GtkWidget *widget;
-  GtkWidget *label;
-  const char *text;
-
-  text = menuItems[which_menu].ungrabbed_text;
-
-  widget = gtk_menu_item_new ();
-  label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL);
-  gtk_label_set_text_with_mnemonic (GTK_LABEL (label), text);
-  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-
-  gtk_container_add (GTK_CONTAINER (widget), label);
-  gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), widget);
-  gtk_widget_show (label);
-
-  menuItems[which_menu].label = label;
-
-  return widget;
-}
-
-static gboolean
-delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
-{
-  DEBUG ("delete_event");
-  return FALSE;
-}
-
-static void
-destroy (GtkWidget *widget, gpointer data)
-{
-  DEBUG ("destroy");
-  gtk_main_quit ();
-}
-
-static void
-help_about (GtkWidget *menu)
-{
-  GtkWidget *about;
-  const char *authors[] = {
-    "Richard W.M. Jones <rjones at redhat.com>",
-    "Daniel P. Berrange <berrange at redhat.com>",
-    "Mohammed Morsi <mmorsi at redhat.com>",
-    NULL
-  };
-
-  about = gtk_about_dialog_new();
-
-  gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(about), "oVirt Viewer");
-  gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about), VERSION);
-  gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about), "http://ovirt.org/");
-  gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(about), "oVirt website");
-  gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about), authors);
-  gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about), 
- "This program is free software; you can redistribute it and/or modify\n" \
- "it under the terms of the GNU General Public License as published by\n" \
- "the Free Software Foundation; either version 2 of the License, or\n" \
- "(at your option) any later version.\n" \
- "\n" \
- "This program is distributed in the hope that it will be useful,\n" \
- "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
- "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" \
- "GNU General Public License for more details.\n" \
- "\n" \
- "You should have received a copy of the GNU General Public License\n" \
- "along with this program; if not, write to the Free Software\n" \
- "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n");
-
-  gtk_dialog_run(GTK_DIALOG(about));
-  gtk_widget_destroy(about);
-}
-
-static void
-refresh_menu_vm_list(GtkWidget *widget, gpointer data)
-{
-   wui_thread_send_refresh_vm_list();
-}
-
-static void
-connect_to_wui_on_enter (GtkWidget *widget, gpointer data)
-{
-  // if key released was not 'enter' key
-  if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return;
-
-  connect_to_wui(widget, data);
-}
-
-static void
-connect_to_wui (GtkWidget *widget, gpointer data)
-{
-  char *uri;
-  int len;
-
-  hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname));
-  if (STREQ (hostname, "")) return;
-
-  /* https:// + hostname + /ovirt + \0 */
-  len = 8 + strlen (hostname) + 6 + 1;
-  uri = g_alloca (len);
-  snprintf (uri, len, HTTPS "://%s/ovirt", hostname);
-
-  wui_thread_send_connect (uri);
-  start_tunnel();
-}
-
-static void
-login_to_wui_on_enter (GtkWidget *widget, gpointer data)
-{
-  // if key released was not 'enter' key
-  if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return;
-
-  login_to_wui(widget, data);
-}
-
-static void
-login_to_wui (GtkWidget *widget, gpointer data)
-{
-  const char *username, *password;
-
-  username = gtk_entry_get_text (GTK_ENTRY (la_username));
-  if (STREQ (username, "")) return;
-  password = gtk_entry_get_text (GTK_ENTRY (la_password));
-
-  wui_thread_send_login (username, password);
-}
-
-/* Connect to a virtual machine.  This callback is called from the
- * connect menu.  It searches the notebook of gtk-vnc widgets to see
- * if we have already connected to this machine, and if not it
- * makes a new connection.
- */
-static void
-connect_to_vm (GtkWidget *widget, gpointer _vm)
-{
-  struct vm *vm = (struct vm *) _vm;
-  int n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));
-  int i, uuidlen, len, fd;
-  GtkWidget *child;
-  const char *label;
-  char *label2;
-  char new_title[97]; // 47 chars for title + 50 for vm name
-
-  DEBUG ("searching tabs for uuid %s", vm->uuid);
-
-  uuidlen = strlen (vm->uuid);
-
-  /* Search the tabs for this UUID, and if found, switch to it and return. */
-  for (i = 0; i < n; ++i) {
-    child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i);
-    label = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (notebook), child);
-    len = strlen (label);
-    if (len >= uuidlen &&
-	STREQ (label + len - uuidlen, vm->uuid)) {
-      DEBUG ("found on tab %d", i);
-      gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
-      return;
-    }
-  }
-
-  DEBUG ("not found, creating new tab");
-
-  // before we open vnc connection, make sure vm is running
-  gboolean vm_running = main_vmlist_has_running_vm(vm);
-
-  if(!vm_running){
-    main_status_error (g_strdup ("VM not running"));
-    return;
-  }
-
-  // FIXME on notebook tab switch, change vm_in_focus
-  vm_in_focus = vm;
-
-  /* This VM isn't in the notebook already, so create a new console. */
-  DEBUG ("connecting to local tunnel on port %i", tunnel_port);
-  fd = viewer_open_vnc_socket("127.0.0.1", tunnel_port);
-  if (fd == -1) return;		/* We've already given an error. */
-
-  child = vnc_display_new ();
-  if (! vnc_display_open_fd (VNC_DISPLAY (child), fd)) {
-    main_status_error (g_strdup ("internal error in Gtk-VNC widget"));
-    return;
-  }
-
-  main_status_error(g_strdup(""));
-
-  for(i = 0; i < 47; ++i) new_title[i] = title_vm[i];
-  for(i = 0; i < 50; ++i){
-     if(vm->description[i] == '\0') break;
-     new_title[47 + i] = vm->description[i];
-  }
-  new_title[i < 50 ? i+47 : 96] = '\0';
-  gtk_window_set_title (GTK_WINDOW (window), new_title);
-
-  /*
-  gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-grab",
-		     GTK_SIGNAL_FUNC(viewer_grab), window);
-  gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-ungrab",
-		     GTK_SIGNAL_FUNC(viewer_ungrab), window);
-  */
-
-  gtk_signal_connect(GTK_OBJECT(child), "delete-event",
-		     GTK_SIGNAL_FUNC(viewer_shutdown), child);
-
-  gtk_signal_connect(GTK_OBJECT(child), "vnc-connected",
-		     GTK_SIGNAL_FUNC(viewer_connected), NULL);
-  gtk_signal_connect(GTK_OBJECT(child), "vnc-initialized",
-		     GTK_SIGNAL_FUNC(viewer_initialized), NULL);
-  gtk_signal_connect(GTK_OBJECT(child), "vnc-disconnected",
-		     GTK_SIGNAL_FUNC(viewer_disconnected), NULL);
-
-  g_signal_connect(GTK_OBJECT(child), "vnc-auth-credential",
-		   GTK_SIGNAL_FUNC(viewer_credential), NULL);
-
-  /* NB. We have to do this before adding it to the notebook. */
-  gtk_widget_show (child);
-
-  /* Choose a tab label, which MUST end with the uuid string, since
-   * we use the tab label to store uuid.
-   */
-  len = strlen (vm->description) + 1 + strlen (vm->uuid) + 1;
-  label2 = g_alloca (len);
-  snprintf (label2, len, "%s %s", vm->description, vm->uuid);
-
-  i = gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child, NULL);
-  gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (notebook), child, label2);
-  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
-
-  DEBUG ("finished creating new tab");
-}
-
-/* Send key to a virtual machine.  This callback is called from the
- * send key menu.  If finds vm in focus, and sends specified key to it
- */
-static void
-send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef)
-{
-  GtkWidget* viewer;
-  struct keyComboDef* key_combo = (struct keyComboDef*) _keyComboDef;
-  int c = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
-
-  if(c < 0){
-    return;
-  }
-
-  viewer = gtk_notebook_get_nth_page(GTK_NOTEBOOK (notebook), c);
-  if(viewer != NULL){
-    DEBUG ("sending keys to vm");
-    vnc_display_send_keys(VNC_DISPLAY(viewer),
-                          key_combo->keys,
-                          key_combo->nkeys);
-  }
-}
-
-
-/*
-static void viewer_grab(GtkWidget *vnc, GtkWidget *window)
-{
-	int i;
-
-	viewer_set_title(VNC_DISPLAY(vnc), window, TRUE);
-
-	for (i = 0 ; i < LAST_MENU; i++) {
-		gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].grabbed_text);
-	}
-}
-
-static void viewer_ungrab(GtkWidget *vnc, GtkWidget *window)
-{
-	int i;
-
-	viewer_set_title(VNC_DISPLAY(vnc), window, FALSE);
-
-	for (i = 0 ; i < LAST_MENU; i++) {
-		gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].ungrabbed_text);
-	}
-}
-*/
-
-static void
-viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc)
-{
-  vnc_display_close (VNC_DISPLAY(vnc));
-
-  /* Just close the notebook tab for now. XXX */
-  gtk_notebook_remove_page (GTK_NOTEBOOK (notebook),
-			    gtk_notebook_page_num (GTK_NOTEBOOK (notebook),
-						   vnc));
-}
-
-#if 0
-static void
-viewer_quit (GtkWidget *src, GtkWidget *vnc)
-{
-  viewer_shutdown (src, NULL, vnc);
-}
-#endif
-
-static void
-viewer_connected (GtkWidget *vnc)
-{
-  DEBUG ("Connected to server");
-}
-
-static void
-viewer_initialized (GtkWidget *vnc, GtkWidget *data)
-{
-  DEBUG ("Connection initialized");
-}
-
-static void
-viewer_disconnected (GtkWidget *vnc)
-{
-  DEBUG ("Disconnected from server");
-}
-
-static void
-viewer_credential (GtkWidget *vnc, GValueArray *credList)
-{
-        GtkWidget *dialog = NULL;
-        int response;
-        unsigned int i, prompt = 0;
-        const char **data;
-
-        DEBUG ("Got credential request for %d credential(s)",
-	       credList->n_values);
-
-        data = g_new0(const char *, credList->n_values);
-
-        for (i = 0 ; i < credList->n_values ; i++) {
-                GValue *cred = g_value_array_get_nth(credList, i);
-                switch (g_value_get_enum(cred)) {
-                case VNC_DISPLAY_CREDENTIAL_USERNAME:
-                case VNC_DISPLAY_CREDENTIAL_PASSWORD:
-                        prompt++;
-                        break;
-                case VNC_DISPLAY_CREDENTIAL_CLIENTNAME:
-                        data[i] = "libvirt";
-                default:
-                        break;
-                }
-        }
-
-        if (prompt) {
-                GtkWidget **label, **entry, *box, *vbox;
-                int row;
-                dialog = gtk_dialog_new_with_buttons("Authentication required",
-                                                     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);
-
-                box = gtk_table_new(credList->n_values, 2, FALSE);
-                label = g_new(GtkWidget *, prompt);
-                entry = g_new(GtkWidget *, prompt);
-
-                for (i = 0, row =0 ; i < credList->n_values ; i++) {
-                        GValue *cred = g_value_array_get_nth(credList, i);
-                        switch (g_value_get_enum(cred)) {
-                        case VNC_DISPLAY_CREDENTIAL_USERNAME:
-                                label[row] = gtk_label_new("Username:");
-                                break;
-                        case VNC_DISPLAY_CREDENTIAL_PASSWORD:
-                                label[row] = gtk_label_new("Password:");
-                                break;
-                        default:
-                                continue;
-                        }
-                        entry[row] = gtk_entry_new();
-                        if (g_value_get_enum(cred) == VNC_DISPLAY_CREDENTIAL_PASSWORD)
-                                gtk_entry_set_visibility(GTK_ENTRY(entry[row]), FALSE);
-
-                        gtk_table_attach(GTK_TABLE(box), label[i], 0, 1, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3);
-                        gtk_table_attach(GTK_TABLE(box), entry[i], 1, 2, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3);
-                        row++;
-                }
-
-                vbox = gtk_bin_get_child(GTK_BIN(dialog));
-                gtk_container_add(GTK_CONTAINER(vbox), box);
-
-                gtk_widget_show_all(dialog);
-                response = gtk_dialog_run(GTK_DIALOG(dialog));
-                gtk_widget_hide(GTK_WIDGET(dialog));
-
-                if (response == GTK_RESPONSE_OK) {
-                        for (i = 0, row = 0 ; i < credList->n_values ; i++) {
-                                GValue *cred = g_value_array_get_nth(credList, i);
-                                switch (g_value_get_enum(cred)) {
-                                case VNC_DISPLAY_CREDENTIAL_USERNAME:
-                                case VNC_DISPLAY_CREDENTIAL_PASSWORD:
-                                        data[i] = gtk_entry_get_text(GTK_ENTRY(entry[row]));
-                                        break;
-                                }
-                        }
-                }
-        }
-
-        for (i = 0 ; i < credList->n_values ; i++) {
-                GValue *cred = g_value_array_get_nth(credList, i);
-                if (data[i]) {
-                        if (vnc_display_set_credential(VNC_DISPLAY(vnc),
-                                                       g_value_get_enum(cred),
-                                                       data[i])) {
-                                DEBUG("Failed to set credential type %d",
-				      g_value_get_enum(cred));
-                                vnc_display_close(VNC_DISPLAY(vnc));
-                        }
-                } else {
-                        DEBUG("Unsupported credential type %d",
-			      g_value_get_enum(cred));
-                        vnc_display_close(VNC_DISPLAY(vnc));
-                }
-        }
-
-        g_free(data);
-        if (dialog)
-                gtk_widget_destroy(GTK_WIDGET(dialog));
-}
-
-#if defined(HAVE_SOCKET) && defined(HAVE_CONNECT) && defined(HAVE_HTONS)
-
-static int 
-viewer_open_vnc_socket(const char* vnchost, int vncport)
-{
-  int result, socketfd;
-  char port[10];
-  struct addrinfo* vnc_addr;
-
-  sprintf(port, "%d", vncport);
-
-  result = getaddrinfo(vnchost, port, NULL, &vnc_addr);
-  if(result != 0 || vnc_addr == NULL)
-      return -1;
-
-  // just use first found, ignoring rest
-  socketfd = socket(vnc_addr->ai_family,
-                    vnc_addr->ai_socktype,
-                    vnc_addr->ai_protocol);
-
-  if(connect(socketfd, vnc_addr->ai_addr, vnc_addr->ai_addrlen) <0)
-      socketfd = -1;
-
-  freeaddrinfo(vnc_addr);
-
-  return socketfd;
-}
-
-#endif /* defined(HAVE_SOCKET) && defined(HAVE_CONNECT) && defined(HAVE_HTONS) */
-
-/* Remove all menu items from the Connect menu. */
-static void
-remove_menu_item (GtkWidget *menu_item, gpointer data)
-{
-  gtk_container_remove (GTK_CONTAINER (connectmenu), menu_item);
-}
-
-static void
-clear_connectmenu (void)
-{
-  DEBUG ("clear Connect menu");
-  gtk_container_foreach (GTK_CONTAINER (connectmenu), remove_menu_item, NULL);
-}
-
-/* The WUI thread has changed its state to connected. */
-gboolean
-main_connected (gpointer data)
-{
-  DEBUG ("connected");
-  ASSERT_IS_MAIN_THREAD ();
-
-  gtk_label_set_text (GTK_LABEL (ca_error), NULL);
-
-  gtk_widget_hide (connection_area);
-  if (!wui_thread_is_logged_in ())
-    gtk_widget_show (login_area);
-  return FALSE;
-}
-
-/* The WUI thread has changed its state to disconnected. */
-gboolean
-main_disconnected (gpointer data)
-{
-  DEBUG ("disconnected");
-  ASSERT_IS_MAIN_THREAD ();
-
-  gtk_widget_show (connection_area);
-  gtk_widget_hide (login_area);
-
-  clear_connectmenu ();
-  gtk_menu_append (GTK_MENU (connectmenu), no_connections);
-  gtk_widget_show_all (connectmenu);
-
-  return FALSE;
-}
-
-/* The WUI thread has changed its state to logged in. */
-gboolean
-main_logged_in (gpointer data)
-{
-  DEBUG ("logged in");
-  ASSERT_IS_MAIN_THREAD ();
-
-  gtk_widget_hide (login_area);
-  return FALSE;
-}
-
-/* The WUI thread has changed its state to logged out. */
-gboolean
-main_logged_out (gpointer data)
-{
-  DEBUG ("logged out");
-  ASSERT_IS_MAIN_THREAD ();
-
-  if (wui_thread_is_connected ())
-    gtk_widget_show (login_area);
-  return FALSE;
-}
-
-/* The WUI thread has changed its state to busy. */
-gboolean
-main_busy (gpointer data)
-{
-  GdkWindow *gdk_window;
-
-  DEBUG ("busy");
-  ASSERT_IS_MAIN_THREAD ();
-
-  gdk_window = gtk_widget_get_window (window);
-  if (gdk_window) {
-    gdk_window_set_cursor (gdk_window, busy_cursor);
-    gdk_flush ();
-  }
-
-  return FALSE;
-}
-
-/* The WUI thread has changed its state to idle. */
-gboolean
-main_idle (gpointer data)
-{
-  GdkWindow *gdk_window;
-
-  DEBUG ("idle");
-  ASSERT_IS_MAIN_THREAD ();
-
-  gdk_window = gtk_widget_get_window (window);
-  if (gdk_window) {
-    gdk_window_set_cursor (gdk_window, NULL);
-    gdk_flush ();
-  }
-
-  return FALSE;
-}
-
-/* The WUI thread had a connection error.  This function must
- * free the string.
- */
-gboolean
-main_connection_error (gpointer _str)
-{
-  char *str = (char *) _str;
-
-  DEBUG ("connection error: %s", str);
-  ASSERT_IS_MAIN_THREAD ();
-
-  gtk_label_set_text (GTK_LABEL (ca_error), str);
-  g_free (str);
-
-  return FALSE;
-}
-
-/* The WUI thread had a login error.  This function must
- * free the string.
- */
-gboolean
-main_login_error (gpointer _str)
-{
-  char *str = (char *) _str;
-
-  DEBUG ("login error: %s", str);
-  ASSERT_IS_MAIN_THREAD ();
-
-  gtk_label_set_text (GTK_LABEL (la_error), str);
-  g_free (str);
-
-  return FALSE;
-}
-
-/* The WUI thread reports a general status error.  This function
- * must free the string.
- */
-gboolean
-main_status_error (gpointer _str)
-{
-  char *str = (char *) _str;
-
-  DEBUG ("status error: %s", str);
-  ASSERT_IS_MAIN_THREAD ();
-
-  gtk_statusbar_pop (GTK_STATUSBAR (statusbar), statusbar_ctx);
-  gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, str);
-  g_free (str);
-
-  return FALSE;
-}
-
-gboolean
-main_vmlist_updated (gpointer data)
-{
-  GSList *new_vmlist;
-
-  DEBUG ("vmlist updated");
-  ASSERT_IS_MAIN_THREAD ();
-
-  /* Get the new vmlist. */
-  if (wui_thread_get_vmlist (&new_vmlist)) {
-    /* Free the previous vmlist.  This invalidates all the vm pointers
-     * contained in the Connect menu callbacks, but we're going to
-     * delete those callbacks and create news ones in a moment anyway ...
-     */
-    free_vmlist (vmlist);
-
-    vmlist = new_vmlist;
-
-    clear_connectmenu ();
-
-    gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist);
-
-    if (vmlist != NULL) {
-      gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist_separator);
-      g_slist_foreach (vmlist, add_vm_to_connectmenu, NULL);
-    }
-
-    /* Grrrr Gtk is stupid. */
-    gtk_widget_show_all (connectmenu);
-  }
-
-  return FALSE;
-}
-
-static void
-add_vm_to_connectmenu (gpointer _vm, gpointer data)
-{
-  struct vm *vm = (struct vm *) _vm;
-  GtkWidget *item;
-
-  // TODO only present running vms ?
-  // if(vm->state == "running")
-
-  DEBUG ("adding %s to Connect menu", vm->description);
-
-  item = gtk_menu_item_new_with_label (vm->description);
-  gtk_menu_append (GTK_MENU (connectmenu), item);
-
-  g_signal_connect (G_OBJECT (item), "activate",
-		    G_CALLBACK (connect_to_vm), vm);
-}
diff --git a/ovirt-viewer.spec b/ovirt-viewer.spec
index a4d826e..1db5994 100644
--- a/ovirt-viewer.spec
+++ b/ovirt-viewer.spec
@@ -51,7 +51,7 @@ make %{?_smp_mflags}
 %install
 rm -rf $RPM_BUILD_ROOT
 
-%{__install} -Dp -m0755 %{pbuild}/ovirt-viewer %{buildroot}%{_bindir}/ovirt-viewer
+%{__install} -Dp -m0755 %{pbuild}/src/ovirt-viewer %{buildroot}%{_bindir}/ovirt-viewer
 
 %clean
 rm -rf $RPM_BUILD_ROOT
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..cdd8f40
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,5 @@
+bin_PROGRAMS = ovirt-viewer
+
+ovirt_viewer_SOURCES = main.c wui_thread.c tunnel.c internal.h
+ovirt_viewer_CFLAGS = $(OVIRT_VIEWER_CFLAGS) $(CFLAGS) -DCAINFO='"$(CAINFO)"'
+ovirt_viewer_LDADD = $(OVIRT_VIEWER_LIBS)
diff --git a/src/internal.h b/src/internal.h
new file mode 100644
index 0000000..bd65922
--- /dev/null
+++ b/src/internal.h
@@ -0,0 +1,220 @@
+/* ovirt viewer console application
+ * Copyright (C) 2008 Red Hat Inc.
+ * Written by Richard W.M. Jones <rjones at redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef OVIRT_VIEWER_INTERNAL_H
+#define OVIRT_VIEWER_INTERNAL_H
+
+#ifndef G_THREADS_ENABLED
+#error "This program requires GLib threads, and cannot be compiled without."
+#endif
+
+/* Debugging messages are always compiled in, but have to
+ * be turned on using the --debug command line switch.
+ */
+extern gboolean debug;
+
+#define DEBUG(fs,...)							\
+  do {									\
+    if (debug) {							\
+      fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__,	\
+	       g_thread_self ());					\
+      fprintf (stderr, (fs), ## __VA_ARGS__);				\
+      fprintf (stderr, "\n");						\
+    }									\
+  } while (0)
+
+/* Verbose messages are always compiled in, but have to
+ * be turned on using the --verbose command line switch.
+ */
+extern gboolean verbose;
+
+#define VERBOSE(fs,...)							\
+  do {									        \
+    if (verbose) {							\
+      fprintf (stderr, "%s:%d: [thread %p] ", __FILE__, __LINE__,	\
+	       g_thread_self ());					\
+      fprintf (stderr, (fs), ## __VA_ARGS__);				\
+      fprintf (stderr, "\n");						\
+    }									\
+  } while (0)
+
+/* String equality tests, suggested by Jim Meyering. */
+#define STREQ(a,b) (strcmp((a),(b)) == 0)
+#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
+#define STRNEQ(a,b) (strcmp((a),(b)) != 0)
+#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
+#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
+#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
+#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
+#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
+#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
+
+extern const char *cainfo;
+extern gboolean check_cert;
+
+/* server we're connecting to */
+extern const char* hostname;
+
+/* port which to connect to the server via vnc */
+extern int ovirt_server_vnc_port;
+
+/* vm currently in focus */
+extern struct vm* vm_in_focus;
+
+/* Communications between the main thread and the WUI thread.  For
+ * an explanation of the threading model, please see the comment in
+ * main().
+ */
+
+extern void start_wui_thread (void);
+extern void stop_wui_thread (void);
+
+extern void assert_is_main_thread (const char *, int);
+extern void assert_is_wui_thread (const char *, int);
+
+#define ASSERT_IS_MAIN_THREAD() assert_is_main_thread(__FILE__,__LINE__)
+#define ASSERT_IS_WUI_THREAD() assert_is_wui_thread(__FILE__,__LINE__)
+
+/* These are messages (instructions) which can be sent from the main
+ * thread to the WUI thread.
+ */
+
+/* Start connecting to WUI, and set the base URI. */
+extern void wui_thread_send_connect (const char *uri);
+
+/* Disconnect, forget URI, credentials, VMs etc. */
+extern void wui_thread_send_disconnect (void);
+
+/* Set the username and password and tell the WUI to try to log in. */
+extern void wui_thread_send_login (const char *username, const char *password);
+
+/* Tell the WUI thread to refresh the VM list.  Note that the WUI
+ * thread does this automatically anyway after a successful login, and
+ * it also periodically updates the list.  This call just tells it to
+ * do so right away.
+ */
+extern void wui_thread_send_refresh_vm_list (void);
+
+/* Retrieve the list of VMs.
+ *
+ * wui_thread_get_vmlist returns TRUE if there was a valid list of
+ * VMs (even if it is empty), or FALSE if we don't have a valid list
+ * of VMs to return.
+ *
+ * NB: Caller must call free_vmlist once it is done with the list.
+ *
+ */
+extern gboolean wui_thread_get_vmlist (GSList **ret);
+extern void free_vmlist (GSList *vmlist);
+
+struct vm {
+  char *description;
+  int hostid;
+  int id;
+  int vnc_port;
+  int forward_vnc_port;
+  char *uuid;			/* Printable UUID. */
+  char *state;
+
+  /* Only the fields above this point are required.  The remainder may
+   * be NULL / -1 to indicate they were missing in the data we got
+   * back from the WUI.
+   */
+
+  long mem_allocated;		/* Kbytes */
+  long mem_used;		/* Kbytes */
+  int vcpus_allocated;
+  int vcpus_used;
+  char *mac_addr;		/* Printable MAC addr. */
+};
+
+/* Returns true if the WUI thread thinks it is connected to a remote
+ * WUI.  REST is connectionless so really this means that we
+ * successfully made an HTTP/HTTPS request "recently", and we haven't
+ * seen any errors above a certain threshold.
+ */
+extern gboolean wui_thread_is_connected (void);
+
+/* Returns true if we successfully logged in with the username
+ * and password supplied in a recent request, and we haven't
+ * received any authorization failures since.
+ */
+extern gboolean wui_thread_is_logged_in (void);
+
+/* Returns true if we have a valid list of VMs.  Note that because
+ * of race conditions, this doesn't guarantee that wui_thread_get_vmlist
+ * will work.
+ */
+extern gboolean wui_thread_has_valid_vmlist (void);
+
+/* Returns true if the WUI thread is busy performing a request
+ * at the moment.
+ */
+extern gboolean wui_thread_is_busy (void);
+
+
+/* Communications between the main thread and the tunnel thread.*/
+extern void start_tunnel (void);
+extern void stop_tunnel  (void);
+
+/* port which local tunnel is listening on */
+extern int tunnel_port;
+
+
+/* Returns true if the main vm list contains a
+ * running vm w/ the same name as specified one
+ */
+extern gboolean main_vmlist_has_running_vm(struct vm*);
+
+/* Callbacks from the WUI thread to the main thread.  The WUI thread
+ * adds these to the Glib main loop using g_idle_add, which means they
+ * actually get executed in the context of the main thread.
+ */
+
+/* The WUI thread has changed its state to connected. */
+extern gboolean main_connected (gpointer);
+
+/* The WUI thread has changed its state to disconnected. */
+extern gboolean main_disconnected (gpointer);
+
+/* The WUI thread has changed its state to logged in. */
+extern gboolean main_logged_in (gpointer);
+
+/* The WUI thread has changed its state to logged out. */
+extern gboolean main_logged_out (gpointer);
+
+/* The WUI thread has changed its state to busy. */
+extern gboolean main_busy (gpointer);
+
+/* The WUI thread has changed its state to idle. */
+extern gboolean main_idle (gpointer);
+
+/* The WUI thread had a connection problem. */
+extern gboolean main_connection_error (gpointer str);
+
+/* The WUI thread had a login problem. */
+extern gboolean main_login_error (gpointer str);
+
+/* The WUI thread reports a general status error. */
+extern gboolean main_status_error (gpointer str);
+
+/* The WUI thread has updated the vm list. */
+extern gboolean main_vmlist_updated (gpointer);
+
+#endif /* OVIRT_VIEWER_INTERNAL_H */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..dd26c93
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,1148 @@
+/* ovirt viewer console application
+ * Copyright (C) 2008 Red Hat Inc.
+ * Written by Richard W.M. Jones <rjones at redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <vncdisplay.h>
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+#include <arpa/inet.h>
+
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+#ifdef HAVE_WINDOWS_H
+#include <windows.h>
+#endif
+
+#include "internal.h"
+
+/*#define HTTPS "https"*/
+#define HTTPS "http"
+
+gboolean debug = 0;
+
+gboolean verbose = 0;
+
+/* Usually /etc/pki/tls/certs/ca-bundle.crt unless overridden during
+ * configure or on the command line.
+ */
+const char *cainfo = CAINFO;
+gboolean check_cert = FALSE; // do we want this enabled by default ?
+                      // would require a CA by default (self-signed wont work)
+                      // (don't set to true, change var/flag to no_check_cert)
+
+/* The WUI thread has updated the vm list.  Here in the main thread
+* we keep our own copy of the vmlist.
+*/
+static GSList *vmlist = NULL;
+
+/*  internal.h shared constructs */
+const char* hostname;
+struct vm* vm_in_focus;
+int ovirt_server_vnc_port = 5900;
+
+/* Private functions. */
+static void start_ui (void);
+static GtkWidget *menu_item_new (int which_menu);
+static void refresh_menu_vm_list (GtkWidget *, gpointer);
+static void connect_to_wui_on_enter (GtkWidget *, gpointer);
+static void connect_to_wui (GtkWidget *, gpointer);
+static void send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef);
+static void login_to_wui_on_enter (GtkWidget *, gpointer);
+static void login_to_wui (GtkWidget *, gpointer);
+static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data);
+static void destroy (GtkWidget *widget, gpointer data);
+static void clear_connectmenu (void);
+static void help_about (GtkWidget *menu);
+static void viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc);
+#if 0
+static void viewer_quit (GtkWidget *src, GtkWidget *vnc);
+#endif
+static void viewer_connected (GtkWidget *vnc);
+static void viewer_initialized (GtkWidget *vnc, GtkWidget *data);
+static void viewer_disconnected (GtkWidget *vnc);
+static void viewer_credential (GtkWidget *vnc, GValueArray *credList);
+static int viewer_open_vnc_socket (const char *vnchost, int vncport);
+static void add_vm_to_connectmenu (gpointer _vm, gpointer data);
+
+/* For any widgets accessed from multiple functions. */
+static GtkWidget *window;
+static GtkWidget *connectitem;
+static GtkWidget *connectmenu;
+static GtkWidget *no_connections;
+static GtkWidget *refresh_vmlist;
+static GtkWidget *refresh_vmlist_separator;
+static GtkWidget *connection_area;
+static GtkWidget *ca_hostname;
+static GtkWidget *ca_button;
+static GtkWidget *ca_error;
+static GtkWidget *login_area;
+static GtkWidget *la_username;
+static GtkWidget *la_password;
+static GtkWidget *la_button;
+static GtkWidget *la_error;
+static GtkWidget *notebook;
+static GtkWidget *statusbar;
+static guint statusbar_ctx;
+static GdkCursor *busy_cursor;
+
+/* Menus. */
+enum menuNums {
+  CONNECT_MENU,
+  VIEW_MENU,
+  SEND_KEY_MENU,
+  WINDOW_MENU,
+  HELP_MENU,
+};
+
+struct menuItem {
+  guint menu;
+  GtkWidget *label;
+  const char *ungrabbed_text;
+  const char *grabbed_text;
+};
+
+static struct menuItem menuItems[] = {
+  { CONNECT_MENU, NULL, "_Connect", "Connect" },
+  { VIEW_MENU, NULL, "_View", "View" },
+  { SEND_KEY_MENU, NULL, "_Send Key", "Send Key" },
+  { WINDOW_MENU, NULL, "_Window", "Window" },
+  { HELP_MENU, NULL, "_Help", "Help" }
+};
+
+#define MAX_KEY_COMBO 3
+ struct keyComboDef {
+ guint keys[MAX_KEY_COMBO];
+ guint nkeys;
+ const char *label;
+ };
+
+#define NUM_KEY_COMBOS 17
+static struct keyComboDef keyCombos[] = {
+ { { GDK_Control_L, GDK_Alt_L, GDK_Delete }, 3, "Ctrl+Alt+Del"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_BackSpace }, 3, "Ctrl+Alt+Backspace"},
+ { {}, 0, "" },
+ { { GDK_Control_L, GDK_Alt_L, GDK_F1 }, 3, "Ctrl+Alt+F1"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F2 }, 3, "Ctrl+Alt+F2"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F3 }, 3, "Ctrl+Alt+F3"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F4 }, 3, "Ctrl+Alt+F4"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F5 }, 3, "Ctrl+Alt+F5"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F6 }, 3, "Ctrl+Alt+F6"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F7 }, 3, "Ctrl+Alt+F7"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F8 }, 3, "Ctrl+Alt+F8"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F5 }, 3, "Ctrl+Alt+F9"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F6 }, 3, "Ctrl+Alt+F10"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F7 }, 3, "Ctrl+Alt+F11"},
+ { { GDK_Control_L, GDK_Alt_L, GDK_F8 }, 3, "Ctrl+Alt+F12"},
+ { {}, 0, "" },
+ { { GDK_Print }, 1, "PrintScreen"},
+};
+
+/* Window title. */
+static const char *title = "oVirt Viewer";
+
+// when running vm
+// 47 chars
+static const char *title_vm =
+   "oVirt Viewer: (ctrl+alt to grab/release mouse) ";
+
+/* Gtk widget styles.  Avoid installation hassles by keeping this
+ * inside the binary.  It can still be overridden by the user (who
+ * will do that?)
+ */
+static const char *styles =
+  "style \"ovirt-viewer-yellow-box\"\n"
+  "{\n"
+  "  bg[NORMAL] = shade (1.5, \"yellow\")\n"
+  "}\n"
+  "widget \"*.ovirt-viewer-connection-area\" style \"ovirt-viewer-yellow-box\"\n"
+  ;
+
+/* Command-line arguments. */
+static int print_version = 0;
+
+static const char *help_msg =
+  "Use '" PACKAGE " --help' to see a list of available command line options";
+
+static const GOptionEntry options[] = {
+  { "port", 'p', 0, G_OPTION_ARG_INT, &ovirt_server_vnc_port,
+    "set port which to connect to server via vnc", NULL },
+  { "cainfo", 0, 0, G_OPTION_ARG_STRING, &cainfo,
+    "set the path of the CA certificate bundle", NULL },
+  { "check-certificate", 0, 0, G_OPTION_ARG_NONE, &check_cert,
+    "check the SSL certificate of the server", NULL },
+  { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug,
+    "turn on debugging messages", NULL },
+  { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
+    "turn on verbose messages", NULL },
+  { "version", 'V', 0, G_OPTION_ARG_NONE, &print_version,
+    "display version and exit", NULL },
+  { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
+};
+
+int
+main (int argc, char *argv[])
+{
+  GOptionContext *context;
+  GError *error = NULL;
+
+  /* Initialize GLib threads before anything else.
+   *
+   * There is one main thread, which is used for all Gtk interactions
+   * and to keep the UI responsive, and one WUI thread.  The WUI
+   * thread is used to connect to the WUI, log in, and maintain the list
+   * of virtual machines.  The WUI thread is the only thread allowed
+   * to use the CURL library.
+   *
+   * The main thread sends instructions to the WUI thread (like "connect",
+   * "disconnect", etc.) using a simple message-passing protocol and
+   * a GAsyncQueue.
+   *
+   * The WUI thread keeps the UI updated by adding idle events which are
+   * processed in the main thread - see:
+   * http://mail.gnome.org/archives/gtk-app-devel-list/2007-March/msg00232.html
+   *
+   * A tunnel thread is also started to locally listen for vnc packets
+   * and make them proxyable, adding the vm name, before forwarding onto
+   * the server
+   *
+   * Note that under Win32 you must confine all Gtk/Gdk interactions
+   * to a single thread - see:
+   * http://developer.gimp.org/api/2.0/gdk/gdk-Threads.html
+   */
+  if (!g_thread_supported ()) {
+    g_thread_init (NULL);
+#ifndef WIN32
+    gdk_threads_init ();
+#endif
+  } else {
+    fprintf (stderr, "GLib threads not supported or not working.");
+    exit (1);
+  }
+
+  gtk_init (&argc, &argv);
+
+  /* Parse command-line options. */
+  context = g_option_context_new ("oVirt viewer");
+  g_option_context_add_main_entries (context, options, NULL);
+  g_option_context_add_group (context, gtk_get_option_group (TRUE));
+  g_option_context_add_group (context, vnc_display_get_option_group ());
+  g_option_context_parse (context, &argc, &argv, &error);
+
+  if (error) {
+    g_print ("%s\n%s\n", error->message, help_msg);
+    g_error_free (error);
+    exit (1);
+  }
+
+  if (print_version) {
+    printf ("%s %s\n", PACKAGE, VERSION);
+    exit (0);
+  }
+
+  start_wui_thread ();
+  start_ui ();
+
+  DEBUG ("entering the Gtk main loop");
+
+  gtk_main ();
+
+  stop_wui_thread ();
+  stop_tunnel();
+
+  exit (0);
+}
+
+/* Create the viewer window, menus. */
+static void
+start_ui (void)
+{
+  int i;
+  GtkWidget *vbox;
+  GtkWidget *menubar;
+  GtkWidget *view;
+  GtkWidget *viewmenu;
+  GtkWidget *sendkey;
+  GtkWidget *sendkeymenu;
+  GtkWidget *sendkeymenuitem;
+  GtkWidget *wind;
+  GtkWidget *windmenu;
+  GtkWidget *help;
+  GtkWidget *helpmenu;
+  GtkWidget *about;
+  GtkWidget *ca_vbox;
+  GtkWidget *ca_hbox;
+  GtkWidget *ca_label;
+  GtkWidget *la_vbox;
+  GtkWidget *la_hbox;
+  GtkWidget *la_label;
+
+  DEBUG ("creating viewer windows and menus");
+
+  /* Parse styles. */
+  gtk_rc_parse_string (styles);
+
+  /* Busy cursor, used by main_busy() function.
+   * XXX This cursor is crap - how can we use the Bluecurve/theme cursor?
+   */
+  busy_cursor = gdk_cursor_new (GDK_WATCH);
+
+  /* Window. */
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
+  gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
+  gtk_window_set_title (GTK_WINDOW (window), title);
+
+  g_signal_connect (G_OBJECT (window), "delete_event",
+		    G_CALLBACK (delete_event), NULL);
+  g_signal_connect (G_OBJECT (window), "destroy",
+		    G_CALLBACK (destroy), NULL);
+
+  /* VBox for layout within the window. */
+  vbox = gtk_vbox_new (FALSE, 0);
+
+  /* Menubar. */
+  menubar = gtk_menu_bar_new ();
+  connectitem = menu_item_new (CONNECT_MENU);
+  connectmenu = gtk_menu_new ();
+  gtk_menu_item_set_submenu (GTK_MENU_ITEM (connectitem), connectmenu);
+
+  no_connections = gtk_menu_item_new_with_label ("Not connected");
+  gtk_menu_append (GTK_MENU (connectmenu), no_connections);
+  gtk_widget_set_sensitive (no_connections, FALSE);
+
+  /* This is not added to the menu yet, but will be when we are
+   * connected.
+   */
+  refresh_vmlist =
+    gtk_menu_item_new_with_label ("Refresh list of virtual machines");
+  g_object_ref (refresh_vmlist);
+  refresh_vmlist_separator = gtk_separator_menu_item_new ();
+  g_object_ref (refresh_vmlist_separator);
+
+  g_signal_connect (G_OBJECT (refresh_vmlist), "activate",
+		    G_CALLBACK (refresh_menu_vm_list), NULL);
+
+#if 0
+  screenshot = gtk_menu_item_new_with_mnemonic ("_Screenshot");
+  gtk_menu_append (GTK_MENU (filemenu), screenshot);
+  g_signal_connect (screenshot, "activate",
+		    GTK_SIGNAL_FUNC (take_screenshot), NULL);
+#endif
+
+  view = menu_item_new (VIEW_MENU);
+  viewmenu = gtk_menu_new ();
+  gtk_menu_item_set_submenu (GTK_MENU_ITEM (view), viewmenu);
+
+  sendkey = menu_item_new (SEND_KEY_MENU);
+  sendkeymenu = gtk_menu_new ();
+  gtk_menu_item_set_submenu (GTK_MENU_ITEM (sendkey), sendkeymenu);
+
+  for(i = 0; i < NUM_KEY_COMBOS; ++i){
+    sendkeymenuitem = gtk_menu_item_new_with_label (keyCombos[i].label);
+    gtk_menu_append (GTK_MENU (sendkeymenu), sendkeymenuitem);
+    g_signal_connect (G_OBJECT (sendkeymenuitem), "activate",
+                      G_CALLBACK (send_key_to_vm), &(keyCombos[i]));
+  }
+
+  wind = menu_item_new (WINDOW_MENU);
+  windmenu = gtk_menu_new ();
+  gtk_menu_item_set_submenu (GTK_MENU_ITEM (wind), windmenu);
+
+  help = menu_item_new (HELP_MENU);
+  helpmenu = gtk_menu_new ();
+  gtk_menu_item_set_submenu (GTK_MENU_ITEM (help), helpmenu);
+
+  about = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL);
+  gtk_menu_append(GTK_MENU(helpmenu), about);
+  g_signal_connect(about, "activate", GTK_SIGNAL_FUNC (help_about), NULL);
+
+  gtk_menu_bar_append (GTK_MENU_BAR (menubar), connectitem);
+  gtk_menu_bar_append (GTK_MENU_BAR (menubar), view);
+  gtk_menu_bar_append (GTK_MENU_BAR (menubar), sendkey);
+  gtk_menu_bar_append (GTK_MENU_BAR (menubar), wind);
+  gtk_menu_bar_append (GTK_MENU_BAR (menubar), help);
+
+  /* For login dialogs, etc., usually invisible. */
+  connection_area = gtk_event_box_new ();
+  ca_vbox = gtk_vbox_new (FALSE, 0);
+  ca_label = gtk_label_new ("Give the name of the oVirt management server:");
+  ca_hbox = gtk_hbox_new (FALSE, 0);
+  ca_hostname = gtk_entry_new ();
+  gtk_entry_set_width_chars (GTK_ENTRY (ca_hostname), 24);
+  ca_button = gtk_button_new_with_label ("Connect");
+  ca_error = gtk_label_new (NULL);
+  gtk_box_pack_start (GTK_BOX (ca_hbox), ca_hostname, FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (ca_hbox), ca_button,   FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (ca_vbox), ca_label,    FALSE, FALSE, 4);
+  gtk_box_pack_start (GTK_BOX (ca_vbox), ca_hbox,     TRUE,  FALSE, 4);
+  gtk_box_pack_start (GTK_BOX (ca_vbox), ca_error,    TRUE,  FALSE, 4);
+  gtk_container_add (GTK_CONTAINER (connection_area), ca_vbox);
+
+  gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area");
+
+  g_signal_connect (G_OBJECT (ca_hostname), "key-release-event",
+		    G_CALLBACK (connect_to_wui_on_enter), NULL);
+  g_signal_connect (G_OBJECT (ca_button), "clicked",
+		    G_CALLBACK (connect_to_wui), NULL);
+
+  login_area = gtk_event_box_new ();
+  la_vbox = gtk_vbox_new (FALSE, 0);
+  la_hbox = gtk_hbox_new (FALSE, 0);
+  la_label = gtk_label_new ("Username / password:");
+  la_username = gtk_entry_new ();
+  gtk_entry_set_width_chars (GTK_ENTRY (la_username), 12);
+  la_password = gtk_entry_new ();
+  gtk_entry_set_width_chars (GTK_ENTRY (la_password), 12);
+  gtk_entry_set_visibility (GTK_ENTRY (la_password), FALSE);
+  la_button = gtk_button_new_with_label ("Login");
+  la_error = gtk_label_new (NULL);
+  gtk_container_add (GTK_CONTAINER (la_hbox), la_username);
+  gtk_container_add (GTK_CONTAINER (la_hbox), la_password);
+  gtk_container_add (GTK_CONTAINER (la_hbox), la_button);
+  gtk_box_pack_start (GTK_BOX (la_vbox), la_label,     TRUE,  FALSE, 4);
+  gtk_box_pack_start (GTK_BOX (la_vbox), la_hbox,     TRUE,  FALSE, 4);
+  gtk_box_pack_start (GTK_BOX (la_vbox), la_error,    TRUE,  FALSE, 4);
+  gtk_container_add (GTK_CONTAINER (login_area), la_vbox);
+
+  gtk_widget_set_name (login_area, "ovirt-viewer-login-area");
+
+  g_signal_connect (G_OBJECT (la_username), "key-release-event",
+		    G_CALLBACK (login_to_wui_on_enter), NULL);
+  g_signal_connect (G_OBJECT (la_password), "key-release-event",
+		    G_CALLBACK (login_to_wui_on_enter), NULL);
+  g_signal_connect (G_OBJECT (la_button), "clicked",
+		    G_CALLBACK (login_to_wui), NULL);
+
+  /* Tabbed notebook. */
+  notebook = gtk_notebook_new ();
+  /*gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);*/
+  gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), TRUE);
+  gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
+
+  /* Status bar. */
+  statusbar = gtk_statusbar_new ();
+  statusbar_ctx = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar),
+						"context");
+  gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, "");
+
+  /* Packing. */
+  gtk_container_add (GTK_CONTAINER (window), vbox);
+  gtk_container_add_with_properties (GTK_CONTAINER (vbox), menubar,
+				     "expand", FALSE, NULL);
+  gtk_container_add_with_properties (GTK_CONTAINER (vbox), connection_area,
+				     "expand", FALSE, "fill", TRUE, NULL);
+  gtk_container_add_with_properties (GTK_CONTAINER (vbox), login_area,
+				     "expand", FALSE, "fill", TRUE, NULL);
+  gtk_container_add_with_properties (GTK_CONTAINER (vbox), notebook,
+				     "expand", TRUE, NULL);
+  gtk_container_add_with_properties (GTK_CONTAINER (vbox), statusbar,
+				     "expand", FALSE, NULL);
+
+  /* Show widgets. */
+  gtk_widget_show_all (window);
+
+  if (wui_thread_is_connected ())
+    gtk_widget_hide (connection_area);
+  if (!wui_thread_is_connected () || wui_thread_is_logged_in ())
+    gtk_widget_hide (login_area);
+}
+
+static GtkWidget *
+menu_item_new (int which_menu)
+{
+  GtkWidget *widget;
+  GtkWidget *label;
+  const char *text;
+
+  text = menuItems[which_menu].ungrabbed_text;
+
+  widget = gtk_menu_item_new ();
+  label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL);
+  gtk_label_set_text_with_mnemonic (GTK_LABEL (label), text);
+  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+  gtk_container_add (GTK_CONTAINER (widget), label);
+  gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), widget);
+  gtk_widget_show (label);
+
+  menuItems[which_menu].label = label;
+
+  return widget;
+}
+
+static gboolean
+delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+  DEBUG ("delete_event");
+  return FALSE;
+}
+
+static void
+destroy (GtkWidget *widget, gpointer data)
+{
+  DEBUG ("destroy");
+  gtk_main_quit ();
+}
+
+static void
+help_about (GtkWidget *menu)
+{
+  GtkWidget *about;
+  const char *authors[] = {
+    "Richard W.M. Jones <rjones at redhat.com>",
+    "Daniel P. Berrange <berrange at redhat.com>",
+    "Mohammed Morsi <mmorsi at redhat.com>",
+    NULL
+  };
+
+  about = gtk_about_dialog_new();
+
+  gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(about), "oVirt Viewer");
+  gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about), VERSION);
+  gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about), "http://ovirt.org/");
+  gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(about), "oVirt website");
+  gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about), authors);
+  gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about), 
+ "This program is free software; you can redistribute it and/or modify\n" \
+ "it under the terms of the GNU General Public License as published by\n" \
+ "the Free Software Foundation; either version 2 of the License, or\n" \
+ "(at your option) any later version.\n" \
+ "\n" \
+ "This program is distributed in the hope that it will be useful,\n" \
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" \
+ "GNU General Public License for more details.\n" \
+ "\n" \
+ "You should have received a copy of the GNU General Public License\n" \
+ "along with this program; if not, write to the Free Software\n" \
+ "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n");
+
+  gtk_dialog_run(GTK_DIALOG(about));
+  gtk_widget_destroy(about);
+}
+
+static void
+refresh_menu_vm_list(GtkWidget *widget, gpointer data)
+{
+   wui_thread_send_refresh_vm_list();
+}
+
+static void
+connect_to_wui_on_enter (GtkWidget *widget, gpointer data)
+{
+  // if key released was not 'enter' key
+  if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return;
+
+  connect_to_wui(widget, data);
+}
+
+static void
+connect_to_wui (GtkWidget *widget, gpointer data)
+{
+  char *uri;
+  int len;
+
+  hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname));
+  if (STREQ (hostname, "")) return;
+
+  /* https:// + hostname + /ovirt + \0 */
+  len = 8 + strlen (hostname) + 6 + 1;
+  uri = g_alloca (len);
+  snprintf (uri, len, HTTPS "://%s/ovirt", hostname);
+
+  wui_thread_send_connect (uri);
+  start_tunnel();
+}
+
+static void
+login_to_wui_on_enter (GtkWidget *widget, gpointer data)
+{
+  // if key released was not 'enter' key
+  if(((GdkEventKey *)data)->type == GDK_KEY_RELEASE && (((GdkEventKey *)data)->keyval & 0xFF) != 13 ) return;
+
+  login_to_wui(widget, data);
+}
+
+static void
+login_to_wui (GtkWidget *widget, gpointer data)
+{
+  const char *username, *password;
+
+  username = gtk_entry_get_text (GTK_ENTRY (la_username));
+  if (STREQ (username, "")) return;
+  password = gtk_entry_get_text (GTK_ENTRY (la_password));
+
+  wui_thread_send_login (username, password);
+}
+
+/* Connect to a virtual machine.  This callback is called from the
+ * connect menu.  It searches the notebook of gtk-vnc widgets to see
+ * if we have already connected to this machine, and if not it
+ * makes a new connection.
+ */
+static void
+connect_to_vm (GtkWidget *widget, gpointer _vm)
+{
+  struct vm *vm = (struct vm *) _vm;
+  int n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));
+  int i, uuidlen, len, fd;
+  GtkWidget *child;
+  const char *label;
+  char *label2;
+  char new_title[97]; // 47 chars for title + 50 for vm name
+
+  DEBUG ("searching tabs for uuid %s", vm->uuid);
+
+  uuidlen = strlen (vm->uuid);
+
+  /* Search the tabs for this UUID, and if found, switch to it and return. */
+  for (i = 0; i < n; ++i) {
+    child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i);
+    label = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (notebook), child);
+    len = strlen (label);
+    if (len >= uuidlen &&
+	STREQ (label + len - uuidlen, vm->uuid)) {
+      DEBUG ("found on tab %d", i);
+      gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
+      return;
+    }
+  }
+
+  DEBUG ("not found, creating new tab");
+
+  // before we open vnc connection, make sure vm is running
+  gboolean vm_running = main_vmlist_has_running_vm(vm);
+
+  if(!vm_running){
+    main_status_error (g_strdup ("VM not running"));
+    return;
+  }
+
+  // FIXME on notebook tab switch, change vm_in_focus
+  vm_in_focus = vm;
+
+  /* This VM isn't in the notebook already, so create a new console. */
+  DEBUG ("connecting to local tunnel on port %i", tunnel_port);
+  fd = viewer_open_vnc_socket("127.0.0.1", tunnel_port);
+  if (fd == -1) return;		/* We've already given an error. */
+
+  child = vnc_display_new ();
+  if (! vnc_display_open_fd (VNC_DISPLAY (child), fd)) {
+    main_status_error (g_strdup ("internal error in Gtk-VNC widget"));
+    return;
+  }
+
+  main_status_error(g_strdup(""));
+
+  for(i = 0; i < 47; ++i) new_title[i] = title_vm[i];
+  for(i = 0; i < 50; ++i){
+     if(vm->description[i] == '\0') break;
+     new_title[47 + i] = vm->description[i];
+  }
+  new_title[i < 50 ? i+47 : 96] = '\0';
+  gtk_window_set_title (GTK_WINDOW (window), new_title);
+
+  /*
+  gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-grab",
+		     GTK_SIGNAL_FUNC(viewer_grab), window);
+  gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-ungrab",
+		     GTK_SIGNAL_FUNC(viewer_ungrab), window);
+  */
+
+  gtk_signal_connect(GTK_OBJECT(child), "delete-event",
+		     GTK_SIGNAL_FUNC(viewer_shutdown), child);
+
+  gtk_signal_connect(GTK_OBJECT(child), "vnc-connected",
+		     GTK_SIGNAL_FUNC(viewer_connected), NULL);
+  gtk_signal_connect(GTK_OBJECT(child), "vnc-initialized",
+		     GTK_SIGNAL_FUNC(viewer_initialized), NULL);
+  gtk_signal_connect(GTK_OBJECT(child), "vnc-disconnected",
+		     GTK_SIGNAL_FUNC(viewer_disconnected), NULL);
+
+  g_signal_connect(GTK_OBJECT(child), "vnc-auth-credential",
+		   GTK_SIGNAL_FUNC(viewer_credential), NULL);
+
+  /* NB. We have to do this before adding it to the notebook. */
+  gtk_widget_show (child);
+
+  /* Choose a tab label, which MUST end with the uuid string, since
+   * we use the tab label to store uuid.
+   */
+  len = strlen (vm->description) + 1 + strlen (vm->uuid) + 1;
+  label2 = g_alloca (len);
+  snprintf (label2, len, "%s %s", vm->description, vm->uuid);
+
+  i = gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child, NULL);
+  gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (notebook), child, label2);
+  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
+
+  DEBUG ("finished creating new tab");
+}
+
+/* Send key to a virtual machine.  This callback is called from the
+ * send key menu.  If finds vm in focus, and sends specified key to it
+ */
+static void
+send_key_to_vm (GtkWidget *widget, gpointer _keyComboDef)
+{
+  GtkWidget* viewer;
+  struct keyComboDef* key_combo = (struct keyComboDef*) _keyComboDef;
+  int c = gtk_notebook_get_current_page (GTK_NOTEBOOK (notebook));
+
+  if(c < 0){
+    return;
+  }
+
+  viewer = gtk_notebook_get_nth_page(GTK_NOTEBOOK (notebook), c);
+  if(viewer != NULL){
+    DEBUG ("sending keys to vm");
+    vnc_display_send_keys(VNC_DISPLAY(viewer),
+                          key_combo->keys,
+                          key_combo->nkeys);
+  }
+}
+
+
+/*
+static void viewer_grab(GtkWidget *vnc, GtkWidget *window)
+{
+	int i;
+
+	viewer_set_title(VNC_DISPLAY(vnc), window, TRUE);
+
+	for (i = 0 ; i < LAST_MENU; i++) {
+		gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].grabbed_text);
+	}
+}
+
+static void viewer_ungrab(GtkWidget *vnc, GtkWidget *window)
+{
+	int i;
+
+	viewer_set_title(VNC_DISPLAY(vnc), window, FALSE);
+
+	for (i = 0 ; i < LAST_MENU; i++) {
+		gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].ungrabbed_text);
+	}
+}
+*/
+
+static void
+viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc)
+{
+  vnc_display_close (VNC_DISPLAY(vnc));
+
+  /* Just close the notebook tab for now. XXX */
+  gtk_notebook_remove_page (GTK_NOTEBOOK (notebook),
+			    gtk_notebook_page_num (GTK_NOTEBOOK (notebook),
+						   vnc));
+}
+
+#if 0
+static void
+viewer_quit (GtkWidget *src, GtkWidget *vnc)
+{
+  viewer_shutdown (src, NULL, vnc);
+}
+#endif
+
+static void
+viewer_connected (GtkWidget *vnc)
+{
+  DEBUG ("Connected to server");
+}
+
+static void
+viewer_initialized (GtkWidget *vnc, GtkWidget *data)
+{
+  DEBUG ("Connection initialized");
+}
+
+static void
+viewer_disconnected (GtkWidget *vnc)
+{
+  DEBUG ("Disconnected from server");
+}
+
+static void
+viewer_credential (GtkWidget *vnc, GValueArray *credList)
+{
+        GtkWidget *dialog = NULL;
+        int response;
+        unsigned int i, prompt = 0;
+        const char **data;
+
+        DEBUG ("Got credential request for %d credential(s)",
+	       credList->n_values);
+
+        data = g_new0(const char *, credList->n_values);
+
+        for (i = 0 ; i < credList->n_values ; i++) {
+                GValue *cred = g_value_array_get_nth(credList, i);
+                switch (g_value_get_enum(cred)) {
+                case VNC_DISPLAY_CREDENTIAL_USERNAME:
+                case VNC_DISPLAY_CREDENTIAL_PASSWORD:
+                        prompt++;
+                        break;
+                case VNC_DISPLAY_CREDENTIAL_CLIENTNAME:
+                        data[i] = "libvirt";
+                default:
+                        break;
+                }
+        }
+
+        if (prompt) {
+                GtkWidget **label, **entry, *box, *vbox;
+                int row;
+                dialog = gtk_dialog_new_with_buttons("Authentication required",
+                                                     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);
+
+                box = gtk_table_new(credList->n_values, 2, FALSE);
+                label = g_new(GtkWidget *, prompt);
+                entry = g_new(GtkWidget *, prompt);
+
+                for (i = 0, row =0 ; i < credList->n_values ; i++) {
+                        GValue *cred = g_value_array_get_nth(credList, i);
+                        switch (g_value_get_enum(cred)) {
+                        case VNC_DISPLAY_CREDENTIAL_USERNAME:
+                                label[row] = gtk_label_new("Username:");
+                                break;
+                        case VNC_DISPLAY_CREDENTIAL_PASSWORD:
+                                label[row] = gtk_label_new("Password:");
+                                break;
+                        default:
+                                continue;
+                        }
+                        entry[row] = gtk_entry_new();
+                        if (g_value_get_enum(cred) == VNC_DISPLAY_CREDENTIAL_PASSWORD)
+                                gtk_entry_set_visibility(GTK_ENTRY(entry[row]), FALSE);
+
+                        gtk_table_attach(GTK_TABLE(box), label[i], 0, 1, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3);
+                        gtk_table_attach(GTK_TABLE(box), entry[i], 1, 2, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3);
+                        row++;
+                }
+
+                vbox = gtk_bin_get_child(GTK_BIN(dialog));
+                gtk_container_add(GTK_CONTAINER(vbox), box);
+
+                gtk_widget_show_all(dialog);
+                response = gtk_dialog_run(GTK_DIALOG(dialog));
+                gtk_widget_hide(GTK_WIDGET(dialog));
+
+                if (response == GTK_RESPONSE_OK) {
+                        for (i = 0, row = 0 ; i < credList->n_values ; i++) {
+                                GValue *cred = g_value_array_get_nth(credList, i);
+                                switch (g_value_get_enum(cred)) {
+                                case VNC_DISPLAY_CREDENTIAL_USERNAME:
+                                case VNC_DISPLAY_CREDENTIAL_PASSWORD:
+                                        data[i] = gtk_entry_get_text(GTK_ENTRY(entry[row]));
+                                        break;
+                                }
+                        }
+                }
+        }
+
+        for (i = 0 ; i < credList->n_values ; i++) {
+                GValue *cred = g_value_array_get_nth(credList, i);
+                if (data[i]) {
+                        if (vnc_display_set_credential(VNC_DISPLAY(vnc),
+                                                       g_value_get_enum(cred),
+                                                       data[i])) {
+                                DEBUG("Failed to set credential type %d",
+				      g_value_get_enum(cred));
+                                vnc_display_close(VNC_DISPLAY(vnc));
+                        }
+                } else {
+                        DEBUG("Unsupported credential type %d",
+			      g_value_get_enum(cred));
+                        vnc_display_close(VNC_DISPLAY(vnc));
+                }
+        }
+
+        g_free(data);
+        if (dialog)
+                gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+#if defined(HAVE_SOCKET) && defined(HAVE_CONNECT) && defined(HAVE_HTONS)
+
+static int 
+viewer_open_vnc_socket(const char* vnchost, int vncport)
+{
+  int result, socketfd;
+  char port[10];
+  struct addrinfo* vnc_addr;
+
+  sprintf(port, "%d", vncport);
+
+  result = getaddrinfo(vnchost, port, NULL, &vnc_addr);
+  if(result != 0 || vnc_addr == NULL)
+      return -1;
+
+  // just use first found, ignoring rest
+  socketfd = socket(vnc_addr->ai_family,
+                    vnc_addr->ai_socktype,
+                    vnc_addr->ai_protocol);
+
+  if(connect(socketfd, vnc_addr->ai_addr, vnc_addr->ai_addrlen) <0)
+      socketfd = -1;
+
+  freeaddrinfo(vnc_addr);
+
+  return socketfd;
+}
+
+#endif /* defined(HAVE_SOCKET) && defined(HAVE_CONNECT) && defined(HAVE_HTONS) */
+
+/* Remove all menu items from the Connect menu. */
+static void
+remove_menu_item (GtkWidget *menu_item, gpointer data)
+{
+  gtk_container_remove (GTK_CONTAINER (connectmenu), menu_item);
+}
+
+static void
+clear_connectmenu (void)
+{
+  DEBUG ("clear Connect menu");
+  gtk_container_foreach (GTK_CONTAINER (connectmenu), remove_menu_item, NULL);
+}
+
+/* The WUI thread has changed its state to connected. */
+gboolean
+main_connected (gpointer data)
+{
+  DEBUG ("connected");
+  ASSERT_IS_MAIN_THREAD ();
+
+  gtk_label_set_text (GTK_LABEL (ca_error), NULL);
+
+  gtk_widget_hide (connection_area);
+  if (!wui_thread_is_logged_in ())
+    gtk_widget_show (login_area);
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to disconnected. */
+gboolean
+main_disconnected (gpointer data)
+{
+  DEBUG ("disconnected");
+  ASSERT_IS_MAIN_THREAD ();
+
+  gtk_widget_show (connection_area);
+  gtk_widget_hide (login_area);
+
+  clear_connectmenu ();
+  gtk_menu_append (GTK_MENU (connectmenu), no_connections);
+  gtk_widget_show_all (connectmenu);
+
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to logged in. */
+gboolean
+main_logged_in (gpointer data)
+{
+  DEBUG ("logged in");
+  ASSERT_IS_MAIN_THREAD ();
+
+  gtk_widget_hide (login_area);
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to logged out. */
+gboolean
+main_logged_out (gpointer data)
+{
+  DEBUG ("logged out");
+  ASSERT_IS_MAIN_THREAD ();
+
+  if (wui_thread_is_connected ())
+    gtk_widget_show (login_area);
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to busy. */
+gboolean
+main_busy (gpointer data)
+{
+  GdkWindow *gdk_window;
+
+  DEBUG ("busy");
+  ASSERT_IS_MAIN_THREAD ();
+
+  gdk_window = gtk_widget_get_window (window);
+  if (gdk_window) {
+    gdk_window_set_cursor (gdk_window, busy_cursor);
+    gdk_flush ();
+  }
+
+  return FALSE;
+}
+
+/* The WUI thread has changed its state to idle. */
+gboolean
+main_idle (gpointer data)
+{
+  GdkWindow *gdk_window;
+
+  DEBUG ("idle");
+  ASSERT_IS_MAIN_THREAD ();
+
+  gdk_window = gtk_widget_get_window (window);
+  if (gdk_window) {
+    gdk_window_set_cursor (gdk_window, NULL);
+    gdk_flush ();
+  }
+
+  return FALSE;
+}
+
+/* The WUI thread had a connection error.  This function must
+ * free the string.
+ */
+gboolean
+main_connection_error (gpointer _str)
+{
+  char *str = (char *) _str;
+
+  DEBUG ("connection error: %s", str);
+  ASSERT_IS_MAIN_THREAD ();
+
+  gtk_label_set_text (GTK_LABEL (ca_error), str);
+  g_free (str);
+
+  return FALSE;
+}
+
+/* The WUI thread had a login error.  This function must
+ * free the string.
+ */
+gboolean
+main_login_error (gpointer _str)
+{
+  char *str = (char *) _str;
+
+  DEBUG ("login error: %s", str);
+  ASSERT_IS_MAIN_THREAD ();
+
+  gtk_label_set_text (GTK_LABEL (la_error), str);
+  g_free (str);
+
+  return FALSE;
+}
+
+/* The WUI thread reports a general status error.  This function
+ * must free the string.
+ */
+gboolean
+main_status_error (gpointer _str)
+{
+  char *str = (char *) _str;
+
+  DEBUG ("status error: %s", str);
+  ASSERT_IS_MAIN_THREAD ();
+
+  gtk_statusbar_pop (GTK_STATUSBAR (statusbar), statusbar_ctx);
+  gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, str);
+  g_free (str);
+
+  return FALSE;
+}
+
+gboolean
+main_vmlist_updated (gpointer data)
+{
+  GSList *new_vmlist;
+
+  DEBUG ("vmlist updated");
+  ASSERT_IS_MAIN_THREAD ();
+
+  /* Get the new vmlist. */
+  if (wui_thread_get_vmlist (&new_vmlist)) {
+    /* Free the previous vmlist.  This invalidates all the vm pointers
+     * contained in the Connect menu callbacks, but we're going to
+     * delete those callbacks and create news ones in a moment anyway ...
+     */
+    free_vmlist (vmlist);
+
+    vmlist = new_vmlist;
+
+    clear_connectmenu ();
+
+    gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist);
+
+    if (vmlist != NULL) {
+      gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist_separator);
+      g_slist_foreach (vmlist, add_vm_to_connectmenu, NULL);
+    }
+
+    /* Grrrr Gtk is stupid. */
+    gtk_widget_show_all (connectmenu);
+  }
+
+  return FALSE;
+}
+
+static void
+add_vm_to_connectmenu (gpointer _vm, gpointer data)
+{
+  struct vm *vm = (struct vm *) _vm;
+  GtkWidget *item;
+
+  // TODO only present running vms ?
+  // if(vm->state == "running")
+
+  DEBUG ("adding %s to Connect menu", vm->description);
+
+  item = gtk_menu_item_new_with_label (vm->description);
+  gtk_menu_append (GTK_MENU (connectmenu), item);
+
+  g_signal_connect (G_OBJECT (item), "activate",
+		    G_CALLBACK (connect_to_vm), vm);
+}
diff --git a/src/tunnel.c b/src/tunnel.c
new file mode 100644
index 0000000..17b512c
--- /dev/null
+++ b/src/tunnel.c
@@ -0,0 +1,327 @@
+/* ovirt viewer console application
+ * Copyright (C) 2008 Red Hat Inc.
+ * Written by Mohammed Morsi <mmorsi at redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* ovirt-viewer starts listening on network port to 
+ *  encapsulate vnc packets including the vm's name
+ *  so as to be able to be proxied. 
+ *
+ * This operation takes place in another thread
+ * which can be started/stopped by calling 
+ * start_tunnel / stop_tunnel. 
+ *
+ * An additional connection thread is created and maintained
+ * internally for each vm / vnc connection open in ovirt-viewer
+ * establishing a connection w/ the ovirt server.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "internal.h"
+
+/* constants */
+
+// max length of a vm name
+const int VM_NAME_MAX_LEN = 250;
+
+// max length of vnc data
+const int VNC_DATA_MAX_LEN = 800000;
+
+/* Private thread functions */
+static gpointer tunnel_thread(gpointer data);
+static gpointer client_server_thread(gpointer data);
+static gpointer server_client_thread(gpointer data);
+
+/* Other private functions */
+static void close_socket(gpointer _socket, gpointer data);
+static void wait_for_thread(gpointer _thread, gpointer data);
+
+/* tunnel and main threads */
+static GThread *tunnel_gthread = NULL;
+static GThread *main_gthread = NULL;
+
+/* list of communication threads */
+static GSList *communication_threads = NULL;
+
+/* list of sockets */
+static GSList *sockets = NULL;
+
+/* thread termination flag */
+static gboolean run_tunnel = FALSE;
+
+/*  internal.h shared constructs */
+int tunnel_port;
+
+/////////////////
+
+/** public implementations **/
+
+/* start tunnel thread */
+void 
+start_tunnel(void)
+{
+  GError *error = NULL;
+
+  DEBUG ("starting the tunnel thread");
+
+  assert (tunnel_gthread == NULL);
+
+  run_tunnel = TRUE;
+
+  main_gthread = g_thread_self ();
+
+  tunnel_gthread = g_thread_create (tunnel_thread, NULL, TRUE, &error);
+  if (error) {
+    g_print ("%s\n", error->message);
+    g_error_free (error);
+    exit (1);
+  }
+};
+
+/* stop tunnel thread */
+void 
+stop_tunnel(void)
+{
+  if(!run_tunnel)
+      return;
+
+  DEBUG ("stopping the tunnel thread");
+
+  assert (tunnel_gthread != NULL);
+  ASSERT_IS_MAIN_THREAD ();
+
+  run_tunnel = FALSE;
+
+  g_slist_foreach(sockets, close_socket, NULL);
+
+  (void) g_thread_join (tunnel_gthread);
+  tunnel_gthread = NULL;
+};
+
+/////////////////
+
+/** private implementations **/
+
+/* the tunnel thread */
+static gpointer
+tunnel_thread (gpointer _data)
+{
+  struct hostent *dns_serv;
+
+  //char vm_data[VM_NAME_MAX_LEN];
+  int local_server_socketfd, ovirt_server_socket, client_socketfd;
+  unsigned int local_server_len, client_len, ovirt_server_len;
+
+  struct sockaddr_in local_server_address;
+  struct sockaddr_in ovirt_server_address;
+  struct sockaddr_in client_address;
+
+  struct sockaddr_in local_server_address_lookup;
+  unsigned int local_server_address_lookup_len = sizeof(local_server_address_lookup);
+
+  GThread *client_server_gthread = NULL;
+  GThread *server_client_gthread = NULL;
+
+  int sockets_param[2];
+  int * c_socket;
+
+  DEBUG ("tunnel thread starting up");
+
+  // ovirt server address
+  dns_serv = gethostbyname(hostname);
+  if(dns_serv == NULL){
+      DEBUG("ovirt server lookup failed");
+      return NULL;
+  }
+  ovirt_server_address.sin_family = PF_INET;
+  ovirt_server_address.sin_addr.s_addr = ((struct in_addr*)(dns_serv->h_addr))->s_addr; //inet_addr(hostname);
+  ovirt_server_address.sin_port = htons(ovirt_server_vnc_port);
+  ovirt_server_len = sizeof(ovirt_server_address);
+
+  // create local net socket
+  local_server_socketfd = socket(PF_INET, SOCK_STREAM, 0);
+  c_socket = malloc(sizeof(int)); *c_socket = local_server_socketfd;
+  sockets = g_slist_prepend(sockets, c_socket);
+
+  // local server address
+  local_server_address.sin_family = PF_INET;
+  local_server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
+  local_server_address.sin_port = 0;
+  local_server_len = sizeof(local_server_address);
+
+  // increment ports until one is available
+  if(bind(local_server_socketfd, (struct sockaddr*)&local_server_address, local_server_len) < 0){
+     DEBUG("tunnel bind failed");
+     return NULL;
+  }
+
+  getsockname(local_server_socketfd,
+              (struct sockaddr*) &local_server_address_lookup,
+              &local_server_address_lookup_len);
+  tunnel_port = (int)ntohs(local_server_address_lookup.sin_port);
+  DEBUG ("tunnel bound to local port %i", tunnel_port);
+
+  // increase client buffer size?
+  listen(local_server_socketfd, 5);
+
+  while(run_tunnel) {
+     // accept a client connection
+     DEBUG("tunnel accepting");
+     client_len = sizeof(client_address);
+     client_socketfd = accept(local_server_socketfd, (struct sockaddr*)&client_address, &client_len);
+     if(client_socketfd < 0){
+         DEBUG("tunnel accept failed");
+         break;
+     }
+     // TODO check accept return value for err
+     c_socket = malloc(sizeof(int)); *c_socket = client_socketfd;
+     sockets = g_slist_prepend(sockets, c_socket);
+
+     DEBUG ("client connected to tunnel");
+
+     // establish connection w/ ovirt server
+     ovirt_server_socket = socket(PF_INET, SOCK_STREAM, 0);
+     c_socket = malloc(sizeof(int)); *c_socket = ovirt_server_socket;
+     sockets = g_slist_prepend(sockets, c_socket);
+     DEBUG ("connecting to ovirt server %s on %i", hostname, ovirt_server_vnc_port);
+     if(connect(ovirt_server_socket, (struct sockaddr*)&ovirt_server_address, ovirt_server_len) < 0){
+       DEBUG ("could not connect to ovirt server");
+       break;
+       //return NULL;
+     }
+     DEBUG ("connected to ovirt server");
+
+     sockets_param[0]  = ovirt_server_socket; 
+     sockets_param[1]  = client_socketfd;
+
+     // launch thread for client -> server traffic
+     client_server_gthread = g_thread_create (client_server_thread, 
+                                              &sockets_param, TRUE, NULL);
+
+     // launch thread for server -> client traffic
+     server_client_gthread = g_thread_create (server_client_thread, 
+                                              &sockets_param, TRUE, NULL);
+
+     communication_threads = g_slist_prepend(communication_threads, client_server_gthread);
+     communication_threads = g_slist_prepend(communication_threads, server_client_gthread);
+
+     // send target vm for this session
+     //strcpy(vm_data, vm_in_focus->description);
+     DEBUG ("sending vm %s", vm_in_focus->description);
+     write(ovirt_server_socket, vm_in_focus->description, strlen(vm_in_focus->description));
+
+  }
+
+  DEBUG("terminating tunnel thread");
+
+  // wait for connection threads to finish
+  g_slist_foreach(communication_threads, wait_for_thread, NULL);
+
+  DEBUG ("tunnel thread completed");
+  return NULL;
+};
+
+/* the tunnel thread */
+static gpointer
+client_server_thread (gpointer _data){
+  int nbytes;
+  char vnc_data[VNC_DATA_MAX_LEN];
+
+  int ovirt_server_socket = ((int*)_data)[0],
+      client_socket = ((int*)_data)[1];
+
+  DEBUG ("client/server thread starting up");
+  
+  while(run_tunnel){
+    VERBOSE( "accepting client data");
+
+    // grab vnc data
+    nbytes = read(client_socket, vnc_data, VNC_DATA_MAX_LEN);
+    if(nbytes <= 0){
+      DEBUG ( "error reading data from client" );
+      break;
+    }
+    VERBOSE ("read %i bytes from client", nbytes);
+        
+    // send network_data onto server
+    nbytes = write(ovirt_server_socket, vnc_data, nbytes);
+    if(nbytes <= 0){
+      DEBUG ( "error writing data to server" );
+      break;
+    }
+    VERBOSE ("wrote %i bytes to server", nbytes);
+  }
+
+  DEBUG ("client/server thread completed");
+  return NULL;
+};
+
+/* the server thread */
+static gpointer
+server_client_thread (gpointer _data){
+  char vnc_data[VNC_DATA_MAX_LEN];
+
+  int ovirt_server_socket = ((int*)_data)[0],
+      client_socket = ((int*)_data)[1];
+
+  int nbytes;
+
+  DEBUG ("server/client thread starting up");
+  
+  while(run_tunnel){
+     // grab vnc data
+     nbytes = read(ovirt_server_socket, vnc_data, VNC_DATA_MAX_LEN);
+     if(nbytes <= 0){
+       DEBUG ( "error reading data from server" );
+       break;
+     }
+    VERBOSE ("read %i bytes from server", nbytes);
+
+    // send network_data onto client
+    nbytes = write(client_socket, vnc_data, nbytes);
+    if(nbytes <= 0){
+      DEBUG ( "error writing data to client" );
+      break;
+    }
+    VERBOSE ("wrote %i bytes to client", nbytes);
+  }
+
+  DEBUG ("server/client thread completed");
+  return NULL;
+};
+
+static void close_socket(gpointer _socket, gpointer data){
+  shutdown(*(int*) _socket, 2);
+  close(*(int*) _socket);
+  free((int*) _socket);
+};
+
+static void wait_for_thread(gpointer _thread, gpointer data){
+  g_thread_join((GThread*)_thread);
+};
diff --git a/src/wui_thread.c b/src/wui_thread.c
new file mode 100644
index 0000000..8bfa8ca
--- /dev/null
+++ b/src/wui_thread.c
@@ -0,0 +1,1106 @@
+/* ovirt viewer console application
+ * Copyright (C) 2008 Red Hat Inc.
+ * Written by Richard W.M. Jones <rjones at redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* For an explanation of the threading model, please main(). */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include <libxml/parser.h>
+
+#include <curl/curl.h>
+
+#include "internal.h"
+
+/* Private functions. */
+static gpointer wui_thread (gpointer data);
+static void wui_thread_send_quit (void);
+static void do_curl_init (void);
+static void write_fn_start_capture (void);
+static char *write_fn_finish_capture (void);
+static void write_fn_discard_capture_buffer (void);
+static gboolean do_connect (void);
+static gboolean do_login (void);
+static gboolean refresh_vm_list (void);
+static void parse_vmlist_from_xml (const char *xml);
+static struct vm *parse_vm_from_xml (xmlNodePtr node);
+
+/* Messages (main thread -> WUI thread only).
+ *
+ * These are passed by reference.  They are allocated by the sender
+ * (ie. the main thread) and freed by the receiver (ie. the WUI thread).
+ */
+enum message_type {
+  QUIT,			   /* Tell the WUI thread to quit cleanly. */
+  CONNECT,		   /* Tell to connect (just fetch the URI). */
+  DISCONNECT,		   /* Tell to disconnect, forget state. */
+  LOGIN,		   /* Tell to login, and pass credentials. */
+  REFRESH_VM_LIST,	   /* Tell to refresh the VM list right away. */
+};
+
+struct message {
+  enum message_type type;
+  char *str1;
+  char *str2;
+};
+
+/* Start the WUI thread.  See main() for explanation of the threading model. */
+static GThread *wui_gthread = NULL;
+static GThread *main_gthread = NULL;
+static GAsyncQueue *wui_thread_queue = NULL;
+
+void
+start_wui_thread (void)
+{
+  GError *error = NULL;
+
+  DEBUG ("starting the WUI thread");
+
+  assert (wui_gthread == NULL);
+
+  main_gthread = g_thread_self ();
+
+  /* Create the message queue for main -> WUI thread communications. */
+  wui_thread_queue = g_async_queue_new ();
+
+  wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
+  if (error) {
+    g_print ("%s\n", error->message);
+    g_error_free (error);
+    exit (1);
+  }
+}
+
+void
+stop_wui_thread (void)
+{
+  DEBUG ("stopping the WUI thread");
+
+  assert (wui_gthread != NULL);
+  ASSERT_IS_MAIN_THREAD ();
+
+  /* Send a quit message then wait for the WUI thread to join.
+   *
+   * This "nice" shutdown could cause the UI to hang for an
+   * indefinite period (eg. if the WUI thread is engaged in some
+   * long connection or download from the remote server).  But
+   * I want to keep it this way for the moment so that we can
+   * diagnose problems with the WUI thread.
+   *
+   * (This could be solved with some sort of interruptible
+   * join, but glib doesn't support that AFAICT).
+   */
+  wui_thread_send_quit ();
+  (void) g_thread_join (wui_gthread);
+  g_async_queue_unref (wui_thread_queue);
+  wui_gthread = NULL;
+}
+
+void
+assert_is_wui_thread (const char *filename, int lineno)
+{
+  if (g_thread_self () != wui_gthread) {
+    fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the WUI thread\n", filename, lineno);
+    abort ();
+  }
+}
+
+void
+assert_is_main_thread (const char *filename, int lineno)
+{
+  if (g_thread_self () != main_gthread) {
+    fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the main thread\n", filename, lineno);
+    abort ();
+  }
+}
+
+/* Send the quit message to the WUI thread. */
+static void
+wui_thread_send_quit (void)
+{
+  struct message *msg;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  msg = g_new (struct message, 1);
+  msg->type = QUIT;
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* Send the connect message to the WUI thread. */
+void
+wui_thread_send_connect (const char *uri)
+{
+  struct message *msg;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  msg = g_new (struct message, 1);
+  msg->type = CONNECT;
+  msg->str1 = g_strdup (uri);
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* Send the disconnect message to the WUI thread. */
+void
+wui_thread_send_disconnect (void)
+{
+  struct message *msg;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  msg = g_new (struct message, 1);
+  msg->type = DISCONNECT;
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* Send the login message to the WUI thread. */
+void
+wui_thread_send_login (const char *username, const char *password)
+{
+  struct message *msg;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  msg = g_new (struct message, 1);
+  msg->type = LOGIN;
+  msg->str1 = g_strdup (username);
+  msg->str2 = g_strdup (password);
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* Send the refresh VM list message to the WUI thread. */
+void
+wui_thread_send_refresh_vm_list (void)
+{
+  struct message *msg;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  msg = g_new (struct message, 1);
+  msg->type = REFRESH_VM_LIST;
+  g_async_queue_push (wui_thread_queue, msg);
+}
+
+/* The current state.
+ *
+ * For safety, the main thread must lock this before reading, and the
+ * WUI thread must lock this before writing.  However the WUI thread
+ * does not need to lock before reading, because no other thread
+ * can modify it.
+ */
+static gboolean connected = FALSE;
+static gboolean logged_in = FALSE;
+static gboolean busy = FALSE;
+static GStaticMutex state_mutex;
+
+static void set_connected (gboolean);
+static void set_logged_in (gboolean);
+static void set_busy (gboolean);
+
+/* The private state of the WUI thread. */
+static int secs_between_refresh = 60;
+static CURL *curl = NULL;
+static char curl_error_buffer[CURL_ERROR_SIZE];
+static char *uri = NULL;
+static char *username = NULL;
+static char *password = NULL;
+
+static gboolean process_message (struct message *);
+
+/* The WUI thread.  See main() above for explanation of
+ * the threading model.
+ */
+static gpointer
+wui_thread (gpointer _queue)
+{
+  GAsyncQueue *queue = (GAsyncQueue *) _queue;
+  gboolean quit = FALSE;
+  GTimeVal tv;
+  gpointer _msg;
+  struct message *msg;
+
+  DEBUG ("WUI thread starting up");
+
+  /* This checks wui_gthread global which is actually set in the
+   * main thread.  Of course, it might not be set if the WUI thread
+   * runs first.  Hence we sleep for the main thread to run. (XXX)
+   */
+  g_usleep (100000);
+  ASSERT_IS_WUI_THREAD ();
+
+  g_async_queue_ref (queue);
+
+  /* In the thread's loop we check for new instructions from the main
+   * thread and carry them out.  Also, if we are connected and logged
+   * in then we periodically recheck the list of VMs.
+   */
+  while (!quit) {
+    set_busy (FALSE);
+
+    if (logged_in) {
+      g_get_current_time (&tv);
+      g_time_val_add (&tv, secs_between_refresh * 1000000);
+      _msg = g_async_queue_timed_pop (queue, &tv);
+    } else
+      _msg = g_async_queue_pop (queue);
+
+    set_busy (TRUE);
+
+    msg = (struct message *) _msg;
+
+    if (msg) {
+      DEBUG ("received message with msg->type = %d", msg->type);
+      quit = process_message (msg);
+      /* Don't free any strings in the message - we've saved them. */
+      g_free (msg);
+    } else {
+      /* No message, must have got a timeout instead, which means
+       * we are logged in and we should refresh the list of VMs.
+       * Note it's not an error if we temporarily lose contact
+       * with the WUI.
+       */
+      refresh_vm_list ();
+    }
+  }
+
+  DEBUG ("WUI thread shutting down cleanly");
+
+  g_async_queue_unref (queue);
+  g_thread_exit (NULL);
+  return NULL; /* not reached? */
+}
+
+/* The WUI thread calls this to safely update the state variables.
+ * This also updates elements in the UI by setting idle callbacks
+ * which are executed in the context of the main thread.
+ */
+static void
+set_connected (gboolean new_connected)
+{
+  ASSERT_IS_WUI_THREAD ();
+
+  g_static_mutex_lock (&state_mutex);
+  connected = new_connected;
+  g_static_mutex_unlock (&state_mutex);
+
+  if (connected)
+    g_idle_add (main_connected, NULL);
+  else
+    g_idle_add (main_disconnected, NULL);
+}
+
+static void
+set_logged_in (gboolean new_logged_in)
+{
+  ASSERT_IS_WUI_THREAD ();
+
+  g_static_mutex_lock (&state_mutex);
+  logged_in = new_logged_in;
+  g_static_mutex_unlock (&state_mutex);
+
+  if (logged_in)
+    g_idle_add (main_logged_in, NULL);
+  else
+    g_idle_add (main_logged_out, NULL);
+}
+
+static void
+set_busy (gboolean new_busy)
+{
+  ASSERT_IS_WUI_THREAD ();
+
+  g_static_mutex_lock (&state_mutex);
+  busy = new_busy;
+  g_static_mutex_unlock (&state_mutex);
+
+  if (busy)
+    g_idle_add (main_busy, NULL);
+  else
+    g_idle_add (main_idle, NULL);
+}
+
+/* The main thread should call these functions to get the WUI thread's
+ * current state.
+ */
+gboolean
+wui_thread_is_connected (void)
+{
+  gboolean ret;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  g_static_mutex_lock (&state_mutex);
+  ret = connected;
+  g_static_mutex_unlock (&state_mutex);
+  return ret;
+}
+
+gboolean
+wui_thread_is_logged_in (void)
+{
+  gboolean ret;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  g_static_mutex_lock (&state_mutex);
+  ret = logged_in;
+  g_static_mutex_unlock (&state_mutex);
+  return ret;
+}
+
+gboolean
+wui_thread_is_busy (void)
+{
+  gboolean ret;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  g_static_mutex_lock (&state_mutex);
+  ret = busy;
+  g_static_mutex_unlock (&state_mutex);
+  return ret;
+}
+
+/* Process a message from the main thread. */
+static gboolean
+process_message (struct message *msg)
+{
+  ASSERT_IS_WUI_THREAD ();
+
+  switch (msg->type) {
+  case QUIT:
+    write_fn_discard_capture_buffer ();
+    if (curl) curl_easy_cleanup (curl);
+    if (uri) g_free (uri);
+    if (username) g_free (username);
+    if (password) g_free (password);
+    set_connected (FALSE);
+    set_logged_in (FALSE);
+    return 1;
+
+  case CONNECT:
+    write_fn_discard_capture_buffer ();
+    if (curl) curl_easy_cleanup (curl);
+    do_curl_init ();
+    if (uri) g_free (uri);
+    uri = msg->str1;
+
+    if (do_connect ()) {
+      set_connected (TRUE);
+    } else {
+      set_connected (FALSE);
+      set_logged_in (FALSE);
+    }
+    break;
+
+  case DISCONNECT:
+    /* This just forgets the state. REST is connectionless. */
+    write_fn_discard_capture_buffer ();
+    if (curl) curl_easy_cleanup (curl);
+    curl = NULL;
+    if (uri) g_free (uri);
+    uri = NULL;
+    if (username) g_free (username);
+    username = NULL;
+    if (password) g_free (password);
+    password = NULL;
+    set_connected (FALSE);
+    set_logged_in (FALSE);
+    break;
+
+  case LOGIN:
+    if (username) g_free (username);
+    username = msg->str1;
+    if (password) g_free (password);
+    password = msg->str2;
+
+    /* If we're not connected, this message just updates the
+     * username and password.  Otherwise if we are connected,
+     * try to login and grab the initial list of VMs.
+     */
+    if (connected) {
+      if (do_login ()) {
+	set_logged_in (TRUE);
+	if (refresh_vm_list ())
+	  secs_between_refresh = 60;
+      } else {
+	set_logged_in (FALSE);
+      }
+    }
+    break;
+
+  case REFRESH_VM_LIST:
+    if (connected && logged_in) {
+      refresh_vm_list ();
+      secs_between_refresh = 60;
+    }
+    break;
+
+  default:
+    DEBUG ("unknown message type %d", msg->type);
+    abort ();
+  }
+
+  return 0;
+}
+
+/* Macro for easy handling of CURL errors. */
+#define CURL_CHECK_ERROR(fn, args)					\
+  ({									\
+    CURLcode __r = fn args;						\
+    if (__r != CURLE_OK) {						\
+      fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r));	\
+    }									\
+    __r;								\
+  })
+
+/* Libcurl has a really crufty method for handling HTTP headers and
+ * data.  We set these functions as callbacks, because the default
+ * callback functions print the data out to stderr.  In order to
+ * capture the data, we have to keep state here.
+ */
+
+static char *write_fn_buffer = NULL;
+static ssize_t write_fn_len = -1;
+
+static size_t
+write_fn (void *ptr, size_t size, size_t nmemb, void *stream)
+{
+  int bytes = size * nmemb;
+  int old_start;
+
+  ASSERT_IS_WUI_THREAD ();
+
+  if (write_fn_len >= 0) {	/* We're capturing. */
+    old_start = write_fn_len;
+    write_fn_len += bytes;
+    write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len);
+    memcpy (write_fn_buffer + old_start, ptr, bytes);
+  }
+
+  return bytes;
+}
+
+/* Start capturing HTTP response data. */
+static void
+write_fn_start_capture (void)
+{
+  ASSERT_IS_WUI_THREAD ();
+
+  write_fn_discard_capture_buffer ();
+  write_fn_buffer = NULL;
+  write_fn_len = 0;
+}
+
+/* Finish capture and return the capture buffer.  Caller must free. */
+static char *
+write_fn_finish_capture (void)
+{
+  char *ret;
+
+  ASSERT_IS_WUI_THREAD ();
+
+  /* Make sure the buffer is NUL-terminated before returning it. */
+  write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len+1);
+  write_fn_buffer[write_fn_len] = '\0';
+  ret = write_fn_buffer;
+
+  write_fn_buffer = NULL;
+  write_fn_len = -1;
+  return ret;
+}
+
+/* Stop capturing and discard the capture buffer, if any. */
+static void
+write_fn_discard_capture_buffer (void)
+{
+  ASSERT_IS_WUI_THREAD ();
+
+  g_free (write_fn_buffer);
+  write_fn_buffer = NULL;
+  write_fn_len = -1;
+}
+
+static size_t
+header_fn (void *ptr, size_t size, size_t nmemb, void *stream)
+{
+  int bytes = size * nmemb;
+
+  ASSERT_IS_WUI_THREAD ();
+
+  return bytes;
+}
+
+/* Called from the message loop to initialize the CURL handle. */
+static void
+do_curl_init (void)
+{
+  DEBUG ("initializing libcurl");
+
+  ASSERT_IS_WUI_THREAD ();
+
+  curl = curl_easy_init ();
+  if (!curl) {		   /* This is probably quite bad, so abort. */
+    DEBUG ("curl_easy_init failed");
+    abort ();
+  }
+
+  CURL_CHECK_ERROR (curl_easy_setopt,
+		    (curl, CURLOPT_CAINFO, cainfo));
+  CURL_CHECK_ERROR (curl_easy_setopt,
+		    (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0));
+  CURL_CHECK_ERROR (curl_easy_setopt,
+		    (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0));
+
+  CURL_CHECK_ERROR (curl_easy_setopt,
+		    (curl, CURLOPT_WRITEFUNCTION, write_fn));
+  CURL_CHECK_ERROR (curl_easy_setopt,
+		    (curl, CURLOPT_HEADERFUNCTION, header_fn));
+
+  /* This enables error messages in curl_easy_perform. */
+  CURL_CHECK_ERROR (curl_easy_setopt,
+		    (curl, CURLOPT_ERRORBUFFER, curl_error_buffer));
+
+  /* This enables cookie handling, using an internal cookiejar. */
+  CURL_CHECK_ERROR (curl_easy_setopt,
+		    (curl, CURLOPT_COOKIEFILE, ""));
+}
+
+/* Called from the message loop.  Try to connect to the current URI.
+ * Returns true on success.
+ */
+static gboolean
+do_connect (void)
+{
+  long code = 0;
+  CURLcode r;
+  char *error_str;
+
+  DEBUG ("connecting to uri %s", uri);
+  ASSERT_IS_WUI_THREAD ();
+
+  /* Set the URI for libcurl. */
+  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri));
+
+  /* Try to fetch the URI. */
+  r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
+  if (r != CURLE_OK) {
+    /* Signal an error back to the main thread. */
+    error_str = g_strdup (curl_easy_strerror (r));
+    g_idle_add (main_connection_error, error_str);
+    return FALSE;
+  }
+
+  CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
+  DEBUG ("HTTP return code is %ld", code);
+  if (code != 200 && code != 302 && code != 401) {
+    /* XXX If only glib had g_asprintf. */
+    error_str = g_strdup ("unexpected HTTP return code from server");
+    g_idle_add (main_connection_error, error_str);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/* Called from the message loop.  Try to login to 'URI/login' with the
+ * current username and password.  Returns true on success.
+ */
+static gboolean
+do_login (void)
+{
+  int len;
+  char *login_uri;
+  char *userpwd;
+  char *error_str;
+  CURLcode r;
+  long code = 0;
+
+  DEBUG ("logging in with username %s, password *****", username);
+  ASSERT_IS_WUI_THREAD ();
+
+  /* Generate the login URI from the base URI. */
+  len = strlen (uri) + 6 + 1;
+  login_uri = g_alloca (len);
+  snprintf (login_uri, len, "%s/login", uri);
+
+  DEBUG ("login URI %s", login_uri);
+
+  /* Set the URI for libcurl. */
+  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri));
+
+  /* Construct the username:password for CURL. */
+  len = strlen (username) + strlen (password) + 2;
+  userpwd = g_alloca (len);
+  snprintf (userpwd, len, "%s:%s", username, password);
+
+  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd));
+
+  /* HTTP Basic authentication is OK since we should be sending
+   * this only over HTTPS.
+   */
+  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC));
+
+  /* Follow redirects. */
+  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1));
+  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10));
+
+  /* Try to fetch the URI. */
+  r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
+  if (r != CURLE_OK) {
+    /* Signal an error back to the main thread. */
+    error_str = g_strdup (curl_easy_strerror (r));
+    g_idle_add (main_login_error, error_str);
+    return FALSE;
+  }
+
+  CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
+  DEBUG ("HTTP return code is %ld", code);
+  switch (code)
+    {
+    case 200:
+      DEBUG ("login was successful");
+      return TRUE;
+
+    case 401:
+      error_str = g_strdup ("server rejected the username or password");
+      g_idle_add (main_login_error, error_str);
+      return FALSE;
+
+    default:
+      /* XXX If only glib had g_asprintf. */
+      error_str = g_strdup ("unexpected HTTP return code from server");
+      g_idle_add (main_login_error, error_str);
+      return FALSE;
+    }
+}
+
+/* Called from the message loop.  Refresh the list of VMs. */
+static gboolean
+refresh_vm_list (void)
+{
+  int len;
+  char *vms_uri;
+  char *error_str;
+  CURLcode r;
+  long code = 0;
+  char *xml;
+
+  DEBUG ("refreshing list of VMs");
+  ASSERT_IS_WUI_THREAD ();
+
+  /* Generate the vms URI from the base URI. */
+  len = strlen (uri) + 4 + 1;
+  vms_uri = g_alloca (len);
+  snprintf (vms_uri, len, "%s/vms", uri);
+
+  DEBUG ("vms URI %s", vms_uri);
+
+  /* Set the URI for libcurl. */
+  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri));
+
+  /* We want to capture the output, so tell our write function
+   * to place the output into a buffer.
+   */
+  write_fn_start_capture ();
+
+  /* Try to fetch the URI. */
+  r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
+  if (r != CURLE_OK) {
+    /* Signal an error back to the main thread. */
+    error_str = g_strdup (curl_easy_strerror (r));
+    g_idle_add (main_login_error, error_str);
+    return FALSE;
+  }
+
+  CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
+  DEBUG ("HTTP return code is %ld", code);
+  switch (code)
+    {
+    case 200: break;
+
+      /* Hmm - even though we previously logged in, the server is
+       * rejecting our attempts now with an authorization error.
+       * We move to the logged out state.
+       */
+    case 401:
+      set_logged_in (FALSE);
+      error_str = g_strdup ("server rejected the username or password");
+      g_idle_add (main_login_error, error_str);
+      return FALSE;
+
+    default:
+      /* XXX If only glib had g_asprintf. */
+      error_str = g_strdup ("unexpected HTTP return code from server");
+      g_idle_add (main_status_error, error_str);
+      return FALSE;
+    }
+
+  /* If we got here then we appear to have a correct
+   * XML document describing the list of VMs.
+   */
+  secs_between_refresh <<= 1;
+
+  xml = write_fn_finish_capture ();
+
+  parse_vmlist_from_xml (xml);
+  g_free (xml);
+
+  return TRUE;
+}
+
+/* Functions to deal with the list of VMs, parsing it from the XML, etc.
+ *
+ * A vmlist is a GSList (glib singly-linked list) of vm structures.
+ * The caller must call free_vmlist to free up this list correctly.
+ */
+
+static void
+free_vm (gpointer _vm, gpointer data)
+{
+  struct vm *vm = (struct vm *) _vm;
+
+  g_free (vm->description);
+  g_free (vm->state);
+  g_free (vm->uuid);
+  g_free (vm->mac_addr);
+  g_free (vm);
+}
+
+void
+free_vmlist (GSList *vmlist)
+{
+  g_slist_foreach (vmlist, free_vm, NULL);
+  g_slist_free (vmlist);
+}
+
+static struct vm *
+copy_vm (struct vm *vm)
+{
+  struct vm *vm2;
+
+  vm2 = g_memdup (vm, sizeof (*vm));
+  vm2->description = g_strdup (vm->description);
+  vm2->uuid = g_strdup (vm->uuid);
+  vm2->state = vm->state ? g_strdup (vm->state) : NULL;
+  vm2->mac_addr = vm->mac_addr ? g_strdup (vm->mac_addr) : NULL;
+  return vm2;
+}
+
+static int
+compare_vm (gconstpointer _vm1, gconstpointer _vm2)
+{
+  const struct vm *vm1 = (const struct vm *) _vm1;
+  const struct vm *vm2 = (const struct vm *) _vm2;
+
+  return strcmp (vm1->description, vm2->description);
+}
+
+static GSList *vmlist = NULL;
+static gboolean vmlist_valid = FALSE;
+static GStaticMutex vmlist_mutex;
+
+/* Called from the main thread to find out if we have a valid vmlist. */
+gboolean
+wui_thread_has_valid_vmlist (void)
+{
+  gboolean ret;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  g_static_mutex_lock (&vmlist_mutex);
+  ret = vmlist_valid;
+  g_static_mutex_unlock (&vmlist_mutex);
+  return ret;
+}
+
+/* Called from the main thread to find return the current vmlist.  This
+ * actually returns a deep copy of it that the caller must free.
+ */
+static void
+duplicate_and_insert_vm (gpointer _vm, gpointer _ret)
+{
+  struct vm *vm = (struct vm *) _vm;
+  GSList **ret = (GSList **) _ret;
+
+  *ret = g_slist_prepend (*ret, copy_vm (vm));
+}
+
+gboolean
+wui_thread_get_vmlist (GSList **ret)
+{
+  gboolean r;
+
+  ASSERT_IS_MAIN_THREAD ();
+
+  r = FALSE;
+  *ret = NULL;
+
+  g_static_mutex_lock (&vmlist_mutex);
+  if (!vmlist_valid) goto done;
+
+  g_slist_foreach (vmlist, duplicate_and_insert_vm, ret);
+  *ret = g_slist_sort (*ret, compare_vm);
+
+  r = TRUE;
+ done:
+  g_static_mutex_unlock (&vmlist_mutex);
+  return r;
+}
+
+/* Called from the message loop in the WUI thread, with an XML document
+ * which we turn into a list of VM structures, and update the vmlist
+ * if we can.
+ */
+static void
+parse_vmlist_from_xml (const char *xml)
+{
+  xmlDocPtr doc = NULL;
+  xmlNodePtr root;
+  xmlNodePtr node;
+  char *error_str;
+  GSList *new_vmlist = NULL;
+  struct vm *vm;
+  int len;
+
+  /*DEBUG ("XML =\n%s", xml);*/
+  ASSERT_IS_WUI_THREAD ();
+
+  /* We don't really expect that we won't be able to parse the XML ... */
+  len = strlen (xml);
+  doc = xmlReadMemory (xml, len, NULL, NULL, 0);
+
+  if (!doc) {
+    DEBUG ("error parsing XML document, xml =\n%s", xml);
+    error_str = g_strdup ("error parsing XML document from remote server");
+    g_idle_add (main_status_error, error_str);
+    goto done;
+  }
+
+  root = xmlDocGetRootElement (doc);
+  if (!root) {
+    DEBUG ("XML document was empty");
+    error_str = g_strdup ("XML document was empty");
+    g_idle_add (main_status_error, error_str);
+    goto done;
+  }
+
+  /* We expect the root element will be either "nil-classes"
+   * or "vms", with the former indicating an empty list of VMs.
+   */
+  if (xmlStrcmp (root->name, (const xmlChar *) "nil-classes") == 0) {
+    g_static_mutex_lock (&vmlist_mutex);
+    vmlist_valid = TRUE;
+    free_vmlist (vmlist);
+    vmlist = NULL;
+    g_static_mutex_unlock (&vmlist_mutex);
+
+    /* Signal to the main UI thread that the list has been updated. */
+    g_idle_add (main_vmlist_updated, NULL);
+
+    goto done;
+  }
+
+  if (xmlStrcmp (root->name, (const xmlChar *) "vms") != 0) {
+    DEBUG ("unexpected root node in XML document, xml =\n%s", xml);
+    error_str = g_strdup ("unexpected root node in XML document");
+    g_idle_add (main_status_error, error_str);
+    goto done;
+  }
+
+  /* The document is <vms> with a list of <vm> elements which
+   * we process in turn.
+   */
+  for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
+    if (xmlStrcmp (node->name, (const xmlChar *) "vm") == 0) {
+      vm = parse_vm_from_xml (node);
+      if (!vm) {
+	error_str = g_strdup ("could not parse <vm> element");
+	g_idle_add (main_status_error, error_str);
+
+	free_vmlist (new_vmlist);
+	goto done;
+      }
+      new_vmlist = g_slist_prepend (new_vmlist, vm);
+    }
+  }
+
+  /* Successfully parsed all the <vm> nodes, so swap the old and new
+   * vmlists.
+   */
+  g_static_mutex_lock (&vmlist_mutex);
+  vmlist_valid = TRUE;
+  free_vmlist (vmlist);
+  vmlist = new_vmlist;
+  g_static_mutex_unlock (&vmlist_mutex);
+
+  /* Signal that the vmlist has been updated. */
+  g_idle_add (main_vmlist_updated, NULL);
+
+ done:
+  /* Free up XML resources used before returning. */
+  if (doc) xmlFreeDoc (doc);
+}
+
+static struct vm *
+parse_vm_from_xml (xmlNodePtr node)
+{
+  xmlNodePtr p;
+  struct vm vm, *ret;
+  xmlChar *str;
+
+  memset (&vm, 0, sizeof vm);
+  vm.hostid = -1;
+  vm.id = -1;
+  vm.vnc_port = -1;
+  vm.forward_vnc_port = -1;
+  vm.mem_allocated = -1;
+  vm.mem_used = -1;
+  vm.vcpus_allocated = -1;
+  vm.vcpus_used = -1;
+
+  for (p = node->xmlChildrenNode; p != NULL; p = p->next) {
+    if (xmlStrcmp (p->name, (const xmlChar *) "description") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.description = g_strdup ((char *) str);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "host-id") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.hostid = strtol ((char *) str, NULL, 10);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "id") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.id = strtol ((char *) str, NULL, 10);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "memory-allocated") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.mem_allocated = strtol ((char *) str, NULL, 10);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "memory-used") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.mem_used = strtol ((char *) str, NULL, 10);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-allocated") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.vcpus_allocated = strtol ((char *) str, NULL, 10);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-used") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.vcpus_used = strtol ((char *) str, NULL, 10);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "state") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.state = g_strdup ((char *) str);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "uuid") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.uuid = g_strdup ((char *) str);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "vnc-port") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.vnc_port = strtol ((char *) str, NULL, 10);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "forward-vnc-port") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.forward_vnc_port = strtol ((char *) str, NULL, 10);
+	xmlFree (str);
+      }
+    }
+    else if (xmlStrcmp (p->name, (const xmlChar *) "vnic-mac-addr") == 0) {
+      str = xmlNodeGetContent (p);
+      if (str != NULL) {
+	vm.mac_addr = g_strdup ((char *) str);
+	xmlFree (str);
+      }
+    }
+  }
+
+  /* Make sure we've got the required fields. */
+  ret = NULL;
+  if (vm.description == NULL)
+    DEBUG ("required field \"description\" missing from <vm> structure");
+  else if (vm.hostid == -1)
+    DEBUG ("required field \"description\" missing from <vm> structure");
+  else if (vm.id == -1)
+    DEBUG ("required field \"description\" missing from <vm> structure");
+  else if (vm.vnc_port == -1)
+    DEBUG ("required field \"vnc-port\" missing from <vm> structure");
+  else if (vm.forward_vnc_port == -1)
+    DEBUG ("required field \"forward-vnc-port\" missing from <vm> structure");
+  else if (vm.uuid == NULL)
+    DEBUG ("required field \"uuid\" missing from <vm> structure");
+  else
+    ret = g_memdup (&vm, sizeof vm);
+
+  return ret;
+}
+
+gboolean
+main_vmlist_has_running_vm(struct vm* _vm)
+{
+  // TODO ? get list and wait to be retreived
+  // wui_thread_send_refresh_vm_list();
+
+  // find vm in list
+  GSList* res = g_slist_find_custom (vmlist, _vm, compare_vm);
+
+  // return true if running
+  if(res != NULL) return STREQ (((struct vm*) res->data)->state, "running");
+
+  return FALSE;
+}
diff --git a/tunnel.c b/tunnel.c
deleted file mode 100644
index 17b512c..0000000
--- a/tunnel.c
+++ /dev/null
@@ -1,327 +0,0 @@
-/* ovirt viewer console application
- * Copyright (C) 2008 Red Hat Inc.
- * Written by Mohammed Morsi <mmorsi at redhat.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-/* ovirt-viewer starts listening on network port to 
- *  encapsulate vnc packets including the vm's name
- *  so as to be able to be proxied. 
- *
- * This operation takes place in another thread
- * which can be started/stopped by calling 
- * start_tunnel / stop_tunnel. 
- *
- * An additional connection thread is created and maintained
- * internally for each vm / vnc connection open in ovirt-viewer
- * establishing a connection w/ the ovirt server.
- */
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <unistd.h>
-#include <assert.h>
-#include <string.h>
-
-#include <glib.h>
-
-#include "internal.h"
-
-/* constants */
-
-// max length of a vm name
-const int VM_NAME_MAX_LEN = 250;
-
-// max length of vnc data
-const int VNC_DATA_MAX_LEN = 800000;
-
-/* Private thread functions */
-static gpointer tunnel_thread(gpointer data);
-static gpointer client_server_thread(gpointer data);
-static gpointer server_client_thread(gpointer data);
-
-/* Other private functions */
-static void close_socket(gpointer _socket, gpointer data);
-static void wait_for_thread(gpointer _thread, gpointer data);
-
-/* tunnel and main threads */
-static GThread *tunnel_gthread = NULL;
-static GThread *main_gthread = NULL;
-
-/* list of communication threads */
-static GSList *communication_threads = NULL;
-
-/* list of sockets */
-static GSList *sockets = NULL;
-
-/* thread termination flag */
-static gboolean run_tunnel = FALSE;
-
-/*  internal.h shared constructs */
-int tunnel_port;
-
-/////////////////
-
-/** public implementations **/
-
-/* start tunnel thread */
-void 
-start_tunnel(void)
-{
-  GError *error = NULL;
-
-  DEBUG ("starting the tunnel thread");
-
-  assert (tunnel_gthread == NULL);
-
-  run_tunnel = TRUE;
-
-  main_gthread = g_thread_self ();
-
-  tunnel_gthread = g_thread_create (tunnel_thread, NULL, TRUE, &error);
-  if (error) {
-    g_print ("%s\n", error->message);
-    g_error_free (error);
-    exit (1);
-  }
-};
-
-/* stop tunnel thread */
-void 
-stop_tunnel(void)
-{
-  if(!run_tunnel)
-      return;
-
-  DEBUG ("stopping the tunnel thread");
-
-  assert (tunnel_gthread != NULL);
-  ASSERT_IS_MAIN_THREAD ();
-
-  run_tunnel = FALSE;
-
-  g_slist_foreach(sockets, close_socket, NULL);
-
-  (void) g_thread_join (tunnel_gthread);
-  tunnel_gthread = NULL;
-};
-
-/////////////////
-
-/** private implementations **/
-
-/* the tunnel thread */
-static gpointer
-tunnel_thread (gpointer _data)
-{
-  struct hostent *dns_serv;
-
-  //char vm_data[VM_NAME_MAX_LEN];
-  int local_server_socketfd, ovirt_server_socket, client_socketfd;
-  unsigned int local_server_len, client_len, ovirt_server_len;
-
-  struct sockaddr_in local_server_address;
-  struct sockaddr_in ovirt_server_address;
-  struct sockaddr_in client_address;
-
-  struct sockaddr_in local_server_address_lookup;
-  unsigned int local_server_address_lookup_len = sizeof(local_server_address_lookup);
-
-  GThread *client_server_gthread = NULL;
-  GThread *server_client_gthread = NULL;
-
-  int sockets_param[2];
-  int * c_socket;
-
-  DEBUG ("tunnel thread starting up");
-
-  // ovirt server address
-  dns_serv = gethostbyname(hostname);
-  if(dns_serv == NULL){
-      DEBUG("ovirt server lookup failed");
-      return NULL;
-  }
-  ovirt_server_address.sin_family = PF_INET;
-  ovirt_server_address.sin_addr.s_addr = ((struct in_addr*)(dns_serv->h_addr))->s_addr; //inet_addr(hostname);
-  ovirt_server_address.sin_port = htons(ovirt_server_vnc_port);
-  ovirt_server_len = sizeof(ovirt_server_address);
-
-  // create local net socket
-  local_server_socketfd = socket(PF_INET, SOCK_STREAM, 0);
-  c_socket = malloc(sizeof(int)); *c_socket = local_server_socketfd;
-  sockets = g_slist_prepend(sockets, c_socket);
-
-  // local server address
-  local_server_address.sin_family = PF_INET;
-  local_server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
-  local_server_address.sin_port = 0;
-  local_server_len = sizeof(local_server_address);
-
-  // increment ports until one is available
-  if(bind(local_server_socketfd, (struct sockaddr*)&local_server_address, local_server_len) < 0){
-     DEBUG("tunnel bind failed");
-     return NULL;
-  }
-
-  getsockname(local_server_socketfd,
-              (struct sockaddr*) &local_server_address_lookup,
-              &local_server_address_lookup_len);
-  tunnel_port = (int)ntohs(local_server_address_lookup.sin_port);
-  DEBUG ("tunnel bound to local port %i", tunnel_port);
-
-  // increase client buffer size?
-  listen(local_server_socketfd, 5);
-
-  while(run_tunnel) {
-     // accept a client connection
-     DEBUG("tunnel accepting");
-     client_len = sizeof(client_address);
-     client_socketfd = accept(local_server_socketfd, (struct sockaddr*)&client_address, &client_len);
-     if(client_socketfd < 0){
-         DEBUG("tunnel accept failed");
-         break;
-     }
-     // TODO check accept return value for err
-     c_socket = malloc(sizeof(int)); *c_socket = client_socketfd;
-     sockets = g_slist_prepend(sockets, c_socket);
-
-     DEBUG ("client connected to tunnel");
-
-     // establish connection w/ ovirt server
-     ovirt_server_socket = socket(PF_INET, SOCK_STREAM, 0);
-     c_socket = malloc(sizeof(int)); *c_socket = ovirt_server_socket;
-     sockets = g_slist_prepend(sockets, c_socket);
-     DEBUG ("connecting to ovirt server %s on %i", hostname, ovirt_server_vnc_port);
-     if(connect(ovirt_server_socket, (struct sockaddr*)&ovirt_server_address, ovirt_server_len) < 0){
-       DEBUG ("could not connect to ovirt server");
-       break;
-       //return NULL;
-     }
-     DEBUG ("connected to ovirt server");
-
-     sockets_param[0]  = ovirt_server_socket; 
-     sockets_param[1]  = client_socketfd;
-
-     // launch thread for client -> server traffic
-     client_server_gthread = g_thread_create (client_server_thread, 
-                                              &sockets_param, TRUE, NULL);
-
-     // launch thread for server -> client traffic
-     server_client_gthread = g_thread_create (server_client_thread, 
-                                              &sockets_param, TRUE, NULL);
-
-     communication_threads = g_slist_prepend(communication_threads, client_server_gthread);
-     communication_threads = g_slist_prepend(communication_threads, server_client_gthread);
-
-     // send target vm for this session
-     //strcpy(vm_data, vm_in_focus->description);
-     DEBUG ("sending vm %s", vm_in_focus->description);
-     write(ovirt_server_socket, vm_in_focus->description, strlen(vm_in_focus->description));
-
-  }
-
-  DEBUG("terminating tunnel thread");
-
-  // wait for connection threads to finish
-  g_slist_foreach(communication_threads, wait_for_thread, NULL);
-
-  DEBUG ("tunnel thread completed");
-  return NULL;
-};
-
-/* the tunnel thread */
-static gpointer
-client_server_thread (gpointer _data){
-  int nbytes;
-  char vnc_data[VNC_DATA_MAX_LEN];
-
-  int ovirt_server_socket = ((int*)_data)[0],
-      client_socket = ((int*)_data)[1];
-
-  DEBUG ("client/server thread starting up");
-  
-  while(run_tunnel){
-    VERBOSE( "accepting client data");
-
-    // grab vnc data
-    nbytes = read(client_socket, vnc_data, VNC_DATA_MAX_LEN);
-    if(nbytes <= 0){
-      DEBUG ( "error reading data from client" );
-      break;
-    }
-    VERBOSE ("read %i bytes from client", nbytes);
-        
-    // send network_data onto server
-    nbytes = write(ovirt_server_socket, vnc_data, nbytes);
-    if(nbytes <= 0){
-      DEBUG ( "error writing data to server" );
-      break;
-    }
-    VERBOSE ("wrote %i bytes to server", nbytes);
-  }
-
-  DEBUG ("client/server thread completed");
-  return NULL;
-};
-
-/* the server thread */
-static gpointer
-server_client_thread (gpointer _data){
-  char vnc_data[VNC_DATA_MAX_LEN];
-
-  int ovirt_server_socket = ((int*)_data)[0],
-      client_socket = ((int*)_data)[1];
-
-  int nbytes;
-
-  DEBUG ("server/client thread starting up");
-  
-  while(run_tunnel){
-     // grab vnc data
-     nbytes = read(ovirt_server_socket, vnc_data, VNC_DATA_MAX_LEN);
-     if(nbytes <= 0){
-       DEBUG ( "error reading data from server" );
-       break;
-     }
-    VERBOSE ("read %i bytes from server", nbytes);
-
-    // send network_data onto client
-    nbytes = write(client_socket, vnc_data, nbytes);
-    if(nbytes <= 0){
-      DEBUG ( "error writing data to client" );
-      break;
-    }
-    VERBOSE ("wrote %i bytes to client", nbytes);
-  }
-
-  DEBUG ("server/client thread completed");
-  return NULL;
-};
-
-static void close_socket(gpointer _socket, gpointer data){
-  shutdown(*(int*) _socket, 2);
-  close(*(int*) _socket);
-  free((int*) _socket);
-};
-
-static void wait_for_thread(gpointer _thread, gpointer data){
-  g_thread_join((GThread*)_thread);
-};
diff --git a/wui_thread.c b/wui_thread.c
deleted file mode 100644
index 8bfa8ca..0000000
--- a/wui_thread.c
+++ /dev/null
@@ -1,1106 +0,0 @@
-/* ovirt viewer console application
- * Copyright (C) 2008 Red Hat Inc.
- * Written by Richard W.M. Jones <rjones at redhat.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-/* For an explanation of the threading model, please main(). */
-
-#include <config.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include <glib.h>
-#include <glib/gprintf.h>
-
-#include <libxml/parser.h>
-
-#include <curl/curl.h>
-
-#include "internal.h"
-
-/* Private functions. */
-static gpointer wui_thread (gpointer data);
-static void wui_thread_send_quit (void);
-static void do_curl_init (void);
-static void write_fn_start_capture (void);
-static char *write_fn_finish_capture (void);
-static void write_fn_discard_capture_buffer (void);
-static gboolean do_connect (void);
-static gboolean do_login (void);
-static gboolean refresh_vm_list (void);
-static void parse_vmlist_from_xml (const char *xml);
-static struct vm *parse_vm_from_xml (xmlNodePtr node);
-
-/* Messages (main thread -> WUI thread only).
- *
- * These are passed by reference.  They are allocated by the sender
- * (ie. the main thread) and freed by the receiver (ie. the WUI thread).
- */
-enum message_type {
-  QUIT,			   /* Tell the WUI thread to quit cleanly. */
-  CONNECT,		   /* Tell to connect (just fetch the URI). */
-  DISCONNECT,		   /* Tell to disconnect, forget state. */
-  LOGIN,		   /* Tell to login, and pass credentials. */
-  REFRESH_VM_LIST,	   /* Tell to refresh the VM list right away. */
-};
-
-struct message {
-  enum message_type type;
-  char *str1;
-  char *str2;
-};
-
-/* Start the WUI thread.  See main() for explanation of the threading model. */
-static GThread *wui_gthread = NULL;
-static GThread *main_gthread = NULL;
-static GAsyncQueue *wui_thread_queue = NULL;
-
-void
-start_wui_thread (void)
-{
-  GError *error = NULL;
-
-  DEBUG ("starting the WUI thread");
-
-  assert (wui_gthread == NULL);
-
-  main_gthread = g_thread_self ();
-
-  /* Create the message queue for main -> WUI thread communications. */
-  wui_thread_queue = g_async_queue_new ();
-
-  wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
-  if (error) {
-    g_print ("%s\n", error->message);
-    g_error_free (error);
-    exit (1);
-  }
-}
-
-void
-stop_wui_thread (void)
-{
-  DEBUG ("stopping the WUI thread");
-
-  assert (wui_gthread != NULL);
-  ASSERT_IS_MAIN_THREAD ();
-
-  /* Send a quit message then wait for the WUI thread to join.
-   *
-   * This "nice" shutdown could cause the UI to hang for an
-   * indefinite period (eg. if the WUI thread is engaged in some
-   * long connection or download from the remote server).  But
-   * I want to keep it this way for the moment so that we can
-   * diagnose problems with the WUI thread.
-   *
-   * (This could be solved with some sort of interruptible
-   * join, but glib doesn't support that AFAICT).
-   */
-  wui_thread_send_quit ();
-  (void) g_thread_join (wui_gthread);
-  g_async_queue_unref (wui_thread_queue);
-  wui_gthread = NULL;
-}
-
-void
-assert_is_wui_thread (const char *filename, int lineno)
-{
-  if (g_thread_self () != wui_gthread) {
-    fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the WUI thread\n", filename, lineno);
-    abort ();
-  }
-}
-
-void
-assert_is_main_thread (const char *filename, int lineno)
-{
-  if (g_thread_self () != main_gthread) {
-    fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the main thread\n", filename, lineno);
-    abort ();
-  }
-}
-
-/* Send the quit message to the WUI thread. */
-static void
-wui_thread_send_quit (void)
-{
-  struct message *msg;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  msg = g_new (struct message, 1);
-  msg->type = QUIT;
-  g_async_queue_push (wui_thread_queue, msg);
-}
-
-/* Send the connect message to the WUI thread. */
-void
-wui_thread_send_connect (const char *uri)
-{
-  struct message *msg;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  msg = g_new (struct message, 1);
-  msg->type = CONNECT;
-  msg->str1 = g_strdup (uri);
-  g_async_queue_push (wui_thread_queue, msg);
-}
-
-/* Send the disconnect message to the WUI thread. */
-void
-wui_thread_send_disconnect (void)
-{
-  struct message *msg;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  msg = g_new (struct message, 1);
-  msg->type = DISCONNECT;
-  g_async_queue_push (wui_thread_queue, msg);
-}
-
-/* Send the login message to the WUI thread. */
-void
-wui_thread_send_login (const char *username, const char *password)
-{
-  struct message *msg;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  msg = g_new (struct message, 1);
-  msg->type = LOGIN;
-  msg->str1 = g_strdup (username);
-  msg->str2 = g_strdup (password);
-  g_async_queue_push (wui_thread_queue, msg);
-}
-
-/* Send the refresh VM list message to the WUI thread. */
-void
-wui_thread_send_refresh_vm_list (void)
-{
-  struct message *msg;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  msg = g_new (struct message, 1);
-  msg->type = REFRESH_VM_LIST;
-  g_async_queue_push (wui_thread_queue, msg);
-}
-
-/* The current state.
- *
- * For safety, the main thread must lock this before reading, and the
- * WUI thread must lock this before writing.  However the WUI thread
- * does not need to lock before reading, because no other thread
- * can modify it.
- */
-static gboolean connected = FALSE;
-static gboolean logged_in = FALSE;
-static gboolean busy = FALSE;
-static GStaticMutex state_mutex;
-
-static void set_connected (gboolean);
-static void set_logged_in (gboolean);
-static void set_busy (gboolean);
-
-/* The private state of the WUI thread. */
-static int secs_between_refresh = 60;
-static CURL *curl = NULL;
-static char curl_error_buffer[CURL_ERROR_SIZE];
-static char *uri = NULL;
-static char *username = NULL;
-static char *password = NULL;
-
-static gboolean process_message (struct message *);
-
-/* The WUI thread.  See main() above for explanation of
- * the threading model.
- */
-static gpointer
-wui_thread (gpointer _queue)
-{
-  GAsyncQueue *queue = (GAsyncQueue *) _queue;
-  gboolean quit = FALSE;
-  GTimeVal tv;
-  gpointer _msg;
-  struct message *msg;
-
-  DEBUG ("WUI thread starting up");
-
-  /* This checks wui_gthread global which is actually set in the
-   * main thread.  Of course, it might not be set if the WUI thread
-   * runs first.  Hence we sleep for the main thread to run. (XXX)
-   */
-  g_usleep (100000);
-  ASSERT_IS_WUI_THREAD ();
-
-  g_async_queue_ref (queue);
-
-  /* In the thread's loop we check for new instructions from the main
-   * thread and carry them out.  Also, if we are connected and logged
-   * in then we periodically recheck the list of VMs.
-   */
-  while (!quit) {
-    set_busy (FALSE);
-
-    if (logged_in) {
-      g_get_current_time (&tv);
-      g_time_val_add (&tv, secs_between_refresh * 1000000);
-      _msg = g_async_queue_timed_pop (queue, &tv);
-    } else
-      _msg = g_async_queue_pop (queue);
-
-    set_busy (TRUE);
-
-    msg = (struct message *) _msg;
-
-    if (msg) {
-      DEBUG ("received message with msg->type = %d", msg->type);
-      quit = process_message (msg);
-      /* Don't free any strings in the message - we've saved them. */
-      g_free (msg);
-    } else {
-      /* No message, must have got a timeout instead, which means
-       * we are logged in and we should refresh the list of VMs.
-       * Note it's not an error if we temporarily lose contact
-       * with the WUI.
-       */
-      refresh_vm_list ();
-    }
-  }
-
-  DEBUG ("WUI thread shutting down cleanly");
-
-  g_async_queue_unref (queue);
-  g_thread_exit (NULL);
-  return NULL; /* not reached? */
-}
-
-/* The WUI thread calls this to safely update the state variables.
- * This also updates elements in the UI by setting idle callbacks
- * which are executed in the context of the main thread.
- */
-static void
-set_connected (gboolean new_connected)
-{
-  ASSERT_IS_WUI_THREAD ();
-
-  g_static_mutex_lock (&state_mutex);
-  connected = new_connected;
-  g_static_mutex_unlock (&state_mutex);
-
-  if (connected)
-    g_idle_add (main_connected, NULL);
-  else
-    g_idle_add (main_disconnected, NULL);
-}
-
-static void
-set_logged_in (gboolean new_logged_in)
-{
-  ASSERT_IS_WUI_THREAD ();
-
-  g_static_mutex_lock (&state_mutex);
-  logged_in = new_logged_in;
-  g_static_mutex_unlock (&state_mutex);
-
-  if (logged_in)
-    g_idle_add (main_logged_in, NULL);
-  else
-    g_idle_add (main_logged_out, NULL);
-}
-
-static void
-set_busy (gboolean new_busy)
-{
-  ASSERT_IS_WUI_THREAD ();
-
-  g_static_mutex_lock (&state_mutex);
-  busy = new_busy;
-  g_static_mutex_unlock (&state_mutex);
-
-  if (busy)
-    g_idle_add (main_busy, NULL);
-  else
-    g_idle_add (main_idle, NULL);
-}
-
-/* The main thread should call these functions to get the WUI thread's
- * current state.
- */
-gboolean
-wui_thread_is_connected (void)
-{
-  gboolean ret;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  g_static_mutex_lock (&state_mutex);
-  ret = connected;
-  g_static_mutex_unlock (&state_mutex);
-  return ret;
-}
-
-gboolean
-wui_thread_is_logged_in (void)
-{
-  gboolean ret;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  g_static_mutex_lock (&state_mutex);
-  ret = logged_in;
-  g_static_mutex_unlock (&state_mutex);
-  return ret;
-}
-
-gboolean
-wui_thread_is_busy (void)
-{
-  gboolean ret;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  g_static_mutex_lock (&state_mutex);
-  ret = busy;
-  g_static_mutex_unlock (&state_mutex);
-  return ret;
-}
-
-/* Process a message from the main thread. */
-static gboolean
-process_message (struct message *msg)
-{
-  ASSERT_IS_WUI_THREAD ();
-
-  switch (msg->type) {
-  case QUIT:
-    write_fn_discard_capture_buffer ();
-    if (curl) curl_easy_cleanup (curl);
-    if (uri) g_free (uri);
-    if (username) g_free (username);
-    if (password) g_free (password);
-    set_connected (FALSE);
-    set_logged_in (FALSE);
-    return 1;
-
-  case CONNECT:
-    write_fn_discard_capture_buffer ();
-    if (curl) curl_easy_cleanup (curl);
-    do_curl_init ();
-    if (uri) g_free (uri);
-    uri = msg->str1;
-
-    if (do_connect ()) {
-      set_connected (TRUE);
-    } else {
-      set_connected (FALSE);
-      set_logged_in (FALSE);
-    }
-    break;
-
-  case DISCONNECT:
-    /* This just forgets the state. REST is connectionless. */
-    write_fn_discard_capture_buffer ();
-    if (curl) curl_easy_cleanup (curl);
-    curl = NULL;
-    if (uri) g_free (uri);
-    uri = NULL;
-    if (username) g_free (username);
-    username = NULL;
-    if (password) g_free (password);
-    password = NULL;
-    set_connected (FALSE);
-    set_logged_in (FALSE);
-    break;
-
-  case LOGIN:
-    if (username) g_free (username);
-    username = msg->str1;
-    if (password) g_free (password);
-    password = msg->str2;
-
-    /* If we're not connected, this message just updates the
-     * username and password.  Otherwise if we are connected,
-     * try to login and grab the initial list of VMs.
-     */
-    if (connected) {
-      if (do_login ()) {
-	set_logged_in (TRUE);
-	if (refresh_vm_list ())
-	  secs_between_refresh = 60;
-      } else {
-	set_logged_in (FALSE);
-      }
-    }
-    break;
-
-  case REFRESH_VM_LIST:
-    if (connected && logged_in) {
-      refresh_vm_list ();
-      secs_between_refresh = 60;
-    }
-    break;
-
-  default:
-    DEBUG ("unknown message type %d", msg->type);
-    abort ();
-  }
-
-  return 0;
-}
-
-/* Macro for easy handling of CURL errors. */
-#define CURL_CHECK_ERROR(fn, args)					\
-  ({									\
-    CURLcode __r = fn args;						\
-    if (__r != CURLE_OK) {						\
-      fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r));	\
-    }									\
-    __r;								\
-  })
-
-/* Libcurl has a really crufty method for handling HTTP headers and
- * data.  We set these functions as callbacks, because the default
- * callback functions print the data out to stderr.  In order to
- * capture the data, we have to keep state here.
- */
-
-static char *write_fn_buffer = NULL;
-static ssize_t write_fn_len = -1;
-
-static size_t
-write_fn (void *ptr, size_t size, size_t nmemb, void *stream)
-{
-  int bytes = size * nmemb;
-  int old_start;
-
-  ASSERT_IS_WUI_THREAD ();
-
-  if (write_fn_len >= 0) {	/* We're capturing. */
-    old_start = write_fn_len;
-    write_fn_len += bytes;
-    write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len);
-    memcpy (write_fn_buffer + old_start, ptr, bytes);
-  }
-
-  return bytes;
-}
-
-/* Start capturing HTTP response data. */
-static void
-write_fn_start_capture (void)
-{
-  ASSERT_IS_WUI_THREAD ();
-
-  write_fn_discard_capture_buffer ();
-  write_fn_buffer = NULL;
-  write_fn_len = 0;
-}
-
-/* Finish capture and return the capture buffer.  Caller must free. */
-static char *
-write_fn_finish_capture (void)
-{
-  char *ret;
-
-  ASSERT_IS_WUI_THREAD ();
-
-  /* Make sure the buffer is NUL-terminated before returning it. */
-  write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len+1);
-  write_fn_buffer[write_fn_len] = '\0';
-  ret = write_fn_buffer;
-
-  write_fn_buffer = NULL;
-  write_fn_len = -1;
-  return ret;
-}
-
-/* Stop capturing and discard the capture buffer, if any. */
-static void
-write_fn_discard_capture_buffer (void)
-{
-  ASSERT_IS_WUI_THREAD ();
-
-  g_free (write_fn_buffer);
-  write_fn_buffer = NULL;
-  write_fn_len = -1;
-}
-
-static size_t
-header_fn (void *ptr, size_t size, size_t nmemb, void *stream)
-{
-  int bytes = size * nmemb;
-
-  ASSERT_IS_WUI_THREAD ();
-
-  return bytes;
-}
-
-/* Called from the message loop to initialize the CURL handle. */
-static void
-do_curl_init (void)
-{
-  DEBUG ("initializing libcurl");
-
-  ASSERT_IS_WUI_THREAD ();
-
-  curl = curl_easy_init ();
-  if (!curl) {		   /* This is probably quite bad, so abort. */
-    DEBUG ("curl_easy_init failed");
-    abort ();
-  }
-
-  CURL_CHECK_ERROR (curl_easy_setopt,
-		    (curl, CURLOPT_CAINFO, cainfo));
-  CURL_CHECK_ERROR (curl_easy_setopt,
-		    (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0));
-  CURL_CHECK_ERROR (curl_easy_setopt,
-		    (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0));
-
-  CURL_CHECK_ERROR (curl_easy_setopt,
-		    (curl, CURLOPT_WRITEFUNCTION, write_fn));
-  CURL_CHECK_ERROR (curl_easy_setopt,
-		    (curl, CURLOPT_HEADERFUNCTION, header_fn));
-
-  /* This enables error messages in curl_easy_perform. */
-  CURL_CHECK_ERROR (curl_easy_setopt,
-		    (curl, CURLOPT_ERRORBUFFER, curl_error_buffer));
-
-  /* This enables cookie handling, using an internal cookiejar. */
-  CURL_CHECK_ERROR (curl_easy_setopt,
-		    (curl, CURLOPT_COOKIEFILE, ""));
-}
-
-/* Called from the message loop.  Try to connect to the current URI.
- * Returns true on success.
- */
-static gboolean
-do_connect (void)
-{
-  long code = 0;
-  CURLcode r;
-  char *error_str;
-
-  DEBUG ("connecting to uri %s", uri);
-  ASSERT_IS_WUI_THREAD ();
-
-  /* Set the URI for libcurl. */
-  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri));
-
-  /* Try to fetch the URI. */
-  r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
-  if (r != CURLE_OK) {
-    /* Signal an error back to the main thread. */
-    error_str = g_strdup (curl_easy_strerror (r));
-    g_idle_add (main_connection_error, error_str);
-    return FALSE;
-  }
-
-  CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
-  DEBUG ("HTTP return code is %ld", code);
-  if (code != 200 && code != 302 && code != 401) {
-    /* XXX If only glib had g_asprintf. */
-    error_str = g_strdup ("unexpected HTTP return code from server");
-    g_idle_add (main_connection_error, error_str);
-    return FALSE;
-  }
-
-  return TRUE;
-}
-
-/* Called from the message loop.  Try to login to 'URI/login' with the
- * current username and password.  Returns true on success.
- */
-static gboolean
-do_login (void)
-{
-  int len;
-  char *login_uri;
-  char *userpwd;
-  char *error_str;
-  CURLcode r;
-  long code = 0;
-
-  DEBUG ("logging in with username %s, password *****", username);
-  ASSERT_IS_WUI_THREAD ();
-
-  /* Generate the login URI from the base URI. */
-  len = strlen (uri) + 6 + 1;
-  login_uri = g_alloca (len);
-  snprintf (login_uri, len, "%s/login", uri);
-
-  DEBUG ("login URI %s", login_uri);
-
-  /* Set the URI for libcurl. */
-  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri));
-
-  /* Construct the username:password for CURL. */
-  len = strlen (username) + strlen (password) + 2;
-  userpwd = g_alloca (len);
-  snprintf (userpwd, len, "%s:%s", username, password);
-
-  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd));
-
-  /* HTTP Basic authentication is OK since we should be sending
-   * this only over HTTPS.
-   */
-  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC));
-
-  /* Follow redirects. */
-  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1));
-  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10));
-
-  /* Try to fetch the URI. */
-  r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
-  if (r != CURLE_OK) {
-    /* Signal an error back to the main thread. */
-    error_str = g_strdup (curl_easy_strerror (r));
-    g_idle_add (main_login_error, error_str);
-    return FALSE;
-  }
-
-  CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
-  DEBUG ("HTTP return code is %ld", code);
-  switch (code)
-    {
-    case 200:
-      DEBUG ("login was successful");
-      return TRUE;
-
-    case 401:
-      error_str = g_strdup ("server rejected the username or password");
-      g_idle_add (main_login_error, error_str);
-      return FALSE;
-
-    default:
-      /* XXX If only glib had g_asprintf. */
-      error_str = g_strdup ("unexpected HTTP return code from server");
-      g_idle_add (main_login_error, error_str);
-      return FALSE;
-    }
-}
-
-/* Called from the message loop.  Refresh the list of VMs. */
-static gboolean
-refresh_vm_list (void)
-{
-  int len;
-  char *vms_uri;
-  char *error_str;
-  CURLcode r;
-  long code = 0;
-  char *xml;
-
-  DEBUG ("refreshing list of VMs");
-  ASSERT_IS_WUI_THREAD ();
-
-  /* Generate the vms URI from the base URI. */
-  len = strlen (uri) + 4 + 1;
-  vms_uri = g_alloca (len);
-  snprintf (vms_uri, len, "%s/vms", uri);
-
-  DEBUG ("vms URI %s", vms_uri);
-
-  /* Set the URI for libcurl. */
-  CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri));
-
-  /* We want to capture the output, so tell our write function
-   * to place the output into a buffer.
-   */
-  write_fn_start_capture ();
-
-  /* Try to fetch the URI. */
-  r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
-  if (r != CURLE_OK) {
-    /* Signal an error back to the main thread. */
-    error_str = g_strdup (curl_easy_strerror (r));
-    g_idle_add (main_login_error, error_str);
-    return FALSE;
-  }
-
-  CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
-  DEBUG ("HTTP return code is %ld", code);
-  switch (code)
-    {
-    case 200: break;
-
-      /* Hmm - even though we previously logged in, the server is
-       * rejecting our attempts now with an authorization error.
-       * We move to the logged out state.
-       */
-    case 401:
-      set_logged_in (FALSE);
-      error_str = g_strdup ("server rejected the username or password");
-      g_idle_add (main_login_error, error_str);
-      return FALSE;
-
-    default:
-      /* XXX If only glib had g_asprintf. */
-      error_str = g_strdup ("unexpected HTTP return code from server");
-      g_idle_add (main_status_error, error_str);
-      return FALSE;
-    }
-
-  /* If we got here then we appear to have a correct
-   * XML document describing the list of VMs.
-   */
-  secs_between_refresh <<= 1;
-
-  xml = write_fn_finish_capture ();
-
-  parse_vmlist_from_xml (xml);
-  g_free (xml);
-
-  return TRUE;
-}
-
-/* Functions to deal with the list of VMs, parsing it from the XML, etc.
- *
- * A vmlist is a GSList (glib singly-linked list) of vm structures.
- * The caller must call free_vmlist to free up this list correctly.
- */
-
-static void
-free_vm (gpointer _vm, gpointer data)
-{
-  struct vm *vm = (struct vm *) _vm;
-
-  g_free (vm->description);
-  g_free (vm->state);
-  g_free (vm->uuid);
-  g_free (vm->mac_addr);
-  g_free (vm);
-}
-
-void
-free_vmlist (GSList *vmlist)
-{
-  g_slist_foreach (vmlist, free_vm, NULL);
-  g_slist_free (vmlist);
-}
-
-static struct vm *
-copy_vm (struct vm *vm)
-{
-  struct vm *vm2;
-
-  vm2 = g_memdup (vm, sizeof (*vm));
-  vm2->description = g_strdup (vm->description);
-  vm2->uuid = g_strdup (vm->uuid);
-  vm2->state = vm->state ? g_strdup (vm->state) : NULL;
-  vm2->mac_addr = vm->mac_addr ? g_strdup (vm->mac_addr) : NULL;
-  return vm2;
-}
-
-static int
-compare_vm (gconstpointer _vm1, gconstpointer _vm2)
-{
-  const struct vm *vm1 = (const struct vm *) _vm1;
-  const struct vm *vm2 = (const struct vm *) _vm2;
-
-  return strcmp (vm1->description, vm2->description);
-}
-
-static GSList *vmlist = NULL;
-static gboolean vmlist_valid = FALSE;
-static GStaticMutex vmlist_mutex;
-
-/* Called from the main thread to find out if we have a valid vmlist. */
-gboolean
-wui_thread_has_valid_vmlist (void)
-{
-  gboolean ret;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  g_static_mutex_lock (&vmlist_mutex);
-  ret = vmlist_valid;
-  g_static_mutex_unlock (&vmlist_mutex);
-  return ret;
-}
-
-/* Called from the main thread to find return the current vmlist.  This
- * actually returns a deep copy of it that the caller must free.
- */
-static void
-duplicate_and_insert_vm (gpointer _vm, gpointer _ret)
-{
-  struct vm *vm = (struct vm *) _vm;
-  GSList **ret = (GSList **) _ret;
-
-  *ret = g_slist_prepend (*ret, copy_vm (vm));
-}
-
-gboolean
-wui_thread_get_vmlist (GSList **ret)
-{
-  gboolean r;
-
-  ASSERT_IS_MAIN_THREAD ();
-
-  r = FALSE;
-  *ret = NULL;
-
-  g_static_mutex_lock (&vmlist_mutex);
-  if (!vmlist_valid) goto done;
-
-  g_slist_foreach (vmlist, duplicate_and_insert_vm, ret);
-  *ret = g_slist_sort (*ret, compare_vm);
-
-  r = TRUE;
- done:
-  g_static_mutex_unlock (&vmlist_mutex);
-  return r;
-}
-
-/* Called from the message loop in the WUI thread, with an XML document
- * which we turn into a list of VM structures, and update the vmlist
- * if we can.
- */
-static void
-parse_vmlist_from_xml (const char *xml)
-{
-  xmlDocPtr doc = NULL;
-  xmlNodePtr root;
-  xmlNodePtr node;
-  char *error_str;
-  GSList *new_vmlist = NULL;
-  struct vm *vm;
-  int len;
-
-  /*DEBUG ("XML =\n%s", xml);*/
-  ASSERT_IS_WUI_THREAD ();
-
-  /* We don't really expect that we won't be able to parse the XML ... */
-  len = strlen (xml);
-  doc = xmlReadMemory (xml, len, NULL, NULL, 0);
-
-  if (!doc) {
-    DEBUG ("error parsing XML document, xml =\n%s", xml);
-    error_str = g_strdup ("error parsing XML document from remote server");
-    g_idle_add (main_status_error, error_str);
-    goto done;
-  }
-
-  root = xmlDocGetRootElement (doc);
-  if (!root) {
-    DEBUG ("XML document was empty");
-    error_str = g_strdup ("XML document was empty");
-    g_idle_add (main_status_error, error_str);
-    goto done;
-  }
-
-  /* We expect the root element will be either "nil-classes"
-   * or "vms", with the former indicating an empty list of VMs.
-   */
-  if (xmlStrcmp (root->name, (const xmlChar *) "nil-classes") == 0) {
-    g_static_mutex_lock (&vmlist_mutex);
-    vmlist_valid = TRUE;
-    free_vmlist (vmlist);
-    vmlist = NULL;
-    g_static_mutex_unlock (&vmlist_mutex);
-
-    /* Signal to the main UI thread that the list has been updated. */
-    g_idle_add (main_vmlist_updated, NULL);
-
-    goto done;
-  }
-
-  if (xmlStrcmp (root->name, (const xmlChar *) "vms") != 0) {
-    DEBUG ("unexpected root node in XML document, xml =\n%s", xml);
-    error_str = g_strdup ("unexpected root node in XML document");
-    g_idle_add (main_status_error, error_str);
-    goto done;
-  }
-
-  /* The document is <vms> with a list of <vm> elements which
-   * we process in turn.
-   */
-  for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
-    if (xmlStrcmp (node->name, (const xmlChar *) "vm") == 0) {
-      vm = parse_vm_from_xml (node);
-      if (!vm) {
-	error_str = g_strdup ("could not parse <vm> element");
-	g_idle_add (main_status_error, error_str);
-
-	free_vmlist (new_vmlist);
-	goto done;
-      }
-      new_vmlist = g_slist_prepend (new_vmlist, vm);
-    }
-  }
-
-  /* Successfully parsed all the <vm> nodes, so swap the old and new
-   * vmlists.
-   */
-  g_static_mutex_lock (&vmlist_mutex);
-  vmlist_valid = TRUE;
-  free_vmlist (vmlist);
-  vmlist = new_vmlist;
-  g_static_mutex_unlock (&vmlist_mutex);
-
-  /* Signal that the vmlist has been updated. */
-  g_idle_add (main_vmlist_updated, NULL);
-
- done:
-  /* Free up XML resources used before returning. */
-  if (doc) xmlFreeDoc (doc);
-}
-
-static struct vm *
-parse_vm_from_xml (xmlNodePtr node)
-{
-  xmlNodePtr p;
-  struct vm vm, *ret;
-  xmlChar *str;
-
-  memset (&vm, 0, sizeof vm);
-  vm.hostid = -1;
-  vm.id = -1;
-  vm.vnc_port = -1;
-  vm.forward_vnc_port = -1;
-  vm.mem_allocated = -1;
-  vm.mem_used = -1;
-  vm.vcpus_allocated = -1;
-  vm.vcpus_used = -1;
-
-  for (p = node->xmlChildrenNode; p != NULL; p = p->next) {
-    if (xmlStrcmp (p->name, (const xmlChar *) "description") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.description = g_strdup ((char *) str);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "host-id") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.hostid = strtol ((char *) str, NULL, 10);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "id") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.id = strtol ((char *) str, NULL, 10);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "memory-allocated") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.mem_allocated = strtol ((char *) str, NULL, 10);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "memory-used") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.mem_used = strtol ((char *) str, NULL, 10);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-allocated") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.vcpus_allocated = strtol ((char *) str, NULL, 10);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-used") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.vcpus_used = strtol ((char *) str, NULL, 10);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "state") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.state = g_strdup ((char *) str);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "uuid") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.uuid = g_strdup ((char *) str);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "vnc-port") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.vnc_port = strtol ((char *) str, NULL, 10);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "forward-vnc-port") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.forward_vnc_port = strtol ((char *) str, NULL, 10);
-	xmlFree (str);
-      }
-    }
-    else if (xmlStrcmp (p->name, (const xmlChar *) "vnic-mac-addr") == 0) {
-      str = xmlNodeGetContent (p);
-      if (str != NULL) {
-	vm.mac_addr = g_strdup ((char *) str);
-	xmlFree (str);
-      }
-    }
-  }
-
-  /* Make sure we've got the required fields. */
-  ret = NULL;
-  if (vm.description == NULL)
-    DEBUG ("required field \"description\" missing from <vm> structure");
-  else if (vm.hostid == -1)
-    DEBUG ("required field \"description\" missing from <vm> structure");
-  else if (vm.id == -1)
-    DEBUG ("required field \"description\" missing from <vm> structure");
-  else if (vm.vnc_port == -1)
-    DEBUG ("required field \"vnc-port\" missing from <vm> structure");
-  else if (vm.forward_vnc_port == -1)
-    DEBUG ("required field \"forward-vnc-port\" missing from <vm> structure");
-  else if (vm.uuid == NULL)
-    DEBUG ("required field \"uuid\" missing from <vm> structure");
-  else
-    ret = g_memdup (&vm, sizeof vm);
-
-  return ret;
-}
-
-gboolean
-main_vmlist_has_running_vm(struct vm* _vm)
-{
-  // TODO ? get list and wait to be retreived
-  // wui_thread_send_refresh_vm_list();
-
-  // find vm in list
-  GSList* res = g_slist_find_custom (vmlist, _vm, compare_vm);
-
-  // return true if running
-  if(res != NULL) return STREQ (((struct vm*) res->data)->state, "running");
-
-  return FALSE;
-}
-- 
1.6.0.6




More information about the ovirt-devel mailing list