summaryrefslogtreecommitdiff
path: root/src/gtk_fb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gtk_fb.c')
-rw-r--r--src/gtk_fb.c210
1 files changed, 210 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
+};
© All Rights Reserved