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