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