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