From 7e9794277b619bb232ffc874ee7efa4ead8ddb8e Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 27 Nov 2008 16:14:53 +0000 Subject: [PATCH] Restructure main program and separate thread for WUI interaction. --- .gitignore | 2 + Makefile.am | 6 +- README | 13 ++ configure.ac | 10 +- internal.h | 117 ++++++++++++++++++ main.c | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++ wui_thread.c | 389 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 873 insertions(+), 4 deletions(-) create mode 100644 internal.h create mode 100644 wui_thread.c diff --git a/.gitignore b/.gitignore index c4e1764..98de2c8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ config.status configure depcomp install-sh +mingw32-config.cache missing +ovirt-viewer ovirt-viewer.exe stamp-h1 diff --git a/Makefile.am b/Makefile.am index 253a367..1c98123 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,9 +18,9 @@ bin_PROGRAMS = ovirt-viewer -ovirt_viewer_SOURCES = main.c -ovirt_viewer_CFLAGS = $(OVIRT_VIEWER_CFLAGS) -ovirt_viewer_LIBS = $(OVIRT_VIEWER_LIBS) +ovirt_viewer_SOURCES = main.c wui_thread.c internal.h +ovirt_viewer_CFLAGS = $(OVIRT_VIEWER_CFLAGS) -Wall +ovirt_viewer_LDADD = $(OVIRT_VIEWER_LIBS) EXTRA_DIST = $(PACKAGE).spec diff --git a/README b/README index e69de29..e56fdc6 100644 --- a/README +++ b/README @@ -0,0 +1,13 @@ +╔════════════════════════════════════════════════════════════════════╗ +║ ovirt-viewer - oVirt viewer console application ║ +║ Copyright (C) 2008 Red Hat Inc. ║ +║ Written by Richard W.M. Jones ║ +║ with extensive code derived from virt-viewer. ║ +╚════════════════════════════════════════════════════════════════════╝ + +About ovirt-viewer +═════════════════════════════════════════════════════════════════════ + +oVirt viewer is an application for accessing oVirt virtual machine +consoles from Linux, Unix or Windows-based desktops. + diff --git a/configure.ac b/configure.ac index 46c5d20..12269e8 100644 --- a/configure.ac +++ b/configure.ac @@ -19,14 +19,22 @@ AC_INIT(ovirt-viewer, 0.1) AM_INIT_AUTOMAKE +dnl Basic C compiler environment. AC_PROG_INSTALL AC_PROG_CC AM_PROG_CC_C_O +dnl Check for required packages. +dnl Note that we are using GLib threads, which are supported on Linux +dnl and Windows, but possibly not on some obscure platforms. PKG_PROG_PKG_CONFIG +PKG_CHECK_MODULES([OVIRT_VIEWER], + [gtk+-2.0 gtk-vnc-1.0 glib-2.0 libxml-2.0 gnutls gthread-2.0]) -PKG_CHECK_MODULES([OVIRT_VIEWER],[gtk+-2.0 gtk-vnc-1.0 glib-2.0]) +dnl Header files. +AC_CHECK_HEADERS([sys/socket.h sys/un.h windows.h]) +dnl Output. AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/internal.h b/internal.h new file mode 100644 index 0000000..b512fc3 --- /dev/null +++ b/internal.h @@ -0,0 +1,117 @@ +/* ovirt viewer console application + * Copyright (C) 2008 Red Hat Inc. + * Written by Richard W.M. Jones + * + * 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) + +/* 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) + +/* 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); + +/* 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. + * + * NB: Caller must call free_vmlist once it is done with the list. + * + * This can return NULL if the WUI thread doesn't have a valid + * list of VMs to return to the caller. In the case of a valid, + * empty list, you will get a non-NULL GSList pointer to an empty + * list. + */ +extern GSList *wui_thread_get_vmlist (void); +extern void free_vmlist (GSList *vmlist); + +/* 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); + +#endif /* OVIRT_VIEWER_INTERNAL_H */ diff --git a/main.c b/main.c index 8509f43..9c992f6 100644 --- a/main.c +++ b/main.c @@ -22,11 +22,351 @@ #include #include #include +#include +#include #include #include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_SYS_UN_H +#include +#endif + +#ifdef HAVE_WINDOWS_H +#include +#endif + +#ifndef G_THREADS_ENABLED +#error "This program requires GLib threads, and cannot be compiled without." +#endif + +#include "internal.h" + +/*#define HTTPS "https"*/ +#define HTTPS "http" + +gboolean debug = 0; + +/* Private functions. */ +static void start_ui (void); +static GtkWidget *menu_item_new (int which_menu); +static void connect_to_wui (GtkWidget *, gpointer); +static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data); +static void destroy (GtkWidget *widget, gpointer data); + +/* For any widgets accessed from multiple functions. */ +static GtkWidget *connection_area; +static GtkWidget *ca_hostname; +static GtkWidget *ca_button; +static GtkWidget *login_area; +static GtkWidget *la_username; +static GtkWidget *la_password; +static GtkWidget *la_button; + +/* Menus. */ +enum menuNums { + FILE_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[] = { + { FILE_MENU, NULL, "_File", "File" }, + { VIEW_MENU, NULL, "_View", "View" }, + { SEND_KEY_MENU, NULL, "_Send Key", "Send Key" }, + { WINDOW_MENU, NULL, "_Window", "Window" }, + { HELP_MENU, NULL, "_Help", "Help" } +}; + +/* Window title. */ +static const char *title = "oVirt Viewer"; + +/* 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[] = { + { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, + "turn on debugging 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 + */ + if (!g_thread_supported ()) { + g_thread_init (NULL); + gdk_threads_init (); + } 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 (); + + gtk_main (); + + stop_wui_thread (); + + exit (0); +} + +/* Create the viewer window, menus. */ +static void +start_ui (void) +{ + GtkWidget *window; + GtkWidget *vbox; + GtkWidget *menubar; + GtkWidget *file; + GtkWidget *filemenu; + GtkWidget *view; + GtkWidget *viewmenu; + GtkWidget *sendkey; + GtkWidget *sendkeymenu; + GtkWidget *wind; + GtkWidget *windmenu; + GtkWidget *help; + GtkWidget *helpmenu; + GtkWidget *ca_vbox; + GtkWidget *ca_hbox; + GtkWidget *ca_label; + GtkWidget *la_hbox; + GtkWidget *notebook; + + /* Parse styles. */ + gtk_rc_parse_string (styles); + + /* 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 (); + file = menu_item_new (FILE_MENU); + filemenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (file), filemenu); + +#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); + + 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); + + gtk_menu_bar_append (GTK_MENU_BAR (menubar), file); + 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"); + 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_container_add (GTK_CONTAINER (connection_area), ca_vbox); + + gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area"); + + g_signal_connect (G_OBJECT (ca_button), "clicked", + G_CALLBACK (connect_to_wui), NULL); + + login_area = gtk_event_box_new (); + la_hbox = gtk_hbox_new (FALSE, 0); + 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"); + 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_container_add (GTK_CONTAINER (login_area), la_hbox); + + gtk_widget_set_name (login_area, "ovirt-viewer-login-area"); + + /* 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); + + /* 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); + + /* 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 +connect_to_wui (GtkWidget *widget, gpointer data) +{ + const char *hostname; + 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 = alloca (len); + snprintf (uri, len, HTTPS "://%s/ovirt", hostname); + + wui_thread_send_connect (uri); } diff --git a/wui_thread.c b/wui_thread.c new file mode 100644 index 0000000..0db43c8 --- /dev/null +++ b/wui_thread.c @@ -0,0 +1,389 @@ +/* ovirt viewer console application + * Copyright (C) 2008 Red Hat Inc. + * Written by Richard W.M. Jones + * + * 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 + +#include +#include +#include + +#include + +#include "internal.h" + +/* Private functions. */ +static gpointer wui_thread (gpointer data); +static void wui_thread_send_quit (void); +static gboolean do_connect (void); +static gboolean do_login (void); +static void refresh_vm_list (void); + +/* 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 GAsyncQueue *wui_thread_queue = NULL; + +void +start_wui_thread (void) +{ + GError *error = NULL; + + DEBUG ("starting the WUI thread"); + + /* 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); + + /* 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; +} + +/* Send the quit message to the WUI thread. */ +static void +wui_thread_send_quit (void) +{ + struct message *msg; + + 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; + + 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; + + 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; + + 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; + + 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 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"); + + 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 %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) +{ + g_static_mutex_lock (&state_mutex); + connected = new_connected; + g_static_mutex_unlock (&state_mutex); +} + +static void +set_logged_in (gboolean new_logged_in) +{ + g_static_mutex_lock (&state_mutex); + logged_in = new_logged_in; + g_static_mutex_unlock (&state_mutex); +} + +static void +set_busy (gboolean new_busy) +{ + g_static_mutex_lock (&state_mutex); + busy = new_busy; + g_static_mutex_unlock (&state_mutex); +} + +/* The main thread should call these functions to get the WUI thread's + * current state. + */ +gboolean +wui_thread_is_connected (void) +{ + gboolean ret; + + 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; + + 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; + + 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) +{ + switch (msg->type) { + case QUIT: + 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: + 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. */ + if (uri) g_free (uri); + if (username) g_free (username); + if (password) g_free (password); + 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 (connected) { + if (do_login ()) { + set_logged_in (TRUE); + refresh_vm_list (); + } else { + set_logged_in (FALSE); + } + } + break; + + case REFRESH_VM_LIST: + refresh_vm_list (); + secs_between_refresh = 60; + break; + + default: + DEBUG ("unknown message type %d", msg->type); + abort (); + } + + return 0; +} + +/* Try to connect to the current URI. Returns true on success. */ +static gboolean +do_connect (void) +{ + DEBUG ("connecting to uri %s", uri); + return FALSE; +} + +/* Try to login to URI/login with the username and password given. + * Returns true on success. + */ +static gboolean +do_login (void) +{ + DEBUG ("logging in with username %s, password *****", username); + return FALSE; +} + +/* Refresh the list of VMs. */ +static void +refresh_vm_list (void) +{ + DEBUG ("refreshing list of VMs"); +} -- 1.8.3.1