summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gtk_fb.c210
-rw-r--r--src/main.c192
2 files changed, 402 insertions, 0 deletions
diff --git a/src/gtk_fb.c b/src/gtk_fb.c
new file mode 100644
index 0000000..137e560
--- /dev/null
+++ b/src/gtk_fb.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 - Vito Caputo - <vcaputo@pengaru.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <assert.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include "fb.h"
+#include "settings.h"
+
+/* glimmer's GTK+-3.0 backend fb for rototiller */
+
+typedef struct gtk_fb_t {
+ GtkWidget *window;
+ GtkWidget *image;
+ guint tick_callback;
+ unsigned width, height;
+ unsigned fullscreen:1;
+} gtk_fb_t;
+
+typedef struct gtk_fb_page_t gtk_fb_page_t;
+
+struct gtk_fb_page_t {
+ cairo_surface_t *surface;
+};
+
+
+/* this doesn't really do anything significant on gtk */
+static int gtk_fb_init(const settings_t *settings, void **res_context)
+{
+ const char *fullscreen;
+ const char *size;
+ gtk_fb_t *c;
+ int r;
+
+ assert(settings);
+ assert(res_context);
+
+ fullscreen = settings_get_value(settings, "fullscreen");
+ if (!fullscreen)
+ return -EINVAL;
+
+ size = settings_get_value(settings, "size");
+ if (!size && !strcasecmp(fullscreen, "off"))
+ return -EINVAL;
+
+ c = calloc(1, sizeof(gtk_fb_t));
+ if (!c)
+ return -ENOMEM;
+
+ if (!strcasecmp(fullscreen, "on"))
+ c->fullscreen = 1;
+
+ if (size) /* TODO: errors */
+ sscanf(size, "%u%*[xX]%u", &c->width, &c->height);
+
+ *res_context = c;
+
+ return 0;
+}
+
+
+static void gtk_fb_shutdown(fb_t *fb, void *context)
+{
+ gtk_fb_t *c = context;
+
+ free(c);
+}
+
+
+/* This performs the page flip on the "draw" signal, triggerd
+ * on every "tick" by queue_draw_cb() below.
+ * Note that "tick" in this context is a gtk concept, and unrelated to
+ * rototiller ticks. See gtk frame clocks for more info.
+ * This is a little awkward as we're calling the public fb API from
+ * the underlying implementation, maybe fix it up later.
+ */
+static gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer user_data)
+{
+ fb_t *fb = user_data;
+
+ fb_flip(fb);
+
+ return FALSE;
+}
+
+/* this just queues drawing the image on the "tick" */
+static gboolean queue_draw_cb(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data)
+{
+ gtk_fb_t *c = user_data;
+
+ gtk_widget_queue_draw(c->image);
+
+ return G_SOURCE_CONTINUE;
+}
+
+
+static int gtk_fb_acquire(fb_t *fb, void *context, void *page)
+{
+ gtk_fb_t *c = context;
+ gtk_fb_page_t *p = page;
+
+ c->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ c->image = gtk_image_new_from_surface(p->surface);
+ g_signal_connect(c->image, "draw", G_CALLBACK(draw_cb), fb);
+ c->tick_callback = gtk_widget_add_tick_callback(c->image, queue_draw_cb, c, NULL);
+ gtk_container_add(GTK_CONTAINER(c->window), c->image);
+ gtk_widget_show_all(c->window);
+
+ return 0;
+}
+
+
+static void gtk_fb_release(fb_t *fb, void *context)
+{
+ gtk_fb_t *c = context;
+
+ gtk_widget_remove_tick_callback(c->image, c->tick_callback);
+ gtk_widget_destroy(c->window);
+ gtk_widget_destroy(c->image);
+}
+
+
+static void * gtk_fb_page_alloc(fb_t *fb, void *context, fb_page_t *res_page)
+{
+ gtk_fb_t *c = context;
+ gtk_fb_page_t *p;
+
+ p = calloc(1, sizeof(gtk_fb_page_t));
+ if (!p)
+ return NULL;
+
+ p->surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, c->width, c->height);
+
+ res_page->fragment.buf = (uint32_t *)cairo_image_surface_get_data(p->surface);
+ res_page->fragment.width = c->width;
+ res_page->fragment.frame_width = c->width;
+ res_page->fragment.height = c->height;
+ res_page->fragment.frame_height = c->height;
+ res_page->fragment.stride = cairo_image_surface_get_stride(p->surface) - (c->width * 4);
+ res_page->fragment.pitch = cairo_image_surface_get_stride(p->surface);
+
+ cairo_surface_flush(p->surface);
+ cairo_surface_mark_dirty(p->surface);
+
+ return p;
+}
+
+
+static int gtk_fb_page_free(fb_t *fb, void *context, void *page)
+{
+ gtk_fb_t *c = context;
+ gtk_fb_page_t *p = page;
+
+ cairo_surface_destroy(p->surface);
+ free(p);
+
+ return 0;
+}
+
+
+/* XXX: due to gtk's event-driven nature, this isn't a vsync-synchronous page flip,
+ * so and fb_flip() must be scheduled independently to not just spin.
+ * The "draw" signal on the image is used to drive fb_flip() on frameclock "ticks",
+ * a method suggested by Christian Hergert, thanks!
+ */
+static int gtk_fb_page_flip(fb_t *fb, void *context, void *page)
+{
+ gtk_fb_t *c = context;
+ gtk_fb_page_t *p = page;
+
+ cairo_surface_mark_dirty(p->surface);
+ gtk_image_set_from_surface(GTK_IMAGE(c->image), p->surface);
+
+ return 0;
+}
+
+
+fb_ops_t gtk_fb_ops = {
+ /* TODO: .setup may not be necessary in the gtk frontend, unless maybe
+ * it learns to use multiple fb backends, and would like to do the whole dynamic
+ * settings iterative dance established by rototiller.
+ .setup = gtk_fb_setup,
+ */
+
+ /* everything else seems to not be too far out of wack for the new frontend as-is,
+ * I only had to plumb down the fb_t *fb, which classic rototiller didn't need to do.
+ */
+ .init = gtk_fb_init,
+ .shutdown = gtk_fb_shutdown,
+ .acquire = gtk_fb_acquire,
+ .release = gtk_fb_release,
+ .page_alloc = gtk_fb_page_alloc,
+ .page_free = gtk_fb_page_free,
+ .page_flip = gtk_fb_page_flip
+};
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..5866bb5
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2021 - Vito Caputo - <vcaputo@pengaru.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+#include <pthread.h>
+#include <sys/time.h>
+
+#include "rototiller.h"
+
+/* glimmer is a GTK+-3.0 frontend for rototiller */
+
+extern fb_ops_t gtk_fb_ops;
+
+#define DEFAULT_WIDTH 320
+#define DEFAULT_HEIGHT 480
+
+#define BOX_SPACING 4
+#define NUM_FB_PAGES 3
+
+static struct glimmer_t {
+ GtkWidget *modules_list;
+
+ fb_t *fb;
+ settings_t *fb_settings;
+
+ settings_t *module_settings;
+ const rototiller_module_t *module;
+ void *module_context;
+ pthread_t thread;
+ struct timeval start_tv;
+ unsigned ticks_offset; /* XXX: this isn't leveraged currently */
+} glimmer;
+
+
+static unsigned 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;
+}
+
+
+/* TODO: this should probably move into librototiller */
+static void * glimmer_thread(void *foo)
+{
+ struct timeval now;
+
+ for (;;) {
+ fb_page_t *page;
+ unsigned ticks;
+
+ page = fb_page_get(glimmer.fb);
+ gettimeofday(&now, NULL);
+ ticks = get_ticks(&glimmer.start_tv, &now, glimmer.ticks_offset);
+ rototiller_module_render(glimmer.module, glimmer.module_context, ticks, &page->fragment);
+ fb_page_put(glimmer.fb, page);
+ }
+}
+
+
+static void glimmer_go(GtkButton *button, gpointer user_data)
+{
+ int r;
+
+ if (glimmer.fb) {
+ pthread_cancel(glimmer.thread);
+ pthread_join(glimmer.thread, NULL);
+
+ /* FIXME: all these _free() functions in librototiller should return NULL */
+ fb_free(glimmer.fb);
+ glimmer.fb = NULL;
+ settings_free(glimmer.fb_settings);
+ glimmer.fb_settings = NULL;
+ settings_free(glimmer.module_settings);
+ glimmer.module_settings = NULL;
+ }
+
+ /* 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.fb_settings = settings_new("fullscreen=off,size=640x480");
+ glimmer.module_settings = settings_new("TODO");
+
+ r = fb_new(&gtk_fb_ops, glimmer.fb_settings, NUM_FB_PAGES, &glimmer.fb);
+ if (r < 0) {
+ puts("fb no go!");
+ return;
+ }
+
+ gettimeofday(&glimmer.start_tv, NULL);
+ glimmer.module = rototiller_lookup_module(gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(glimmer.modules_list)));
+ r = rototiller_module_create_context(
+ glimmer.module,
+ get_ticks(
+ &glimmer.start_tv,
+ &glimmer.start_tv,
+ glimmer.ticks_offset),
+ &glimmer.module_context);
+ if (r < 0) {
+ puts("context no go!");
+ return;
+ }
+
+ pthread_t thread;
+
+ pthread_create(&glimmer.thread, NULL, glimmer_thread, NULL);
+}
+
+
+static void activate(GtkApplication *app, gpointer user_data)
+{
+ GtkWidget *window, *vbox, *settings, *button;
+ const rototiller_module_t **modules;
+ size_t n_modules;
+
+ rototiller_get_modules(&modules, &n_modules);
+
+ window = gtk_application_window_new(app);
+ gtk_window_set_title(GTK_WINDOW(window), "glimmer");
+ gtk_window_set_default_size(GTK_WINDOW(window), DEFAULT_WIDTH, DEFAULT_HEIGHT);
+
+ 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);
+ }
+ 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);
+
+ /* button to rototill as configured */
+ button = gtk_button_new_with_label("Go!");
+ gtk_container_add(GTK_CONTAINER(vbox), button);
+ g_signal_connect(button, "clicked", G_CALLBACK(glimmer_go), NULL);
+
+ gtk_widget_show_all(window);
+}
+
+
+int main(int argc, char **argv)
+{
+ GtkApplication *app;
+ int status;
+
+ rototiller_init();
+ 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_object_unref(app);
+ rototiller_shutdown();
+
+ return status;
+}
© All Rights Reserved