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