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