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