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