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