cf37b318c0d0ef9c38733502b1699796a151ba2d
[ovirt-viewer.git] / main.c
1 /* ovirt viewer console application
2  * Copyright (C) 2008 Red Hat Inc.
3  * Written by Richard W.M. Jones <rjones@redhat.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28 #include <vncdisplay.h>
29
30 #ifdef HAVE_SYS_SOCKET_H
31 #include <sys/socket.h>
32 #endif
33
34 #ifdef HAVE_SYS_UN_H
35 #include <sys/un.h>
36 #endif
37
38 #ifdef HAVE_WINDOWS_H
39 #include <windows.h>
40 #endif
41
42 #include "internal.h"
43
44 /*#define HTTPS "https"*/
45 #define HTTPS "http"
46
47 gboolean debug = 0;
48
49 /* Usually /etc/pki/tls/certs/ca-bundle.crt unless overridden during
50  * configure or on the command line.
51  */
52 const char *cainfo = CAINFO;
53 gboolean check_cert = TRUE;
54
55 /* Private functions. */
56 static void start_ui (void);
57 static GtkWidget *menu_item_new (int which_menu);
58 static void connect_to_wui (GtkWidget *, gpointer);
59 static void login_to_wui (GtkWidget *, gpointer);
60 static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data);
61 static void destroy (GtkWidget *widget, gpointer data);
62 static void clear_connectmenu (void);
63
64 /* For any widgets accessed from multiple functions. */
65 static GtkWidget *window;
66 static GtkWidget *connectitem;
67 static GtkWidget *connectmenu;
68 static GtkWidget *no_connections;
69 static GtkWidget *refresh_vmlist;
70 static GtkWidget *refresh_vmlist_separator;
71 static GtkWidget *connection_area;
72 static GtkWidget *ca_hostname;
73 static GtkWidget *ca_button;
74 static GtkWidget *ca_error;
75 static GtkWidget *login_area;
76 static GtkWidget *la_username;
77 static GtkWidget *la_password;
78 static GtkWidget *la_button;
79 static GtkWidget *notebook;
80 static GtkWidget *statusbar;
81 static guint statusbar_ctx;
82 static GdkCursor *busy_cursor;
83
84 /* Menus. */
85 enum menuNums {
86   CONNECT_MENU,
87   VIEW_MENU,
88   SEND_KEY_MENU,
89   WINDOW_MENU,
90   HELP_MENU,
91 };
92
93 struct menuItem {
94   guint menu;
95   GtkWidget *label;
96   const char *ungrabbed_text;
97   const char *grabbed_text;
98 };
99
100 static struct menuItem menuItems[] = {
101   { CONNECT_MENU, NULL, "_Connect", "Connect" },
102   { VIEW_MENU, NULL, "_View", "View" },
103   { SEND_KEY_MENU, NULL, "_Send Key", "Send Key" },
104   { WINDOW_MENU, NULL, "_Window", "Window" },
105   { HELP_MENU, NULL, "_Help", "Help" }
106 };
107
108 /* Window title. */
109 static const char *title = "oVirt Viewer";
110
111 /* Gtk widget styles.  Avoid installation hassles by keeping this
112  * inside the binary.  It can still be overridden by the user (who
113  * will do that?)
114  */
115 static const char *styles =
116   "style \"ovirt-viewer-yellow-box\"\n"
117   "{\n"
118   "  bg[NORMAL] = shade (1.5, \"yellow\")\n"
119   "}\n"
120   "widget \"*.ovirt-viewer-connection-area\" style \"ovirt-viewer-yellow-box\"\n"
121   ;
122
123 /* Command-line arguments. */
124 static int print_version = 0;
125
126 static const char *help_msg =
127   "Use '" PACKAGE " --help' to see a list of available command line options";
128
129 static const GOptionEntry options[] = {
130   { "cainfo", 0, 0, G_OPTION_ARG_STRING, &cainfo,
131     "set the path of the CA certificate bundle", NULL },
132   { "check-certificate", 0, 0, G_OPTION_ARG_NONE, &check_cert,
133     "if --no-check-certificate is passed we don't check the SSL certificate of the server", NULL },
134   { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug,
135     "turn on debugging messages", NULL },
136   { "version", 'V', 0, G_OPTION_ARG_NONE, &print_version,
137     "display version and exit", NULL },
138   { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
139 };
140
141 int
142 main (int argc, char *argv[])
143 {
144   GOptionContext *context;
145   GError *error = NULL;
146
147   /* Initialize GLib threads before anything else.
148    *
149    * There is one main thread, which is used for all Gtk interactions
150    * and to keep the UI responsive, and one WUI thread.  The WUI
151    * thread is used to connect to the WUI, log in, and maintain the list
152    * of virtual machines.  The WUI thread is the only thread allowed
153    * to use the CURL library.
154    *
155    * The main thread sends instructions to the WUI thread (like "connect",
156    * "disconnect", etc.) using a simple message-passing protocol and
157    * a GAsyncQueue.
158    *
159    * The WUI thread keeps the UI updated by adding idle events which are
160    * processed in the main thread - see:
161    * http://mail.gnome.org/archives/gtk-app-devel-list/2007-March/msg00232.html
162    *
163    * Note that under Win32 you must confine all Gtk/Gdk interactions
164    * to a single thread - see:
165    * http://developer.gimp.org/api/2.0/gdk/gdk-Threads.html
166    */
167   if (!g_thread_supported ()) {
168     g_thread_init (NULL);
169 #ifndef WIN32
170     gdk_threads_init ();
171 #endif
172   } else {
173     fprintf (stderr, "GLib threads not supported or not working.");
174     exit (1);
175   }
176
177   gtk_init (&argc, &argv);
178
179   /* Parse command-line options. */
180   context = g_option_context_new ("oVirt viewer");
181   g_option_context_add_main_entries (context, options, NULL);
182   g_option_context_add_group (context, gtk_get_option_group (TRUE));
183   g_option_context_add_group (context, vnc_display_get_option_group ());
184   g_option_context_parse (context, &argc, &argv, &error);
185
186   if (error) {
187     g_print ("%s\n%s\n", error->message, help_msg);
188     g_error_free (error);
189     exit (1);
190   }
191
192   if (print_version) {
193     printf ("%s %s\n", PACKAGE, VERSION);
194     exit (0);
195   }
196
197   start_wui_thread ();
198   start_ui ();
199
200   DEBUG ("entering the Gtk main loop");
201
202   gtk_main ();
203
204   stop_wui_thread ();
205
206   exit (0);
207 }
208
209 /* Create the viewer window, menus. */
210 static void
211 start_ui (void)
212 {
213   GtkWidget *vbox;
214   GtkWidget *menubar;
215   GtkWidget *view;
216   GtkWidget *viewmenu;
217   GtkWidget *sendkey;
218   GtkWidget *sendkeymenu;
219   GtkWidget *wind;
220   GtkWidget *windmenu;
221   GtkWidget *help;
222   GtkWidget *helpmenu;
223   GtkWidget *ca_vbox;
224   GtkWidget *ca_hbox;
225   GtkWidget *ca_label;
226   GtkWidget *la_hbox;
227
228   DEBUG ("creating viewer windows and menus");
229
230   /* Parse styles. */
231   gtk_rc_parse_string (styles);
232
233   /* Busy cursor, used by main_busy() function.
234    * XXX This cursor is crap - how can we use the Bluecurve/theme cursor?
235    */
236   busy_cursor = gdk_cursor_new (GDK_WATCH);
237
238   /* Window. */
239   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
240   gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
241   gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
242   gtk_window_set_title (GTK_WINDOW (window), title);
243
244   g_signal_connect (G_OBJECT (window), "delete_event",
245                     G_CALLBACK (delete_event), NULL);
246   g_signal_connect (G_OBJECT (window), "destroy",
247                     G_CALLBACK (destroy), NULL);
248
249   /* VBox for layout within the window. */
250   vbox = gtk_vbox_new (FALSE, 0);
251
252   /* Menubar. */
253   menubar = gtk_menu_bar_new ();
254   connectitem = menu_item_new (CONNECT_MENU);
255   connectmenu = gtk_menu_new ();
256   gtk_menu_item_set_submenu (GTK_MENU_ITEM (connectitem), connectmenu);
257
258   no_connections = gtk_menu_item_new_with_label ("Not connected");
259   gtk_menu_append (GTK_MENU (connectmenu), no_connections);
260   gtk_widget_set_sensitive (no_connections, FALSE);
261
262   /* This is not added to the menu yet, but will be when we are
263    * connected.
264    */
265   refresh_vmlist =
266     gtk_menu_item_new_with_label ("Refresh list of virtual machines");
267   g_object_ref (refresh_vmlist);
268   refresh_vmlist_separator = gtk_separator_menu_item_new ();
269   g_object_ref (refresh_vmlist_separator);
270
271 #if 0
272   screenshot = gtk_menu_item_new_with_mnemonic ("_Screenshot");
273   gtk_menu_append (GTK_MENU (filemenu), screenshot);
274   g_signal_connect (screenshot, "activate",
275                     GTK_SIGNAL_FUNC (take_screenshot), NULL);
276 #endif
277
278   view = menu_item_new (VIEW_MENU);
279   viewmenu = gtk_menu_new ();
280   gtk_menu_item_set_submenu (GTK_MENU_ITEM (view), viewmenu);
281
282   sendkey = menu_item_new (SEND_KEY_MENU);
283   sendkeymenu = gtk_menu_new ();
284   gtk_menu_item_set_submenu (GTK_MENU_ITEM (sendkey), sendkeymenu);
285
286   wind = menu_item_new (WINDOW_MENU);
287   windmenu = gtk_menu_new ();
288   gtk_menu_item_set_submenu (GTK_MENU_ITEM (wind), windmenu);
289
290   help = menu_item_new (HELP_MENU);
291   helpmenu = gtk_menu_new ();
292   gtk_menu_item_set_submenu (GTK_MENU_ITEM (help), helpmenu);
293
294   gtk_menu_bar_append (GTK_MENU_BAR (menubar), connectitem);
295   gtk_menu_bar_append (GTK_MENU_BAR (menubar), view);
296   gtk_menu_bar_append (GTK_MENU_BAR (menubar), sendkey);
297   gtk_menu_bar_append (GTK_MENU_BAR (menubar), wind);
298   gtk_menu_bar_append (GTK_MENU_BAR (menubar), help);
299
300   /* For login dialogs, etc., usually invisible. */
301   connection_area = gtk_event_box_new ();
302   ca_vbox = gtk_vbox_new (FALSE, 0);
303   ca_label = gtk_label_new ("Give the name of the oVirt management server:");
304   ca_hbox = gtk_hbox_new (FALSE, 0);
305   ca_hostname = gtk_entry_new ();
306   gtk_entry_set_width_chars (GTK_ENTRY (ca_hostname), 24);
307   ca_button = gtk_button_new_with_label ("Connect");
308   ca_error = gtk_label_new (NULL);
309   gtk_box_pack_start (GTK_BOX (ca_hbox), ca_hostname, FALSE, FALSE, 0);
310   gtk_box_pack_start (GTK_BOX (ca_hbox), ca_button,   FALSE, FALSE, 0);
311   gtk_box_pack_start (GTK_BOX (ca_vbox), ca_label,    FALSE, FALSE, 4);
312   gtk_box_pack_start (GTK_BOX (ca_vbox), ca_hbox,     TRUE,  FALSE, 4);
313   gtk_box_pack_start (GTK_BOX (ca_vbox), ca_error,    TRUE,  FALSE, 4);
314   gtk_container_add (GTK_CONTAINER (connection_area), ca_vbox);
315
316   gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area");
317
318   g_signal_connect (G_OBJECT (ca_button), "clicked",
319                     G_CALLBACK (connect_to_wui), NULL);
320
321   login_area = gtk_event_box_new ();
322   la_hbox = gtk_hbox_new (FALSE, 0);
323   la_username = gtk_entry_new ();
324   gtk_entry_set_width_chars (GTK_ENTRY (la_username), 12);
325   la_password = gtk_entry_new ();
326   gtk_entry_set_width_chars (GTK_ENTRY (la_password), 12);
327   gtk_entry_set_visibility (GTK_ENTRY (la_password), FALSE);
328   la_button = gtk_button_new_with_label ("Login");
329   gtk_container_add (GTK_CONTAINER (la_hbox), la_username);
330   gtk_container_add (GTK_CONTAINER (la_hbox), la_password);
331   gtk_container_add (GTK_CONTAINER (la_hbox), la_button);
332   gtk_container_add (GTK_CONTAINER (login_area), la_hbox);
333
334   gtk_widget_set_name (login_area, "ovirt-viewer-login-area");
335
336   g_signal_connect (G_OBJECT (la_button), "clicked",
337                     G_CALLBACK (login_to_wui), NULL);
338
339   /* Tabbed notebook. */
340   notebook = gtk_notebook_new ();
341   /*gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);*/
342   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), TRUE);
343   gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
344
345   /* Status bar. */
346   statusbar = gtk_statusbar_new ();
347   statusbar_ctx = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar),
348                                                 "context");
349   gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, "");
350
351   /* Packing. */
352   gtk_container_add (GTK_CONTAINER (window), vbox);
353   gtk_container_add_with_properties (GTK_CONTAINER (vbox), menubar,
354                                      "expand", FALSE, NULL);
355   gtk_container_add_with_properties (GTK_CONTAINER (vbox), connection_area,
356                                      "expand", FALSE, "fill", TRUE, NULL);
357   gtk_container_add_with_properties (GTK_CONTAINER (vbox), login_area,
358                                      "expand", FALSE, "fill", TRUE, NULL);
359   gtk_container_add_with_properties (GTK_CONTAINER (vbox), notebook,
360                                      "expand", TRUE, NULL);
361   gtk_container_add_with_properties (GTK_CONTAINER (vbox), statusbar,
362                                      "expand", FALSE, NULL);
363
364   /* Show widgets. */
365   gtk_widget_show_all (window);
366
367   if (wui_thread_is_connected ())
368     gtk_widget_hide (connection_area);
369   if (!wui_thread_is_connected () || wui_thread_is_logged_in ())
370     gtk_widget_hide (login_area);
371 }
372
373 static GtkWidget *
374 menu_item_new (int which_menu)
375 {
376   GtkWidget *widget;
377   GtkWidget *label;
378   const char *text;
379
380   text = menuItems[which_menu].ungrabbed_text;
381
382   widget = gtk_menu_item_new ();
383   label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL);
384   gtk_label_set_text_with_mnemonic (GTK_LABEL (label), text);
385   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
386
387   gtk_container_add (GTK_CONTAINER (widget), label);
388   gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), widget);
389   gtk_widget_show (label);
390
391   menuItems[which_menu].label = label;
392
393   return widget;
394 }
395
396 static gboolean
397 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
398 {
399   DEBUG ("delete_event");
400   return FALSE;
401 }
402
403 static void
404 destroy (GtkWidget *widget, gpointer data)
405 {
406   DEBUG ("destroy");
407   gtk_main_quit ();
408 }
409
410 static void
411 connect_to_wui (GtkWidget *widget, gpointer data)
412 {
413   const char *hostname;
414   char *uri;
415   int len;
416
417   hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname));
418   if (STREQ (hostname, "")) return;
419
420   /* https:// + hostname + /ovirt + \0 */
421   len = 8 + strlen (hostname) + 6 + 1;
422   uri = g_alloca (len);
423   snprintf (uri, len, HTTPS "://%s/ovirt", hostname);
424
425   wui_thread_send_connect (uri);
426 }
427
428 static void
429 login_to_wui (GtkWidget *widget, gpointer data)
430 {
431   const char *username, *password;
432
433   username = gtk_entry_get_text (GTK_ENTRY (la_username));
434   if (STREQ (username, "")) return;
435   password = gtk_entry_get_text (GTK_ENTRY (la_password));
436
437   wui_thread_send_login (username, password);
438 }
439
440 /* Connect to a virtual machine.  This callback is called from the
441  * connect menu.  It searches the notebook of gtk-vnc widgets to see
442  * if we have already connected to this machine, and if not it
443  * makes a new connection.
444  */
445 static void
446 connect_to_vm (GtkWidget *widget, gpointer _vm)
447 {
448   struct vm *vm = (struct vm *) _vm;
449   int n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));
450   int i, uuidlen, len;
451   GtkWidget *child;
452   const char *label;
453   char *label2;
454
455   DEBUG ("searching tabs for uuid %s", vm->uuid);
456
457   uuidlen = strlen (vm->uuid);
458
459   /* Search the tabs for this UUID, and if found, switch to it and return. */
460   for (i = 0; i < n; ++i) {
461     child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i);
462     label = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (notebook), child);
463     len = strlen (label);
464     if (len >= uuidlen &&
465         STREQ (label + len - uuidlen, vm->uuid)) {
466       DEBUG ("found on tab %d", i);
467       gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
468       return;
469     }
470   }
471
472   DEBUG ("not found, creating new tab");
473
474   /* This VM isn't in the notebook already, so create a new console. */
475   len = strlen (vm->description) + 1 + strlen (vm->uuid) + 1;
476   label2 = g_alloca (len);
477   snprintf (label2, len, "%s %s", vm->description, vm->uuid);
478
479   child = gtk_label_new (label2); /* XXX */
480
481   /* NB. We have to do this before adding it to the notebook. */
482   gtk_widget_show (child);
483
484   i = gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child, NULL);
485   gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (notebook), child, label2);
486   gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
487
488   DEBUG ("finished creating new tab");
489 }
490
491 /* Remove all menu items from the Connect menu. */
492 static void
493 remove_menu_item (GtkWidget *menu_item, gpointer data)
494 {
495   gtk_container_remove (GTK_CONTAINER (connectmenu), menu_item);
496 }
497
498 static void
499 clear_connectmenu (void)
500 {
501   DEBUG ("clear Connect menu");
502   gtk_container_foreach (GTK_CONTAINER (connectmenu), remove_menu_item, NULL);
503 }
504
505 /* The WUI thread has changed its state to connected. */
506 gboolean
507 main_connected (gpointer data)
508 {
509   DEBUG ("connected");
510   ASSERT_IS_MAIN_THREAD ();
511
512   gtk_label_set_text (GTK_LABEL (ca_error), NULL);
513
514   gtk_widget_hide (connection_area);
515   if (!wui_thread_is_logged_in ())
516     gtk_widget_show (login_area);
517   return FALSE;
518 }
519
520 /* The WUI thread has changed its state to disconnected. */
521 gboolean
522 main_disconnected (gpointer data)
523 {
524   DEBUG ("disconnected");
525   ASSERT_IS_MAIN_THREAD ();
526
527   gtk_widget_show (connection_area);
528   gtk_widget_hide (login_area);
529
530   clear_connectmenu ();
531   gtk_menu_append (GTK_MENU (connectmenu), no_connections);
532   gtk_widget_show_all (connectmenu);
533
534   return FALSE;
535 }
536
537 /* The WUI thread has changed its state to logged in. */
538 gboolean
539 main_logged_in (gpointer data)
540 {
541   DEBUG ("logged in");
542   ASSERT_IS_MAIN_THREAD ();
543
544   gtk_widget_hide (login_area);
545   return FALSE;
546 }
547
548 /* The WUI thread has changed its state to logged out. */
549 gboolean
550 main_logged_out (gpointer data)
551 {
552   DEBUG ("logged out");
553   ASSERT_IS_MAIN_THREAD ();
554
555   if (wui_thread_is_connected ())
556     gtk_widget_show (login_area);
557   return FALSE;
558 }
559
560 /* The WUI thread has changed its state to busy. */
561 gboolean
562 main_busy (gpointer data)
563 {
564   GdkWindow *gdk_window;
565
566   DEBUG ("busy");
567   ASSERT_IS_MAIN_THREAD ();
568
569   gdk_window = gtk_widget_get_window (window);
570   if (gdk_window) {
571     gdk_window_set_cursor (gdk_window, busy_cursor);
572     gdk_flush ();
573   }
574
575   return FALSE;
576 }
577
578 /* The WUI thread has changed its state to idle. */
579 gboolean
580 main_idle (gpointer data)
581 {
582   GdkWindow *gdk_window;
583
584   DEBUG ("idle");
585   ASSERT_IS_MAIN_THREAD ();
586
587   gdk_window = gtk_widget_get_window (window);
588   if (gdk_window) {
589     gdk_window_set_cursor (gdk_window, NULL);
590     gdk_flush ();
591   }
592
593   return FALSE;
594 }
595
596 /* The WUI thread had a connection error.  This function must
597  * free the string.
598  */
599 gboolean
600 main_connection_error (gpointer _str)
601 {
602   char *str = (char *) _str;
603
604   DEBUG ("connection error: %s", str);
605   ASSERT_IS_MAIN_THREAD ();
606
607   gtk_label_set_text (GTK_LABEL (ca_error), str);
608   g_free (str);
609
610   return FALSE;
611 }
612
613 /* The WUI thread had a login error.  This function must
614  * free the string.
615  */
616 gboolean
617 main_login_error (gpointer _str)
618 {
619   char *str = (char *) _str;
620
621   DEBUG ("login error: %s", str);
622   ASSERT_IS_MAIN_THREAD ();
623
624   /*
625   gtk_label_set_text (GTK_LABEL (la_error), str);
626   */
627   g_free (str);
628
629   return FALSE;
630 }
631
632 /* The WUI thread reports a general status error.  This function
633  * must free the string.
634  */
635 gboolean
636 main_status_error (gpointer _str)
637 {
638   char *str = (char *) _str;
639
640   DEBUG ("status error: %s", str);
641   ASSERT_IS_MAIN_THREAD ();
642
643   gtk_statusbar_pop (GTK_STATUSBAR (statusbar), statusbar_ctx);
644   gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, str);
645   g_free (str);
646
647   return FALSE;
648 }
649
650 /* The WUI thread has updated the vm list.  Here in the main thread
651  * we keep our own copy of the vmlist.
652  */
653 static GSList *vmlist = NULL;
654
655 static void add_vm_to_connectmenu (gpointer _vm, gpointer data);
656
657 gboolean
658 main_vmlist_updated (gpointer data)
659 {
660   GSList *new_vmlist;
661
662   DEBUG ("vmlist updated");
663   ASSERT_IS_MAIN_THREAD ();
664
665   /* Get the new vmlist. */
666   if (wui_thread_get_vmlist (&new_vmlist)) {
667     /* Free the previous vmlist.  This invalidates all the vm pointers
668      * contained in the Connect menu callbacks, but we're going to
669      * delete those callbacks and create news ones in a moment anyway ...
670      */
671     free_vmlist (vmlist);
672
673     vmlist = new_vmlist;
674
675     clear_connectmenu ();
676
677     gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist);
678
679     if (vmlist != NULL) {
680       gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist_separator);
681       g_slist_foreach (vmlist, add_vm_to_connectmenu, NULL);
682     }
683
684     /* Grrrr Gtk is stupid. */
685     gtk_widget_show_all (connectmenu);
686   }
687
688   return FALSE;
689 }
690
691 static void
692 add_vm_to_connectmenu (gpointer _vm, gpointer data)
693 {
694   struct vm *vm = (struct vm *) _vm;
695   GtkWidget *item;
696
697   DEBUG ("adding %s to Connect menu", vm->description);
698
699   item = gtk_menu_item_new_with_label (vm->description);
700   gtk_menu_append (GTK_MENU (connectmenu), item);
701
702   g_signal_connect (G_OBJECT (item), "activate",
703                     G_CALLBACK (connect_to_vm), vm);
704 }