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