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