gcompat: provide backwards compatible functions for older gtk versions
[fio.git] / gfio.c
1 /*
2  * gfio - gui front end for fio - the flexible io tester
3  *
4  * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com> 
5  * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
6  *
7  * The license below covers all files distributed with fio unless otherwise
8  * noted in the file itself.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License version 2 as
12  *  published by the Free Software Foundation.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
24 #include <locale.h>
25 #include <malloc.h>
26 #include <string.h>
27
28 #include <glib.h>
29 #include <cairo.h>
30 #include <gtk/gtk.h>
31
32 #include "fio.h"
33 #include "gfio.h"
34 #include "ghelpers.h"
35 #include "goptions.h"
36 #include "gerror.h"
37 #include "gclient.h"
38 #include "graph.h"
39
40 static int gfio_server_running;
41 static unsigned int gfio_graph_limit = 100;
42
43 GdkColor gfio_color_white;
44 const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
45
46 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
47
48 static void connect_clicked(GtkWidget *widget, gpointer data);
49 static void start_job_clicked(GtkWidget *widget, gpointer data);
50 static void send_clicked(GtkWidget *widget, gpointer data);
51
52 static struct button_spec {
53         const char *buttontext;
54         clickfunction f;
55         const char *tooltiptext[2];
56         const int start_sensitive;
57 } buttonspeclist[] = {
58         {
59           .buttontext           = "Connect",
60           .f                    = connect_clicked,
61           .tooltiptext          = { "Disconnect from host", "Connect to host" },
62           .start_sensitive      = 1,
63         },
64         {
65           .buttontext           = "Send",
66           .f                    = send_clicked,
67           .tooltiptext          = { "Send job description to host", NULL },
68           .start_sensitive      = 0,
69         },
70         {
71           .buttontext           = "Start Job",
72           .f                    = start_job_clicked,
73           .tooltiptext          = { "Start the current job on the server", NULL },
74           .start_sensitive      = 0,
75         },
76 };
77
78 static void setup_iops_graph(struct gfio_graphs *gg)
79 {
80         struct graph *g;
81
82         g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
83         graph_title(g, "IOPS (IOs/sec)");
84         graph_x_title(g, "Time (secs)");
85         gg->read_iops = graph_add_label(g, "Read IOPS");
86         gg->write_iops = graph_add_label(g, "Write IOPS");
87         graph_set_color(g, gg->read_iops, 0.13, 0.54, 0.13);
88         graph_set_color(g, gg->write_iops, 1.0, 0.0, 0.0);
89         line_graph_set_data_count_limit(g, gfio_graph_limit);
90         graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
91         graph_set_graph_all_zeroes(g, 0);
92         gg->iops_graph = g;
93 }
94
95 static void setup_bandwidth_graph(struct gfio_graphs *gg)
96 {
97         struct graph *g;
98
99         g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
100         graph_title(g, "Bandwidth (bytes/sec)");
101         graph_x_title(g, "Time (secs)");
102         gg->read_bw = graph_add_label(g, "Read Bandwidth");
103         gg->write_bw = graph_add_label(g, "Write Bandwidth");
104         graph_set_color(g, gg->read_bw, 0.13, 0.54, 0.13);
105         graph_set_color(g, gg->write_bw, 1.0, 0.0, 0.0);
106         graph_set_base_offset(g, 1);
107         line_graph_set_data_count_limit(g, 100);
108         graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
109         graph_set_graph_all_zeroes(g, 0);
110         gg->bandwidth_graph = g;
111 }
112
113 static void setup_graphs(struct gfio_graphs *g)
114 {
115         setup_iops_graph(g);
116         setup_bandwidth_graph(g);
117 }
118
119 void clear_ge_ui_info(struct gui_entry *ge)
120 {
121         gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
122         gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
123         gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
124         gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
125 #if 0
126         /* should we empty it... */
127         gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
128 #endif
129         multitext_update_entry(&ge->eta.iotype, 0, "");
130         multitext_update_entry(&ge->eta.bs, 0, "");
131         multitext_update_entry(&ge->eta.ioengine, 0, "");
132         multitext_update_entry(&ge->eta.iodepth, 0, "");
133         gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
134         gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
135         gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
136         gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
137         gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
138         gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
139 }
140
141 static void set_menu_entry_text(struct gui *ui, const char *path,
142                                 const char *text)
143 {
144         GtkWidget *w;
145
146         w = gtk_ui_manager_get_widget(ui->uimanager, path);
147         if (w)
148                 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
149         else
150                 fprintf(stderr, "gfio: can't find path %s\n", path);
151 }
152
153
154 static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
155 {
156         GtkWidget *w;
157
158         w = gtk_ui_manager_get_widget(ui->uimanager, path);
159         if (w)
160                 gtk_widget_set_sensitive(w, show);
161         else
162                 fprintf(stderr, "gfio: can't find path %s\n", path);
163 }
164
165 static void set_job_menu_visible(struct gui *ui, int visible)
166 {
167         set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
168 }
169
170 static void set_view_results_visible(struct gui *ui, int visible)
171 {
172         set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
173 }
174
175 static const char *get_button_tooltip(struct button_spec *s, int sensitive)
176 {
177         if (s->tooltiptext[sensitive])
178                 return s->tooltiptext[sensitive];
179
180         return s->tooltiptext[0];
181 }
182
183 static GtkWidget *add_button(GtkWidget *buttonbox,
184                              struct button_spec *buttonspec, gpointer data)
185 {
186         GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
187         gboolean sens = buttonspec->start_sensitive;
188
189         g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
190         gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
191
192         sens = buttonspec->start_sensitive;
193         gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
194         gtk_widget_set_sensitive(button, sens);
195
196         return button;
197 }
198
199 static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
200                         int nbuttons)
201 {
202         int i;
203
204         for (i = 0; i < nbuttons; i++)
205                 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
206 }
207
208 /*
209  * Update sensitivity of job buttons and job menu items, based on the
210  * state of the client.
211  */
212 static void update_button_states(struct gui *ui, struct gui_entry *ge)
213 {
214         unsigned int connect_state, send_state, start_state, edit_state;
215         const char *connect_str = NULL;
216
217         switch (ge->state) {
218         default:
219                 gfio_report_error(ge, "Bad client state: %u\n", ge->state);
220                 /* fall through to new state */
221         case GE_STATE_NEW:
222                 connect_state = 1;
223                 edit_state = 1;
224                 connect_str = "Connect";
225                 send_state = 0;
226                 start_state = 0;
227                 break;
228         case GE_STATE_CONNECTED:
229                 connect_state = 1;
230                 edit_state = 1;
231                 connect_str = "Disconnect";
232                 send_state = 1;
233                 start_state = 0;
234                 break;
235         case GE_STATE_JOB_SENT:
236                 connect_state = 1;
237                 edit_state = 1;
238                 connect_str = "Disconnect";
239                 send_state = 0;
240                 start_state = 1;
241                 break;
242         case GE_STATE_JOB_STARTED:
243                 connect_state = 1;
244                 edit_state = 1;
245                 connect_str = "Disconnect";
246                 send_state = 0;
247                 start_state = 1;
248                 break;
249         case GE_STATE_JOB_RUNNING:
250                 connect_state = 1;
251                 edit_state = 0;
252                 connect_str = "Disconnect";
253                 send_state = 0;
254                 start_state = 0;
255                 break;
256         case GE_STATE_JOB_DONE:
257                 connect_state = 1;
258                 edit_state = 0;
259                 connect_str = "Connect";
260                 send_state = 0;
261                 start_state = 0;
262                 break;
263         }
264
265         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
266         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
267         gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
268         gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
269         gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
270
271         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
272         set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
273
274         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
275         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
276         set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
277
278         if (ge->client && ge->client->nr_results)
279                 set_view_results_visible(ui, 1);
280         else
281                 set_view_results_visible(ui, 0);
282 }
283
284 void gfio_set_state(struct gui_entry *ge, unsigned int state)
285 {
286         ge->state = state;
287         update_button_states(ge->ui, ge);
288 }
289
290 static void gfio_ui_setup_log(struct gui *ui)
291 {
292         GtkTreeSelection *selection;
293         GtkListStore *model;
294         GtkWidget *tree_view;
295
296         model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
297
298         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
299         gtk_widget_set_can_focus(tree_view, FALSE);
300
301         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
302         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
303         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
304                 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
305
306         tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
307         tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
308         tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
309         tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
310
311         ui->log_model = model;
312         ui->log_tree = tree_view;
313 }
314
315 static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
316                                    gpointer data)
317 {
318         struct gfio_graphs *g = data;
319
320         graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
321         graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
322         graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
323         graph_set_position(g->bandwidth_graph, 0, 0);
324         return TRUE;
325 }
326
327 static void draw_graph(struct graph *g, cairo_t *cr)
328 {
329         line_graph_draw(g, cr);
330         cairo_stroke(cr);
331 }
332
333 static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
334                               gboolean keyboard_mode, GtkTooltip *tooltip,
335                               gpointer data)
336 {
337         struct gfio_graphs *g = data;
338         const char *text = NULL;
339
340         if (graph_contains_xy(g->iops_graph, x, y))
341                 text = graph_find_tooltip(g->iops_graph, x, y);
342         else if (graph_contains_xy(g->bandwidth_graph, x, y))
343                 text = graph_find_tooltip(g->bandwidth_graph, x, y);
344
345         if (text) {
346                 gtk_tooltip_set_text(tooltip, text);
347                 return TRUE;
348         }
349
350         return FALSE;
351 }
352
353 static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
354 {
355         struct gfio_graphs *g = p;
356         cairo_t *cr;
357
358         cr = gdk_cairo_create(w->window);
359
360         if (graph_has_tooltips(g->iops_graph) ||
361             graph_has_tooltips(g->bandwidth_graph)) {
362                 g_object_set(w, "has-tooltip", TRUE, NULL);
363                 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
364         }
365
366         cairo_set_source_rgb(cr, 0, 0, 0);
367         draw_graph(g->iops_graph, cr);
368         draw_graph(g->bandwidth_graph, cr);
369         cairo_destroy(cr);
370
371         return FALSE;
372 }
373
374 /*
375  * FIXME: need more handling here
376  */
377 static void ge_destroy(struct gui_entry *ge)
378 {
379         struct gfio_client *gc = ge->client;
380
381         if (gc) {
382                 if (gc->client) {
383                         if (ge->state >= GE_STATE_CONNECTED)
384                                 fio_client_terminate(gc->client);
385
386                         fio_put_client(gc->client);
387                 }
388                 free(gc);
389         }
390
391         g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
392
393         free(ge->job_file);
394         free(ge->host);
395         free(ge);
396 }
397
398 static void ge_widget_destroy(GtkWidget *w, gpointer data)
399 {
400         struct gui_entry *ge = (struct gui_entry *) data;
401
402         ge_destroy(ge);
403 }
404
405 static void gfio_quit(struct gui *ui)
406 {
407         gtk_main_quit();
408 }
409
410 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
411                 __attribute__((unused)) gpointer data)
412 {
413         struct gui *ui = (struct gui *) data;
414
415         gfio_quit(ui);
416 }
417
418 static void *job_thread(void *arg)
419 {
420         struct gui *ui = arg;
421
422         ui->handler_running = 1;
423         fio_handle_clients(&gfio_client_ops);
424         ui->handler_running = 0;
425         return NULL;
426 }
427
428 static int send_job_file(struct gui_entry *ge)
429 {
430         struct gfio_client *gc = ge->client;
431         int ret = 0;
432
433         /*
434          * Prune old options, we are expecting the return options
435          * when the job file is parsed remotely and returned to us.
436          */
437         while (!flist_empty(&gc->o_list)) {
438                 struct gfio_client_options *gco;
439
440                 gco = flist_entry(gc->o_list.next, struct gfio_client_options, list);
441                 flist_del(&gco->list);
442                 free(gco);
443         }
444
445         ret = fio_client_send_ini(gc->client, ge->job_file);
446         if (!ret)
447                 return 0;
448
449         gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
450         return 1;
451 }
452
453 static void *server_thread(void *arg)
454 {
455         is_backend = 1;
456         gfio_server_running = 1;
457         fio_start_server(NULL);
458         gfio_server_running = 0;
459         return NULL;
460 }
461
462 static void gfio_start_server(struct gui *ui)
463 {
464         if (!gfio_server_running) {
465                 gfio_server_running = 1;
466                 pthread_create(&ui->server_t, NULL, server_thread, NULL);
467                 pthread_detach(ui->server_t);
468         }
469 }
470
471 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
472                 gpointer data)
473 {
474         struct gui_entry *ge = data;
475         struct gfio_client *gc = ge->client;
476
477         if (gc)
478                 fio_start_client(gc->client);
479 }
480
481 static void file_open(GtkWidget *w, gpointer data);
482
483 struct connection_widgets
484 {
485         GtkWidget *hentry;
486         GtkWidget *combo;
487         GtkWidget *button;
488 };
489
490 static void hostname_cb(GtkEntry *entry, gpointer data)
491 {
492         struct connection_widgets *cw = data;
493         int uses_net = 0, is_localhost = 0;
494         const gchar *text;
495         gchar *ctext;
496
497         /*
498          * Check whether to display the 'auto start backend' box
499          * or not. Show it if we are a localhost and using network,
500          * or using a socket.
501          */
502         ctext = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw->combo));
503         if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
504                 uses_net = 1;
505         g_free(ctext);
506
507         if (uses_net) {
508                 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
509                 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
510                     !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
511                     !strcmp(text, "ip6-loopback"))
512                         is_localhost = 1;
513         }
514
515         if (!uses_net || is_localhost) {
516                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
517                 gtk_widget_set_sensitive(cw->button, 1);
518         } else {
519                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
520                 gtk_widget_set_sensitive(cw->button, 0);
521         }
522 }
523
524 static int get_connection_details(struct gui_entry *ge)
525 {
526         GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
527         struct connection_widgets cw;
528         struct gui *ui = ge->ui;
529         char *typeentry;
530
531         if (ge->host)
532                 return 0;
533
534         dialog = gtk_dialog_new_with_buttons("Connection details",
535                         GTK_WINDOW(ui->window),
536                         GTK_DIALOG_DESTROY_WITH_PARENT,
537                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
538                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
539
540         frame = gtk_frame_new("Hostname / socket name");
541         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
542         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
543
544         box = gtk_vbox_new(FALSE, 6);
545         gtk_container_add(GTK_CONTAINER(frame), box);
546
547         hbox = gtk_hbox_new(TRUE, 10);
548         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
549         cw.hentry = gtk_entry_new();
550         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
551         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
552
553         frame = gtk_frame_new("Port");
554         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
555         box = gtk_vbox_new(FALSE, 10);
556         gtk_container_add(GTK_CONTAINER(frame), box);
557
558         hbox = gtk_hbox_new(TRUE, 4);
559         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
560         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
561
562         frame = gtk_frame_new("Type");
563         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
564         box = gtk_vbox_new(FALSE, 10);
565         gtk_container_add(GTK_CONTAINER(frame), box);
566
567         hbox = gtk_hbox_new(TRUE, 4);
568         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
569
570         cw.combo = gtk_combo_box_text_new();
571         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv4");
572         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "IPv6");
573         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cw.combo), "local socket");
574         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
575
576         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
577
578         frame = gtk_frame_new("Options");
579         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
580         box = gtk_vbox_new(FALSE, 10);
581         gtk_container_add(GTK_CONTAINER(frame), box);
582
583         hbox = gtk_hbox_new(TRUE, 4);
584         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
585
586         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
587         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
588         gtk_widget_set_tooltip_text(cw.button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
589         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
590
591         /*
592          * Connect edit signal, so we can show/not-show the auto start button
593          */
594         g_signal_connect(G_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
595         g_signal_connect(G_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
596
597         gtk_widget_show_all(dialog);
598
599         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
600                 gtk_widget_destroy(dialog);
601                 return 1;
602         }
603
604         ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
605         ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
606
607         typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(cw.combo));
608         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
609                 ge->type = Fio_client_ipv4;
610         else if (!strncmp(typeentry, "IPv6", 4))
611                 ge->type = Fio_client_ipv6;
612         else
613                 ge->type = Fio_client_socket;
614         g_free(typeentry);
615
616         ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
617
618         gtk_widget_destroy(dialog);
619         return 0;
620 }
621
622 static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
623 {
624         gc->client = fio_get_client(client);
625         client->client_data = gc;
626 }
627
628 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
629 {
630         struct gfio_client_options *gco;
631         struct gfio_client *gc;
632
633         gc = calloc(1, sizeof(*gc));
634         INIT_FLIST_HEAD(&gc->o_list);
635         gc->ge = ge;
636         ge->client = gc;
637         gfio_set_client(gc, client);
638
639         /*
640          * Just add a default set of options, need to consider how best
641          * to handle this
642          */
643         gco = calloc(1, sizeof(*gco));
644         INIT_FLIST_HEAD(&gco->list);
645         options_default_fill(&gco->o);
646         flist_add_tail(&gco->list, &gc->o_list);
647         gc->o_list_nr++;
648 }
649
650 static void connect_clicked(GtkWidget *widget, gpointer data)
651 {
652         struct gui_entry *ge = data;
653         struct gfio_client *gc = ge->client;
654
655         if (ge->state == GE_STATE_NEW) {
656                 int ret;
657
658                 if (!ge->job_file)
659                         file_open(widget, ge->ui);
660                 if (!ge->job_file)
661                         return;
662
663                 gc = ge->client;
664
665                 if (!gc->client) {
666                         struct fio_client *client;
667
668                         if (get_connection_details(ge)) {
669                                 gfio_report_error(ge, "Failed to get connection details\n");
670                                 return;
671                         }
672
673                         client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
674                         if (!client) {
675                                 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
676                                 free(ge->host);
677                                 ge->host = NULL;
678                                 return;
679                         }
680                         gfio_set_client(gc, client);
681                 }
682
683                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
684                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
685                 ret = fio_client_connect(gc->client);
686                 if (!ret) {
687                         if (!ge->ui->handler_running)
688                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
689                         gfio_set_state(ge, GE_STATE_CONNECTED);
690                 } else {
691                         gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
692                 }
693         } else {
694                 fio_client_terminate(gc->client);
695                 gfio_set_state(ge, GE_STATE_NEW);
696                 clear_ge_ui_info(ge);
697         }
698 }
699
700 static void send_clicked(GtkWidget *widget, gpointer data)
701 {
702         struct gui_entry *ge = data;
703
704         if (send_job_file(ge))
705                 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
706 }
707
708 static GtkWidget *new_client_page(struct gui_entry *ge);
709
710 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
711 {
712         struct gui_entry *ge;
713
714         ge = malloc(sizeof(*ge));
715         memset(ge, 0, sizeof(*ge));
716         ge->state = GE_STATE_NEW;
717         ge->ui = ui;
718         return ge;
719 }
720
721 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
722 {
723         struct gui_entry *ge;
724
725         ge = alloc_new_gui_entry(ui);
726
727         ge->vbox = new_client_page(ge);
728         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
729
730         ge->page_label = gtk_label_new(name);
731         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
732
733         g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
734
735         gtk_widget_show_all(ui->window);
736         return ge;
737 }
738
739 static void file_new(GtkWidget *w, gpointer data)
740 {
741         struct gui *ui = (struct gui *) data;
742         struct gui_entry *ge;
743
744         ge = get_new_ge_with_tab(ui, "Untitled");
745         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
746 }
747
748 /*
749  * Return the 'ge' corresponding to the tab. If the active tab is the
750  * main tab, open a new tab.
751  */
752 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
753                                           int *created)
754 {
755         if (!cur_page) {
756                 if (created)
757                         *created = 1;
758                 return get_new_ge_with_tab(ui, "Untitled");
759         }
760
761         if (created)
762                 *created = 0;
763
764         return g_hash_table_lookup(ui->ge_hash, &cur_page);
765 }
766
767 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
768 {
769         gint cur_page;
770
771         /*
772          * Main tab is tab 0, so any current page other than 0 holds
773          * a ge entry.
774          */
775         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
776         if (cur_page)
777                 return get_ge_from_page(ui, cur_page, NULL);
778
779         return NULL;
780 }
781
782 static void file_close(GtkWidget *w, gpointer data)
783 {
784         struct gui *ui = (struct gui *) data;
785         struct gui_entry *ge;
786
787         /*
788          * Can't close the main tab
789          */
790         ge = get_ge_from_cur_tab(ui);
791         if (ge) {
792                 gtk_widget_destroy(ge->vbox);
793                 return;
794         }
795
796         if (g_hash_table_size(ui->ge_hash)) {
797                 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
798                 return;
799         }
800
801         gfio_quit(ui);
802 }
803
804 static void file_add_recent(struct gui *ui, const gchar *uri)
805 {
806         GtkRecentData grd;
807
808         memset(&grd, 0, sizeof(grd));
809         grd.display_name = strdup("gfio");
810         grd.description = strdup("Fio job file");
811         grd.mime_type = strdup(GFIO_MIME);
812         grd.app_name = strdup(g_get_application_name());
813         grd.app_exec = strdup("gfio %f/%u");
814
815         gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
816 }
817
818 static gchar *get_filename_from_uri(const gchar *uri)
819 {
820         if (strncmp(uri, "file://", 7))
821                 return strdup(uri);
822
823         return strdup(uri + 7);
824 }
825
826 static int do_file_open(struct gui_entry *ge, const gchar *uri)
827 {
828         struct fio_client *client;
829
830         assert(!ge->job_file);
831
832         ge->job_file = get_filename_from_uri(uri);
833
834         client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
835         if (client) {
836                 char *label = strdup(uri);
837
838                 basename(label);
839                 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
840                 free(label);
841
842                 gfio_client_added(ge, client);
843                 file_add_recent(ge->ui, uri);
844                 return 0;
845         }
846
847         gfio_report_error(ge, "Failed to add client %s\n", ge->host);
848         free(ge->host);
849         ge->host = NULL;
850         free(ge->job_file);
851         ge->job_file = NULL;
852         return 1;
853 }
854
855 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
856 {
857         struct gui_entry *ge;
858         gint cur_page;
859         int ret, ge_is_new = 0;
860
861         /*
862          * Creates new tab if current tab is the main window, or the
863          * current tab already has a client.
864          */
865         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
866         ge = get_ge_from_page(ui, cur_page, &ge_is_new);
867         if (ge->client) {
868                 ge = get_new_ge_with_tab(ui, "Untitled");
869                 ge_is_new = 1;
870         }
871
872         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
873
874         if (get_connection_details(ge)) {
875                 if (ge_is_new)
876                         gtk_widget_destroy(ge->vbox);
877                         
878                 return 1;
879         }
880
881         ret = do_file_open(ge, uri);
882
883         if (!ret) {
884                 if (ge->server_start)
885                         gfio_start_server(ui);
886         } else {
887                 if (ge_is_new)
888                         gtk_widget_destroy(ge->vbox);
889         }
890
891         return ret;
892 }
893
894 static void recent_open(GtkAction *action, gpointer data)
895 {
896         struct gui *ui = (struct gui *) data;
897         GtkRecentInfo *info;
898         const gchar *uri;
899
900         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
901         uri = gtk_recent_info_get_uri(info);
902
903         do_file_open_with_tab(ui, uri);
904 }
905
906 static void file_open(GtkWidget *w, gpointer data)
907 {
908         struct gui *ui = data;
909         GtkWidget *dialog;
910         GtkFileFilter *filter;
911         gchar *filename;
912
913         dialog = gtk_file_chooser_dialog_new("Open File",
914                 GTK_WINDOW(ui->window),
915                 GTK_FILE_CHOOSER_ACTION_OPEN,
916                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
917                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
918                 NULL);
919         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
920
921         filter = gtk_file_filter_new();
922         gtk_file_filter_add_pattern(filter, "*.fio");
923         gtk_file_filter_add_pattern(filter, "*.job");
924         gtk_file_filter_add_pattern(filter, "*.ini");
925         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
926         gtk_file_filter_set_name(filter, "Fio job file");
927         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
928
929         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
930                 gtk_widget_destroy(dialog);
931                 return;
932         }
933
934         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
935
936         gtk_widget_destroy(dialog);
937
938         do_file_open_with_tab(ui, filename);
939         g_free(filename);
940 }
941
942 static void file_save(GtkWidget *w, gpointer data)
943 {
944         struct gui *ui = data;
945         GtkWidget *dialog;
946
947         dialog = gtk_file_chooser_dialog_new("Save File",
948                 GTK_WINDOW(ui->window),
949                 GTK_FILE_CHOOSER_ACTION_SAVE,
950                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
951                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
952                 NULL);
953
954         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
955         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
956
957         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
958                 char *filename;
959
960                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
961                 // save_job_file(filename);
962                 g_free(filename);
963         }
964         gtk_widget_destroy(dialog);
965 }
966
967 static void view_log_destroy(GtkWidget *w, gpointer data)
968 {
969         struct gui *ui = (struct gui *) data;
970
971         g_object_ref(G_OBJECT(ui->log_tree));
972         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
973         gtk_widget_destroy(w);
974         ui->log_view = NULL;
975 }
976
977 void gfio_view_log(struct gui *ui)
978 {
979         GtkWidget *win, *scroll, *vbox, *box;
980
981         if (ui->log_view)
982                 return;
983
984         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
985         gtk_window_set_title(GTK_WINDOW(win), "Log");
986         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
987
988         scroll = gtk_scrolled_window_new(NULL, NULL);
989
990         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
991
992         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
993
994         box = gtk_hbox_new(TRUE, 0);
995         gtk_box_pack_start(GTK_BOX(box), ui->log_tree, TRUE, TRUE, 0);
996         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
997         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
998
999         vbox = gtk_vbox_new(TRUE, 5);
1000         gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
1001
1002         gtk_container_add(GTK_CONTAINER(win), vbox);
1003         gtk_widget_show_all(win);
1004 }
1005
1006 static void view_log(GtkWidget *w, gpointer data)
1007 {
1008         struct gui *ui = (struct gui *) data;
1009
1010         gfio_view_log(ui);
1011 }
1012
1013 static void connect_job_entry(GtkWidget *w, gpointer data)
1014 {
1015         struct gui *ui = (struct gui *) data;
1016         struct gui_entry *ge;
1017         
1018         ge = get_ge_from_cur_tab(ui);
1019         if (ge)
1020                 connect_clicked(w, ge);
1021 }
1022
1023 static void send_job_entry(GtkWidget *w, gpointer data)
1024 {
1025         struct gui *ui = (struct gui *) data;
1026         struct gui_entry *ge;
1027
1028         ge = get_ge_from_cur_tab(ui);
1029         if (ge)
1030                 send_clicked(w, ge);
1031 }
1032
1033 static void edit_job_entry(GtkWidget *w, gpointer data)
1034 {
1035         struct gui *ui = (struct gui *) data;
1036         struct gui_entry *ge;
1037
1038         ge = get_ge_from_cur_tab(ui);
1039         if (ge && ge->client)
1040                 gopt_get_options_window(ui->window, ge->client);
1041 }
1042
1043 static void start_job_entry(GtkWidget *w, gpointer data)
1044 {
1045         struct gui *ui = (struct gui *) data;
1046         struct gui_entry *ge;
1047
1048         ge = get_ge_from_cur_tab(ui);
1049         if (ge)
1050                 start_job_clicked(w, ge);
1051 }
1052
1053 static void view_results(GtkWidget *w, gpointer data)
1054 {
1055         struct gui *ui = (struct gui *) data;
1056         struct gfio_client *gc;
1057         struct gui_entry *ge;
1058
1059         ge = get_ge_from_cur_tab(ui);
1060         if (!ge)
1061                 return;
1062
1063         if (ge->results_window)
1064                 return;
1065
1066         gc = ge->client;
1067         if (gc && gc->nr_results)
1068                 gfio_display_end_results(gc);
1069 }
1070
1071 static void __update_graph_settings(struct gfio_graphs *g)
1072 {
1073         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1074         graph_set_font(g->iops_graph, gfio_graph_font);
1075         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1076         graph_set_font(g->bandwidth_graph, gfio_graph_font);
1077 }
1078
1079 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
1080 {
1081         struct gui_entry *ge = (struct gui_entry *) value;
1082         GdkEvent *ev;
1083
1084         __update_graph_settings(&ge->graphs);
1085
1086         ev = gdk_event_new(GDK_EXPOSE);
1087         g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), "expose_event", GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
1088         gdk_event_free(ev);
1089 }
1090
1091 static void update_graph_limits(void)
1092 {
1093         struct gui *ui = &main_ui;
1094         GdkEvent *ev;
1095
1096         __update_graph_settings(&ui->graphs);
1097
1098         ev = gdk_event_new(GDK_EXPOSE);
1099         g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), "expose_event", GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
1100         gdk_event_free(ev);
1101
1102         g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
1103 }
1104
1105 static void preferences(GtkWidget *w, gpointer data)
1106 {
1107         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1108         GtkWidget *hbox, *spin, *entry, *spin_int;
1109         struct gui *ui = (struct gui *) data;
1110         int i;
1111
1112         dialog = gtk_dialog_new_with_buttons("Preferences",
1113                 GTK_WINDOW(ui->window),
1114                 GTK_DIALOG_DESTROY_WITH_PARENT,
1115                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1116                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1117                 NULL);
1118
1119         frame = gtk_frame_new("Graphing");
1120         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1121         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1122         vbox = gtk_vbox_new(FALSE, 6);
1123         gtk_container_add(GTK_CONTAINER(frame), vbox);
1124
1125         hbox = gtk_hbox_new(FALSE, 5);
1126         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1127         entry = gtk_label_new("Font face to use for graph labels");
1128         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1129
1130         font = gtk_font_button_new_with_font(gfio_graph_font);
1131         gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
1132
1133         box = gtk_vbox_new(FALSE, 6);
1134         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1135
1136         hbox = gtk_hbox_new(FALSE, 5);
1137         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1138         entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1139         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1140
1141         spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
1142
1143         box = gtk_vbox_new(FALSE, 6);
1144         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1145
1146         hbox = gtk_hbox_new(FALSE, 5);
1147         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1148         entry = gtk_label_new("Client ETA request interval (msec)");
1149         gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1150
1151         spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
1152         frame = gtk_frame_new("Debug logging");
1153         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1154         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1155         vbox = gtk_vbox_new(FALSE, 6);
1156         gtk_container_add(GTK_CONTAINER(frame), vbox);
1157
1158         box = gtk_hbox_new(FALSE, 6);
1159         gtk_container_add(GTK_CONTAINER(vbox), box);
1160
1161         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1162
1163         for (i = 0; i < FD_DEBUG_MAX; i++) {
1164                 if (i == 7) {
1165                         box = gtk_hbox_new(FALSE, 6);
1166                         gtk_container_add(GTK_CONTAINER(vbox), box);
1167                 }
1168
1169
1170                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1171                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1172                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1173         }
1174
1175         gtk_widget_show_all(dialog);
1176
1177         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1178                 gtk_widget_destroy(dialog);
1179                 return;
1180         }
1181
1182         for (i = 0; i < FD_DEBUG_MAX; i++) {
1183                 int set;
1184
1185                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1186                 if (set)
1187                         fio_debug |= (1UL << i);
1188         }
1189
1190         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1191         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1192         update_graph_limits();
1193         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1194
1195         gtk_widget_destroy(dialog);
1196 }
1197
1198 static void about_dialog(GtkWidget *w, gpointer data)
1199 {
1200         const char *authors[] = {
1201                 "Jens Axboe <axboe@kernel.dk>",
1202                 "Stephen Carmeron <stephenmcameron@gmail.com>",
1203                 NULL
1204         };
1205         const char *license[] = {
1206                 "Fio is free software; you can redistribute it and/or modify "
1207                 "it under the terms of the GNU General Public License as published by "
1208                 "the Free Software Foundation; either version 2 of the License, or "
1209                 "(at your option) any later version.\n",
1210                 "Fio is distributed in the hope that it will be useful, "
1211                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1212                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
1213                 "GNU General Public License for more details.\n",
1214                 "You should have received a copy of the GNU General Public License "
1215                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1216                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
1217         };
1218         char *license_trans;
1219
1220         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1221                                      license[2], "\n", NULL);
1222
1223         gtk_show_about_dialog(NULL,
1224                 "program-name", "gfio",
1225                 "comments", "Gtk2 UI for fio",
1226                 "license", license_trans,
1227                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1228                 "authors", authors,
1229                 "version", fio_version_string,
1230                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1231                 "logo-icon-name", "fio",
1232                 /* Must be last: */
1233                 "wrap-license", TRUE,
1234                 NULL);
1235
1236         g_free(license_trans);
1237 }
1238
1239 static GtkActionEntry menu_items[] = {
1240         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1241         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1242         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1243         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1244         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1245         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1246         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
1247         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
1248         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1249         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1250         { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1251         { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1252         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1253         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1254         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1255         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1256         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
1257 };
1258 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1259
1260 static const gchar *ui_string = " \
1261         <ui> \
1262                 <menubar name=\"MainMenu\"> \
1263                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1264                                 <menuitem name=\"New\" action=\"NewFile\" /> \
1265                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1266                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1267                                 <separator name=\"Separator1\"/> \
1268                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1269                                 <separator name=\"Separator2\"/> \
1270                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1271                                 <separator name=\"Separator3\"/> \
1272                                 <placeholder name=\"FileRecentFiles\"/> \
1273                                 <separator name=\"Separator4\"/> \
1274                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
1275                         </menu> \
1276                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1277                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1278                                 <separator name=\"Separator5\"/> \
1279                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1280                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1281                                 <separator name=\"Separator6\"/> \
1282                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1283                         </menu>\
1284                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1285                                 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1286                                 <separator name=\"Separator7\"/> \
1287                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1288                         </menu>\
1289                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
1290                                 <menuitem name=\"About\" action=\"About\" /> \
1291                         </menu> \
1292                 </menubar> \
1293         </ui> \
1294 ";
1295
1296 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1297                                    struct gui *ui)
1298 {
1299         GtkActionGroup *action_group;
1300         GError *error = 0;
1301
1302         action_group = gtk_action_group_new("Menu");
1303         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1304
1305         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1306         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1307
1308         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1309
1310         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1311 }
1312
1313 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1314                    GtkWidget *vbox, GtkUIManager *ui_manager)
1315 {
1316         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1317 }
1318
1319 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1320 {
1321         struct gui_entry *ge = (struct gui_entry *) data;
1322         gint index;
1323
1324         index = gtk_combo_box_get_active(box);
1325
1326         multitext_set_entry(&ge->eta.iotype, index);
1327         multitext_set_entry(&ge->eta.bs, index);
1328         multitext_set_entry(&ge->eta.ioengine, index);
1329         multitext_set_entry(&ge->eta.iodepth, index);
1330 }
1331
1332 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1333 {
1334         struct gui_entry *ge = (struct gui_entry *) data;
1335
1336         multitext_free(&ge->eta.iotype);
1337         multitext_free(&ge->eta.bs);
1338         multitext_free(&ge->eta.ioengine);
1339         multitext_free(&ge->eta.iodepth);
1340 }
1341
1342 static GtkWidget *new_client_page(struct gui_entry *ge)
1343 {
1344         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1345         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1346
1347         main_vbox = gtk_vbox_new(FALSE, 3);
1348
1349         top_align = gtk_alignment_new(0, 0, 1, 0);
1350         top_vbox = gtk_vbox_new(FALSE, 3);
1351         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1352         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1353
1354         probe = gtk_frame_new("Job");
1355         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1356         probe_frame = gtk_vbox_new(FALSE, 3);
1357         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1358
1359         probe_box = gtk_hbox_new(FALSE, 3);
1360         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1361         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1362         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1363         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1364         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1365
1366         probe_box = gtk_hbox_new(FALSE, 3);
1367         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1368
1369         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1370         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1371         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1372         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1373         ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1374         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1375         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1376         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1377         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1378
1379         probe_box = gtk_hbox_new(FALSE, 3);
1380         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1381         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1382         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1383         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1384         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1385
1386         /*
1387          * Only add this if we have a commit rate
1388          */
1389 #if 0
1390         probe_box = gtk_hbox_new(FALSE, 3);
1391         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1392
1393         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1394         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1395
1396         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1397         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1398 #endif
1399
1400         /*
1401          * Set up a drawing area and IOPS and bandwidth graphs
1402          */
1403         ge->graphs.drawing_area = gtk_drawing_area_new();
1404         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1405                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1406         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1407         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1408                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1409         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1410                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1411         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1412         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1413                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1414         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1415                                         ge->graphs.drawing_area);
1416         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1417
1418         setup_graphs(&ge->graphs);
1419
1420         /*
1421          * Set up alignments for widgets at the bottom of ui, 
1422          * align bottom left, expand horizontally but not vertically
1423          */
1424         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1425         ge->buttonbox = gtk_hbox_new(FALSE, 0);
1426         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1427         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1428
1429         add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
1430
1431         /*
1432          * Set up thread status progress bar
1433          */
1434         ge->thread_status_pb = gtk_progress_bar_new();
1435         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1436         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1437         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1438
1439
1440         return main_vbox;
1441 }
1442
1443 static GtkWidget *new_main_page(struct gui *ui)
1444 {
1445         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1446         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1447
1448         main_vbox = gtk_vbox_new(FALSE, 3);
1449
1450         /*
1451          * Set up alignments for widgets at the top of ui,
1452          * align top left, expand horizontally but not vertically
1453          */
1454         top_align = gtk_alignment_new(0, 0, 1, 0);
1455         top_vbox = gtk_vbox_new(FALSE, 0);
1456         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1457         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1458
1459         probe = gtk_frame_new("Run statistics");
1460         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1461         probe_frame = gtk_vbox_new(FALSE, 3);
1462         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1463
1464         probe_box = gtk_hbox_new(FALSE, 3);
1465         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1466         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1467         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1468         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1469         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1470         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1471
1472         /*
1473          * Only add this if we have a commit rate
1474          */
1475 #if 0
1476         probe_box = gtk_hbox_new(FALSE, 3);
1477         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1478
1479         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1480         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1481
1482         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1483         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1484 #endif
1485
1486         /*
1487          * Set up a drawing area and IOPS and bandwidth graphs
1488          */
1489         ui->graphs.drawing_area = gtk_drawing_area_new();
1490         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1491                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1492         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1493         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1494                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1495         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1496                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
1497         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1498         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1499                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1500         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1501                                         ui->graphs.drawing_area);
1502         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1503                         TRUE, TRUE, 0);
1504
1505         setup_graphs(&ui->graphs);
1506
1507         /*
1508          * Set up alignments for widgets at the bottom of ui, 
1509          * align bottom left, expand horizontally but not vertically
1510          */
1511         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1512         ui->buttonbox = gtk_hbox_new(FALSE, 0);
1513         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1514         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1515
1516         /*
1517          * Set up thread status progress bar
1518          */
1519         ui->thread_status_pb = gtk_progress_bar_new();
1520         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1521         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1522         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1523
1524         return main_vbox;
1525 }
1526
1527 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1528                                      guint page, gpointer data)
1529
1530 {
1531         struct gui *ui = (struct gui *) data;
1532         struct gui_entry *ge;
1533
1534         if (!page) {
1535                 set_job_menu_visible(ui, 0);
1536                 set_view_results_visible(ui, 0);
1537                 return TRUE;
1538         }
1539
1540         set_job_menu_visible(ui, 1);
1541         ge = get_ge_from_page(ui, page, NULL);
1542         if (ge)
1543                 update_button_states(ui, ge);
1544
1545         return TRUE;
1546 }
1547
1548 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1549 {
1550         time_t time_a = gtk_recent_info_get_visited(a);
1551         time_t time_b = gtk_recent_info_get_visited(b);
1552
1553         return time_b - time_a;
1554 }
1555
1556 static void add_recent_file_items(struct gui *ui)
1557 {
1558         const gchar *gfio = g_get_application_name();
1559         GList *items, *item;
1560         int i = 0;
1561
1562         if (ui->recent_ui_id) {
1563                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1564                 gtk_ui_manager_ensure_update(ui->uimanager);
1565         }
1566         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1567
1568         if (ui->actiongroup) {
1569                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1570                 g_object_unref(ui->actiongroup);
1571         }
1572         ui->actiongroup = gtk_action_group_new("RecentFileActions");
1573
1574         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1575
1576         items = gtk_recent_manager_get_items(ui->recentmanager);
1577         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1578
1579         for (item = items; item && item->data; item = g_list_next(item)) {
1580                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1581                 gchar *action_name;
1582                 const gchar *label;
1583                 GtkAction *action;
1584
1585                 if (!gtk_recent_info_has_application(info, gfio))
1586                         continue;
1587
1588                 /*
1589                  * We only support local files for now
1590                  */
1591                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1592                         continue;
1593
1594                 action_name = g_strdup_printf("RecentFile%u", i++);
1595                 label = gtk_recent_info_get_display_name(info);
1596
1597                 action = g_object_new(GTK_TYPE_ACTION,
1598                                         "name", action_name,
1599                                         "label", label, NULL);
1600
1601                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1602                                         gtk_recent_info_ref(info),
1603                                         (GDestroyNotify) gtk_recent_info_unref);
1604
1605
1606                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1607
1608                 gtk_action_group_add_action(ui->actiongroup, action);
1609                 g_object_unref(action);
1610
1611                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1612                                         "/MainMenu/FileMenu/FileRecentFiles",
1613                                         label, action_name,
1614                                         GTK_UI_MANAGER_MENUITEM, FALSE);
1615
1616                 g_free(action_name);
1617
1618                 if (i == 8)
1619                         break;
1620         }
1621
1622         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1623         g_list_free(items);
1624 }
1625
1626 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1627                                    gint x, gint y, GtkSelectionData *seldata,
1628                                    guint info, guint time, gpointer *data)
1629 {
1630         struct gui *ui = (struct gui *) data;
1631         gchar **uris;
1632         GtkWidget *source;
1633
1634         source = gtk_drag_get_source_widget(ctx);
1635         if (source && widget == gtk_widget_get_toplevel(source)) {
1636                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1637                 return;
1638         }
1639
1640         uris = gtk_selection_data_get_uris(seldata);
1641         if (!uris) {
1642                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1643                 return;
1644         }
1645
1646         if (uris[0])
1647                 do_file_open_with_tab(ui, uris[0]);
1648
1649         gtk_drag_finish(ctx, TRUE, FALSE, time);
1650         g_strfreev(uris);
1651 }
1652
1653 static void init_ui(int *argc, char **argv[], struct gui *ui)
1654 {
1655         GtkSettings *settings;
1656         GtkWidget *vbox;
1657
1658         /* Magical g*thread incantation, you just need this thread stuff.
1659          * Without it, the update that happens in gfio_update_thread_status
1660          * doesn't really happen in a timely fashion, you need expose events
1661          */
1662         if (!g_thread_supported())
1663                 g_thread_init(NULL);
1664         gdk_threads_init();
1665
1666         gtk_init(argc, argv);
1667         settings = gtk_settings_get_default();
1668         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1669         g_type_init();
1670         gdk_color_parse("white", &gfio_color_white);
1671         
1672         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1673         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1674         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1675
1676         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1677         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1678
1679         ui->vbox = gtk_vbox_new(FALSE, 0);
1680         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1681
1682         ui->uimanager = gtk_ui_manager_new();
1683         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1684         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1685
1686         ui->recentmanager = gtk_recent_manager_get_default();
1687         add_recent_file_items(ui);
1688
1689         ui->notebook = gtk_notebook_new();
1690         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1691         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1692         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1693         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1694
1695         vbox = new_main_page(ui);
1696         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1697         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1698         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1699
1700         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1701
1702         gfio_ui_setup_log(ui);
1703
1704         gtk_widget_show_all(ui->window);
1705 }
1706
1707 int main(int argc, char *argv[], char *envp[])
1708 {
1709         if (initialize_fio(envp))
1710                 return 1;
1711         if (fio_init_options())
1712                 return 1;
1713
1714         gopt_init();
1715
1716         memset(&main_ui, 0, sizeof(main_ui));
1717         main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1718
1719         init_ui(&argc, &argv, &main_ui);
1720
1721         gdk_threads_enter();
1722         gtk_main();
1723         gdk_threads_leave();
1724
1725         g_hash_table_destroy(main_ui.ge_hash);
1726
1727         gopt_exit();
1728         return 0;
1729 }