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