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