diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2021-10-03 18:13:36 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2022-03-19 16:35:27 -0700 |
commit | 642b63f5a64b1dfda2ecd041fed3a008645d72cc (patch) | |
tree | 17cb6ee53461f73d224ecdac34a47225dd801522 /src/main.c | |
parent | 7c5f7bcc46afe9ad8261347222b5cc26d26e6c6b (diff) |
glimmer: first stab at adding settings support
This bumps the rototiller submodule for the new described
settings stuff, among other improvements.
There are some ugly kludges surrounding widget lifecycle since I
destroy setting widgets from their signal callbacks and that
seems to anger gtk/glib. I'm unclear on what the right way is
here, but leaking for now is relatively harmless.
It seems like gtk+ should be holding a reference on the
respective widget across signal deliveries so the callbacks can
potentialy destroy them with the underlying resource freeing
becoming queued. Perhaps there's something like signal
propagation up the heirarchy happening and since I'm destroying
the settings frame higher up the tree and not the specific widget
being signaled things fall over. It's a TODO item to sort out,
at least the resources leaked aren't substantial and it's only on
interactive configuration.
This has also been done in fashion preserving the
--module=foo,bar=baz arguments consistent with classic
rototiller. You can start glimmer with the same syntax --module=
flag, and it will select the specified module and apply the
supplied settings in a way reflected in the GUI.
There should probably be a new '--go' flag added to enable
starting the rendering via commandline. As-is glimmer will
always leave you at a settings dialog waiting for a "Go!" click,
even if you specified module+settings comprehensively via the
CLI.
Diffstat (limited to 'src/main.c')
-rw-r--r-- | src/main.c | 377 |
1 files changed, 317 insertions, 60 deletions
@@ -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; |