gfio: add per-job option view
[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_get_active_text(GTK_COMBO_BOX(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         /* gtk_dialog_get_content_area() is 2.14 and newer */
542         vbox = GTK_DIALOG(dialog)->vbox;
543         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
544
545         box = gtk_vbox_new(FALSE, 6);
546         gtk_container_add(GTK_CONTAINER(frame), box);
547
548         hbox = gtk_hbox_new(TRUE, 10);
549         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
550         cw.hentry = gtk_entry_new();
551         gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
552         gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
553
554         frame = gtk_frame_new("Port");
555         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
556         box = gtk_vbox_new(FALSE, 10);
557         gtk_container_add(GTK_CONTAINER(frame), box);
558
559         hbox = gtk_hbox_new(TRUE, 4);
560         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
561         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
562
563         frame = gtk_frame_new("Type");
564         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
565         box = gtk_vbox_new(FALSE, 10);
566         gtk_container_add(GTK_CONTAINER(frame), box);
567
568         hbox = gtk_hbox_new(TRUE, 4);
569         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
570
571         cw.combo = gtk_combo_box_new_text();
572         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
573         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
574         gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
575         gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
576
577         gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
578
579         frame = gtk_frame_new("Options");
580         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
581         box = gtk_vbox_new(FALSE, 10);
582         gtk_container_add(GTK_CONTAINER(frame), box);
583
584         hbox = gtk_hbox_new(TRUE, 4);
585         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
586
587         cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
588         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
589         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.");
590         gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
591
592         /*
593          * Connect edit signal, so we can show/not-show the auto start button
594          */
595         g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
596         g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
597
598         gtk_widget_show_all(dialog);
599
600         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
601                 gtk_widget_destroy(dialog);
602                 return 1;
603         }
604
605         ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
606         ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
607
608         typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
609         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
610                 ge->type = Fio_client_ipv4;
611         else if (!strncmp(typeentry, "IPv6", 4))
612                 ge->type = Fio_client_ipv6;
613         else
614                 ge->type = Fio_client_socket;
615         g_free(typeentry);
616
617         ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
618
619         gtk_widget_destroy(dialog);
620         return 0;
621 }
622
623 static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
624 {
625         gc->client = fio_get_client(client);
626         client->client_data = gc;
627 }
628
629 static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
630 {
631         struct gfio_client_options *gco;
632         struct gfio_client *gc;
633
634         gc = calloc(1, sizeof(*gc));
635         INIT_FLIST_HEAD(&gc->o_list);
636         gc->ge = ge;
637         ge->client = gc;
638         gfio_set_client(gc, client);
639
640         /*
641          * Just add a default set of options, need to consider how best
642          * to handle this
643          */
644         gco = calloc(1, sizeof(*gco));
645         INIT_FLIST_HEAD(&gco->list);
646         options_default_fill(&gco->o);
647         flist_add_tail(&gco->list, &gc->o_list);
648         gc->o_list_nr++;
649 }
650
651 static void connect_clicked(GtkWidget *widget, gpointer data)
652 {
653         struct gui_entry *ge = data;
654         struct gfio_client *gc = ge->client;
655
656         if (ge->state == GE_STATE_NEW) {
657                 int ret;
658
659                 if (!ge->job_file)
660                         file_open(widget, ge->ui);
661                 if (!ge->job_file)
662                         return;
663
664                 gc = ge->client;
665
666                 if (!gc->client) {
667                         struct fio_client *client;
668
669                         if (get_connection_details(ge)) {
670                                 gfio_report_error(ge, "Failed to get connection details\n");
671                                 return;
672                         }
673
674                         client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
675                         if (!client) {
676                                 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
677                                 free(ge->host);
678                                 ge->host = NULL;
679                                 return;
680                         }
681                         gfio_set_client(gc, client);
682                 }
683
684                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
685                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
686                 ret = fio_client_connect(gc->client);
687                 if (!ret) {
688                         if (!ge->ui->handler_running)
689                                 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
690                         gfio_set_state(ge, GE_STATE_CONNECTED);
691                 } else {
692                         gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
693                 }
694         } else {
695                 fio_client_terminate(gc->client);
696                 gfio_set_state(ge, GE_STATE_NEW);
697                 clear_ge_ui_info(ge);
698         }
699 }
700
701 static void send_clicked(GtkWidget *widget, gpointer data)
702 {
703         struct gui_entry *ge = data;
704
705         if (send_job_file(ge))
706                 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
707 }
708
709 static GtkWidget *new_client_page(struct gui_entry *ge);
710
711 static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
712 {
713         struct gui_entry *ge;
714
715         ge = malloc(sizeof(*ge));
716         memset(ge, 0, sizeof(*ge));
717         ge->state = GE_STATE_NEW;
718         ge->ui = ui;
719         return ge;
720 }
721
722 static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
723 {
724         struct gui_entry *ge;
725
726         ge = alloc_new_gui_entry(ui);
727
728         ge->vbox = new_client_page(ge);
729         g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
730
731         ge->page_label = gtk_label_new(name);
732         ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
733
734         g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
735
736         gtk_widget_show_all(ui->window);
737         return ge;
738 }
739
740 static void file_new(GtkWidget *w, gpointer data)
741 {
742         struct gui *ui = (struct gui *) data;
743         struct gui_entry *ge;
744
745         ge = get_new_ge_with_tab(ui, "Untitled");
746         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
747 }
748
749 /*
750  * Return the 'ge' corresponding to the tab. If the active tab is the
751  * main tab, open a new tab.
752  */
753 static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
754                                           int *created)
755 {
756         if (!cur_page) {
757                 if (created)
758                         *created = 1;
759                 return get_new_ge_with_tab(ui, "Untitled");
760         }
761
762         if (created)
763                 *created = 0;
764
765         return g_hash_table_lookup(ui->ge_hash, &cur_page);
766 }
767
768 static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
769 {
770         gint cur_page;
771
772         /*
773          * Main tab is tab 0, so any current page other than 0 holds
774          * a ge entry.
775          */
776         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
777         if (cur_page)
778                 return get_ge_from_page(ui, cur_page, NULL);
779
780         return NULL;
781 }
782
783 static void file_close(GtkWidget *w, gpointer data)
784 {
785         struct gui *ui = (struct gui *) data;
786         struct gui_entry *ge;
787
788         /*
789          * Can't close the main tab
790          */
791         ge = get_ge_from_cur_tab(ui);
792         if (ge) {
793                 gtk_widget_destroy(ge->vbox);
794                 return;
795         }
796
797         if (g_hash_table_size(ui->ge_hash)) {
798                 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
799                 return;
800         }
801
802         gfio_quit(ui);
803 }
804
805 static void file_add_recent(struct gui *ui, const gchar *uri)
806 {
807         GtkRecentData grd;
808
809         memset(&grd, 0, sizeof(grd));
810         grd.display_name = strdup("gfio");
811         grd.description = strdup("Fio job file");
812         grd.mime_type = strdup(GFIO_MIME);
813         grd.app_name = strdup(g_get_application_name());
814         grd.app_exec = strdup("gfio %f/%u");
815
816         gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
817 }
818
819 static gchar *get_filename_from_uri(const gchar *uri)
820 {
821         if (strncmp(uri, "file://", 7))
822                 return strdup(uri);
823
824         return strdup(uri + 7);
825 }
826
827 static int do_file_open(struct gui_entry *ge, const gchar *uri)
828 {
829         struct fio_client *client;
830
831         assert(!ge->job_file);
832
833         ge->job_file = get_filename_from_uri(uri);
834
835         client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
836         if (client) {
837                 char *label = strdup(uri);
838
839                 basename(label);
840                 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
841                 free(label);
842
843                 gfio_client_added(ge, client);
844                 file_add_recent(ge->ui, uri);
845                 return 0;
846         }
847
848         gfio_report_error(ge, "Failed to add client %s\n", ge->host);
849         free(ge->host);
850         ge->host = NULL;
851         free(ge->job_file);
852         ge->job_file = NULL;
853         return 1;
854 }
855
856 static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
857 {
858         struct gui_entry *ge;
859         gint cur_page;
860         int ret, ge_is_new = 0;
861
862         /*
863          * Creates new tab if current tab is the main window, or the
864          * current tab already has a client.
865          */
866         cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
867         ge = get_ge_from_page(ui, cur_page, &ge_is_new);
868         if (ge->client) {
869                 ge = get_new_ge_with_tab(ui, "Untitled");
870                 ge_is_new = 1;
871         }
872
873         gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
874
875         if (get_connection_details(ge)) {
876                 if (ge_is_new)
877                         gtk_widget_destroy(ge->vbox);
878                         
879                 return 1;
880         }
881
882         ret = do_file_open(ge, uri);
883
884         if (!ret) {
885                 if (ge->server_start)
886                         gfio_start_server(ui);
887         } else {
888                 if (ge_is_new)
889                         gtk_widget_destroy(ge->vbox);
890         }
891
892         return ret;
893 }
894
895 static void recent_open(GtkAction *action, gpointer data)
896 {
897         struct gui *ui = (struct gui *) data;
898         GtkRecentInfo *info;
899         const gchar *uri;
900
901         info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
902         uri = gtk_recent_info_get_uri(info);
903
904         do_file_open_with_tab(ui, uri);
905 }
906
907 static void file_open(GtkWidget *w, gpointer data)
908 {
909         struct gui *ui = data;
910         GtkWidget *dialog;
911         GtkFileFilter *filter;
912         gchar *filename;
913
914         dialog = gtk_file_chooser_dialog_new("Open File",
915                 GTK_WINDOW(ui->window),
916                 GTK_FILE_CHOOSER_ACTION_OPEN,
917                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
918                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
919                 NULL);
920         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
921
922         filter = gtk_file_filter_new();
923         gtk_file_filter_add_pattern(filter, "*.fio");
924         gtk_file_filter_add_pattern(filter, "*.job");
925         gtk_file_filter_add_pattern(filter, "*.ini");
926         gtk_file_filter_add_mime_type(filter, GFIO_MIME);
927         gtk_file_filter_set_name(filter, "Fio job file");
928         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
929
930         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
931                 gtk_widget_destroy(dialog);
932                 return;
933         }
934
935         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
936
937         gtk_widget_destroy(dialog);
938
939         do_file_open_with_tab(ui, filename);
940         g_free(filename);
941 }
942
943 static void file_save(GtkWidget *w, gpointer data)
944 {
945         struct gui *ui = data;
946         GtkWidget *dialog;
947
948         dialog = gtk_file_chooser_dialog_new("Save File",
949                 GTK_WINDOW(ui->window),
950                 GTK_FILE_CHOOSER_ACTION_SAVE,
951                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
952                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
953                 NULL);
954
955         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
956         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
957
958         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
959                 char *filename;
960
961                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
962                 // save_job_file(filename);
963                 g_free(filename);
964         }
965         gtk_widget_destroy(dialog);
966 }
967
968 static void view_log_destroy(GtkWidget *w, gpointer data)
969 {
970         struct gui *ui = (struct gui *) data;
971
972         gtk_widget_ref(ui->log_tree);
973         gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
974         gtk_widget_destroy(w);
975         ui->log_view = NULL;
976 }
977
978 void gfio_view_log(struct gui *ui)
979 {
980         GtkWidget *win, *scroll, *vbox, *box;
981
982         if (ui->log_view)
983                 return;
984
985         ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
986         gtk_window_set_title(GTK_WINDOW(win), "Log");
987         gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
988
989         scroll = gtk_scrolled_window_new(NULL, NULL);
990
991         gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
992
993         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
994
995         box = gtk_hbox_new(TRUE, 0);
996         gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
997         g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
998         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
999
1000         vbox = gtk_vbox_new(TRUE, 5);
1001         gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
1002
1003         gtk_container_add(GTK_CONTAINER(win), vbox);
1004         gtk_widget_show_all(win);
1005 }
1006
1007 static void view_log(GtkWidget *w, gpointer data)
1008 {
1009         struct gui *ui = (struct gui *) data;
1010
1011         gfio_view_log(ui);
1012 }
1013
1014 static void connect_job_entry(GtkWidget *w, gpointer data)
1015 {
1016         struct gui *ui = (struct gui *) data;
1017         struct gui_entry *ge;
1018         
1019         ge = get_ge_from_cur_tab(ui);
1020         if (ge)
1021                 connect_clicked(w, ge);
1022 }
1023
1024 static void send_job_entry(GtkWidget *w, gpointer data)
1025 {
1026         struct gui *ui = (struct gui *) data;
1027         struct gui_entry *ge;
1028
1029         ge = get_ge_from_cur_tab(ui);
1030         if (ge)
1031                 send_clicked(w, ge);
1032 }
1033
1034 static void edit_job_entry(GtkWidget *w, gpointer data)
1035 {
1036         struct gui *ui = (struct gui *) data;
1037         struct gui_entry *ge;
1038
1039         ge = get_ge_from_cur_tab(ui);
1040         if (ge && ge->client)
1041                 gopt_get_options_window(ui->window, ge->client);
1042 }
1043
1044 static void start_job_entry(GtkWidget *w, gpointer data)
1045 {
1046         struct gui *ui = (struct gui *) data;
1047         struct gui_entry *ge;
1048
1049         ge = get_ge_from_cur_tab(ui);
1050         if (ge)
1051                 start_job_clicked(w, ge);
1052 }
1053
1054 static void view_results(GtkWidget *w, gpointer data)
1055 {
1056         struct gui *ui = (struct gui *) data;
1057         struct gfio_client *gc;
1058         struct gui_entry *ge;
1059
1060         ge = get_ge_from_cur_tab(ui);
1061         if (!ge)
1062                 return;
1063
1064         if (ge->results_window)
1065                 return;
1066
1067         gc = ge->client;
1068         if (gc && gc->nr_results)
1069                 gfio_display_end_results(gc);
1070 }
1071
1072 static void __update_graph_settings(struct gfio_graphs *g)
1073 {
1074         line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1075         graph_set_font(g->iops_graph, gfio_graph_font);
1076         line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1077         graph_set_font(g->bandwidth_graph, gfio_graph_font);
1078 }
1079
1080 static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
1081 {
1082         struct gui_entry *ge = (struct gui_entry *) value;
1083         GdkEvent *ev;
1084
1085         __update_graph_settings(&ge->graphs);
1086
1087         ev = gdk_event_new(GDK_EXPOSE);
1088         g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), "expose_event", GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
1089         gdk_event_free(ev);
1090 }
1091
1092 static void update_graph_limits(void)
1093 {
1094         struct gui *ui = &main_ui;
1095         GdkEvent *ev;
1096
1097         __update_graph_settings(&ui->graphs);
1098
1099         ev = gdk_event_new(GDK_EXPOSE);
1100         g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), "expose_event", GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
1101         gdk_event_free(ev);
1102
1103         g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
1104 }
1105
1106 static void preferences(GtkWidget *w, gpointer data)
1107 {
1108         GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1109         GtkWidget *hbox, *spin, *entry, *spin_int;
1110         struct gui *ui = (struct gui *) data;
1111         int i;
1112
1113         dialog = gtk_dialog_new_with_buttons("Preferences",
1114                 GTK_WINDOW(ui->window),
1115                 GTK_DIALOG_DESTROY_WITH_PARENT,
1116                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1117                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1118                 NULL);
1119
1120         frame = gtk_frame_new("Graphing");
1121         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->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         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1154         vbox = gtk_vbox_new(FALSE, 6);
1155         gtk_container_add(GTK_CONTAINER(frame), vbox);
1156
1157         box = gtk_hbox_new(FALSE, 6);
1158         gtk_container_add(GTK_CONTAINER(vbox), box);
1159
1160         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1161
1162         for (i = 0; i < FD_DEBUG_MAX; i++) {
1163                 if (i == 7) {
1164                         box = gtk_hbox_new(FALSE, 6);
1165                         gtk_container_add(GTK_CONTAINER(vbox), box);
1166                 }
1167
1168
1169                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1170                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1171                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1172         }
1173
1174         gtk_widget_show_all(dialog);
1175
1176         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1177                 gtk_widget_destroy(dialog);
1178                 return;
1179         }
1180
1181         for (i = 0; i < FD_DEBUG_MAX; i++) {
1182                 int set;
1183
1184                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1185                 if (set)
1186                         fio_debug |= (1UL << i);
1187         }
1188
1189         gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
1190         gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1191         update_graph_limits();
1192         gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
1193
1194         gtk_widget_destroy(dialog);
1195 }
1196
1197 static void about_dialog(GtkWidget *w, gpointer data)
1198 {
1199         const char *authors[] = {
1200                 "Jens Axboe <axboe@kernel.dk>",
1201                 "Stephen Carmeron <stephenmcameron@gmail.com>",
1202                 NULL
1203         };
1204         const char *license[] = {
1205                 "Fio is free software; you can redistribute it and/or modify "
1206                 "it under the terms of the GNU General Public License as published by "
1207                 "the Free Software Foundation; either version 2 of the License, or "
1208                 "(at your option) any later version.\n",
1209                 "Fio is distributed in the hope that it will be useful, "
1210                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1211                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the "
1212                 "GNU General Public License for more details.\n",
1213                 "You should have received a copy of the GNU General Public License "
1214                 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1215                 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA\n"
1216         };
1217         char *license_trans;
1218
1219         license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1220                                      license[2], "\n", NULL);
1221
1222         gtk_show_about_dialog(NULL,
1223                 "program-name", "gfio",
1224                 "comments", "Gtk2 UI for fio",
1225                 "license", license_trans,
1226                 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1227                 "authors", authors,
1228                 "version", fio_version_string,
1229                 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
1230                 "logo-icon-name", "fio",
1231                 /* Must be last: */
1232                 "wrap-license", TRUE,
1233                 NULL);
1234
1235         g_free(license_trans);
1236 }
1237
1238 static GtkActionEntry menu_items[] = {
1239         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1240         { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
1241         { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
1242         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1243         { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
1244         { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
1245         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
1246         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
1247         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1248         { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
1249         { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
1250         { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
1251         { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1252         { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1253         { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
1254         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1255         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
1256 };
1257 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1258
1259 static const gchar *ui_string = " \
1260         <ui> \
1261                 <menubar name=\"MainMenu\"> \
1262                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1263                                 <menuitem name=\"New\" action=\"NewFile\" /> \
1264                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1265                                 <menuitem name=\"Close\" action=\"CloseFile\" /> \
1266                                 <separator name=\"Separator1\"/> \
1267                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1268                                 <separator name=\"Separator2\"/> \
1269                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1270                                 <separator name=\"Separator3\"/> \
1271                                 <placeholder name=\"FileRecentFiles\"/> \
1272                                 <separator name=\"Separator4\"/> \
1273                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
1274                         </menu> \
1275                         <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
1276                                 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
1277                                 <separator name=\"Separator5\"/> \
1278                                 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1279                                 <menuitem name=\"Send job\" action=\"SendJob\" /> \
1280                                 <separator name=\"Separator6\"/> \
1281                                 <menuitem name=\"Start job\" action=\"StartJob\" /> \
1282                         </menu>\
1283                         <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
1284                                 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1285                                 <separator name=\"Separator7\"/> \
1286                                 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1287                         </menu>\
1288                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
1289                                 <menuitem name=\"About\" action=\"About\" /> \
1290                         </menu> \
1291                 </menubar> \
1292         </ui> \
1293 ";
1294
1295 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1296                                    struct gui *ui)
1297 {
1298         GtkActionGroup *action_group;
1299         GError *error = 0;
1300
1301         action_group = gtk_action_group_new("Menu");
1302         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
1303
1304         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1305         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1306
1307         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1308
1309         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1310 }
1311
1312 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1313                    GtkWidget *vbox, GtkUIManager *ui_manager)
1314 {
1315         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1316 }
1317
1318 static void combo_entry_changed(GtkComboBox *box, gpointer data)
1319 {
1320         struct gui_entry *ge = (struct gui_entry *) data;
1321         gint index;
1322
1323         index = gtk_combo_box_get_active(box);
1324
1325         multitext_set_entry(&ge->eta.iotype, index);
1326         multitext_set_entry(&ge->eta.bs, index);
1327         multitext_set_entry(&ge->eta.ioengine, index);
1328         multitext_set_entry(&ge->eta.iodepth, index);
1329 }
1330
1331 static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1332 {
1333         struct gui_entry *ge = (struct gui_entry *) data;
1334
1335         multitext_free(&ge->eta.iotype);
1336         multitext_free(&ge->eta.bs);
1337         multitext_free(&ge->eta.ioengine);
1338         multitext_free(&ge->eta.iodepth);
1339 }
1340
1341 static GtkWidget *new_client_page(struct gui_entry *ge)
1342 {
1343         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1344         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1345
1346         main_vbox = gtk_vbox_new(FALSE, 3);
1347
1348         top_align = gtk_alignment_new(0, 0, 1, 0);
1349         top_vbox = gtk_vbox_new(FALSE, 3);
1350         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1351         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1352
1353         probe = gtk_frame_new("Job");
1354         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1355         probe_frame = gtk_vbox_new(FALSE, 3);
1356         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1357
1358         probe_box = gtk_hbox_new(FALSE, 3);
1359         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1360         ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1361         ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1362         ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1363         ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1364
1365         probe_box = gtk_hbox_new(FALSE, 3);
1366         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1367
1368         ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
1369         g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1370         g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1371         ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
1372         ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
1373         ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1374         ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
1375         ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1376         ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1377
1378         probe_box = gtk_hbox_new(FALSE, 3);
1379         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1380         ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1381         ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1382         ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1383         ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1384
1385         /*
1386          * Only add this if we have a commit rate
1387          */
1388 #if 0
1389         probe_box = gtk_hbox_new(FALSE, 3);
1390         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1391
1392         ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1393         ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1394
1395         ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1396         ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1397 #endif
1398
1399         /*
1400          * Set up a drawing area and IOPS and bandwidth graphs
1401          */
1402         ge->graphs.drawing_area = gtk_drawing_area_new();
1403         gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
1404                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1405         gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1406         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1407                                 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1408         g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1409                                 G_CALLBACK(on_config_drawing_area), &ge->graphs);
1410         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1411         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1412                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1413         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1414                                         ge->graphs.drawing_area);
1415         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
1416
1417         setup_graphs(&ge->graphs);
1418
1419         /*
1420          * Set up alignments for widgets at the bottom of ui, 
1421          * align bottom left, expand horizontally but not vertically
1422          */
1423         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1424         ge->buttonbox = gtk_hbox_new(FALSE, 0);
1425         gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1426         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1427
1428         add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
1429
1430         /*
1431          * Set up thread status progress bar
1432          */
1433         ge->thread_status_pb = gtk_progress_bar_new();
1434         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1435         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1436         gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1437
1438
1439         return main_vbox;
1440 }
1441
1442 static GtkWidget *new_main_page(struct gui *ui)
1443 {
1444         GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
1445         GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
1446
1447         main_vbox = gtk_vbox_new(FALSE, 3);
1448
1449         /*
1450          * Set up alignments for widgets at the top of ui,
1451          * align top left, expand horizontally but not vertically
1452          */
1453         top_align = gtk_alignment_new(0, 0, 1, 0);
1454         top_vbox = gtk_vbox_new(FALSE, 0);
1455         gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1456         gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
1457
1458         probe = gtk_frame_new("Run statistics");
1459         gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1460         probe_frame = gtk_vbox_new(FALSE, 3);
1461         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1462
1463         probe_box = gtk_hbox_new(FALSE, 3);
1464         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1465         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
1466         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1467         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1468         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1469         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1470
1471         /*
1472          * Only add this if we have a commit rate
1473          */
1474 #if 0
1475         probe_box = gtk_hbox_new(FALSE, 3);
1476         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1477
1478         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1479         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1480
1481         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1482         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1483 #endif
1484
1485         /*
1486          * Set up a drawing area and IOPS and bandwidth graphs
1487          */
1488         ui->graphs.drawing_area = gtk_drawing_area_new();
1489         gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
1490                 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1491         gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
1492         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1493                         G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1494         g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1495                         G_CALLBACK(on_config_drawing_area), &ui->graphs);
1496         scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1497         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1498                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1499         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
1500                                         ui->graphs.drawing_area);
1501         gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
1502                         TRUE, TRUE, 0);
1503
1504         setup_graphs(&ui->graphs);
1505
1506         /*
1507          * Set up alignments for widgets at the bottom of ui, 
1508          * align bottom left, expand horizontally but not vertically
1509          */
1510         bottom_align = gtk_alignment_new(0, 1, 1, 0);
1511         ui->buttonbox = gtk_hbox_new(FALSE, 0);
1512         gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1513         gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
1514
1515         /*
1516          * Set up thread status progress bar
1517          */
1518         ui->thread_status_pb = gtk_progress_bar_new();
1519         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1520         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1521         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1522
1523         return main_vbox;
1524 }
1525
1526 static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1527                                      guint page, gpointer data)
1528
1529 {
1530         struct gui *ui = (struct gui *) data;
1531         struct gui_entry *ge;
1532
1533         if (!page) {
1534                 set_job_menu_visible(ui, 0);
1535                 set_view_results_visible(ui, 0);
1536                 return TRUE;
1537         }
1538
1539         set_job_menu_visible(ui, 1);
1540         ge = get_ge_from_page(ui, page, NULL);
1541         if (ge)
1542                 update_button_states(ui, ge);
1543
1544         return TRUE;
1545 }
1546
1547 static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1548 {
1549         time_t time_a = gtk_recent_info_get_visited(a);
1550         time_t time_b = gtk_recent_info_get_visited(b);
1551
1552         return time_b - time_a;
1553 }
1554
1555 static void add_recent_file_items(struct gui *ui)
1556 {
1557         const gchar *gfio = g_get_application_name();
1558         GList *items, *item;
1559         int i = 0;
1560
1561         if (ui->recent_ui_id) {
1562                 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1563                 gtk_ui_manager_ensure_update(ui->uimanager);
1564         }
1565         ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1566
1567         if (ui->actiongroup) {
1568                 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1569                 g_object_unref(ui->actiongroup);
1570         }
1571         ui->actiongroup = gtk_action_group_new("RecentFileActions");
1572
1573         gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1574
1575         items = gtk_recent_manager_get_items(ui->recentmanager);
1576         items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1577
1578         for (item = items; item && item->data; item = g_list_next(item)) {
1579                 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1580                 gchar *action_name;
1581                 const gchar *label;
1582                 GtkAction *action;
1583
1584                 if (!gtk_recent_info_has_application(info, gfio))
1585                         continue;
1586
1587                 /*
1588                  * We only support local files for now
1589                  */
1590                 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1591                         continue;
1592
1593                 action_name = g_strdup_printf("RecentFile%u", i++);
1594                 label = gtk_recent_info_get_display_name(info);
1595
1596                 action = g_object_new(GTK_TYPE_ACTION,
1597                                         "name", action_name,
1598                                         "label", label, NULL);
1599
1600                 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1601                                         gtk_recent_info_ref(info),
1602                                         (GDestroyNotify) gtk_recent_info_unref);
1603
1604
1605                 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1606
1607                 gtk_action_group_add_action(ui->actiongroup, action);
1608                 g_object_unref(action);
1609
1610                 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1611                                         "/MainMenu/FileMenu/FileRecentFiles",
1612                                         label, action_name,
1613                                         GTK_UI_MANAGER_MENUITEM, FALSE);
1614
1615                 g_free(action_name);
1616
1617                 if (i == 8)
1618                         break;
1619         }
1620
1621         g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1622         g_list_free(items);
1623 }
1624
1625 static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
1626                                    gint x, gint y, GtkSelectionData *seldata,
1627                                    guint info, guint time, gpointer *data)
1628 {
1629         struct gui *ui = (struct gui *) data;
1630         gchar **uris;
1631         GtkWidget *source;
1632
1633         source = gtk_drag_get_source_widget(ctx);
1634         if (source && widget == gtk_widget_get_toplevel(source)) {
1635                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1636                 return;
1637         }
1638
1639         uris = gtk_selection_data_get_uris(seldata);
1640         if (!uris) {
1641                 gtk_drag_finish(ctx, FALSE, FALSE, time);
1642                 return;
1643         }
1644
1645         if (uris[0])
1646                 do_file_open_with_tab(ui, uris[0]);
1647
1648         gtk_drag_finish(ctx, TRUE, FALSE, time);
1649         g_strfreev(uris);
1650 }
1651
1652 static void init_ui(int *argc, char **argv[], struct gui *ui)
1653 {
1654         GtkSettings *settings;
1655         GtkWidget *vbox;
1656
1657         /* Magical g*thread incantation, you just need this thread stuff.
1658          * Without it, the update that happens in gfio_update_thread_status
1659          * doesn't really happen in a timely fashion, you need expose events
1660          */
1661         if (!g_thread_supported())
1662                 g_thread_init(NULL);
1663         gdk_threads_init();
1664
1665         gtk_init(argc, argv);
1666         settings = gtk_settings_get_default();
1667         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1668         g_type_init();
1669         gdk_color_parse("white", &gfio_color_white);
1670         
1671         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1672         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1673         gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1674
1675         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1676         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
1677
1678         ui->vbox = gtk_vbox_new(FALSE, 0);
1679         gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1680
1681         ui->uimanager = gtk_ui_manager_new();
1682         ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1683         gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
1684
1685         ui->recentmanager = gtk_recent_manager_get_default();
1686         add_recent_file_items(ui);
1687
1688         ui->notebook = gtk_notebook_new();
1689         g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
1690         gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
1691         gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
1692         gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1693
1694         vbox = new_main_page(ui);
1695         gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
1696         gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1697         g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
1698
1699         gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1700
1701         gfio_ui_setup_log(ui);
1702
1703         gtk_widget_show_all(ui->window);
1704 }
1705
1706 int main(int argc, char *argv[], char *envp[])
1707 {
1708         if (initialize_fio(envp))
1709                 return 1;
1710         if (fio_init_options())
1711                 return 1;
1712
1713         gopt_init();
1714
1715         memset(&main_ui, 0, sizeof(main_ui));
1716         main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
1717
1718         init_ui(&argc, &argv, &main_ui);
1719
1720         gdk_threads_enter();
1721         gtk_main();
1722         gdk_threads_leave();
1723
1724         g_hash_table_destroy(main_ui.ge_hash);
1725
1726         gopt_exit();
1727         return 0;
1728 }