f6e5e08ed7d14a2d27f0aa6cb9a8baba31b82ccb
[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 static void help_about (GtkWidget *menu);
64 static void viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc);
65 #if 0
66 static void viewer_quit (GtkWidget *src, GtkWidget *vnc);
67 #endif
68 static void viewer_connected (GtkWidget *vnc);
69 static void viewer_initialized (GtkWidget *vnc, GtkWidget *data);
70 static void viewer_disconnected (GtkWidget *vnc);
71 static void viewer_credential (GtkWidget *vnc, GValueArray *credList);
72 static int viewer_open_tunnel_ssh (const char *sshhost, int sshport, const char *sshuser, int vncport);
73
74 /* For any widgets accessed from multiple functions. */
75 static GtkWidget *window;
76 static GtkWidget *connectitem;
77 static GtkWidget *connectmenu;
78 static GtkWidget *no_connections;
79 static GtkWidget *refresh_vmlist;
80 static GtkWidget *refresh_vmlist_separator;
81 static GtkWidget *connection_area;
82 static GtkWidget *ca_hostname;
83 static GtkWidget *ca_button;
84 static GtkWidget *ca_error;
85 static GtkWidget *login_area;
86 static GtkWidget *la_username;
87 static GtkWidget *la_password;
88 static GtkWidget *la_button;
89 static GtkWidget *notebook;
90 static GtkWidget *statusbar;
91 static guint statusbar_ctx;
92 static GdkCursor *busy_cursor;
93
94 /* Menus. */
95 enum menuNums {
96   CONNECT_MENU,
97   VIEW_MENU,
98   SEND_KEY_MENU,
99   WINDOW_MENU,
100   HELP_MENU,
101 };
102
103 struct menuItem {
104   guint menu;
105   GtkWidget *label;
106   const char *ungrabbed_text;
107   const char *grabbed_text;
108 };
109
110 static struct menuItem menuItems[] = {
111   { CONNECT_MENU, NULL, "_Connect", "Connect" },
112   { VIEW_MENU, NULL, "_View", "View" },
113   { SEND_KEY_MENU, NULL, "_Send Key", "Send Key" },
114   { WINDOW_MENU, NULL, "_Window", "Window" },
115   { HELP_MENU, NULL, "_Help", "Help" }
116 };
117
118 /* Window title. */
119 static const char *title = "oVirt Viewer";
120
121 /* Gtk widget styles.  Avoid installation hassles by keeping this
122  * inside the binary.  It can still be overridden by the user (who
123  * will do that?)
124  */
125 static const char *styles =
126   "style \"ovirt-viewer-yellow-box\"\n"
127   "{\n"
128   "  bg[NORMAL] = shade (1.5, \"yellow\")\n"
129   "}\n"
130   "widget \"*.ovirt-viewer-connection-area\" style \"ovirt-viewer-yellow-box\"\n"
131   ;
132
133 /* Command-line arguments. */
134 static int print_version = 0;
135
136 static const char *help_msg =
137   "Use '" PACKAGE " --help' to see a list of available command line options";
138
139 static const GOptionEntry options[] = {
140   { "cainfo", 0, 0, G_OPTION_ARG_STRING, &cainfo,
141     "set the path of the CA certificate bundle", NULL },
142   { "check-certificate", 0, 0, G_OPTION_ARG_NONE, &check_cert,
143     "if --no-check-certificate is passed we don't check the SSL certificate of the server", NULL },
144   { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug,
145     "turn on debugging messages", NULL },
146   { "version", 'V', 0, G_OPTION_ARG_NONE, &print_version,
147     "display version and exit", NULL },
148   { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
149 };
150
151 int
152 main (int argc, char *argv[])
153 {
154   GOptionContext *context;
155   GError *error = NULL;
156
157   /* Initialize GLib threads before anything else.
158    *
159    * There is one main thread, which is used for all Gtk interactions
160    * and to keep the UI responsive, and one WUI thread.  The WUI
161    * thread is used to connect to the WUI, log in, and maintain the list
162    * of virtual machines.  The WUI thread is the only thread allowed
163    * to use the CURL library.
164    *
165    * The main thread sends instructions to the WUI thread (like "connect",
166    * "disconnect", etc.) using a simple message-passing protocol and
167    * a GAsyncQueue.
168    *
169    * The WUI thread keeps the UI updated by adding idle events which are
170    * processed in the main thread - see:
171    * http://mail.gnome.org/archives/gtk-app-devel-list/2007-March/msg00232.html
172    *
173    * Note that under Win32 you must confine all Gtk/Gdk interactions
174    * to a single thread - see:
175    * http://developer.gimp.org/api/2.0/gdk/gdk-Threads.html
176    */
177   if (!g_thread_supported ()) {
178     g_thread_init (NULL);
179 #ifndef WIN32
180     gdk_threads_init ();
181 #endif
182   } else {
183     fprintf (stderr, "GLib threads not supported or not working.");
184     exit (1);
185   }
186
187   gtk_init (&argc, &argv);
188
189   /* Parse command-line options. */
190   context = g_option_context_new ("oVirt viewer");
191   g_option_context_add_main_entries (context, options, NULL);
192   g_option_context_add_group (context, gtk_get_option_group (TRUE));
193   g_option_context_add_group (context, vnc_display_get_option_group ());
194   g_option_context_parse (context, &argc, &argv, &error);
195
196   if (error) {
197     g_print ("%s\n%s\n", error->message, help_msg);
198     g_error_free (error);
199     exit (1);
200   }
201
202   if (print_version) {
203     printf ("%s %s\n", PACKAGE, VERSION);
204     exit (0);
205   }
206
207   start_wui_thread ();
208   start_ui ();
209
210   DEBUG ("entering the Gtk main loop");
211
212   gtk_main ();
213
214   stop_wui_thread ();
215
216   exit (0);
217 }
218
219 /* Create the viewer window, menus. */
220 static void
221 start_ui (void)
222 {
223   GtkWidget *vbox;
224   GtkWidget *menubar;
225   GtkWidget *view;
226   GtkWidget *viewmenu;
227   GtkWidget *sendkey;
228   GtkWidget *sendkeymenu;
229   GtkWidget *wind;
230   GtkWidget *windmenu;
231   GtkWidget *help;
232   GtkWidget *helpmenu;
233   GtkWidget *about;
234   GtkWidget *ca_vbox;
235   GtkWidget *ca_hbox;
236   GtkWidget *ca_label;
237   GtkWidget *la_hbox;
238
239   DEBUG ("creating viewer windows and menus");
240
241   /* Parse styles. */
242   gtk_rc_parse_string (styles);
243
244   /* Busy cursor, used by main_busy() function.
245    * XXX This cursor is crap - how can we use the Bluecurve/theme cursor?
246    */
247   busy_cursor = gdk_cursor_new (GDK_WATCH);
248
249   /* Window. */
250   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
251   gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
252   gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
253   gtk_window_set_title (GTK_WINDOW (window), title);
254
255   g_signal_connect (G_OBJECT (window), "delete_event",
256                     G_CALLBACK (delete_event), NULL);
257   g_signal_connect (G_OBJECT (window), "destroy",
258                     G_CALLBACK (destroy), NULL);
259
260   /* VBox for layout within the window. */
261   vbox = gtk_vbox_new (FALSE, 0);
262
263   /* Menubar. */
264   menubar = gtk_menu_bar_new ();
265   connectitem = menu_item_new (CONNECT_MENU);
266   connectmenu = gtk_menu_new ();
267   gtk_menu_item_set_submenu (GTK_MENU_ITEM (connectitem), connectmenu);
268
269   no_connections = gtk_menu_item_new_with_label ("Not connected");
270   gtk_menu_append (GTK_MENU (connectmenu), no_connections);
271   gtk_widget_set_sensitive (no_connections, FALSE);
272
273   /* This is not added to the menu yet, but will be when we are
274    * connected.
275    */
276   refresh_vmlist =
277     gtk_menu_item_new_with_label ("Refresh list of virtual machines");
278   g_object_ref (refresh_vmlist);
279   refresh_vmlist_separator = gtk_separator_menu_item_new ();
280   g_object_ref (refresh_vmlist_separator);
281
282 #if 0
283   screenshot = gtk_menu_item_new_with_mnemonic ("_Screenshot");
284   gtk_menu_append (GTK_MENU (filemenu), screenshot);
285   g_signal_connect (screenshot, "activate",
286                     GTK_SIGNAL_FUNC (take_screenshot), NULL);
287 #endif
288
289   view = menu_item_new (VIEW_MENU);
290   viewmenu = gtk_menu_new ();
291   gtk_menu_item_set_submenu (GTK_MENU_ITEM (view), viewmenu);
292
293   sendkey = menu_item_new (SEND_KEY_MENU);
294   sendkeymenu = gtk_menu_new ();
295   gtk_menu_item_set_submenu (GTK_MENU_ITEM (sendkey), sendkeymenu);
296
297   wind = menu_item_new (WINDOW_MENU);
298   windmenu = gtk_menu_new ();
299   gtk_menu_item_set_submenu (GTK_MENU_ITEM (wind), windmenu);
300
301   help = menu_item_new (HELP_MENU);
302   helpmenu = gtk_menu_new ();
303   gtk_menu_item_set_submenu (GTK_MENU_ITEM (help), helpmenu);
304
305   about = gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, NULL);
306   gtk_menu_append(GTK_MENU(helpmenu), about);
307   g_signal_connect(about, "activate", GTK_SIGNAL_FUNC (help_about), NULL);
308
309   gtk_menu_bar_append (GTK_MENU_BAR (menubar), connectitem);
310   gtk_menu_bar_append (GTK_MENU_BAR (menubar), view);
311   gtk_menu_bar_append (GTK_MENU_BAR (menubar), sendkey);
312   gtk_menu_bar_append (GTK_MENU_BAR (menubar), wind);
313   gtk_menu_bar_append (GTK_MENU_BAR (menubar), help);
314
315   /* For login dialogs, etc., usually invisible. */
316   connection_area = gtk_event_box_new ();
317   ca_vbox = gtk_vbox_new (FALSE, 0);
318   ca_label = gtk_label_new ("Give the name of the oVirt management server:");
319   ca_hbox = gtk_hbox_new (FALSE, 0);
320   ca_hostname = gtk_entry_new ();
321   gtk_entry_set_width_chars (GTK_ENTRY (ca_hostname), 24);
322   ca_button = gtk_button_new_with_label ("Connect");
323   ca_error = gtk_label_new (NULL);
324   gtk_box_pack_start (GTK_BOX (ca_hbox), ca_hostname, FALSE, FALSE, 0);
325   gtk_box_pack_start (GTK_BOX (ca_hbox), ca_button,   FALSE, FALSE, 0);
326   gtk_box_pack_start (GTK_BOX (ca_vbox), ca_label,    FALSE, FALSE, 4);
327   gtk_box_pack_start (GTK_BOX (ca_vbox), ca_hbox,     TRUE,  FALSE, 4);
328   gtk_box_pack_start (GTK_BOX (ca_vbox), ca_error,    TRUE,  FALSE, 4);
329   gtk_container_add (GTK_CONTAINER (connection_area), ca_vbox);
330
331   gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area");
332
333   g_signal_connect (G_OBJECT (ca_button), "clicked",
334                     G_CALLBACK (connect_to_wui), NULL);
335
336   login_area = gtk_event_box_new ();
337   la_hbox = gtk_hbox_new (FALSE, 0);
338   la_username = gtk_entry_new ();
339   gtk_entry_set_width_chars (GTK_ENTRY (la_username), 12);
340   la_password = gtk_entry_new ();
341   gtk_entry_set_width_chars (GTK_ENTRY (la_password), 12);
342   gtk_entry_set_visibility (GTK_ENTRY (la_password), FALSE);
343   la_button = gtk_button_new_with_label ("Login");
344   gtk_container_add (GTK_CONTAINER (la_hbox), la_username);
345   gtk_container_add (GTK_CONTAINER (la_hbox), la_password);
346   gtk_container_add (GTK_CONTAINER (la_hbox), la_button);
347   gtk_container_add (GTK_CONTAINER (login_area), la_hbox);
348
349   gtk_widget_set_name (login_area, "ovirt-viewer-login-area");
350
351   g_signal_connect (G_OBJECT (la_button), "clicked",
352                     G_CALLBACK (login_to_wui), NULL);
353
354   /* Tabbed notebook. */
355   notebook = gtk_notebook_new ();
356   /*gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);*/
357   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), TRUE);
358   gtk_notebook_set_scrollable (GTK_NOTEBOOK (notebook), TRUE);
359
360   /* Status bar. */
361   statusbar = gtk_statusbar_new ();
362   statusbar_ctx = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar),
363                                                 "context");
364   gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, "");
365
366   /* Packing. */
367   gtk_container_add (GTK_CONTAINER (window), vbox);
368   gtk_container_add_with_properties (GTK_CONTAINER (vbox), menubar,
369                                      "expand", FALSE, NULL);
370   gtk_container_add_with_properties (GTK_CONTAINER (vbox), connection_area,
371                                      "expand", FALSE, "fill", TRUE, NULL);
372   gtk_container_add_with_properties (GTK_CONTAINER (vbox), login_area,
373                                      "expand", FALSE, "fill", TRUE, NULL);
374   gtk_container_add_with_properties (GTK_CONTAINER (vbox), notebook,
375                                      "expand", TRUE, NULL);
376   gtk_container_add_with_properties (GTK_CONTAINER (vbox), statusbar,
377                                      "expand", FALSE, NULL);
378
379   /* Show widgets. */
380   gtk_widget_show_all (window);
381
382   if (wui_thread_is_connected ())
383     gtk_widget_hide (connection_area);
384   if (!wui_thread_is_connected () || wui_thread_is_logged_in ())
385     gtk_widget_hide (login_area);
386 }
387
388 static GtkWidget *
389 menu_item_new (int which_menu)
390 {
391   GtkWidget *widget;
392   GtkWidget *label;
393   const char *text;
394
395   text = menuItems[which_menu].ungrabbed_text;
396
397   widget = gtk_menu_item_new ();
398   label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL);
399   gtk_label_set_text_with_mnemonic (GTK_LABEL (label), text);
400   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
401
402   gtk_container_add (GTK_CONTAINER (widget), label);
403   gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), widget);
404   gtk_widget_show (label);
405
406   menuItems[which_menu].label = label;
407
408   return widget;
409 }
410
411 static gboolean
412 delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
413 {
414   DEBUG ("delete_event");
415   return FALSE;
416 }
417
418 static void
419 destroy (GtkWidget *widget, gpointer data)
420 {
421   DEBUG ("destroy");
422   gtk_main_quit ();
423 }
424
425 static void
426 help_about (GtkWidget *menu)
427 {
428   GtkWidget *about;
429   const char *authors[] = {
430     "Richard W.M. Jones <rjones@redhat.com>",
431     "Daniel P. Berrange <berrange@redhat.com>",
432     NULL
433   };
434
435   about = gtk_about_dialog_new();
436
437   gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(about), "oVirt Viewer");
438   gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about), VERSION);
439   gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about), "http://ovirt.org/");
440   gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(about), "oVirt website");
441   gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about), authors);
442   gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about), 
443  "This program is free software; you can redistribute it and/or modify\n" \
444  "it under the terms of the GNU General Public License as published by\n" \
445  "the Free Software Foundation; either version 2 of the License, or\n" \
446  "(at your option) any later version.\n" \
447  "\n" \
448  "This program is distributed in the hope that it will be useful,\n" \
449  "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
450  "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" \
451  "GNU General Public License for more details.\n" \
452  "\n" \
453  "You should have received a copy of the GNU General Public License\n" \
454  "along with this program; if not, write to the Free Software\n" \
455  "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n");
456
457   gtk_dialog_run(GTK_DIALOG(about));
458   gtk_widget_destroy(about);
459 }
460
461 static void
462 connect_to_wui (GtkWidget *widget, gpointer data)
463 {
464   const char *hostname;
465   char *uri;
466   int len;
467
468   hostname = gtk_entry_get_text (GTK_ENTRY (ca_hostname));
469   if (STREQ (hostname, "")) return;
470
471   /* https:// + hostname + /ovirt + \0 */
472   len = 8 + strlen (hostname) + 6 + 1;
473   uri = g_alloca (len);
474   snprintf (uri, len, HTTPS "://%s/ovirt", hostname);
475
476   wui_thread_send_connect (uri);
477 }
478
479 static void
480 login_to_wui (GtkWidget *widget, gpointer data)
481 {
482   const char *username, *password;
483
484   username = gtk_entry_get_text (GTK_ENTRY (la_username));
485   if (STREQ (username, "")) return;
486   password = gtk_entry_get_text (GTK_ENTRY (la_password));
487
488   wui_thread_send_login (username, password);
489 }
490
491 /* Connect to a virtual machine.  This callback is called from the
492  * connect menu.  It searches the notebook of gtk-vnc widgets to see
493  * if we have already connected to this machine, and if not it
494  * makes a new connection.
495  */
496 static void
497 connect_to_vm (GtkWidget *widget, gpointer _vm)
498 {
499   struct vm *vm = (struct vm *) _vm;
500   int n = gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook));
501   int i, uuidlen, len, fd;
502   GtkWidget *child;
503   const char *label;
504   char *label2;
505
506   DEBUG ("searching tabs for uuid %s", vm->uuid);
507
508   uuidlen = strlen (vm->uuid);
509
510   /* Search the tabs for this UUID, and if found, switch to it and return. */
511   for (i = 0; i < n; ++i) {
512     child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), i);
513     label = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (notebook), child);
514     len = strlen (label);
515     if (len >= uuidlen &&
516         STREQ (label + len - uuidlen, vm->uuid)) {
517       DEBUG ("found on tab %d", i);
518       gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
519       return;
520     }
521   }
522
523   DEBUG ("not found, creating new tab");
524
525   /* This VM isn't in the notebook already, so create a new console. */
526   fd = viewer_open_tunnel_ssh (/*vm->host XXX*/ "192.168.50.6",
527                                0, /* Default SSH port. */
528                                "root", /* Root account. */
529                                vm->vnc_port);
530   if (fd == -1) return;         /* We've already given an error. */
531
532   child = vnc_display_new ();
533   if (! vnc_display_open_fd (VNC_DISPLAY (child), fd)) {
534     main_status_error (g_strdup ("internal error in Gtk-VNC widget"));
535     return;
536   }
537
538   /*
539   gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-grab",
540                      GTK_SIGNAL_FUNC(viewer_grab), window);
541   gtk_signal_connect(GTK_OBJECT(child), "vnc-pointer-ungrab",
542                      GTK_SIGNAL_FUNC(viewer_ungrab), window);
543   */
544
545   gtk_signal_connect(GTK_OBJECT(child), "delete-event",
546                      GTK_SIGNAL_FUNC(viewer_shutdown), child);
547
548   gtk_signal_connect(GTK_OBJECT(child), "vnc-connected",
549                      GTK_SIGNAL_FUNC(viewer_connected), NULL);
550   gtk_signal_connect(GTK_OBJECT(child), "vnc-initialized",
551                      GTK_SIGNAL_FUNC(viewer_initialized), NULL);
552   gtk_signal_connect(GTK_OBJECT(child), "vnc-disconnected",
553                      GTK_SIGNAL_FUNC(viewer_disconnected), NULL);
554
555   g_signal_connect(GTK_OBJECT(child), "vnc-auth-credential",
556                    GTK_SIGNAL_FUNC(viewer_credential), NULL);
557
558   /* NB. We have to do this before adding it to the notebook. */
559   gtk_widget_show (child);
560
561   /* Choose a tab label, which MUST end with the uuid string, since
562    * we use the tab label to store uuid.
563    */
564   len = strlen (vm->description) + 1 + strlen (vm->uuid) + 1;
565   label2 = g_alloca (len);
566   snprintf (label2, len, "%s %s", vm->description, vm->uuid);
567
568   i = gtk_notebook_append_page (GTK_NOTEBOOK (notebook), child, NULL);
569   gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (notebook), child, label2);
570   gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), i);
571
572   DEBUG ("finished creating new tab");
573 }
574
575 /*
576 static void viewer_grab(GtkWidget *vnc, GtkWidget *window)
577 {
578         int i;
579
580         viewer_set_title(VNC_DISPLAY(vnc), window, TRUE);
581
582         for (i = 0 ; i < LAST_MENU; i++) {
583                 gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].grabbed_text);
584         }
585 }
586
587 static void viewer_ungrab(GtkWidget *vnc, GtkWidget *window)
588 {
589         int i;
590
591         viewer_set_title(VNC_DISPLAY(vnc), window, FALSE);
592
593         for (i = 0 ; i < LAST_MENU; i++) {
594                 gtk_label_set_text_with_mnemonic(GTK_LABEL(menuItems[i].label), menuItems[i].ungrabbed_text);
595         }
596 }
597 */
598
599 static void
600 viewer_shutdown (GtkWidget *src, void *dummy, GtkWidget *vnc)
601 {
602   vnc_display_close (VNC_DISPLAY(vnc));
603
604   /* Just close the notebook tab for now. XXX */
605   gtk_notebook_remove_page (GTK_NOTEBOOK (notebook),
606                             gtk_notebook_page_num (GTK_NOTEBOOK (notebook),
607                                                    vnc));
608 }
609
610 #if 0
611 static void
612 viewer_quit (GtkWidget *src, GtkWidget *vnc)
613 {
614   viewer_shutdown (src, NULL, vnc);
615 }
616 #endif
617
618 static void
619 viewer_connected (GtkWidget *vnc)
620 {
621   DEBUG ("Connected to server");
622 }
623
624 static void
625 viewer_initialized (GtkWidget *vnc, GtkWidget *data)
626 {
627   DEBUG ("Connection initialized");
628 }
629
630 static void
631 viewer_disconnected (GtkWidget *vnc)
632 {
633   DEBUG ("Disconnected from server");
634 }
635
636 static void
637 viewer_credential (GtkWidget *vnc, GValueArray *credList)
638 {
639         GtkWidget *dialog = NULL;
640         int response;
641         unsigned int i, prompt = 0;
642         const char **data;
643
644         DEBUG ("Got credential request for %d credential(s)",
645                credList->n_values);
646
647         data = g_new0(const char *, credList->n_values);
648
649         for (i = 0 ; i < credList->n_values ; i++) {
650                 GValue *cred = g_value_array_get_nth(credList, i);
651                 switch (g_value_get_enum(cred)) {
652                 case VNC_DISPLAY_CREDENTIAL_USERNAME:
653                 case VNC_DISPLAY_CREDENTIAL_PASSWORD:
654                         prompt++;
655                         break;
656                 case VNC_DISPLAY_CREDENTIAL_CLIENTNAME:
657                         data[i] = "libvirt";
658                 default:
659                         break;
660                 }
661         }
662
663         if (prompt) {
664                 GtkWidget **label, **entry, *box, *vbox;
665                 int row;
666                 dialog = gtk_dialog_new_with_buttons("Authentication required",
667                                                      NULL,
668                                                      0,
669                                                      GTK_STOCK_CANCEL,
670                                                      GTK_RESPONSE_CANCEL,
671                                                      GTK_STOCK_OK,
672                                                      GTK_RESPONSE_OK,
673                                                      NULL);
674                 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
675
676                 box = gtk_table_new(credList->n_values, 2, FALSE);
677                 label = g_new(GtkWidget *, prompt);
678                 entry = g_new(GtkWidget *, prompt);
679
680                 for (i = 0, row =0 ; i < credList->n_values ; i++) {
681                         GValue *cred = g_value_array_get_nth(credList, i);
682                         switch (g_value_get_enum(cred)) {
683                         case VNC_DISPLAY_CREDENTIAL_USERNAME:
684                                 label[row] = gtk_label_new("Username:");
685                                 break;
686                         case VNC_DISPLAY_CREDENTIAL_PASSWORD:
687                                 label[row] = gtk_label_new("Password:");
688                                 break;
689                         default:
690                                 continue;
691                         }
692                         entry[row] = gtk_entry_new();
693                         if (g_value_get_enum(cred) == VNC_DISPLAY_CREDENTIAL_PASSWORD)
694                                 gtk_entry_set_visibility(GTK_ENTRY(entry[row]), FALSE);
695
696                         gtk_table_attach(GTK_TABLE(box), label[i], 0, 1, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3);
697                         gtk_table_attach(GTK_TABLE(box), entry[i], 1, 2, row, row+1, GTK_SHRINK, GTK_SHRINK, 3, 3);
698                         row++;
699                 }
700
701                 vbox = gtk_bin_get_child(GTK_BIN(dialog));
702                 gtk_container_add(GTK_CONTAINER(vbox), box);
703
704                 gtk_widget_show_all(dialog);
705                 response = gtk_dialog_run(GTK_DIALOG(dialog));
706                 gtk_widget_hide(GTK_WIDGET(dialog));
707
708                 if (response == GTK_RESPONSE_OK) {
709                         for (i = 0, row = 0 ; i < credList->n_values ; i++) {
710                                 GValue *cred = g_value_array_get_nth(credList, i);
711                                 switch (g_value_get_enum(cred)) {
712                                 case VNC_DISPLAY_CREDENTIAL_USERNAME:
713                                 case VNC_DISPLAY_CREDENTIAL_PASSWORD:
714                                         data[i] = gtk_entry_get_text(GTK_ENTRY(entry[row]));
715                                         break;
716                                 }
717                         }
718                 }
719         }
720
721         for (i = 0 ; i < credList->n_values ; i++) {
722                 GValue *cred = g_value_array_get_nth(credList, i);
723                 if (data[i]) {
724                         if (vnc_display_set_credential(VNC_DISPLAY(vnc),
725                                                        g_value_get_enum(cred),
726                                                        data[i])) {
727                                 DEBUG("Failed to set credential type %d",
728                                       g_value_get_enum(cred));
729                                 vnc_display_close(VNC_DISPLAY(vnc));
730                         }
731                 } else {
732                         DEBUG("Unsupported credential type %d",
733                               g_value_get_enum(cred));
734                         vnc_display_close(VNC_DISPLAY(vnc));
735                 }
736         }
737
738         g_free(data);
739         if (dialog)
740                 gtk_widget_destroy(GTK_WIDGET(dialog));
741 }
742
743 #if defined(HAVE_SOCKETPAIR) && defined(HAVE_FORK)
744
745 static int viewer_open_tunnel(const char **cmd)
746 {
747         int fd[2];
748         pid_t pid;
749
750         if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd) < 0)
751                 return -1;
752
753         pid = fork();
754         if (pid == -1) {
755                 close(fd[0]);
756                 close(fd[1]);
757                 return -1;
758         }
759
760         if (pid == 0) { /* child */
761                 close(fd[0]);
762                 close(0);
763                 close(1);
764                 if (dup(fd[1]) < 0)
765                         _exit(1);
766                 if (dup(fd[1]) < 0)
767                         _exit(1);
768                 close(fd[1]);
769                 execvp("ssh", (char *const*)cmd);
770                 _exit(1);
771         }
772         close(fd[1]);
773         return fd[0];
774 }
775
776 static int
777 viewer_open_tunnel_ssh (const char *sshhost, int sshport, const char *sshuser,
778                         int vncport)
779 {
780   const char *cmd[10];
781   char portstr[50], portstr2[50];
782   int n = 0;
783
784   if (!sshport)
785     sshport = 22;
786
787   snprintf (portstr, sizeof portstr, "%d", sshport);
788   snprintf (portstr2, sizeof portstr2, "%d", vncport);
789
790   cmd[n++] = "ssh";
791   cmd[n++] = "-p";
792   cmd[n++] = portstr;
793   if (sshuser) {
794     cmd[n++] = "-l";
795     cmd[n++] = sshuser;
796   }
797   cmd[n++] = sshhost;
798   cmd[n++] = "nc";
799   cmd[n++] = "localhost";
800   cmd[n++] = portstr2;
801   cmd[n++] = NULL;
802
803   return viewer_open_tunnel(cmd);
804 }
805
806 #endif /* defined(HAVE_SOCKETPAIR) && defined(HAVE_FORK) */
807
808 /* Remove all menu items from the Connect menu. */
809 static void
810 remove_menu_item (GtkWidget *menu_item, gpointer data)
811 {
812   gtk_container_remove (GTK_CONTAINER (connectmenu), menu_item);
813 }
814
815 static void
816 clear_connectmenu (void)
817 {
818   DEBUG ("clear Connect menu");
819   gtk_container_foreach (GTK_CONTAINER (connectmenu), remove_menu_item, NULL);
820 }
821
822 /* The WUI thread has changed its state to connected. */
823 gboolean
824 main_connected (gpointer data)
825 {
826   DEBUG ("connected");
827   ASSERT_IS_MAIN_THREAD ();
828
829   gtk_label_set_text (GTK_LABEL (ca_error), NULL);
830
831   gtk_widget_hide (connection_area);
832   if (!wui_thread_is_logged_in ())
833     gtk_widget_show (login_area);
834   return FALSE;
835 }
836
837 /* The WUI thread has changed its state to disconnected. */
838 gboolean
839 main_disconnected (gpointer data)
840 {
841   DEBUG ("disconnected");
842   ASSERT_IS_MAIN_THREAD ();
843
844   gtk_widget_show (connection_area);
845   gtk_widget_hide (login_area);
846
847   clear_connectmenu ();
848   gtk_menu_append (GTK_MENU (connectmenu), no_connections);
849   gtk_widget_show_all (connectmenu);
850
851   return FALSE;
852 }
853
854 /* The WUI thread has changed its state to logged in. */
855 gboolean
856 main_logged_in (gpointer data)
857 {
858   DEBUG ("logged in");
859   ASSERT_IS_MAIN_THREAD ();
860
861   gtk_widget_hide (login_area);
862   return FALSE;
863 }
864
865 /* The WUI thread has changed its state to logged out. */
866 gboolean
867 main_logged_out (gpointer data)
868 {
869   DEBUG ("logged out");
870   ASSERT_IS_MAIN_THREAD ();
871
872   if (wui_thread_is_connected ())
873     gtk_widget_show (login_area);
874   return FALSE;
875 }
876
877 /* The WUI thread has changed its state to busy. */
878 gboolean
879 main_busy (gpointer data)
880 {
881   GdkWindow *gdk_window;
882
883   DEBUG ("busy");
884   ASSERT_IS_MAIN_THREAD ();
885
886   gdk_window = gtk_widget_get_window (window);
887   if (gdk_window) {
888     gdk_window_set_cursor (gdk_window, busy_cursor);
889     gdk_flush ();
890   }
891
892   return FALSE;
893 }
894
895 /* The WUI thread has changed its state to idle. */
896 gboolean
897 main_idle (gpointer data)
898 {
899   GdkWindow *gdk_window;
900
901   DEBUG ("idle");
902   ASSERT_IS_MAIN_THREAD ();
903
904   gdk_window = gtk_widget_get_window (window);
905   if (gdk_window) {
906     gdk_window_set_cursor (gdk_window, NULL);
907     gdk_flush ();
908   }
909
910   return FALSE;
911 }
912
913 /* The WUI thread had a connection error.  This function must
914  * free the string.
915  */
916 gboolean
917 main_connection_error (gpointer _str)
918 {
919   char *str = (char *) _str;
920
921   DEBUG ("connection error: %s", str);
922   ASSERT_IS_MAIN_THREAD ();
923
924   gtk_label_set_text (GTK_LABEL (ca_error), str);
925   g_free (str);
926
927   return FALSE;
928 }
929
930 /* The WUI thread had a login error.  This function must
931  * free the string.
932  */
933 gboolean
934 main_login_error (gpointer _str)
935 {
936   char *str = (char *) _str;
937
938   DEBUG ("login error: %s", str);
939   ASSERT_IS_MAIN_THREAD ();
940
941   /*
942   gtk_label_set_text (GTK_LABEL (la_error), str);
943   */
944   g_free (str);
945
946   return FALSE;
947 }
948
949 /* The WUI thread reports a general status error.  This function
950  * must free the string.
951  */
952 gboolean
953 main_status_error (gpointer _str)
954 {
955   char *str = (char *) _str;
956
957   DEBUG ("status error: %s", str);
958   ASSERT_IS_MAIN_THREAD ();
959
960   gtk_statusbar_pop (GTK_STATUSBAR (statusbar), statusbar_ctx);
961   gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar_ctx, str);
962   g_free (str);
963
964   return FALSE;
965 }
966
967 /* The WUI thread has updated the vm list.  Here in the main thread
968  * we keep our own copy of the vmlist.
969  */
970 static GSList *vmlist = NULL;
971
972 static void add_vm_to_connectmenu (gpointer _vm, gpointer data);
973
974 gboolean
975 main_vmlist_updated (gpointer data)
976 {
977   GSList *new_vmlist;
978
979   DEBUG ("vmlist updated");
980   ASSERT_IS_MAIN_THREAD ();
981
982   /* Get the new vmlist. */
983   if (wui_thread_get_vmlist (&new_vmlist)) {
984     /* Free the previous vmlist.  This invalidates all the vm pointers
985      * contained in the Connect menu callbacks, but we're going to
986      * delete those callbacks and create news ones in a moment anyway ...
987      */
988     free_vmlist (vmlist);
989
990     vmlist = new_vmlist;
991
992     clear_connectmenu ();
993
994     gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist);
995
996     if (vmlist != NULL) {
997       gtk_menu_append (GTK_MENU (connectmenu), refresh_vmlist_separator);
998       g_slist_foreach (vmlist, add_vm_to_connectmenu, NULL);
999     }
1000
1001     /* Grrrr Gtk is stupid. */
1002     gtk_widget_show_all (connectmenu);
1003   }
1004
1005   return FALSE;
1006 }
1007
1008 static void
1009 add_vm_to_connectmenu (gpointer _vm, gpointer data)
1010 {
1011   struct vm *vm = (struct vm *) _vm;
1012   GtkWidget *item;
1013
1014   DEBUG ("adding %s to Connect menu", vm->description);
1015
1016   item = gtk_menu_item_new_with_label (vm->description);
1017   gtk_menu_append (GTK_MENU (connectmenu), item);
1018
1019   g_signal_connect (G_OBJECT (item), "activate",
1020                     G_CALLBACK (connect_to_vm), vm);
1021 }