diff options
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; |