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