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