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