diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/gtk_fb.c | 4 | ||||
| -rw-r--r-- | src/main.c | 377 | 
2 files changed, 319 insertions, 62 deletions
diff --git a/src/gtk_fb.c b/src/gtk_fb.c index 2306db5..4520df7 100644 --- a/src/gtk_fb.c +++ b/src/gtk_fb.c @@ -74,11 +74,11 @@ static int gtk_fb_init(const til_settings_t *settings, void **res_context)  	assert(settings);  	assert(res_context); -	fullscreen = til_settings_get_value(settings, "fullscreen"); +	fullscreen = til_settings_get_value(settings, "fullscreen", NULL);  	if (!fullscreen)  		return -EINVAL; -	size = til_settings_get_value(settings, "size"); +	size = til_settings_get_value(settings, "size", NULL);  	if (!size && !strcasecmp(fullscreen, "off"))  		return -EINVAL; @@ -19,6 +19,7 @@  #include <sys/time.h>  #include <til.h> +#include <til_args.h>  /* glimmer is a GTK+-3.0 frontend for rototiller */ @@ -27,16 +28,21 @@ extern til_fb_ops_t gtk_fb_ops;  #define DEFAULT_WIDTH	320  #define DEFAULT_HEIGHT	480 -#define BOX_SPACING	4 +#define DEFAULT_MODULE	"rtv" +#define BOX_SPACING	1 +#define FRAME_MARGIN	8 +#define LABEL_MARGIN	4  #define NUM_FB_PAGES	3  static struct glimmer_t { -	GtkWidget		*modules_list; +	GtkComboBox		*modules_combobox; +	GtkWidget		*module_box, *module_frame; -	til_fb_t		*fb; +	til_args_t		args;  	til_settings_t		*video_settings; -  	til_settings_t		*module_settings; + +	til_fb_t		*fb;  	const til_module_t	*module;  	void			*module_context;  	pthread_t		thread; @@ -45,12 +51,44 @@ static struct glimmer_t {  } glimmer; -static unsigned get_ticks(const struct timeval *start, const struct timeval *now, unsigned offset) +static void glimmer_module_setup(const til_module_t *module, til_settings_t *settings); + + +static unsigned glimmer_get_ticks(const struct timeval *start, const struct timeval *now, unsigned offset)  {  	return (unsigned)((now->tv_sec - start->tv_sec) * 1000 + (now->tv_usec - start->tv_usec) / 1000) + offset;  } +static void glimmer_active_module(const til_module_t **res_module, til_settings_t **res_settings) +{ +	GtkTreeIter iter; + +	if (gtk_combo_box_get_active_iter(glimmer.modules_combobox, &iter)) { +		GtkTreeModel *model = gtk_combo_box_get_model(glimmer.modules_combobox); +		char		*name; + +		gtk_tree_model_get(model, &iter, +				0, &name, +				1, res_module, +				2, res_settings, +				-1); + +		g_free(name); +	} +} + + +static void glimmer_active_module_setup(void) +{ +	const til_module_t	*module; +	til_settings_t	*settings; + +	glimmer_active_module(&module, &settings); +	glimmer_module_setup(module, settings); +} + +  /* TODO: this should probably move into libtil */  static void * glimmer_thread(void *foo)  { @@ -62,7 +100,7 @@ static void * glimmer_thread(void *foo)  		page = til_fb_page_get(glimmer.fb);  		gettimeofday(&now, NULL); -		ticks = get_ticks(&glimmer.start_tv, &now, glimmer.ticks_offset); +		ticks = glimmer_get_ticks(&glimmer.start_tv, &now, glimmer.ticks_offset);  		til_module_render(glimmer.module, glimmer.module_context, ticks, &page->fragment);  		til_fb_page_put(glimmer.fb, page);  	} @@ -71,7 +109,8 @@ static void * glimmer_thread(void *foo)  static void glimmer_go(GtkButton *button, gpointer user_data)  { -	int	r; +	til_settings_t	*settings; +	int		r;  	if (glimmer.fb) {  		pthread_cancel(glimmer.thread); @@ -79,21 +118,10 @@ static void glimmer_go(GtkButton *button, gpointer user_data)  		til_quiesce();  		glimmer.fb = til_fb_free(glimmer.fb); -		glimmer.video_settings = til_settings_free(glimmer.video_settings); -		glimmer.module_settings = til_settings_free(glimmer.module_settings); +		glimmer.module_context = til_module_destroy_context(glimmer.module, glimmer.module_context);  	} -	/* TODO: translate the GTK+ settings panel values into -	 * glimmer.{fb,module}_settings -	 */ - -	/* For now, construct a simple 640x480 non-fullscreen fb, and -	 * simply don't do any module setup (those *should* have static builtin -	 * defaults that at least work on some level. -	 */ -	glimmer.video_settings = til_settings_new("fullscreen=off,size=640x480"); -	glimmer.module_settings = til_settings_new("TODO"); - +	/* TODO: prolly stop recreating fb on every go, or maybe only if the settings changed */  	r = til_fb_new(>k_fb_ops, glimmer.video_settings, NUM_FB_PAGES, &glimmer.fb);  	if (r < 0) {  		puts("fb no go!"); @@ -101,10 +129,10 @@ static void glimmer_go(GtkButton *button, gpointer user_data)  	}  	gettimeofday(&glimmer.start_tv, NULL); -	glimmer.module = til_lookup_module(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(glimmer.modules_list))); +	glimmer_active_module(&glimmer.module, &settings);  	r = til_module_create_context(  					glimmer.module, -					get_ticks( +					glimmer_get_ticks(  						&glimmer.start_tv,  						&glimmer.start_tv,  						glimmer.ticks_offset), @@ -118,13 +146,186 @@ static void glimmer_go(GtkButton *button, gpointer user_data)  } -static void activate(GtkApplication *app, gpointer user_data) +static void glimmer_combobox_setting_changed_cb(GtkComboBoxText *combobox, gpointer user_data)  { -	GtkWidget		*window, *vbox, *settings, *button; -	const til_module_t	**modules; -	size_t			n_modules; +	til_setting_t	*setting = user_data; + +	/* XXX FIXME FIXME XXX */ +	/* XXX FIXME FIXME XXX */ +	/* I don't know gtk+ well enough to know what's the non-leaky way to do this: +	 * glimmer_active_module_setup() will destroy the module frame which encompasses the +	 * widget this signal emitted from.  It appears that there isn't a reference held across +	 * the signal callbacks so they can safely perform a queued destroy of the originating widget +	 * within the callback to then become realized at the end of all the signal deliveries and +	 * callback processing when the final reference gets removed. +	 * +	 * for now I'm working around this by simply adding a ref, leaking the memory, until I find +	 * the Right Way. +	 */ +	g_object_ref(combobox); +	/* XXX FIXME FIXME XXX */ +	/* XXX FIXME FIXME XXX */ -	til_get_modules(&modules, &n_modules); +	setting->value = gtk_combo_box_text_get_active_text(combobox); +	glimmer_active_module_setup(); +} + +static void glimmer_entry_setting_changed_cb(GtkEntry *entry, gpointer user_data) +{ +	til_setting_t	*setting = user_data; + +	/* XXX FIXME: see above comment for combobox, but oddly I'm only seeing +	 * errors printed for the combobox case.  I'm just assuming the problem exists here as well. +	 */ +	g_object_ref(entry); + +	/* FIXME TODO there needs to be some validation of the free-form input against setting->desc->regex, +	 * though that probably shouldn't happen here. +	 */ +	setting->value = strdup(gtk_entry_get_text(entry)); +	glimmer_active_module_setup(); +} + + +/* (re)construct the gui settings pane to reflect *module and *settings */ +static void glimmer_module_setup(const til_module_t *module, til_settings_t *settings) +{ +	GtkWidget	*vbox, *label; + +	if (glimmer.module_frame) +		gtk_widget_destroy(glimmer.module_frame); + +	glimmer.module_frame = g_object_new(	GTK_TYPE_FRAME, +						"parent", GTK_CONTAINER(glimmer.module_box), +						"label", module->name, +						"label-xalign", .01f, +						"margin", FRAME_MARGIN, +						"visible", TRUE, +						NULL); +	gtk_box_set_child_packing(	GTK_BOX(glimmer.module_box), +					GTK_WIDGET(glimmer.module_frame), +					TRUE, +					FALSE, +					BOX_SPACING, +					GTK_PACK_START); + +	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, BOX_SPACING); +	gtk_container_add(GTK_CONTAINER(glimmer.module_frame), vbox); + +	label = g_object_new(	GTK_TYPE_LABEL, +				"parent", GTK_CONTAINER(vbox), +				"label", module->description, +				"halign", GTK_ALIGN_START, +				"margin", LABEL_MARGIN, +				"visible", TRUE, +				NULL); + +	label = g_object_new(	GTK_TYPE_LABEL, +				"parent", GTK_CONTAINER(vbox), +				"label", module->author, +				"halign", GTK_ALIGN_START, +				"margin", LABEL_MARGIN, +				"visible", TRUE, +				NULL); + +	if (module->setup) { +		GtkWidget			*frame, *svbox; +		til_setting_t			*setting; +		const til_setting_desc_t	*desc; + +		frame = g_object_new(	GTK_TYPE_FRAME, +					"parent", GTK_CONTAINER(vbox), +					"label", "Settings", +					"label-xalign", .01f, +					"margin", FRAME_MARGIN, +					"visible", TRUE, +					NULL); +		gtk_box_set_child_packing(	GTK_BOX(vbox), +						GTK_WIDGET(frame), +						TRUE, +						TRUE, +						BOX_SPACING, +						GTK_PACK_START); + +		svbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, BOX_SPACING); +		gtk_container_add(GTK_CONTAINER(frame), svbox); + +		til_settings_reset_descs(settings); +		while (module->setup(settings, &setting, &desc) > 0) { +			GtkWidget	*shbox; + +			if (!setting) { +				til_settings_add_value(settings, desc->key, desc->preferred, NULL); +				continue; +			} + +			shbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, BOX_SPACING); +			gtk_container_add(GTK_CONTAINER(svbox), shbox); +			gtk_widget_set_halign(GTK_WIDGET(shbox), GTK_ALIGN_START); + +			label = g_object_new(	GTK_TYPE_LABEL, +						"parent", GTK_CONTAINER(shbox), +						"label", desc->name, +						"halign", GTK_ALIGN_START, +						"margin", LABEL_MARGIN, +						"visible", TRUE, +						NULL); + +			if (desc->values) { +				GtkWidget	*combobox; + +				/* combo box */ +				combobox = gtk_combo_box_text_new(); +				gtk_container_add(GTK_CONTAINER(shbox), combobox); +				gtk_widget_set_halign(GTK_WIDGET(combobox), GTK_ALIGN_END); +				for (int i = 0; desc->values[i]; i++) { +					gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combobox), NULL, desc->values[i]); +					if (!strcmp(setting->value, desc->values[i])) +						gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), i); +				} +				g_signal_connect(combobox, "changed", G_CALLBACK(glimmer_combobox_setting_changed_cb), setting); + +			} else { +				GtkWidget	*entry; + +				/* plain unstructured text input box */ +				entry = gtk_entry_new(); +				gtk_entry_set_text(GTK_ENTRY(entry), setting->value); +				gtk_container_add(GTK_CONTAINER(shbox), entry); +				gtk_widget_set_halign(GTK_WIDGET(entry), GTK_ALIGN_END); + +				/* XXX FIXME */ +				/* XXX FIXME */ +				/* XXX FIXME */ +				/* "activate" only occurs on hitting Enter in the GtkEntry.  So we'll miss +				 * edits that are visible but not Entered before hitting Go!  We likely need +				 * to catch more signals... +				 */ +				/* XXX FIXME */ +				/* XXX FIXME */ +				/* XXX FIXME */ + +				g_signal_connect(entry, "activate", G_CALLBACK(glimmer_entry_setting_changed_cb), setting); +			} + +			if (!setting->desc) +				setting->desc = desc; +		} +	} + +	gtk_widget_show_all(glimmer.module_frame); +} + + +static void glimmer_module_changed_cb(GtkComboBox *box, G_GNUC_UNUSED gpointer user_data) +{ +	glimmer_active_module_setup(); +} + + +static void glimmer_activate(GtkApplication *app, gpointer user_data) +{ +	GtkWidget	*window, *vbox, *button;  	window = gtk_application_window_new(app);  	gtk_window_set_title(GTK_WINDOW(window), "glimmer"); @@ -133,54 +334,110 @@ static void activate(GtkApplication *app, gpointer user_data)  	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, BOX_SPACING);  	gtk_container_add(GTK_CONTAINER(window), vbox); -	glimmer.modules_list = gtk_combo_box_text_new(); -	for (size_t i = 0; i < n_modules; i++) { -		gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(glimmer.modules_list), NULL, modules[i]->name); - -		/* like rototiller, default to rtv */ -		if (!strcmp(modules[i]->name, "rtv")) -			gtk_combo_box_set_active(GTK_COMBO_BOX(glimmer.modules_list), i); +	{ /* construct modules list combobox, associating a name, module, and settings per entry */ +		const til_module_t	**modules; +		size_t			n_modules; +		const char		*module; +		GtkComboBox		*combobox; +		GtkListStore		*store; +		GtkCellRenderer		*text; + +		til_get_modules(&modules, &n_modules); +		module = til_settings_get_key(glimmer.module_settings, 0, NULL); + +		combobox = g_object_new(GTK_TYPE_COMBO_BOX, "visible", TRUE, NULL); +		store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER); +		for (size_t i = 0; i < n_modules; i++) { +			GtkTreeIter iter; + +			gtk_list_store_append(store, &iter); +			gtk_list_store_set(	store, &iter, +						0, modules[i]->name, +						1, modules[i], +						2, (module && !strcmp(module, modules[i]->name)) ? glimmer.module_settings : til_settings_new(NULL), +						-1); +		} + +		gtk_combo_box_set_model(combobox, GTK_TREE_MODEL(store)); +		gtk_combo_box_set_id_column(combobox, 0); +		gtk_combo_box_set_active_id(combobox, module ? : DEFAULT_MODULE); + +		g_signal_connect(combobox, "changed", G_CALLBACK(glimmer_module_changed_cb), NULL); + +		text = gtk_cell_renderer_text_new(); +		gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), text, TRUE); +		gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combobox), text, "text", 0); + +		glimmer.modules_combobox = combobox;  	} -	gtk_container_add(GTK_CONTAINER(vbox), glimmer.modules_list); - -	gtk_box_set_child_packing( -			GTK_BOX(vbox), -			glimmer.modules_list, -			FALSE, -			FALSE, -			BOX_SPACING * 4, /* FIXME: having the combo box too near the window edge puts the pointer into the scroll-up arrow on click :/ */ -			GTK_PACK_START); - -	/* TODO: below the combobox, present framebuffer and the selected module's settings */ -	settings = gtk_label_new("TODO: fb/module settings here"); -	gtk_container_add(GTK_CONTAINER(vbox), settings); -	gtk_box_set_child_packing( -			GTK_BOX(vbox), -			settings, -			TRUE, -			TRUE, -			BOX_SPACING, -			GTK_PACK_START); + +	gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(glimmer.modules_combobox)); +	gtk_box_set_child_packing(	GTK_BOX(vbox), +					GTK_WIDGET(glimmer.modules_combobox), +					FALSE, +					FALSE, +					BOX_SPACING * 4, +					GTK_PACK_START); + +	glimmer.module_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, BOX_SPACING); +	gtk_container_add(GTK_CONTAINER(vbox), glimmer.module_box); +	gtk_box_set_child_packing(	GTK_BOX(vbox), +					glimmer.module_box, +					TRUE, +					TRUE, +					BOX_SPACING, +					GTK_PACK_START); + +	glimmer_active_module_setup();  	/* button to rototill as configured */ -	button = gtk_button_new_with_label("Go!"); -	gtk_container_add(GTK_CONTAINER(vbox), button); +	button = g_object_new(	GTK_TYPE_BUTTON, +				"parent", GTK_CONTAINER(vbox), +				"label", "Go!", +				"visible", TRUE, +				NULL);  	g_signal_connect(button, "clicked", G_CALLBACK(glimmer_go), NULL);  	gtk_widget_show_all(window);  } -int main(int argc, char **argv) +int main(int argc, const char *argv[])  { +	int		r, status, pruned_argc; +	const char	**pruned_argv;  	GtkApplication	*app; -	int		status;  	til_init(); + +	r = til_args_pruned_parse(argc, argv, &glimmer.args, &pruned_argc, &pruned_argv); +	if (r < 0) { +		fprintf(stderr, "Unable to parse args: %s\n", strerror(-r)); +		return EXIT_FAILURE; +	} + +	glimmer.module_settings = til_settings_new(glimmer.args.module); +	/* TODO: glimmer doesn't currently handle video settings, gtk_fb doesn't even +	 * implement a .setup() method.  It would be an interesting exercise to bring +	 * in support for rototiller's sdl and drm fb backends, but it immediately +	 * becomes awkward with obvious conflicts like drm wanting to own the display +	 * implicitly being shared when glimmer's already using gtk if not on distinct +	 * devices. +	 * +	 * But it'd be nice to at least support window sizing/fullscreen +	 * startup via args w/gtk_fb, so at some point I should add a +	 * gtk_fb.setup() method for filling in the blanks of what the args omit. +	 * For now these statically defined comprehensive settings simply skirt +	 * the issue. +	 */ +	//glimmer.video_settings = til_settings_new(glimmer.args.video); +	glimmer.video_settings = til_settings_new("fullscreen=off,size=640x480"); +  	app = gtk_application_new("com.pengaru.glimmer", G_APPLICATION_FLAGS_NONE); -	g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); -	status = g_application_run(G_APPLICATION(app), argc, argv); +	g_signal_connect(app, "activate", G_CALLBACK(glimmer_activate), NULL); +	status = g_application_run(G_APPLICATION(app), pruned_argc, (char **)pruned_argv);  	g_object_unref(app); +  	til_shutdown();  	return status;  | 
