From 7c7dc660e587eac1aa3c8b3405eba95ba558e682 Mon Sep 17 00:00:00 2001 From: Harry Jeffery Date: Sat, 15 Jun 2019 14:28:29 +0100 Subject: Big glfw refactor I did a lot of this in a very ad-hoc fashion with no proper commit history. As such, the kindest thing to do seemed to be to just squash it into this one commit. --- Makefile | 11 +- src/backend_freeimage.c | 29 +- src/backend_libjpeg.c | 17 +- src/backend_libpng.c | 19 +- src/backend_librsvg.c | 37 +- src/backend_libtiff.c | 17 +- src/binds.c | 64 +-- src/binds.h | 4 +- src/canvas.c | 284 ++++++++++++ src/canvas.h | 51 +++ src/console.c | 112 +++++ src/console.h | 47 ++ src/image.c | 182 ++------ src/image.h | 18 +- src/imv.c | 1094 +++++++++++++++++++++++++---------------------- src/keyboard.c | 118 +++++ src/keyboard.h | 27 ++ src/log.c | 59 ++- src/log.h | 11 +- src/source.h | 14 +- src/util.c | 139 ------ src/util.h | 22 - src/viewport.c | 104 ++--- src/viewport.h | 17 +- 24 files changed, 1465 insertions(+), 1032 deletions(-) create mode 100644 src/canvas.c create mode 100644 src/canvas.h create mode 100644 src/console.c create mode 100644 src/console.h create mode 100644 src/keyboard.c create mode 100644 src/keyboard.h delete mode 100644 src/util.c delete mode 100644 src/util.h diff --git a/Makefile b/Makefile index 049e658..baf2498 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,9 @@ INSTALL_DATA ?= install -m 0644 INSTALL_MAN ?= install -m 0644 INSTALL_PROGRAM ?= install -m 0755 -override CFLAGS += -std=c99 -W -Wall -Wpedantic -Wextra -override CPPFLAGS += $(shell sdl2-config --cflags) -D_XOPEN_SOURCE=700 -override LIBS := $(shell sdl2-config --libs) -override LIBS += -lSDL2_ttf -lfontconfig -lpthread +override CFLAGS += -std=c99 -W -Wall -Wpedantic -Wextra $(shell pkg-config --cflags pangocairo) +override CPPFLAGS += -D_XOPEN_SOURCE=700 +override LIBS := -lglfw -lGL -lpthread -lxkbcommon $(shell pkg-config --libs pangocairo) BUILDDIR ?= build TARGET := $(BUILDDIR)/imv @@ -24,14 +23,16 @@ SOURCES := src/main.c SOURCES += src/binds.c SOURCES += src/bitmap.c +SOURCES += src/canvas.c SOURCES += src/commands.c +SOURCES += src/console.c SOURCES += src/image.c SOURCES += src/imv.c SOURCES += src/ini.c +SOURCES += src/keyboard.c SOURCES += src/list.c SOURCES += src/log.c SOURCES += src/navigator.c -SOURCES += src/util.c SOURCES += src/viewport.c # Add backends to build as configured diff --git a/src/backend_freeimage.c b/src/backend_freeimage.c index aaab46b..1c3cb24 100644 --- a/src/backend_freeimage.c +++ b/src/backend_freeimage.c @@ -47,7 +47,7 @@ static void source_free(struct imv_source *src) free(src); } -static struct imv_bitmap *to_imv_bitmap(FIBITMAP *in_bmp) +static struct imv_image *to_image(FIBITMAP *in_bmp) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = FreeImage_GetWidth(in_bmp); @@ -56,7 +56,8 @@ static struct imv_bitmap *to_imv_bitmap(FIBITMAP *in_bmp) bmp->data = malloc(4 * bmp->width * bmp->height); FreeImage_ConvertToRawBits(bmp->data, in_bmp, 4 * bmp->width, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, TRUE); - return bmp; + struct imv_image *image = imv_image_create_from_bitmap(bmp); + return image; } static void report_error(struct imv_source *src) @@ -66,7 +67,7 @@ static void report_error(struct imv_source *src) struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = NULL; + msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); @@ -80,7 +81,7 @@ static void send_bitmap(struct imv_source *src, FIBITMAP *fibitmap, int frametim struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = to_imv_bitmap(fibitmap); + msg.image = to_image(fibitmap); msg.frametime = frametime; msg.error = NULL; @@ -88,11 +89,11 @@ static void send_bitmap(struct imv_source *src, FIBITMAP *fibitmap, int frametim src->callback(&msg); } -static int first_frame(struct imv_source *src) +static void *first_frame(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { - return -1; + return NULL; } FIBITMAP *bmp = NULL; @@ -114,12 +115,12 @@ static int first_frame(struct imv_source *src) /* flags */ GIF_LOAD256); } else { report_error(src); - return -1; + return NULL; } if (!private->multibitmap) { report_error(src); - return -1; + return NULL; } FIBITMAP *frame = FreeImage_LockPage(private->multibitmap, 0); @@ -148,7 +149,7 @@ static int first_frame(struct imv_source *src) } if (!fibitmap) { report_error(src); - return -1; + return NULL; } bmp = FreeImage_ConvertTo32Bits(fibitmap); FreeImage_Unload(fibitmap); @@ -158,20 +159,20 @@ static int first_frame(struct imv_source *src) src->height = FreeImage_GetHeight(bmp); private->last_frame = bmp; send_bitmap(src, bmp, frametime); - return 0; + return NULL; } -static int next_frame(struct imv_source *src) +static void *next_frame(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { - return -1; + return NULL; } struct private *private = src->private; if (src->num_frames == 1) { send_bitmap(src, private->last_frame, 0); - return 0; + return NULL; } FITAG *tag = NULL; @@ -257,7 +258,7 @@ static int next_frame(struct imv_source *src) src->next_frame = (src->next_frame + 1) % src->num_frames; send_bitmap(src, private->last_frame, frametime); - return 0; + return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) diff --git a/src/backend_libjpeg.c b/src/backend_libjpeg.c index e8942eb..3c7ead7 100644 --- a/src/backend_libjpeg.c +++ b/src/backend_libjpeg.c @@ -43,14 +43,15 @@ static void source_free(struct imv_source *src) free(src); } -static struct imv_bitmap *to_imv_bitmap(int width, int height, void *bitmap) +static struct imv_image *to_image(int width, int height, void *bitmap) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = width; bmp->height = height; bmp->format = IMV_ABGR; bmp->data = bitmap; - return bmp; + struct imv_image *image = imv_image_create_from_bitmap(bmp); + return image; } static void report_error(struct imv_source *src) @@ -60,7 +61,7 @@ static void report_error(struct imv_source *src) struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = NULL; + msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); @@ -74,7 +75,7 @@ static void send_bitmap(struct imv_source *src, void *bitmap) struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = to_imv_bitmap(src->width, src->height, bitmap); + msg.image = to_image(src->width, src->height, bitmap); msg.frametime = 0; msg.error = NULL; @@ -82,11 +83,11 @@ static void send_bitmap(struct imv_source *src, void *bitmap) src->callback(&msg); } -static int load_image(struct imv_source *src) +static void *load_image(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { - return -1; + return NULL; } struct private *private = src->private; @@ -98,11 +99,11 @@ static int load_image(struct imv_source *src) if (rcode) { free(bitmap); report_error(src); - return -1; + return NULL; } send_bitmap(src, bitmap); - return 0; + return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) diff --git a/src/backend_libpng.c b/src/backend_libpng.c index 2acc903..edf7ba3 100644 --- a/src/backend_libpng.c +++ b/src/backend_libpng.c @@ -35,14 +35,15 @@ static void source_free(struct imv_source *src) free(src); } -static struct imv_bitmap *to_imv_bitmap(int width, int height, void *bitmap) +static struct imv_image *to_image(int width, int height, void *bitmap) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = width; bmp->height = height; bmp->format = IMV_ABGR; bmp->data = bitmap; - return bmp; + struct imv_image *image = imv_image_create_from_bitmap(bmp); + return image; } static void report_error(struct imv_source *src) @@ -52,7 +53,7 @@ static void report_error(struct imv_source *src) struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = NULL; + msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); @@ -67,7 +68,7 @@ static void send_bitmap(struct imv_source *src, void *bitmap) struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = to_imv_bitmap(src->width, src->height, bitmap); + msg.image = to_image(src->width, src->height, bitmap); msg.frametime = 0; msg.error = NULL; @@ -75,18 +76,18 @@ static void send_bitmap(struct imv_source *src, void *bitmap) src->callback(&msg); } -static int load_image(struct imv_source *src) +static void *load_image(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { - return -1; + return NULL; } struct private *private = src->private; if (setjmp(png_jmpbuf(private->png))) { report_error(src); - return -1; + return NULL; } png_bytep *rows = malloc(sizeof(png_bytep) * src->height); @@ -100,7 +101,7 @@ static int load_image(struct imv_source *src) free(rows[0]); free(rows); report_error(src); - return -1; + return NULL; } png_read_image(private->png, rows); @@ -109,7 +110,7 @@ static int load_image(struct imv_source *src) fclose(private->file); private->file = NULL; send_bitmap(src, bmp); - return 0; + return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) diff --git a/src/backend_librsvg.c b/src/backend_librsvg.c index c8dde19..d8c4557 100644 --- a/src/backend_librsvg.c +++ b/src/backend_librsvg.c @@ -33,18 +33,6 @@ static void source_free(struct imv_source *src) free(src); } -static struct imv_bitmap *to_imv_bitmap(GdkPixbuf *bitmap) -{ - struct imv_bitmap *bmp = malloc(sizeof *bmp); - bmp->width = gdk_pixbuf_get_width(bitmap); - bmp->height = gdk_pixbuf_get_height(bitmap); - bmp->format = IMV_ABGR; - size_t len = bmp->width * bmp->height * 4; - bmp->data = malloc(len); - memcpy(bmp->data, gdk_pixbuf_get_pixels(bitmap), len); - return bmp; -} - static void report_error(struct imv_source *src) { assert(src->callback); @@ -52,22 +40,21 @@ static void report_error(struct imv_source *src) struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = NULL; + msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); src->callback(&msg); } - -static void send_bitmap(struct imv_source *src, GdkPixbuf *bitmap) +static void send_svg(struct imv_source *src, RsvgHandle *handle) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = to_imv_bitmap(bitmap); + msg.image = imv_image_create_from_svg(handle); msg.frametime = 0; msg.error = NULL; @@ -75,11 +62,11 @@ static void send_bitmap(struct imv_source *src, GdkPixbuf *bitmap) src->callback(&msg); } -static int load_image(struct imv_source *src) +static void *load_image(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { - return -1; + return NULL; } RsvgHandle *handle = NULL; @@ -96,7 +83,7 @@ static int load_image(struct imv_source *src) if (!handle) { report_error(src); - return -1; + return NULL; } RsvgDimensionData dim; @@ -104,16 +91,8 @@ static int load_image(struct imv_source *src) src->width = dim.width; src->height = dim.height; - GdkPixbuf *buf = rsvg_handle_get_pixbuf(handle); - if (!buf) { - rsvg_handle_close(handle, &error); - report_error(src); - return -1; - } - - rsvg_handle_close(handle, &error); - send_bitmap(src, buf); - return 0; + send_svg(src, handle); + return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) diff --git a/src/backend_libtiff.c b/src/backend_libtiff.c index 482a80b..a3cbb7c 100644 --- a/src/backend_libtiff.c +++ b/src/backend_libtiff.c @@ -79,14 +79,15 @@ static void source_free(struct imv_source *src) free(src); } -static struct imv_bitmap *to_imv_bitmap(int width, int height, void *bitmap) +static struct imv_image *to_image(int width, int height, void *bitmap) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = width; bmp->height = height; bmp->format = IMV_ABGR; bmp->data = bitmap; - return bmp; + struct imv_image *image = imv_image_create_from_bitmap(bmp); + return image; } static void report_error(struct imv_source *src) @@ -96,7 +97,7 @@ static void report_error(struct imv_source *src) struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = NULL; + msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); @@ -110,7 +111,7 @@ static void send_bitmap(struct imv_source *src, void *bitmap) struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; - msg.bitmap = to_imv_bitmap(src->width, src->height, bitmap); + msg.image = to_image(src->width, src->height, bitmap); msg.frametime = 0; msg.error = NULL; @@ -118,11 +119,11 @@ static void send_bitmap(struct imv_source *src, void *bitmap) src->callback(&msg); } -static int load_image(struct imv_source *src) +static void *load_image(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { - return -1; + return NULL; } struct private *private = src->private; @@ -140,11 +141,11 @@ static int load_image(struct imv_source *src) if (rcode != 1) { free(bitmap); report_error(src); - return -1; + return NULL; } send_bitmap(src, bitmap); - return 0; + return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) diff --git a/src/binds.c b/src/binds.c index f8c4597..07416cf 100644 --- a/src/binds.c +++ b/src/binds.c @@ -2,6 +2,7 @@ #include "list.h" #include +#include struct bind_node { char *key; /* input key to reach this node */ @@ -186,69 +187,17 @@ static enum lookup_result bind_lookup(struct bind_node *node, struct list *keys, return LOOKUP_PARTIAL; } -static int print_event(char *buf, size_t len, const SDL_Event *event) -{ - /* only accept keydown events */ - if(event->type != SDL_KEYDOWN) { - buf[0] = 0; - return 0; - } - - const SDL_KeyboardEvent *kevent = &event->key; - - /* filter out modifier keys */ - switch(kevent->keysym.sym) { - case SDLK_LCTRL: - case SDLK_RCTRL: - case SDLK_LALT: - case SDLK_RALT: - case SDLK_LSHIFT: - case SDLK_RSHIFT: - return 0; - } - - /* Build prefix first: */ - char prefix[32] = {0}; - snprintf(prefix, sizeof prefix, "%s%s%s", - SDL_GetModState() & KMOD_CTRL ? "Ctrl+" : "", - SDL_GetModState() & KMOD_ALT ? "Meta+" : "", - SDL_GetModState() & KMOD_SHIFT ? "Shift+" : ""); - - /* Try plain old character input */ - const char *keyname = SDL_GetKeyName(kevent->keysym.sym); - char singlekey[2] = {0}; - - /* Because '<' and '>' have special meaning in our syntax, and '=', '[', and - * ']' are restricted within ini files, we rename these. */ - if(!strcmp(keyname, "<")) { - keyname = "Less"; - } else if(!strcmp(keyname, ">")) { - keyname = "Greater"; - } else if(!strcmp(keyname, "=")) { - keyname = "Equals"; - } else if(!strcmp(keyname, "[")) { - keyname = "LeftSquareBracket"; - } else if(!strcmp(keyname, "]")) { - keyname = "RightSquareBracket"; - } else if(strlen(keyname) == 1 && isalpha(*keyname)) { - singlekey[0] = tolower(*keyname); - keyname = singlekey; - } - - return snprintf(buf, len, "%s%s", prefix, keyname); -} - void imv_bind_clear_input(struct imv_binds *binds) { list_deep_free(binds->keys); binds->keys = list_create(); } -struct list *imv_bind_handle_event(struct imv_binds *binds, const SDL_Event *event) +struct list *imv_bind_handle_event(struct imv_binds *binds, const char *event) { /* If the user hits Escape twice in a row, treat that as backtracking out * of the current key sequence. */ - if (event->key.keysym.sym == SDLK_ESCAPE) { + if (!strcmp("Escape", event)) { if (binds->aborting_sequence) { /* The last thing they hit was escape, so abort the current entry */ binds->aborting_sequence = false; @@ -263,12 +212,7 @@ struct list *imv_bind_handle_event(struct imv_binds *binds, const SDL_Event *eve binds->aborting_sequence = false; } - char buffer[128]; - if (!print_event(buffer, sizeof(buffer), event)) { - /* invalid event - do nothing */ - return NULL; - } - list_append(binds->keys, strdup(buffer)); + list_append(binds->keys, strdup(event)); struct list *commands = NULL; enum lookup_result result = bind_lookup(&binds->bind_tree, binds->keys, &commands); diff --git a/src/binds.h b/src/binds.h index 5af7b5a..608b57e 100644 --- a/src/binds.h +++ b/src/binds.h @@ -1,7 +1,7 @@ #ifndef IMV_BINDS_H #define IMV_BINDS_H -#include +#include struct imv_binds; struct list; @@ -35,7 +35,7 @@ const struct list *imv_bind_input_buffer(struct imv_binds *binds); void imv_bind_clear_input(struct imv_binds *binds); /* Handle an input event, if a bind is triggered, return its command */ -struct list *imv_bind_handle_event(struct imv_binds *binds, const SDL_Event *event); +struct list *imv_bind_handle_event(struct imv_binds *binds, const char *event); /* Convert a string (such as from a config) to a key list */ struct list *imv_bind_parse_keys(const char *keys); diff --git a/src/canvas.c b/src/canvas.c new file mode 100644 index 0000000..74435cb --- /dev/null +++ b/src/canvas.c @@ -0,0 +1,284 @@ +#include "canvas.h" + +#include "image.h" +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef IMV_BACKEND_LIBRSVG +#include +#endif + +struct imv_canvas { + cairo_surface_t *surface; + cairo_t *cairo; + PangoFontDescription *font; + GLuint texture; + int width; + int height; + struct { + struct imv_bitmap *bitmap; + GLuint texture; + } cache; +}; + +struct imv_canvas *imv_canvas_create(int width, int height) +{ + struct imv_canvas *canvas = calloc(1, sizeof *canvas); + canvas->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + assert(canvas->surface); + canvas->cairo = cairo_create(canvas->surface); + assert(canvas->cairo); + + canvas->font = pango_font_description_new(); + assert(canvas->font); + + glGenTextures(1, &canvas->texture); + assert(canvas->texture); + + canvas->width = width; + canvas->height = height; + + return canvas; +} + +void imv_canvas_free(struct imv_canvas *canvas) +{ + pango_font_description_free(canvas->font); + canvas->font = NULL; + cairo_destroy(canvas->cairo); + canvas->cairo = NULL; + cairo_surface_destroy(canvas->surface); + canvas->surface = NULL; + glDeleteTextures(1, &canvas->texture); + if (canvas->cache.texture) { + glDeleteTextures(1, &canvas->cache.texture); + } + free(canvas); +} + +void imv_canvas_resize(struct imv_canvas *canvas, int width, int height) +{ + cairo_destroy(canvas->cairo); + cairo_surface_destroy(canvas->surface); + + canvas->width = width; + canvas->height = height; + + canvas->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + canvas->width, canvas->height); + assert(canvas->surface); + canvas->cairo = cairo_create(canvas->surface); + assert(canvas->cairo); +} + +void imv_canvas_clear(struct imv_canvas *canvas) +{ + cairo_save(canvas->cairo); + cairo_set_source_rgba(canvas->cairo, 0, 0, 0, 0); + cairo_set_operator(canvas->cairo, CAIRO_OPERATOR_SOURCE); + cairo_paint(canvas->cairo); + cairo_restore(canvas->cairo); +} + +void imv_canvas_color(struct imv_canvas *canvas, float r, float g, float b, float a) +{ + cairo_set_source_rgba(canvas->cairo, r, g, b, a); +} + +void imv_canvas_fill_rectangle(struct imv_canvas *canvas, int x, int y, int width, int height) +{ + cairo_rectangle(canvas->cairo, x, y, width, height); + cairo_fill(canvas->cairo); +} + +void imv_canvas_fill(struct imv_canvas *canvas) +{ + cairo_rectangle(canvas->cairo, 0, 0, canvas->width, canvas->height); + cairo_fill(canvas->cairo); +} + +void imv_canvas_fill_checkers(struct imv_canvas *canvas, int size) +{ + for (int x = 0; x < canvas->width; x += size) { + for (int y = 0; y < canvas->height; y += size) { + float color = ((x/size + y/size) % 2 == 0) ? 0.25 : 0.75; + cairo_set_source_rgba(canvas->cairo, color, color, color, 1); + cairo_rectangle(canvas->cairo, x, y, size, size); + cairo_fill(canvas->cairo); + } + } +} + +void imv_canvas_font(struct imv_canvas *canvas, const char *name, int size) +{ + pango_font_description_set_family(canvas->font, name); + pango_font_description_set_weight(canvas->font, PANGO_WEIGHT_NORMAL); + pango_font_description_set_absolute_size(canvas->font, size * PANGO_SCALE); +} + +void imv_canvas_printf(struct imv_canvas *canvas, int x, int y, const char *fmt, ...) +{ + char line[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(line, sizeof line, fmt, args); + + PangoLayout *layout = pango_cairo_create_layout(canvas->cairo); + pango_layout_set_font_description(layout, canvas->font); + pango_layout_set_text(layout, line, -1); + + cairo_move_to(canvas->cairo, x, y); + pango_cairo_show_layout(canvas->cairo, layout); + g_object_unref(layout); + + va_end(args); +} + +void imv_canvas_draw(struct imv_canvas *canvas) +{ + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + glPushMatrix(); + glOrtho(0.0, 1.0, 1.0, 0.0, 0.0, 10.0); + + void *data = cairo_image_surface_get_data(canvas->surface); + + glEnable(GL_TEXTURE_RECTANGLE); + glBindTexture(GL_TEXTURE_RECTANGLE, canvas->texture); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, canvas->width); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, canvas->width, canvas->height, + 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_TRIANGLE_FAN); + glTexCoord2i(0, 0); glVertex2i(0.0, 0.0); + glTexCoord2i(canvas->width, 0); glVertex2i(1.0, 0.0); + glTexCoord2i(canvas->width, canvas->height); glVertex2i(1.0, 1.0); + glTexCoord2i(0, canvas->height); glVertex2i(0.0, 1.0); + glEnd(); + glDisable(GL_BLEND); + + glBindTexture(GL_TEXTURE_RECTANGLE, 0); + glDisable(GL_TEXTURE_RECTANGLE); + glPopMatrix(); +} + +struct imv_bitmap *imv_image_get_bitmap(const struct imv_image *image); + +static int convert_pixelformat(enum imv_pixelformat fmt) +{ + /* opengl uses RGBA order, not ARGB, so we get it to + * flip the bytes around so ARGB -> BGRA + */ + if (fmt == IMV_ARGB) { + return GL_BGRA; + } else if (fmt == IMV_ABGR) { + return GL_RGBA; + } else { + imv_log(IMV_WARNING, "Unknown pixel format. Defaulting to ARGB\n"); + return GL_BGRA; + } +} + +static void draw_bitmap(struct imv_canvas *canvas, + struct imv_bitmap *bitmap, + int bx, int by, double scale, + enum upscaling_method upscaling_method) +{ + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + + glPushMatrix(); + glOrtho(0.0, viewport[2], viewport[3], 0.0, 0.0, 10.0); + + if (!canvas->cache.texture) { + glGenTextures(1, &canvas->cache.texture); + } + + const int format = convert_pixelformat(bitmap->format); + + glBindTexture(GL_TEXTURE_RECTANGLE, canvas->cache.texture); + + if (canvas->cache.bitmap != bitmap) { + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, bitmap->width); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, bitmap->width, bitmap->height, + 0, format, GL_UNSIGNED_INT_8_8_8_8_REV, bitmap->data); + } + canvas->cache.bitmap = bitmap; + + glEnable(GL_TEXTURE_RECTANGLE); + + if (upscaling_method == UPSCALING_LINEAR) { + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else if (upscaling_method == UPSCALING_NEAREST_NEIGHBOUR) { + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + imv_log(IMV_ERROR, "Unknown upscaling method: %d\n", upscaling_method); + abort(); + } + + const int left = bx; + const int top = by; + const int right = left + bitmap->width * scale; + const int bottom = top + bitmap->height * scale; + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2i(0, 0); glVertex2i(left, top); + glTexCoord2i(bitmap->width, 0); glVertex2i(right, top); + glTexCoord2i(bitmap->width, bitmap->height); glVertex2i(right, bottom); + glTexCoord2i(0, bitmap->height); glVertex2i(left, bottom); + glEnd(); + + glDisable(GL_BLEND); + + glBindTexture(GL_TEXTURE_RECTANGLE, 0); + glDisable(GL_TEXTURE_RECTANGLE); + glPopMatrix(); +} + +#ifdef IMV_BACKEND_LIBRSVG +RsvgHandle *imv_image_get_svg(const struct imv_image *image); +#endif + +void imv_canvas_draw_image(struct imv_canvas *canvas, struct imv_image *image, + int x, int y, double scale, + enum upscaling_method upscaling_method) +{ + struct imv_bitmap *bitmap = imv_image_get_bitmap(image); + if (bitmap) { + draw_bitmap(canvas, bitmap, x, y, scale, upscaling_method); + return; + } + +#ifdef IMV_BACKEND_LIBRSVG + RsvgHandle *svg = imv_image_get_svg(image); + if (svg) { + imv_canvas_clear(canvas); + cairo_translate(canvas->cairo, x, y); + cairo_scale(canvas->cairo, scale, scale); + rsvg_handle_render_cairo(svg, canvas->cairo); + cairo_identity_matrix(canvas->cairo); + imv_canvas_draw(canvas); + return; + } +#endif +} diff --git a/src/canvas.h b/src/canvas.h new file mode 100644 index 0000000..4f520fe --- /dev/null +++ b/src/canvas.h @@ -0,0 +1,51 @@ +#ifndef IMV_CANVAS_H +#define IMV_CANVAS_H + +struct imv_canvas; +struct imv_image; + +enum upscaling_method { + UPSCALING_LINEAR, + UPSCALING_NEAREST_NEIGHBOUR, + UPSCALING_METHOD_COUNT, +}; + +/* Create a canvas instance */ +struct imv_canvas *imv_canvas_create(int width, int height); + +/* Clean up a canvas */ +void imv_canvas_free(struct imv_canvas *canvas); + +/* Set the buffer size of the canvas */ +void imv_canvas_resize(struct imv_canvas *canvas, int width, int height); + +/* Blank the canvas to be empty and transparent */ +void imv_canvas_clear(struct imv_canvas *canvas); + +/* Set the current drawing color of the canvas */ +void imv_canvas_color(struct imv_canvas *canvas, float r, float g, float b, float a); + +/* Fill a rectangle on the canvas with the current color */ +void imv_canvas_fill_rectangle(struct imv_canvas *canvas, int x, int y, int width, int height); + +/* Fill the whole canvas with the current color */ +void imv_canvas_fill(struct imv_canvas *canvas); + +/* Fill the whole canvas with a chequerboard pattern */ +void imv_canvas_fill_checkers(struct imv_canvas *canvas, int size); + +/* Select the font to draw text with */ +void imv_canvas_font(struct imv_canvas *canvas, const char *name, int size); + +/* Draw some text on the canvas */ +void imv_canvas_printf(struct imv_canvas *canvas, int x, int y, const char *fmt, ...); + +/* Blit the canvas to the current OpenGL framebuffer */ +void imv_canvas_draw(struct imv_canvas *canvas); + +/* Blit the given image to the current OpenGL framebuffer */ +void imv_canvas_draw_image(struct imv_canvas *canvas, struct imv_image *image, + int x, int y, double scale, + enum upscaling_method upscaling_method); + +#endif diff --git a/src/console.c b/src/console.c new file mode 100644 index 0000000..92e578d --- /dev/null +++ b/src/console.c @@ -0,0 +1,112 @@ +#include "console.h" + +#include +#include + +struct imv_console { + char *buffer; + size_t buffer_len; + imv_console_callback callback; + void *callback_data; +}; + +struct imv_console *imv_console_create(void) +{ + struct imv_console *console = calloc(1, sizeof *console); + console->buffer_len = 1024; + return console; +} + +void imv_console_free(struct imv_console *console) +{ + if (console->buffer) { + free(console->buffer); + console->buffer = NULL; + } + free(console); +} + +void imv_console_set_command_callback(struct imv_console *console, + imv_console_callback callback, void *data) +{ + console->callback = callback; + console->callback_data = data; +} + +bool imv_console_is_active(struct imv_console *console) +{ + return console->buffer != NULL; +} + +void imv_console_activate(struct imv_console *console) +{ + if (console->buffer) { + return; + } + + console->buffer = calloc(1, console->buffer_len); +} + +void imv_console_input(struct imv_console *console, const char *text) +{ + if (!console || !console->buffer) { + return; + } + + strncat(console->buffer, text, console->buffer_len - 1); +} + +bool imv_console_key(struct imv_console *console, const char *key) +{ + if (!console || !console->buffer) { + return false; + } + + if (!strcmp("Escape", key)) { + free(console->buffer); + console->buffer = NULL; + return true; + } + + if (!strcmp("Return", key)) { + if (console->callback) { + console->callback(console->buffer, console->callback_data); + } + free(console->buffer); + console->buffer = NULL; + return true; + } + + if (!strcmp("BackSpace", key)) { + const size_t len = strlen(console->buffer); + if (len > 0) { + console->buffer[len - 1] = '\0'; + } + return true; + } + + return false; +} + +const char *imv_console_prompt(struct imv_console *console) +{ + return console->buffer; +} + +const char *imv_console_backlog(struct imv_console *console) +{ + (void)console; + return NULL; +} + +void imv_console_write(struct imv_console *console, const char *text) +{ + (void)console; + (void)text; +} + +void imv_console_add_completion(struct imv_console *console, const char *template) +{ + (void)console; + (void)template; +} diff --git a/src/console.h b/src/console.h new file mode 100644 index 0000000..22cf9b6 --- /dev/null +++ b/src/console.h @@ -0,0 +1,47 @@ +#ifndef IMV_CONSOLE +#define IMV_CONSOLE + +#include + +struct imv_console; + +/* Create a console instance */ +struct imv_console *imv_console_create(void); + +/* Clean up a console */ +void imv_console_free(struct imv_console *console); + +/* Set the callback to be invoked when a command to run by the console */ +typedef void (*imv_console_callback)(const char *command, void *data); +void imv_console_set_command_callback(struct imv_console *console, + imv_console_callback callback, void *data); + +/* Returns true if console is still active (i.e. user hasn't hit enter/escape yet */ +bool imv_console_is_active(struct imv_console *console); + +/* Mark console as activated until user exits or submits a command */ +void imv_console_activate(struct imv_console *console); + +/* Pass text input to the console */ +void imv_console_input(struct imv_console *console, const char *text); + +/* Pass a key input to the console. Returns true if consumed. If so, + * do not also send input text to the console. + */ +bool imv_console_key(struct imv_console *console, const char *key); + +/* What is the console prompt's current text? */ +const char *imv_console_prompt(struct imv_console *console); + +/* What is the output history of the console? */ +const char *imv_console_backlog(struct imv_console *console); + +/* Write some text to the console's backlog */ +void imv_console_write(struct imv_console *console, const char *text); + +/* Add a tab-completion template. If the users hits tab, the rest of the + * command will be suggested. If multiple matches, tab will cycle through them. + */ +void imv_console_add_completion(struct imv_console *console, const char *template); + +#endif diff --git a/src/image.c b/src/image.c index e57d2c9..f4df7c9 100644 --- a/src/image.c +++ b/src/image.c @@ -1,170 +1,80 @@ #include "image.h" -#include "log.h" -#include +#include "bitmap.h" struct imv_image { - int width; /* width of the image overall */ - int height; /* height of the image overall */ - int num_chunks; /* number of chunks allocated */ - SDL_Texture **chunks; /* array of chunks */ - int num_chunks_wide; /* number of chunks per row of the image */ - int num_chunks_tall; /* number of chunks per column of the image */ - int chunk_width; /* chunk width */ - int chunk_height; /* chunk height */ - int last_chunk_width; /* width of rightmost chunk */ - int last_chunk_height; /* height of bottommost chunk */ - SDL_Renderer *renderer; /* SDL renderer to draw to */ + int width; + int height; + struct imv_bitmap *bitmap; + #ifdef IMV_BACKEND_LIBRSVG + RsvgHandle *svg; + #endif }; -struct imv_image *imv_image_create(SDL_Renderer *r) +struct imv_image *imv_image_create_from_bitmap(struct imv_bitmap *bmp) { - struct imv_image *image = malloc(sizeof *image); - memset(image, 0, sizeof(struct imv_image)); - image->renderer = r; - - SDL_RendererInfo ri; - SDL_GetRendererInfo(r, &ri); - image->chunk_width = ri.max_texture_width != 0 ? ri.max_texture_width : 4096; - image->chunk_height = ri.max_texture_height != 0 ? ri.max_texture_height : 4096; + struct imv_image *image = calloc(1, sizeof *image); + image->width = bmp->width; + image->height = bmp->height; + image->bitmap = bmp; return image; } -void imv_image_free(struct imv_image *image) +#ifdef IMV_BACKEND_LIBRSVG +struct imv_image *imv_image_create_from_svg(RsvgHandle *handle) { - if(!image) { - return; - } - if(image->num_chunks > 0) { - for(int i = 0; i < image->num_chunks; ++i) { - SDL_DestroyTexture(image->chunks[i]); - } - free(image->chunks); - image->num_chunks = 0; - image->chunks = NULL; - image->renderer = NULL; - } - free(image); -} + struct imv_image *image = calloc(1, sizeof *image); + image->svg = handle; -static int convert_pixelformat(enum imv_pixelformat fmt) -{ - if (fmt == IMV_ARGB) { - return SDL_PIXELFORMAT_ARGB8888; - } else if (fmt == IMV_ABGR) { - return SDL_PIXELFORMAT_ABGR8888; - } else { - imv_log(IMV_WARNING, "Unknown pixel format. Defaulting to ARGB\n"); - return SDL_PIXELFORMAT_ARGB8888; - } + RsvgDimensionData dim; + rsvg_handle_get_dimensions(handle, &dim); + image->width = dim.width; + image->height = dim.height; + return image; } +#endif -int imv_image_set_bitmap(struct imv_image *image, struct imv_bitmap *bmp) +void imv_image_free(struct imv_image *image) { - image->width = bmp->width; - image->height = bmp->height; - - /* figure out how many chunks are needed, and create them */ - if(image->num_chunks > 0) { - for(int i = 0; i < image->num_chunks; ++i) { - SDL_DestroyTexture(image->chunks[i]); - } - free(image->chunks); - } - - image->num_chunks_wide = 1 + image->width / image->chunk_width; - image->num_chunks_tall = 1 + image->height / image->chunk_height; - - image->last_chunk_width = image->width % image->chunk_width; - image->last_chunk_height = image->height % image->chunk_height; - - if(image->last_chunk_width == 0) { - image->last_chunk_width = image->chunk_width; - } - if(image->last_chunk_height == 0) { - image->last_chunk_height = image->chunk_height; + if (!image) { + return; } - image->num_chunks = image->num_chunks_wide * image->num_chunks_tall; - image->chunks = malloc(sizeof(SDL_Texture*) * image->num_chunks); - - const int format = convert_pixelformat(bmp->format); - size_t failed_at = -1; - for(int y = 0; y < image->num_chunks_tall; ++y) { - for(int x = 0; x < image->num_chunks_wide; ++x) { - const bool is_last_h_chunk = (x == image->num_chunks_wide - 1); - const bool is_last_v_chunk = (y == image->num_chunks_tall - 1); - const size_t index = x + y * image->num_chunks_wide; - image->chunks[index] = - SDL_CreateTexture(image->renderer, - format, - SDL_TEXTUREACCESS_STATIC, - is_last_h_chunk ? image->last_chunk_width : image->chunk_width, - is_last_v_chunk ? image->last_chunk_height : image->chunk_height); - SDL_SetTextureBlendMode(image->chunks[index], - SDL_BLENDMODE_BLEND); - if (image->chunks[index] == NULL) { - failed_at = x + y * image->num_chunks_wide; - break; - } - } + if (image->bitmap) { + imv_bitmap_free(image->bitmap); } - if (failed_at != (size_t)-1) { - for (size_t i = 0; i < failed_at; ++i) { - SDL_DestroyTexture(image->chunks[i]); - } - free(image->chunks); - image->num_chunks = 0; - image->chunks = NULL; - return 1; +#ifdef IMV_BACKEND_LIBRSVG + if (image->svg) { + GError *error = NULL; + rsvg_handle_close(image->svg, &error); } +#endif - for (int y = 0; y < image->num_chunks_tall; ++y) { - for (int x = 0; x < image->num_chunks_wide; ++x) { - ptrdiff_t offset = 4 * x * image->chunk_width + - y * 4 * image->width * image->chunk_height; - unsigned char* addr = bmp->data + offset; - SDL_UpdateTexture(image->chunks[x + y * image->num_chunks_wide], - NULL, addr, 4 * image->width); - } - } + free(image); +} - return 0; +int imv_image_width(const struct imv_image *image) +{ + return image ? image->width : 0; } -void imv_image_draw(struct imv_image *image, int bx, int by, double scale) +int imv_image_height(const struct imv_image *image) { - int offset_x = 0; - int offset_y = 0; - for(int y = 0; y < image->num_chunks_tall; ++y) { - for(int x = 0; x < image->num_chunks_wide; ++x) { - int img_w, img_h; - SDL_QueryTexture(image->chunks[x + y * image->num_chunks_wide], NULL, NULL, - &img_w, &img_h); - SDL_Rect view_area = { - bx + offset_x, - by + offset_y, - img_w * scale, - img_h * scale - }; - SDL_RenderCopy(image->renderer, - image->chunks[x + y * image->num_chunks_wide], NULL, &view_area); - offset_x += image->chunk_width * scale; - } - offset_x = 0; - offset_y += image->chunk_height * scale; - } + return image ? image->height : 0; } -int imv_image_width(const struct imv_image *image) +/* Non-public functions, only used by imv_canvas */ +struct imv_bitmap *imv_image_get_bitmap(const struct imv_image *image) { - return image->width; + return image->bitmap; } -int imv_image_height(const struct imv_image *image) +#ifdef IMV_BACKEND_LIBRSVG +RsvgHandle *imv_image_get_svg(const struct imv_image *image) { - return image->height; + return image->svg; } +#endif /* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/image.h b/src/image.h index e2fda4f..b9006e1 100644 --- a/src/image.h +++ b/src/image.h @@ -2,22 +2,22 @@ #define IMV_IMAGE_H #include "bitmap.h" -#include + +#ifdef IMV_BACKEND_LIBRSVG +#include +#endif struct imv_image; -/* Creates an instance of imv_image */ -struct imv_image *imv_image_create(SDL_Renderer *r); +struct imv_image *imv_image_create_from_bitmap(struct imv_bitmap *bmp); + +#ifdef IMV_BACKEND_LIBRSVG +struct imv_image *imv_image_create_from_svg(RsvgHandle *handle); +#endif /* Cleans up an imv_image instance */ void imv_image_free(struct imv_image *image); -/* Updates the image to contain the data in the bitmap parameter */ -int imv_image_set_bitmap(struct imv_image *image, struct imv_bitmap *bmp); - -/* Draw the image at the given position with the given scale */ -void imv_image_draw(struct imv_image *image, int x, int y, double scale); - /* Get the image width */ int imv_image_width(const struct imv_image *image); diff --git a/src/imv.c b/src/imv.c index bf10dcd..50fbbcc 100644 --- a/src/imv.c +++ b/src/imv.c @@ -10,19 +10,23 @@ #include #include -#include -#include +#define GLFW_EXPOSE_NATIVE_X11 +#define GLFW_EXPOSE_NATIVE_WAYLAND +#include +#include #include "backend.h" #include "binds.h" +#include "canvas.h" #include "commands.h" +#include "console.h" #include "image.h" #include "ini.h" +#include "keyboard.h" #include "list.h" #include "log.h" #include "navigator.h" #include "source.h" -#include "util.h" #include "viewport.h" /* Some systems like GNU/Hurd don't define PATH_MAX */ @@ -37,12 +41,6 @@ enum scaling_mode { SCALING_MODE_COUNT }; -enum upscaling_method { - UPSCALING_LINEAR, - UPSCALING_NEAREST_NEIGHBOUR, - UPSCALING_METHOD_COUNT, -}; - static const char *scaling_label[] = { "actual size", "shrink to fit", @@ -67,6 +65,29 @@ struct backend_chain { struct backend_chain *next; }; +enum internal_event_type { + NEW_IMAGE, + BAD_IMAGE, + NEW_PATH +}; + +struct internal_event { + enum internal_event_type type; + union { + struct { + struct imv_image *image; + int frametime; + bool is_new_image; + } new_image; + struct { + char *error; + } bad_image; + struct { + char *path; + } new_path; + } data; +}; + struct imv { /* set to true to trigger clean exit */ bool quit; @@ -113,26 +134,40 @@ struct imv { /* scale up / down images to match window, or actual size */ enum scaling_mode scaling_mode; - /* show a solid background colour, or chequerboard pattern */ - enum background_type background_type; - /* the aforementioned background colour */ - struct { unsigned char r, g, b; } background_color; + struct { + /* show a solid background colour, or chequerboard pattern */ + enum background_type type; + /* the aforementioned background colour */ + struct { unsigned char r, g, b; } color; + } background; /* slideshow state tracking */ - unsigned long slideshow_image_duration; - unsigned long slideshow_time_elapsed; + struct { + double duration; + double elapsed; + } slideshow; - /* for animated images, the GetTicks() time to display the next frame */ - unsigned int next_frame_due; - /* how long the next frame to be put onscreen should be displayed for */ - int next_frame_duration; - /* the next frame of an animated image, pre-fetched */ - struct imv_bitmap *next_frame; + struct { + /* for animated images, the getTime() time to display the next frame */ + double due; + /* how long the next frame to be put onscreen should be displayed for */ + double duration; + /* the next frame of an animated image, pre-fetched */ + struct imv_image *image; + } next_frame; - /* overlay font name */ - char *font_name; - /* buffer for storing input commands, NULL when not in command mode */ - char *input_buffer; + struct imv_image *current_image; + + struct { + double x; + double y; + } last_cursor_position; + + /* overlay font */ + struct { + char *name; + int size; + } font; /* if specified by user, the path of the first image to display */ char *starting_path; @@ -141,43 +176,26 @@ struct imv { char *title_text; char *overlay_text; - /* when true, imv will ignore all window events until it encounters a - * ENABLE_INPUT user-event. This is required to overcome a bug where - * SDL will send input events to us from before we gained focus - */ - bool ignore_window_events; - /* imv subsystems */ struct imv_binds *binds; struct imv_navigator *navigator; struct backend_chain *backends; - struct imv_source *source; + struct imv_source *current_source; struct imv_source *last_source; struct imv_commands *commands; - struct imv_image *image; + struct imv_console *console; struct imv_viewport *view; + struct imv_keyboard *keyboard; + struct imv_canvas *canvas; /* if reading an image from stdin, this is the buffer for it */ void *stdin_image_data; size_t stdin_image_data_len; - /* SDL subsystems */ - SDL_Window *window; - SDL_Renderer *renderer; - TTF_Font *font; - SDL_Texture *background_image; - bool sdl_init; - bool ttf_init; - struct { - unsigned int NEW_IMAGE; - unsigned int BAD_IMAGE; - unsigned int NEW_PATH; - unsigned int ENABLE_INPUT; - } events; - struct { - int width; - int height; - } current_image; + GLFWwindow *window; + bool glfw_init; + struct list *internal_events; + pthread_mutex_t internal_events_mutex; }; void command_quit(struct list *args, const char *argstr, void *data); @@ -198,11 +216,11 @@ void command_set_scaling_mode(struct list *args, const char *argstr, void *data) void command_set_slideshow_duration(struct list *args, const char *argstr, void *data); static bool setup_window(struct imv *imv); -static void handle_event(struct imv *imv, SDL_Event *event); +static void consume_internal_event(struct imv *imv, struct internal_event *event); static void render_window(struct imv *imv); static void update_env_vars(struct imv *imv); static size_t generate_env_text(struct imv *imv, char *buf, size_t len, const char *format); - +static size_t read_from_stdin(void **buffer); /* Finds the next split between commands in a string (';'). Provides a pointer * to the next character after the delimiter as out, or a pointer to '\0' if @@ -244,7 +262,7 @@ static void split_commands(const char *start, const char **out, size_t *len) static bool add_bind(struct imv *imv, const char *keys, const char *commands) { struct list *list = imv_bind_parse_keys(keys); - if(!list) { + if (!list) { imv_log(IMV_ERROR, "Invalid key combination"); return false; } @@ -291,77 +309,196 @@ static bool add_bind(struct imv *imv, const char *keys, const char *commands) return success; } -static int async_free_source_thread(void *raw) +static void *async_free_source_thread(void *raw) { struct imv_source *src = raw; src->free(src); - return 0; + return NULL; } static void async_free_source(struct imv_source *src) { - SDL_Thread *thread = SDL_CreateThread(async_free_source_thread, - "async_free_source", src); - SDL_DetachThread(thread); + typedef void *(*thread_func)(void*); + pthread_t thread; + pthread_create(&thread, NULL, (thread_func)async_free_source_thread, src); + pthread_detach(thread); } static void async_load_first_frame(struct imv_source *src) { - typedef int (*thread_func)(void*); - SDL_Thread *thread = SDL_CreateThread((thread_func)src->load_first_frame, - "async_load_first_frame", - src); - SDL_DetachThread(thread); + typedef void *(*thread_func)(void*); + pthread_t thread; + pthread_create(&thread, NULL, (thread_func)src->load_first_frame, src); + pthread_detach(thread); } static void async_load_next_frame(struct imv_source *src) { - typedef int (*thread_func)(void*); - SDL_Thread *thread = SDL_CreateThread((thread_func)src->load_next_frame, - "async_load_next_frame", - src); - SDL_DetachThread(thread); + typedef void *(*thread_func)(void*); + pthread_t thread; + pthread_create(&thread, NULL, (thread_func)src->load_next_frame, src); + pthread_detach(thread); } static void source_callback(struct imv_source_message *msg) { struct imv *imv = msg->user_data; - if (msg->source != imv->source) { + if (msg->source != imv->current_source) { /* We received a message from an old source, tidy up contents * as required, but ignore it. */ - if (msg->bitmap) { - imv_bitmap_free(msg->bitmap); + if (msg->image) { + imv_image_free(msg->image); } return; } - SDL_Event event; - SDL_zero(event); - - if (msg->bitmap) { - event.type = imv->events.NEW_IMAGE; - event.user.data1 = msg->bitmap; - event.user.code = msg->frametime; + struct internal_event *event = calloc(1, sizeof *event); + if (msg->image) { + event->type = NEW_IMAGE; + event->data.new_image.image = msg->image; + event->data.new_image.frametime = msg->frametime; - /* Keep track of the last source to send us a bitmap in order to detect + /* Keep track of the last source to send us an image in order to detect * when we're getting a new image, as opposed to a new frame from the * same image. */ - uintptr_t is_new_image = msg->source != imv->last_source; - event.user.data2 = (void*)is_new_image; + event->data.new_image.is_new_image = msg->source != imv->last_source; imv->last_source = msg->source; } else { - event.type = imv->events.BAD_IMAGE; + event->type = BAD_IMAGE; /* TODO: Something more elegant with error messages */ - /* event.user.data1 = strdup(msg->error); */ + event->data.bad_image.error = strdup(msg->error); + } + + pthread_mutex_lock(&imv->internal_events_mutex); + list_append(imv->internal_events, event); + pthread_mutex_unlock(&imv->internal_events_mutex); + + glfwPostEmptyEvent(); +} + +static void command_callback(const char *text, void *data) +{ + struct imv *imv = data; + struct list *commands = list_create(); + list_append(commands, strdup(text)); + imv_command_exec_list(imv->commands, commands, imv); + list_deep_free(commands); + imv->need_redraw = true; +} + +static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) +{ + (void)key; + (void)mods; + struct imv *imv = glfwGetWindowUserPointer(window); + + imv_keyboard_update_key(imv->keyboard, scancode, action == GLFW_PRESS); + + if (action != GLFW_PRESS) { + return; + } + + char keyname[128] = {0}; + imv_keyboard_keyname(imv->keyboard, scancode, keyname, sizeof keyname); + + if (imv_console_is_active(imv->console)) { + + if (imv_console_key(imv->console, keyname)) { + imv->need_redraw = true; + return; + } + + char text[128]; + size_t len = imv_keyboard_get_text(imv->keyboard, scancode, text, sizeof text); + if (len >= sizeof text) { + imv_log(IMV_WARNING, "Keyboard input too large for input buffer. Discarding.\n"); + } else { + imv_console_input(imv->console, text); + } + + } else { + /* In regular mode see if we should enter command mode, otherwise send input + * to the bind system. + */ + if (!strcmp("colon", keyname)) { + fprintf(stderr, "active console\n"); + imv_console_activate(imv->console); + imv->need_redraw = true; + return; + } + + char *keyname = imv_keyboard_describe_key(imv->keyboard, scancode); + if (!keyname) { + return; + } + + struct list *cmds = imv_bind_handle_event(imv->binds, keyname); + if (cmds) { + imv_command_exec_list(imv->commands, cmds, imv); + } + fprintf(stderr, "user hit: '%s'\n", keyname); + free(keyname); + } + + imv->need_redraw = true; +} + +static void resize_callback(GLFWwindow *window, int width, int height) +{ + /* This callback is used for both window and framebuffer resize + * events, so ignore what it passes in and explicitly get the values + * we need for both. + */ + (void)width; + (void)height; + struct imv *imv = glfwGetWindowUserPointer(window); + int ww, wh; + int bw, bh; + glfwGetWindowSize(imv->window, &ww, &wh); + glfwGetFramebufferSize(imv->window, &bw, &bh); + imv_viewport_update(imv->view, ww, wh, bw, bh, imv->current_image); + imv_canvas_resize(imv->canvas, bw, bh); + glViewport(0, 0, bw, bh); +} + +static void scroll_callback(GLFWwindow *window, double x, double y) +{ + (void)x; + struct imv *imv = glfwGetWindowUserPointer(window); + imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_MOUSE, + imv->last_cursor_position.x, + imv->last_cursor_position.y, -y); +} + +static void cursor_callback(GLFWwindow *window, double x, double y) +{ + struct imv *imv = glfwGetWindowUserPointer(window); + + if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) { + const double dx = x - imv->last_cursor_position.x; + const double dy = y - imv->last_cursor_position.y; + imv_viewport_move(imv->view, dx, dy, imv->current_image); } + imv->last_cursor_position.x = x; + imv->last_cursor_position.y = y; +} - SDL_PushEvent(&event); + +static void log_to_stderr(enum imv_log_level level, const char *text, void *data) +{ + (void)data; + if (level >= IMV_INFO) { + fprintf(stderr, "%s", text); + } } struct imv *imv_create(void) { + /* Attach log to stderr */ + imv_log_add_log_callback(&log_to_stderr, NULL); + struct imv *imv = calloc(1, sizeof *imv); imv->initial_width = 1280; imv->initial_height = 720; @@ -369,10 +506,13 @@ struct imv *imv_create(void) imv->need_rescale = true; imv->scaling_mode = SCALING_FULL; imv->loop_input = true; - imv->font_name = strdup("Monospace:24"); + imv->font.name = strdup("Monospace"); + imv->font.size = 24; imv->binds = imv_binds_create(); imv->navigator = imv_navigator_create(); imv->commands = imv_commands_create(); + imv->console = imv_console_create(); + imv_console_set_command_callback(imv->console, &command_callback, imv); imv->title_text = strdup( "imv - [${imv_current_index}/${imv_file_count}]" " [${imv_width}x${imv_height}] [${imv_scale}%]" @@ -383,6 +523,8 @@ struct imv *imv_create(void) " [${imv_width}x${imv_height}] [${imv_scale}%]" " $imv_current_file [$imv_scaling_mode]" ); + imv->internal_events = list_create(); + pthread_mutex_init(&imv->internal_events_mutex, NULL); imv_command_register(imv->commands, "quit", &command_quit); imv_command_register(imv->commands, "pan", &command_pan); @@ -403,11 +545,11 @@ struct imv *imv_create(void) add_bind(imv, "q", "quit"); add_bind(imv, "", "select_rel -1"); - add_bind(imv, "", "select_rel -1"); + add_bind(imv, "", "select_rel -1"); add_bind(imv, "", "select_rel 1"); - add_bind(imv, "", "select_rel 1"); + add_bind(imv, "", "select_rel 1"); add_bind(imv, "gg", "select_abs 0"); - add_bind(imv, "", "select_abs -1"); + add_bind(imv, "", "select_abs -1"); add_bind(imv, "j", "pan 0 -50"); add_bind(imv, "k", "pan 0 50"); add_bind(imv, "h", "pan 50 0"); @@ -416,66 +558,56 @@ struct imv *imv_create(void) add_bind(imv, "f", "fullscreen"); add_bind(imv, "d", "overlay"); add_bind(imv, "p", "exec echo $imv_current_file"); - add_bind(imv, "", "zoom 1"); + add_bind(imv, "", "zoom 1"); add_bind(imv, "", "zoom 1"); - add_bind(imv, "+", "zoom 1"); + add_bind(imv, "", "zoom 1"); add_bind(imv, "i", "zoom 1"); add_bind(imv, "", "zoom -1"); - add_bind(imv, "-", "zoom -1"); + add_bind(imv, "", "zoom -1"); add_bind(imv, "o", "zoom -1"); add_bind(imv, "c", "center"); add_bind(imv, "s", "scaling_mode next"); add_bind(imv, "a", "zoom actual"); add_bind(imv, "r", "reset"); - add_bind(imv, ".", "next_frame"); - add_bind(imv, "", "toggle_playing"); + add_bind(imv, "", "next_frame"); + add_bind(imv, "", "toggle_playing"); add_bind(imv, "t", "slideshow_duration +1"); - add_bind(imv, "", "slideshow_duration -1"); + add_bind(imv, "", "slideshow_duration -1"); return imv; } void imv_free(struct imv *imv) { - free(imv->font_name); + free(imv->font.name); free(imv->title_text); free(imv->overlay_text); imv_binds_free(imv->binds); imv_navigator_free(imv->navigator); - if (imv->source) { - imv->source->free(imv->source); + if (imv->current_source) { + imv->current_source->free(imv->current_source); } imv_commands_free(imv->commands); + imv_console_free(imv->console); imv_viewport_free(imv->view); - if (imv->image) { - imv_image_free(imv->image); + imv_keyboard_free(imv->keyboard); + imv_canvas_free(imv->canvas); + if (imv->current_image) { + imv_image_free(imv->current_image); } - if (imv->next_frame) { - imv_bitmap_free(imv->next_frame); + if (imv->next_frame.image) { + imv_image_free(imv->next_frame.image); } - if(imv->stdin_image_data) { + if (imv->stdin_image_data) { free(imv->stdin_image_data); } - if(imv->input_buffer) { - free(imv->input_buffer); - } - if(imv->renderer) { - SDL_DestroyRenderer(imv->renderer); - } - if(imv->window) { - SDL_DestroyWindow(imv->window); - } - if(imv->background_image) { - SDL_DestroyTexture(imv->background_image); + list_free(imv->internal_events); + pthread_mutex_destroy(&imv->internal_events_mutex); + if (imv->window) { + glfwDestroyWindow(imv->window); } - if(imv->font) { - TTF_CloseFont(imv->font); - } - if(imv->ttf_init) { - TTF_Quit(); - } - if(imv->sdl_init) { - SDL_Quit(); + if (imv->glfw_init) { + glfwTerminate(); } free(imv); } @@ -490,21 +622,21 @@ void imv_install_backend(struct imv *imv, const struct imv_backend *backend) static bool parse_bg(struct imv *imv, const char *bg) { - if(strcmp("checks", bg) == 0) { - imv->background_type = BACKGROUND_CHEQUERED; + if (strcmp("checks", bg) == 0) { + imv->background.type = BACKGROUND_CHEQUERED; } else { - imv->background_type = BACKGROUND_SOLID; - if(*bg == '#') + imv->background.type = BACKGROUND_SOLID; + if (*bg == '#') ++bg; char *ep; uint32_t n = strtoul(bg, &ep, 16); - if(*ep != '\0' || ep - bg != 6 || n > 0xFFFFFF) { + if (*ep != '\0' || ep - bg != 6 || n > 0xFFFFFF) { imv_log(IMV_ERROR, "Invalid hex color: '%s'\n", bg); return false; } - imv->background_color.b = n & 0xFF; - imv->background_color.g = (n >> 8) & 0xFF; - imv->background_color.r = (n >> 16); + imv->background.color.b = n & 0xFF; + imv->background.color.g = (n >> 8) & 0xFF; + imv->background.color.r = (n >> 16); } return true; } @@ -512,24 +644,7 @@ static bool parse_bg(struct imv *imv, const char *bg) static bool parse_slideshow_duration(struct imv *imv, const char *duration) { char *decimal; - imv->slideshow_image_duration = strtoul(duration, &decimal, 10); - imv->slideshow_image_duration *= 1000; - if (*decimal == '.') { - char *ep; - long delay = strtoul(++decimal, &ep, 10); - for (int i = 3 - (ep - decimal); i; i--) { - delay *= 10; - } - if (delay < 1000) { - imv->slideshow_image_duration += delay; - } else { - imv->slideshow_image_duration = ULONG_MAX; - } - } - if (imv->slideshow_image_duration == ULONG_MAX) { - imv_log(IMV_ERROR, "Wrong slideshow duration '%s'. Aborting.\n", optarg); - return false; - } + imv->slideshow.duration = strtod(duration, &decimal); return true; } @@ -588,28 +703,31 @@ static bool parse_resizing_mode(struct imv *imv, const char *method) return false; } -static int load_paths_from_stdin(void *data) +static void *load_paths_from_stdin(void *data) { struct imv *imv = data; imv_log(IMV_INFO, "Reading paths from stdin..."); char buf[PATH_MAX]; - while(fgets(buf, sizeof(buf), stdin) != NULL) { + while (fgets(buf, sizeof(buf), stdin) != NULL) { size_t len = strlen(buf); - if(buf[len-1] == '\n') { + if (buf[len-1] == '\n') { buf[--len] = 0; } - if(len > 0) { - /* return the path via SDL event queue */ - SDL_Event event; - SDL_zero(event); - event.type = imv->events.NEW_PATH; - event.user.data1 = strdup(buf); - SDL_PushEvent(&event); + if (len > 0) { + struct internal_event *event = calloc(1, sizeof *event); + event->type = NEW_PATH; + event->data.new_path.path = strdup(buf); + + pthread_mutex_lock(&imv->internal_events_mutex); + list_append(imv->internal_events, event); + pthread_mutex_unlock(&imv->internal_events_mutex); + + glfwPostEmptyEvent(); } } - return 0; + return NULL; } static void print_help(struct imv *imv) @@ -646,7 +764,8 @@ bool imv_parse_args(struct imv *imv, int argc, char **argv) int o; - while((o = getopt(argc, argv, "frdwWxhvlu:s:n:b:t:")) != -1) { + /* TODO getopt_long */ + while ((o = getopt(argc, argv, "frdwWxhvlu:s:n:b:t:")) != -1) { switch(o) { case 'f': imv->fullscreen = true; break; case 'r': imv->recursive_load = true; break; @@ -660,30 +779,30 @@ bool imv_parse_args(struct imv *imv, int argc, char **argv) print_help(imv); imv->quit = true; return true; - case 'v': + case 'v': printf("Version: %s\n", IMV_VERSION); imv->quit = true; return false; case 's': - if(!parse_scaling_mode(imv, optarg)) { + if (!parse_scaling_mode(imv, optarg)) { imv_log(IMV_ERROR, "Invalid scaling mode. Aborting.\n"); return false; } break; case 'u': - if(!parse_upscaling_method(imv, optarg)) { + if (!parse_upscaling_method(imv, optarg)) { imv_log(IMV_ERROR, "Invalid upscaling method. Aborting.\n"); return false; } break; case 'b': - if(!parse_bg(imv, optarg)) { + if (!parse_bg(imv, optarg)) { imv_log(IMV_ERROR, "Invalid background. Aborting.\n"); return false; } break; case 't': - if(!parse_slideshow_duration(imv, optarg)) { + if (!parse_slideshow_duration(imv, optarg)) { imv_log(IMV_ERROR, "Invalid slideshow duration. Aborting.\n"); return false; } @@ -698,19 +817,19 @@ bool imv_parse_args(struct imv *imv, int argc, char **argv) argv += optind; /* if no paths are given as args, expect them from stdin */ - if(argc == 0) { + if (argc == 0) { imv->paths_from_stdin = true; } else { /* otherwise, add the paths */ bool data_from_stdin = false; - for(int i = 0; i < argc; ++i) { + for (int i = 0; i < argc; ++i) { /* Special case: '-' denotes reading image data from stdin */ - if(!strcmp("-", argv[i])) { - if(imv->paths_from_stdin) { + if (!strcmp("-", argv[i])) { + if (imv->paths_from_stdin) { imv_log(IMV_ERROR, "Can't read paths AND image data from stdin. Aborting.\n"); return false; - } else if(data_from_stdin) { + } else if (data_from_stdin) { imv_log(IMV_ERROR, "Can't read image data from stdin twice. Aborting.\n"); return false; } @@ -733,64 +852,67 @@ void imv_add_path(struct imv *imv, const char *path) int imv_run(struct imv *imv) { - if(imv->quit) + if (imv->quit) return 0; - if(!setup_window(imv)) + if (!setup_window(imv)) return 1; /* if loading paths from stdin, kick off a thread to do that - we'll receive - * events back via SDL */ - if(imv->paths_from_stdin) { - SDL_Thread *thread; - thread = SDL_CreateThread(load_paths_from_stdin, "load_paths_from_stdin", imv); - SDL_DetachThread(thread); + * events back via internal events */ + if (imv->paths_from_stdin) { + pthread_t thread; + pthread_create(&thread, NULL, load_paths_from_stdin, imv); + pthread_detach(thread); } - if(imv->starting_path) { + if (imv->starting_path) { int index = imv_navigator_find_path(imv->navigator, imv->starting_path); - if(index == -1) { + if (index == -1) { index = (int) strtol(imv->starting_path, NULL, 10); index -= 1; /* input is 1-indexed, internally we're 0 indexed */ - if(errno == EINVAL) { + if (errno == EINVAL) { index = -1; } } - if(index >= 0) { + if (index >= 0) { imv_navigator_select_str(imv->navigator, index); } else { imv_log(IMV_ERROR, "Invalid starting image: %s\n", imv->starting_path); } } - /* cache current image's dimensions */ - imv->current_image.width = 0; - imv->current_image.height = 0; - /* time keeping */ - unsigned int last_time = SDL_GetTicks(); - unsigned int current_time; + double last_time = glfwGetTime(); + double current_time; + + + while (!imv->quit && !glfwWindowShouldClose(imv->window)) { - while(!imv->quit) { + glfwPollEvents(); - SDL_Event e; - while(!imv->quit && SDL_PollEvent(&e)) { - handle_event(imv, &e); + /* Handle any new internal events */ + pthread_mutex_lock(&imv->internal_events_mutex); + while (imv->internal_events->len > 0) { + struct internal_event *event = imv->internal_events->items[0]; + consume_internal_event(imv, event); + list_remove(imv->internal_events, 0); } + pthread_mutex_unlock(&imv->internal_events_mutex); /* if we're quitting, don't bother drawing any more images */ - if(imv->quit) { + if (imv->quit) { break; } /* Check if navigator wrapped around paths lists */ - if(!imv->loop_input && imv_navigator_wrapped(imv->navigator)) { + if (!imv->loop_input && imv_navigator_wrapped(imv->navigator)) { break; } /* if we're out of images, and we're not expecting more from stdin, quit */ - if(!imv->paths_from_stdin && imv_navigator_length(imv->navigator) == 0) { + if (!imv->paths_from_stdin && imv_navigator_length(imv->navigator) == 0) { imv_log(IMV_INFO, "No input files left. Exiting.\n"); imv->quit = true; continue; @@ -804,7 +926,7 @@ int imv_run(struct imv *imv) while (imv_navigator_poll_changed(imv->navigator)) { const char *current_path = imv_navigator_selection(imv->navigator); /* check we got a path back */ - if(strcmp("", current_path)) { + if (strcmp("", current_path)) { const bool path_is_stdin = !strcmp("-", current_path); struct imv_source *new_source; @@ -843,20 +965,20 @@ int imv_run(struct imv *imv) } if (result == BACKEND_SUCCESS) { - if (imv->source) { - async_free_source(imv->source); + if (imv->current_source) { + async_free_source(imv->current_source); } - imv->source = new_source; - imv->source->callback = &source_callback; - imv->source->user_data = imv; - async_load_first_frame(imv->source); + imv->current_source = new_source; + imv->current_source->callback = &source_callback; + imv->current_source->user_data = imv; + async_load_first_frame(imv->current_source); imv->loading = true; imv_viewport_set_playing(imv->view, true); char title[1024]; generate_env_text(imv, title, sizeof title, imv->title_text); - imv_viewport_set_title(imv->view, title); + glfwSetWindowTitle(imv->window, title); } else { /* Error loading path so remove it from the navigator */ imv_navigator_remove(imv->navigator, current_path); @@ -864,82 +986,96 @@ int imv_run(struct imv *imv) } } - if(imv->need_rescale) { + if (imv->need_rescale) { int ww, wh; - SDL_GetWindowSize(imv->window, &ww, &wh); + glfwGetWindowSize(imv->window, &ww, &wh); imv->need_rescale = false; - if(imv->scaling_mode == SCALING_NONE || + if (imv->scaling_mode == SCALING_NONE || (imv->scaling_mode == SCALING_DOWN - && ww > imv->current_image.width - && wh > imv->current_image.height)) { - imv_viewport_scale_to_actual(imv->view, imv->image); + && ww > imv_image_width(imv->current_image) + && wh > imv_image_height(imv->current_image))) { + imv_viewport_scale_to_actual(imv->view, imv->current_image); } else { - imv_viewport_scale_to_window(imv->view, imv->image); + imv_viewport_scale_to_window(imv->view, imv->current_image); } } - current_time = SDL_GetTicks(); + current_time = glfwGetTime(); /* Check if a new frame is due */ - if (imv_viewport_is_playing(imv->view) && imv->next_frame - && imv->next_frame_due && imv->next_frame_due <= current_time) { - imv_image_set_bitmap(imv->image, imv->next_frame); - imv->current_image.width = imv->next_frame->width; - imv->current_image.height = imv->next_frame->height; - imv_bitmap_free(imv->next_frame); - imv->next_frame = NULL; - imv->next_frame_due = current_time + imv->next_frame_duration; - imv->next_frame_duration = 0; + if (imv_viewport_is_playing(imv->view) && imv->next_frame.image + && imv->next_frame.due && imv->next_frame.due <= current_time) { + if (imv->current_image) { + imv_image_free(imv->current_image); + } + imv->current_image = imv->next_frame.image; + imv->next_frame.image = NULL; + imv->next_frame.due = current_time + imv->next_frame.duration; + imv->next_frame.duration = 0; imv->need_redraw = true; /* Trigger loading of a new frame, now this one's being displayed */ - if (imv->source && imv->source->load_next_frame) { - async_load_next_frame(imv->source); + if (imv->current_source && imv->current_source->load_next_frame) { + async_load_next_frame(imv->current_source); } } /* handle slideshow */ - if(imv->slideshow_image_duration != 0) { - unsigned int dt = current_time - last_time; + if (imv->slideshow.duration != 0.0) { + double dt = current_time - last_time; - imv->slideshow_time_elapsed += dt; - imv->need_redraw = true; /* need to update display */ - if(imv->slideshow_time_elapsed >= imv->slideshow_image_duration) { + imv->slideshow.elapsed += dt; + /* imv->need_redraw = true; /1* need to update display *1/ */ + if (imv->slideshow.elapsed >= imv->slideshow.duration) { imv_navigator_select_rel(imv->navigator, 1); - imv->slideshow_time_elapsed = 0; + imv->slideshow.elapsed = 0; } } last_time = current_time; /* check if the viewport needs a redraw */ - if(imv_viewport_needs_redraw(imv->view)) { + if (imv_viewport_needs_redraw(imv->view)) { imv->need_redraw = true; } - if(imv->need_redraw) { + if (imv->need_redraw) { + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); render_window(imv); - SDL_RenderPresent(imv->renderer); + glfwSwapBuffers(imv->window); } /* sleep until we have something to do */ - unsigned int timeout = 1000; /* milliseconds */ + double timeout = 1.0; /* seconds */ + + /* If we need to display the next frame of an animation soon we should + * limit our sleep until the next frame is due. If the frame is overdue, + * sleep for 1ms at a time to avoid a glfw race between WaitEvents and + * PostEmptyEvent. + */ + if (imv_viewport_is_playing(imv->view) && imv->next_frame.due != 0.0) { + timeout = imv->next_frame.due - current_time; + if (timeout < 0.001) { + timeout = 0.001; + } + } - /* if we need to display the next frame of an animation soon we should - * limit our sleep until the next frame is due */ - if (imv_viewport_is_playing(imv->view) - && imv->next_frame_due > current_time) { - timeout = imv->next_frame_due - current_time; + if (imv->slideshow.duration > 0) { + double timeleft = imv->slideshow.elapsed - imv->slideshow.duration; + if (timeleft > 0.0 && timeleft < timeout) { + timeout = timeleft + 0.005; + } } - /* go to sleep until an input event, etc. or the timeout expires */ - SDL_WaitEventTimeout(NULL, timeout); + /* Go to sleep until an input/internal event or the timeout expires */ + glfwWaitEventsTimeout(timeout); } - if(imv->list_files_at_exit) { - for(size_t i = 0; i < imv_navigator_length(imv->navigator); ++i) + if (imv->list_files_at_exit) { + for (size_t i = 0; i < imv_navigator_length(imv->navigator); ++i) puts(imv_navigator_at(imv->navigator, i)); } @@ -948,81 +1084,74 @@ int imv_run(struct imv *imv) static bool setup_window(struct imv *imv) { - if(SDL_Init(SDL_INIT_VIDEO) != 0) { - imv_log(IMV_ERROR, "SDL Failed to Init: %s\n", SDL_GetError()); + if (!glfwInit()) { + const char *err = NULL; + glfwGetError(&err); + imv_log(IMV_ERROR, "glfw failed to init: %s\n", err); return false; } - /* register custom events */ - imv->events.NEW_IMAGE = SDL_RegisterEvents(1); - imv->events.BAD_IMAGE = SDL_RegisterEvents(1); - imv->events.NEW_PATH = SDL_RegisterEvents(1); - imv->events.ENABLE_INPUT = SDL_RegisterEvents(1); - - imv->sdl_init = true; + imv->glfw_init = true; - imv->window = SDL_CreateWindow( - "imv", - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - imv->initial_width, imv->initial_height, - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + imv->window = glfwCreateWindow(imv->initial_width, imv->initial_height, + "imv", NULL, NULL); - if(!imv->window) { - imv_log(IMV_ERROR, "SDL Failed to create window: %s\n", SDL_GetError()); + if (!imv->window) { + const char *err = NULL; + glfwGetError(&err); + imv_log(IMV_ERROR, "glfw failed to create window: %s\n", err); return false; } - /* we'll use SDL's built-in renderer, hardware accelerated if possible */ - imv->renderer = SDL_CreateRenderer(imv->window, -1, 0); - if(!imv->renderer) { - imv_log(IMV_ERROR, "SDL Failed to create renderer: %s\n", SDL_GetError()); - return false; - } + glfwSetWindowUserPointer(imv->window, imv); + glfwSetKeyCallback(imv->window, &key_callback); + glfwSetScrollCallback(imv->window, &scroll_callback); + glfwSetCursorPosCallback(imv->window, &cursor_callback); + glfwSetWindowSizeCallback(imv->window, &resize_callback); + glfwSetFramebufferSizeCallback(imv->window, &resize_callback); - /* use the appropriate resampling method */ - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, - imv->upscaling_method == UPSCALING_LINEAR? "1" : "0"); + glfwMakeContextCurrent(imv->window); /* allow fullscreen to be maintained even when focus is lost */ - SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, - imv->stay_fullscreen_on_focus_loss ? "0" : "1"); + glfwWindowHint(GLFW_AUTO_ICONIFY, + imv->stay_fullscreen_on_focus_loss ? GLFW_FALSE : GLFW_TRUE); - /* construct a chequered background image */ - if(imv->background_type == BACKGROUND_CHEQUERED) { - imv->background_image = create_chequered(imv->renderer); + { + int ww, wh, bw, bh; + glfwGetWindowSize(imv->window, &ww, &wh); + glfwGetFramebufferSize(imv->window, &bw, &bh); + imv->view = imv_viewport_create(ww, wh, bw, bh); } - /* set up the required fonts and surfaces for displaying the overlay */ - TTF_Init(); - imv->ttf_init = true; - imv->font = load_font(imv->font_name); - if(!imv->font) { - imv_log(IMV_ERROR, "Error loading font: %s\n", TTF_GetError()); - return false; + /* put us in fullscren mode to begin with if requested */ + if (imv->fullscreen) { + GLFWmonitor* monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode* mode = glfwGetVideoMode(monitor); + glfwSetWindowMonitor(imv->window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); } - imv->image = imv_image_create(imv->renderer); - imv->view = imv_viewport_create(imv->window, imv->renderer); - - /* put us in fullscren mode to begin with if requested */ - if(imv->fullscreen) { - imv_viewport_toggle_fullscreen(imv->view); + { + imv->keyboard = imv_keyboard_create(); + assert(imv->keyboard); } - /* start outside of command mode */ - SDL_StopTextInput(); + { + int ww, wh; + glfwGetWindowSize(imv->window, &ww, &wh); + imv->canvas = imv_canvas_create(ww, wh); + imv_canvas_font(imv->canvas, imv->font.name, imv->font.size); + } return true; } -static void handle_new_image(struct imv *imv, struct imv_bitmap *bitmap, int frametime) +static void handle_new_image(struct imv *imv, struct imv_image *image, int frametime) { - imv_image_set_bitmap(imv->image, bitmap); - imv->current_image.width = bitmap->width; - imv->current_image.height = bitmap->height; - imv_bitmap_free(bitmap); + if (imv->current_image) { + imv_image_free(imv->current_image); + } + imv->current_image = image; imv->need_redraw = true; imv->need_rescale = true; /* If autoresizing on every image is enabled, make sure we do so */ @@ -1031,49 +1160,47 @@ static void handle_new_image(struct imv *imv, struct imv_bitmap *bitmap, int fra } if (imv->need_resize) { imv->need_resize = false; - SDL_SetWindowSize(imv->window, imv->current_image.width, imv->current_image.height); + glfwSetWindowSize(imv->window, imv_image_width(image), imv_image_height(image)); if (imv->resize_mode == RESIZE_CENTER) { - SDL_SetWindowPosition(imv->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + /* TODO centering */ + /* glfwSetWindowPos(imv->window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); */ } } imv->loading = false; - imv->next_frame_due = frametime ? SDL_GetTicks() + frametime : 0; - imv->next_frame_duration = 0; + imv->next_frame.due = frametime ? glfwGetTime() + frametime * 0.001 : 0.0; + imv->next_frame.duration = 0.0; /* If this is an animated image, we should kick off loading the next frame */ - if (imv->source && imv->source->load_next_frame && frametime) { - async_load_next_frame(imv->source); + if (imv->current_source && imv->current_source->load_next_frame && frametime) { + async_load_next_frame(imv->current_source); } } -static void handle_new_frame(struct imv *imv, struct imv_bitmap *bitmap, int frametime) +static void handle_new_frame(struct imv *imv, struct imv_image *image, int frametime) { - if (imv->next_frame) { - imv_bitmap_free(imv->next_frame); + if (imv->next_frame.image) { + imv_image_free(imv->next_frame.image); } - imv->next_frame = bitmap; + imv->next_frame.image = image; - imv->next_frame_duration = frametime; + imv->next_frame.duration = frametime * 0.001; } -static void handle_event(struct imv *imv, SDL_Event *event) +static void consume_internal_event(struct imv *imv, struct internal_event *event) { - const int command_buffer_len = 1024; - - if (event->type == imv->events.NEW_IMAGE) { - /* new image vs just a new frame of the same image */ - bool is_new_image = !!event->user.data2; - if (is_new_image) { - handle_new_image(imv, event->user.data1, event->user.code); + if (event->type == NEW_IMAGE) { + /* New image vs just a new frame of the same image */ + if (event->data.new_image.is_new_image) { + handle_new_image(imv, event->data.new_image.image, event->data.new_image.frametime); } else { - handle_new_frame(imv, event->user.data1, event->user.code); + handle_new_frame(imv, event->data.new_image.image, event->data.new_image.frametime); } - return; - } else if (event->type == imv->events.BAD_IMAGE) { - /* an image failed to load, remove it from our image list */ + + } else if (event->type == BAD_IMAGE) { + /* An image failed to load, remove it from our image list */ const char *err_path = imv_navigator_selection(imv->navigator); - /* special case: the image came from stdin */ + /* Special case: the image came from stdin */ if (strcmp(err_path, "-") == 0) { if (imv->stdin_image_data) { free(imv->stdin_image_data); @@ -1084,171 +1211,78 @@ static void handle_event(struct imv *imv, SDL_Event *event) } imv_navigator_remove(imv->navigator, err_path); - return; - } else if (event->type == imv->events.NEW_PATH) { - /* received a new path from the stdin reading thread */ - imv_add_path(imv, event->user.data1); - free(event->user.data1); - /* need to update image count */ + + } else if (event->type == NEW_PATH) { + /* Received a new path from the stdin reading thread */ + imv_add_path(imv, event->data.new_path.path); + free(event->data.new_path.path); + /* Need to update image count in title */ imv->need_redraw = true; - return; - } else if (event->type == imv->events.ENABLE_INPUT) { - imv->ignore_window_events = false; - return; - } else if (imv->ignore_window_events) { - /* Don't try and process this input event, we're in event ignoring mode */ - return; } - switch (event->type) { - case SDL_QUIT: - imv_command_exec(imv->commands, "quit", imv); - break; - case SDL_TEXTINPUT: - strncat(imv->input_buffer, event->text.text, command_buffer_len - 1); - imv->need_redraw = true; - break; - - case SDL_KEYDOWN: - SDL_ShowCursor(SDL_DISABLE); - - if (imv->input_buffer) { - /* in command mode, update the buffer */ - if (event->key.keysym.sym == SDLK_ESCAPE) { - SDL_StopTextInput(); - free(imv->input_buffer); - imv->input_buffer = NULL; - imv->need_redraw = true; - } else if (event->key.keysym.sym == SDLK_RETURN) { - struct list *commands = list_create(); - list_append(commands, imv->input_buffer); - imv_command_exec_list(imv->commands, commands, imv); - SDL_StopTextInput(); - list_free(commands); - free(imv->input_buffer); - imv->input_buffer = NULL; - imv->need_redraw = true; - } else if (event->key.keysym.sym == SDLK_BACKSPACE) { - const size_t len = strlen(imv->input_buffer); - if (len > 0) { - imv->input_buffer[len - 1] = '\0'; - imv->need_redraw = true; - } - } - - return; - } - - /* Hitting : opens command-entry mode, like vim */ - if (event->key.keysym.sym == SDLK_SEMICOLON - && event->key.keysym.mod & KMOD_SHIFT) { - SDL_StartTextInput(); - imv->input_buffer = malloc(command_buffer_len); - imv->input_buffer[0] = '\0'; - imv->need_redraw = true; - return; - } - - /* If none of the above, add the key to the current key sequence and - * see if that triggers a bind */ - struct list *cmds = imv_bind_handle_event(imv->binds, event); - if (cmds) { - imv_command_exec_list(imv->commands, cmds, imv); - } - break; - case SDL_MOUSEWHEEL: - imv_viewport_zoom(imv->view, imv->image, IMV_ZOOM_MOUSE, event->wheel.y); - SDL_ShowCursor(SDL_ENABLE); - break; - case SDL_MOUSEMOTION: - if (event->motion.state & SDL_BUTTON_LMASK) { - imv_viewport_move(imv->view, event->motion.xrel, event->motion.yrel, imv->image); - } - SDL_ShowCursor(SDL_ENABLE); - break; - case SDL_WINDOWEVENT: - /* For some reason SDL passes events to us that occurred before we - * gained focus, and passes them *after* the focus gained event. - * Due to behavioural quirks from such events, whenever we gain focus - * we have to ignore all window events already in the queue. - */ - if (event->window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { - /* disable window event handling */ - imv->ignore_window_events = true; - /* push an event to the back of the event queue to re-enable - * window event handling */ - SDL_Event event; - SDL_zero(event); - event.type = imv->events.ENABLE_INPUT; - SDL_PushEvent(&event); - } - - imv_viewport_update(imv->view, imv->image); - break; - } + free(event); + return; } static void render_window(struct imv *imv) { int ww, wh; - SDL_GetWindowSize(imv->window, &ww, &wh); + glfwGetWindowSize(imv->window, &ww, &wh); /* update window title */ char title_text[1024]; generate_env_text(imv, title_text, sizeof title_text, imv->title_text); - imv_viewport_set_title(imv->view, title_text); + glfwSetWindowTitle(imv->window, title_text); /* first we draw the background */ - if(imv->background_type == BACKGROUND_SOLID) { - /* solid background */ - SDL_SetRenderDrawColor(imv->renderer, - imv->background_color.r, - imv->background_color.g, - imv->background_color.b, - 255); - SDL_RenderClear(imv->renderer); + if (imv->background.type == BACKGROUND_SOLID) { + imv_canvas_clear(imv->canvas); + imv_canvas_color(imv->canvas, + imv->background.color.r, + imv->background.color.g, + imv->background.color.b, + 1.0); + imv_canvas_fill(imv->canvas); + imv_canvas_draw(imv->canvas); } else { /* chequered background */ - int img_w, img_h; - SDL_QueryTexture(imv->background_image, NULL, NULL, &img_w, &img_h); - /* tile the image so it fills the window */ - for(int y = 0; y < wh; y += img_h) { - for(int x = 0; x < ww; x += img_w) { - SDL_Rect dst_rect = {x,y,img_w,img_h}; - SDL_RenderCopy(imv->renderer, imv->background_image, NULL, &dst_rect); - } - } + imv_canvas_fill_checkers(imv->canvas, 16); + imv_canvas_draw(imv->canvas); } /* draw our actual image */ - { + if (imv->current_image) { int x, y; double scale; imv_viewport_get_offset(imv->view, &x, &y); imv_viewport_get_scale(imv->view, &scale); - imv_image_draw(imv->image, x, y, scale); + imv_canvas_draw_image(imv->canvas, imv->current_image, x, y, scale, imv->upscaling_method); } + imv_canvas_clear(imv->canvas); + /* if the overlay needs to be drawn, draw that too */ - if(imv->overlay_enabled && imv->font) { - SDL_Color fg = {255,255,255,255}; - SDL_Color bg = {0,0,0,160}; + if (imv->overlay_enabled) { + const int height = imv->font.size * 1.2; + imv_canvas_color(imv->canvas, 0, 0, 0, 0.75); + imv_canvas_fill_rectangle(imv->canvas, 0, 0, ww, height); + imv_canvas_color(imv->canvas, 1, 1, 1, 1); char overlay_text[1024]; generate_env_text(imv, overlay_text, sizeof overlay_text, imv->overlay_text); - imv_printf(imv->renderer, imv->font, 0, 0, &fg, &bg, "%s", overlay_text); + imv_canvas_printf(imv->canvas, 0, 0, "%s", overlay_text); } /* draw command entry bar if needed */ - if(imv->input_buffer && imv->font) { - SDL_Color fg = {255,255,255,255}; - SDL_Color bg = {0,0,0,160}; - imv_printf(imv->renderer, - imv->font, - 0, wh - TTF_FontHeight(imv->font), - &fg, &bg, - ":%s", imv->input_buffer); + if (imv_console_prompt(imv->console)) { + const int height = imv->font.size * 1.2; + imv_canvas_color(imv->canvas, 0, 0, 0, 0.75); + imv_canvas_fill_rectangle(imv->canvas, 0, wh - height, ww, height); + imv_canvas_color(imv->canvas, 1, 1, 1, 1); + imv_canvas_printf(imv->canvas, 0, wh - height, ":%s|", imv_console_prompt(imv->console)); } + imv_canvas_draw(imv->canvas); + /* redraw complete, unset the flag */ imv->need_redraw = false; } @@ -1265,9 +1299,9 @@ static char *get_config_path(void) "/etc/imv_config", }; - for(size_t i = 0; i < sizeof(config_paths) / sizeof(char*); ++i) { + for (size_t i = 0; i < sizeof(config_paths) / sizeof(char*); ++i) { wordexp_t word; - if(wordexp(config_paths[i], &word, 0) == 0) { + if (wordexp(config_paths[i], &word, 0) == 0) { if (!word.we_wordv[0]) { wordfree(&word); continue; @@ -1276,7 +1310,7 @@ static char *get_config_path(void) char *path = strdup(word.we_wordv[0]); wordfree(&word); - if(!path || access(path, R_OK) == -1) { + if (!path || access(path, R_OK) == -1) { free(path); continue; } @@ -1301,6 +1335,7 @@ static int handle_ini_value(void *user, const char *section, const char *name, const char *value) { struct imv *imv = user; + fprintf(stderr, "got section='%s' name='%s' value='%s'\n", section, name, value); if (!strcmp(section, "binds")) { return add_bind(imv, name, value); @@ -1313,92 +1348,99 @@ static int handle_ini_value(void *user, const char *section, const char *name, if (!strcmp(section, "options")) { - if(!strcmp(name, "fullscreen")) { + if (!strcmp(name, "fullscreen")) { imv->fullscreen = parse_bool(value); return 1; } - if(!strcmp(name, "width")) { + if (!strcmp(name, "width")) { imv->initial_width = strtol(value, NULL, 10); return 1; } - if(!strcmp(name, "height")) { + if (!strcmp(name, "height")) { imv->initial_height = strtol(value, NULL, 10); return 1; } - if(!strcmp(name, "overlay")) { + if (!strcmp(name, "overlay")) { imv->overlay_enabled = parse_bool(value); return 1; } - if(!strcmp(name, "autoresize")) { + if (!strcmp(name, "autoresize")) { return parse_resizing_mode(imv, value); } - if(!strcmp(name, "upscaling_method")) { + if (!strcmp(name, "upscaling_method")) { return parse_upscaling_method(imv, value); } - if(!strcmp(name, "stay_fullscreen_on_focus_loss")) { + if (!strcmp(name, "stay_fullscreen_on_focus_loss")) { imv->stay_fullscreen_on_focus_loss = parse_bool(value); return 1; } - if(!strcmp(name, "recursive")) { + if (!strcmp(name, "recursive")) { imv->recursive_load = parse_bool(value); return 1; } - if(!strcmp(name, "loop_input")) { + if (!strcmp(name, "loop_input")) { imv->loop_input = parse_bool(value); return 1; } - if(!strcmp(name, "list_files_at_exit")) { + if (!strcmp(name, "list_files_at_exit")) { imv->list_files_at_exit = parse_bool(value); return 1; } - if(!strcmp(name, "scaling_mode")) { + if (!strcmp(name, "scaling_mode")) { return parse_scaling_mode(imv, value); } - if(!strcmp(name, "background")) { - if(!parse_bg(imv, value)) { + if (!strcmp(name, "background")) { + if (!parse_bg(imv, value)) { return false; } return 1; } - if(!strcmp(name, "slideshow_duration")) { - if(!parse_slideshow_duration(imv, value)) { + if (!strcmp(name, "slideshow_duration")) { + if (!parse_slideshow_duration(imv, value)) { return false; } return 1; } - if(!strcmp(name, "overlay_font")) { - free(imv->font_name); - imv->font_name = strdup(value); + if (!strcmp(name, "overlay_font")) { + free(imv->font.name); + imv->font.name = strdup(value); + char *sep = strchr(imv->font.name, ':'); + if (sep) { + *sep = 0; + imv->font.size = atoi(sep + 1); + } else { + imv->font.size = 24; + } return 1; } - if(!strcmp(name, "overlay_text")) { + if (!strcmp(name, "overlay_text")) { free(imv->overlay_text); imv->overlay_text = strdup(value); return 1; } - if(!strcmp(name, "title_text")) { + if (!strcmp(name, "title_text")) { free(imv->title_text); imv->title_text = strdup(value); return 1; } - if(!strcmp(name, "suppress_default_binds")) { + if (!strcmp(name, "suppress_default_binds")) { const bool suppress_default_binds = parse_bool(value); - if(suppress_default_binds) { + if (suppress_default_binds) { /* clear out any default binds if requested */ imv_binds_clear(imv->binds); } @@ -1415,7 +1457,7 @@ static int handle_ini_value(void *user, const char *section, const char *name, bool imv_load_config(struct imv *imv) { char *path = get_config_path(); - if(!path) { + if (!path) { /* no config, no problem - we have defaults */ return true; } @@ -1444,55 +1486,55 @@ void command_pan(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; - if(args->len != 3) { + if (args->len != 3) { return; } long int x = strtol(args->items[1], NULL, 10); long int y = strtol(args->items[2], NULL, 10); - imv_viewport_move(imv->view, x, y, imv->image); + imv_viewport_move(imv->view, x, y, imv->current_image); } void command_select_rel(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; - if(args->len != 2) { + if (args->len != 2) { return; } long int index = strtol(args->items[1], NULL, 10); imv_navigator_select_rel(imv->navigator, index); - imv->slideshow_time_elapsed = 0; + imv->slideshow.elapsed = 0; } void command_select_abs(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; - if(args->len != 2) { + if (args->len != 2) { return; } long int index = strtol(args->items[1], NULL, 10); imv_navigator_select_abs(imv->navigator, index); - imv->slideshow_time_elapsed = 0; + imv->slideshow.elapsed = 0; } void command_zoom(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; - if(args->len == 2) { + if (args->len == 2) { const char *str = args->items[1]; - if(!strcmp(str, "actual")) { - imv_viewport_scale_to_actual(imv->view, imv->image); + if (!strcmp(str, "actual")) { + imv_viewport_scale_to_actual(imv->view, imv->current_image); } else { long int amount = strtol(args->items[1], NULL, 10); - imv_viewport_zoom(imv->view, imv->image, IMV_ZOOM_KEYBOARD, amount); + imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_KEYBOARD, 0, 0, amount); } } } @@ -1513,8 +1555,8 @@ void command_open(struct list *args, const char *argstr, void *data) } wordexp_t word; - if(wordexp(args->items[i], &word, 0) == 0) { - for(size_t j = 0; j < word.we_wordc; ++j) { + if (wordexp(args->items[i], &word, 0) == 0) { + for (size_t j = 0; j < word.we_wordc; ++j) { imv_navigator_add(imv->navigator, word.we_wordv[j], recursive); } wordfree(&word); @@ -1531,7 +1573,7 @@ void command_close(struct list *args, const char *argstr, void *data) imv_navigator_remove(imv->navigator, path); free(path); - imv->slideshow_time_elapsed = 0; + imv->slideshow.elapsed = 0; } void command_fullscreen(struct list *args, const char *argstr, void *data) @@ -1539,7 +1581,17 @@ void command_fullscreen(struct list *args, const char *argstr, void *data) (void)args; (void)argstr; struct imv *imv = data; - imv_viewport_toggle_fullscreen(imv->view); + + + if (glfwGetWindowMonitor(imv->window)) { + glfwSetWindowMonitor(imv->window, NULL, 0, 0, imv->initial_width, imv->initial_height, GLFW_DONT_CARE); + imv->fullscreen = false; + } else { + GLFWmonitor* monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode* mode = glfwGetVideoMode(monitor); + glfwSetWindowMonitor(imv->window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); + imv->fullscreen = true; + } } void command_overlay(struct list *args, const char *argstr, void *data) @@ -1564,7 +1616,7 @@ void command_center(struct list *args, const char *argstr, void *data) (void)args; (void)argstr; struct imv *imv = data; - imv_viewport_center(imv->view, imv->image); + imv_viewport_center(imv->view, imv->current_image); } void command_reset(struct list *args, const char *argstr, void *data) @@ -1581,9 +1633,9 @@ void command_next_frame(struct list *args, const char *argstr, void *data) (void)args; (void)argstr; struct imv *imv = data; - if (imv->source && imv->source->load_next_frame) { - async_load_next_frame(imv->source); - imv->next_frame_due = 1; /* Earliest possible non-zero timestamp */ + if (imv->current_source && imv->current_source->load_next_frame) { + async_load_next_frame(imv->current_source); + imv->next_frame.due = 0.0001; /* Earliest possible non-zero timestamp */ } } @@ -1601,20 +1653,20 @@ void command_set_scaling_mode(struct list *args, const char *argstr, void *data) (void)argstr; struct imv *imv = data; - if(args->len != 2) { + if (args->len != 2) { return; } const char *mode = args->items[1]; - if(!strcmp(mode, "next")) { + if (!strcmp(mode, "next")) { imv->scaling_mode++; imv->scaling_mode %= SCALING_MODE_COUNT; - } else if(!strcmp(mode, "none")) { + } else if (!strcmp(mode, "none")) { imv->scaling_mode = SCALING_NONE; - } else if(!strcmp(mode, "shrink")) { + } else if (!strcmp(mode, "shrink")) { imv->scaling_mode = SCALING_DOWN; - } else if(!strcmp(mode, "full")) { + } else if (!strcmp(mode, "full")) { imv->scaling_mode = SCALING_FULL; } else { /* no changes, don't bother to redraw */ @@ -1629,14 +1681,14 @@ void command_set_slideshow_duration(struct list *args, const char *argstr, void { (void)argstr; struct imv *imv = data; - if(args->len == 2) { - long int delta = 1000 * strtol(args->items[1], NULL, 10); + if (args->len == 2) { + long int delta = strtol(args->items[1], NULL, 10); /* Ensure we can't go below 0 */ - if(delta < 0 && (size_t)labs(delta) > imv->slideshow_image_duration) { - imv->slideshow_image_duration = 0; + if (delta < 0 && (size_t)labs(delta) > imv->slideshow.duration) { + imv->slideshow.duration = 0; } else { - imv->slideshow_image_duration += delta; + imv->slideshow.duration += delta; } imv->need_redraw = true; @@ -1657,10 +1709,10 @@ static void update_env_vars(struct imv *imv) snprintf(str, sizeof str, "%zu", imv_navigator_length(imv->navigator)); setenv("imv_file_count", str, 1); - snprintf(str, sizeof str, "%d", imv_image_width(imv->image)); + snprintf(str, sizeof str, "%d", imv_image_width(imv->current_image)); setenv("imv_width", str, 1); - snprintf(str, sizeof str, "%d", imv_image_height(imv->image)); + snprintf(str, sizeof str, "%d", imv_image_height(imv->current_image)); setenv("imv_height", str, 1); { @@ -1670,10 +1722,10 @@ static void update_env_vars(struct imv *imv) setenv("imv_scale", str, 1); } - snprintf(str, sizeof str, "%zu", imv->slideshow_image_duration / 1000); + snprintf(str, sizeof str, "%f", imv->slideshow.duration); setenv("imv_slidshow_duration", str, 1); - snprintf(str, sizeof str, "%zu", imv->slideshow_time_elapsed / 1000); + snprintf(str, sizeof str, "%f", imv->slideshow.elapsed); setenv("imv_slidshow_elapsed", str, 1); } @@ -1683,8 +1735,8 @@ static size_t generate_env_text(struct imv *imv, char *buf, size_t buf_len, cons size_t len = 0; wordexp_t word; - if(wordexp(format, &word, 0) == 0) { - for(size_t i = 0; i < word.we_wordc; ++i) { + if (wordexp(format, &word, 0) == 0) { + for (size_t i = 0; i < word.we_wordc; ++i) { len += snprintf(buf + len, buf_len - len, "%s ", word.we_wordv[i]); } wordfree(&word); @@ -1695,4 +1747,34 @@ static size_t generate_env_text(struct imv *imv, char *buf, size_t buf_len, cons return len; } +static size_t read_from_stdin(void **buffer) +{ + size_t len = 0; + ssize_t r; + size_t step = 4096; /* Arbitrary value of 4 KiB */ + void *p; + + errno = 0; /* clear errno */ + + for (*buffer = NULL; (*buffer = realloc((p = *buffer), len + step)); + len += (size_t)r) { + if ((r = read(STDIN_FILENO, (uint8_t *)*buffer + len, step)) == -1) { + perror(NULL); + break; + } else if (r == 0) { + break; + } + } + + /* realloc(3) leaves old buffer allocated in case of error */ + if (*buffer == NULL && p != NULL) { + int save = errno; + free(p); + errno = save; + len = 0; + } + return len; +} + + /* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/keyboard.c b/src/keyboard.c new file mode 100644 index 0000000..16b2c9d --- /dev/null +++ b/src/keyboard.c @@ -0,0 +1,118 @@ +#include "keyboard.h" + +#include +#include +#include +#include +#include +#include + +struct imv_keyboard { + struct xkb_context *context; + struct xkb_keymap *keymap; + struct xkb_state *state; +}; + +struct imv_keyboard *imv_keyboard_create(void) +{ + struct imv_keyboard *keyboard = calloc(1, sizeof *keyboard); + keyboard->context = xkb_context_new(0); + assert(keyboard->context); + + struct xkb_rule_names names = { + .rules = NULL, + .model = NULL, + .layout = "gb", + .variant = NULL, + .options = "caps:escape,compose:ralt" + }; + keyboard->keymap = xkb_keymap_new_from_names(keyboard->context, &names, 0); + assert(keyboard->keymap); + keyboard->state = xkb_state_new(keyboard->keymap); + assert(keyboard->state); + + return keyboard; +} + +void imv_keyboard_free(struct imv_keyboard *keyboard) +{ + if (!keyboard) { + return; + } + xkb_state_unref(keyboard->state); + keyboard->state = NULL; + xkb_keymap_unref(keyboard->keymap); + keyboard->keymap = NULL; + xkb_context_unref(keyboard->context); + keyboard->context = NULL; + free(keyboard); +} + +static const int scancode_offset = 8; + +void imv_keyboard_update_key(struct imv_keyboard *keyboard, int scancode, bool pressed) +{ + xkb_state_update_key(keyboard->state, scancode + scancode_offset, pressed ? XKB_KEY_DOWN : XKB_KEY_UP); +} + +static const char *describe_prefix(struct imv_keyboard *keyboard) +{ + const bool ctrl = (xkb_state_mod_name_is_active(keyboard->state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0); + const bool alt = (xkb_state_mod_name_is_active(keyboard->state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0); + const bool shift = (xkb_state_mod_name_is_active(keyboard->state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0); + + if (ctrl && !alt && !shift) { + return "Ctrl+"; + } else if (!ctrl && alt && !shift) { + return "Meta+"; + } else if (!ctrl && !alt && shift) { + return "Shift+"; + } else if (ctrl && alt && !shift) { + return "Ctrl+Meta+"; + } else if (ctrl && !alt && shift) { + return "Ctrl+Shift+"; + } else if (!ctrl && alt && shift) { + return "Meta+Shift+"; + } else if (ctrl && alt && shift) { + return "Ctrl+Meta+Shift+"; + } else { + return ""; + } +} + +size_t imv_keyboard_keyname(struct imv_keyboard *keyboard, int scancode, char *buf, size_t buflen) +{ + xkb_keysym_t keysym = xkb_state_key_get_one_sym(keyboard->state, scancode + scancode_offset); + return xkb_keysym_get_name(keysym, buf, buflen); +} + +char *imv_keyboard_describe_key(struct imv_keyboard *keyboard, int scancode) +{ + char keyname[128] = {0}; + imv_keyboard_keyname(keyboard, scancode, keyname, sizeof keyname); + + /* Modifier keys don't count on their own, only when pressed with another key */ + if (!strcmp(keyname, "Control_L") || + !strcmp(keyname, "Control_R") || + !strcmp(keyname, "Alt_L") || + !strcmp(keyname, "Alt_R") || + !strcmp(keyname, "Shift_L") || + !strcmp(keyname, "Shift_R") || + !strcmp(keyname, "Meta_L") || + !strcmp(keyname, "Meta_R") || + !strcmp(keyname, "Super_L") || + !strcmp(keyname, "Super_R")) { + return NULL; + } + + const char *prefix = describe_prefix(keyboard); + + char buf[128]; + snprintf(buf, sizeof buf, "%s%s", prefix, keyname); + return strdup(buf); +} + +size_t imv_keyboard_get_text(struct imv_keyboard *keyboard, int scancode, char *buf, size_t buflen) +{ + return xkb_state_key_get_utf8(keyboard->state, scancode + scancode_offset, buf, buflen); +} diff --git a/src/keyboard.h b/src/keyboard.h new file mode 100644 index 0000000..755bae2 --- /dev/null +++ b/src/keyboard.h @@ -0,0 +1,27 @@ +#ifndef IMV_KEYBOARD_H +#define IMV_KEYBOARD_H + +#include +#include + +struct imv_keyboard; + +/* Create a keyboard instance */ +struct imv_keyboard *imv_keyboard_create(void); + +/* Clean up a keyboard */ +void imv_keyboard_free(struct imv_keyboard *keyboard); + +/* Notify the keyboard of the state of a key */ +void imv_keyboard_update_key(struct imv_keyboard *keyboard, int scancode, bool pressed); + +/* Write the null-terminated name of the key corresponding to scancode into buf */ +size_t imv_keyboard_keyname(struct imv_keyboard *keyboard, int scancode, char *buf, size_t buflen); + +/* Describe the key corresponding to scancode, with modifier keys prefixed */ +char *imv_keyboard_describe_key(struct imv_keyboard *keyboard, int scancode); + +/* Write the null-terminated text generated by scancode being pressed into buf */ +size_t imv_keyboard_get_text(struct imv_keyboard *keyboard, int scancode, char *buf, size_t buflen); + +#endif diff --git a/src/log.c b/src/log.c index 4ce6ae8..c56e510 100644 --- a/src/log.c +++ b/src/log.c @@ -1,24 +1,69 @@ #include "log.h" +#include "list.h" + +#include #include #include #include -static enum imv_log_level g_log_level = IMV_WARNING; +static struct list *g_log_clients = NULL; +static char g_log_buffer[16384]; -void imv_log_set_level(enum imv_log_level level) -{ - g_log_level = level; -} +struct log_client { + imv_log_callback callback; + void *data; +}; void imv_log(enum imv_log_level level, const char *fmt, ...) { - if (level > g_log_level) { + assert(fmt); + + /* Exit early if no one's listening */ + if (!g_log_clients || !g_log_clients->len) { return; } va_list args; va_start(args, fmt); - vfprintf(stderr, fmt, args); + vsnprintf(g_log_buffer, sizeof g_log_buffer, fmt, args); va_end(args); + + for (size_t i = 0; i < g_log_clients->len; ++i) { + struct log_client *client = g_log_clients->items[i]; + client->callback(level, g_log_buffer, client->data); + } +} + +void imv_log_add_log_callback(imv_log_callback callback, void *data) +{ + assert(callback); + + struct log_client *client = calloc(1, sizeof *client); + client->callback = callback; + client->data = data; + + if (!g_log_clients) { + g_log_clients = list_create(); + } + + list_append(g_log_clients, client); +} + +void imv_log_remove_log_callback(imv_log_callback callback) +{ + assert(callback); + + if (!callback || !g_log_clients) { + return; + } + + for (size_t i = 0; i < g_log_clients->len; ++i) { + struct log_client *client = g_log_clients->items[i]; + if (client->callback == callback) { + free(client); + list_remove(g_log_clients, i); + return; + } + } } diff --git a/src/log.h b/src/log.h index a426289..43b1894 100644 --- a/src/log.h +++ b/src/log.h @@ -8,8 +8,15 @@ enum imv_log_level { IMV_ERROR }; -void imv_log_set_level(enum imv_log_level level); - +/* Write to the log */ void imv_log(enum imv_log_level level, const char *fmt, ...); +typedef void (*imv_log_callback)(enum imv_log_level level, const char *text, void *data); + +/* Subscribe to the log, callback is called whenever a log entry is written */ +void imv_log_add_log_callback(imv_log_callback callback, void *data); + +/* Unsubscribe from the log */ +void imv_log_remove_log_callback(imv_log_callback callback); + #endif diff --git a/src/source.h b/src/source.h index 4f1a0dd..eed95c4 100644 --- a/src/source.h +++ b/src/source.h @@ -3,7 +3,7 @@ #include #include -#include "bitmap.h" +#include "image.h" struct imv_source_message { /* Pointer to sender of message */ @@ -12,17 +12,17 @@ struct imv_source_message { /* User-supplied pointer */ void *user_data; - /* Receiver is responsible for destroying bitmap */ - struct imv_bitmap *bitmap; + /* Receiver is responsible for destroying image */ + struct imv_image *image; /* If an animated gif, the frame's duration in milliseconds, else 0 */ int frametime; - /* Error message if bitmap was NULL */ + /* Error message if image was NULL */ const char *error; }; -/* Generic source of one or more bitmaps. Essentially a single image file */ +/* Generic source of one or more images. Essentially a single image file */ struct imv_source { /* usually the path of the image this is the source of */ char *name; @@ -45,10 +45,10 @@ struct imv_source { pthread_mutex_t busy; /* Trigger loading of the first frame. Returns 0 on success. */ - int (*load_first_frame)(struct imv_source *src); + void *(*load_first_frame)(struct imv_source *src); /* Trigger loading of next frame. Returns 0 on success. */ - int (*load_next_frame)(struct imv_source *src); + void *(*load_next_frame)(struct imv_source *src); /* Safely free contents of this source. After this returns * it is safe to dealocate/overwrite the imv_source instance. diff --git a/src/util.c b/src/util.c deleted file mode 100644 index 645d2f4..0000000 --- a/src/util.c +++ /dev/null @@ -1,139 +0,0 @@ -#include "util.h" -#include -#include -#include -#include - -size_t read_from_stdin(void **buffer) { - size_t len = 0; - ssize_t r; - size_t step = 4096; /* Arbitrary value of 4 KiB */ - void *p; - - errno = 0; /* clear errno */ - - for(*buffer = NULL; (*buffer = realloc((p = *buffer), len + step)); - len += (size_t)r) { - if((r = read(STDIN_FILENO, (uint8_t *)*buffer + len, step)) == -1) { - perror(NULL); - break; - } else if (r == 0) { - break; - } - } - - /* realloc(3) leaves old buffer allocated in case of error */ - if(*buffer == NULL && p != NULL) { - int save = errno; - free(p); - errno = save; - len = 0; - } - return len; -} - -TTF_Font *load_font(const char *font_spec) -{ - int font_size; - char *font_name; - - /* figure out font size from name, or default to 24 */ - char *sep = strchr(font_spec, ':'); - if(sep) { - font_name = strndup(font_spec, sep - font_spec); - font_size = strtol(sep+1, NULL, 10); - } else { - font_name = strdup(font_spec); - font_size = 24; - } - - - FcConfig *cfg = FcInitLoadConfigAndFonts(); - FcPattern *pattern = FcNameParse((const FcChar8*)font_name); - FcConfigSubstitute(cfg, pattern, FcMatchPattern); - FcDefaultSubstitute(pattern); - - TTF_Font *ret = NULL; - - FcResult result = FcResultNoMatch; - FcPattern* font = FcFontMatch(cfg, pattern, &result); - if (font) { - FcChar8 *path = NULL; - if (FcPatternGetString(font, FC_FILE, 0, &path) == FcResultMatch) { - ret = TTF_OpenFont((char*)path, font_size); - } - FcPatternDestroy(font); - } - FcPatternDestroy(pattern); - FcConfigDestroy(cfg); - - free(font_name); - return ret; -} - -SDL_Texture *create_chequered(SDL_Renderer *renderer) -{ - SDL_RendererInfo ri; - SDL_GetRendererInfo(renderer, &ri); - int width = 512; - int height = 512; - if(ri.max_texture_width != 0 && ri.max_texture_width < width) { - width = ri.max_texture_width; - } - if(ri.max_texture_height != 0 && ri.max_texture_height < height) { - height = ri.max_texture_height; - } - const int box_size = 16; - /* Create a chequered texture */ - const unsigned char l = 196; - const unsigned char d = 96; - - size_t pixels_len = 3 * width * height; - unsigned char *pixels = malloc(pixels_len); - for(int y = 0; y < height; y++) { - for(int x = 0; x < width; x += box_size) { - unsigned char color = l; - if(((x/box_size) % 2 == 0) ^ ((y/box_size) % 2 == 0)) { - color = d; - } - memset(pixels + 3 * x + 3 * width * y, color, 3 * box_size); - } - } - SDL_Texture *ret = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB24, - SDL_TEXTUREACCESS_STATIC, - width, height); - SDL_UpdateTexture(ret, NULL, pixels, 3 * width); - free(pixels); - return ret; -} - -void imv_printf(SDL_Renderer *renderer, TTF_Font *font, int x, int y, - SDL_Color *fg, SDL_Color *bg, const char *fmt, ...) -{ - char line[512]; - va_list args; - va_start(args, fmt); - vsnprintf(line, sizeof(line), fmt, args); - - SDL_Surface *surf = TTF_RenderUTF8_Blended(font, line, *fg); - SDL_Texture *tex = SDL_CreateTextureFromSurface(renderer, surf); - - SDL_Rect tex_rect = {0,0,0,0}; - SDL_QueryTexture(tex, NULL, NULL, &tex_rect.w, &tex_rect.h); - tex_rect.x = x; - tex_rect.y = y; - - /* draw bg if wanted */ - if(bg->a > 0) { - SDL_SetRenderDrawColor(renderer, bg->r, bg->g, bg->b, bg->a); - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - SDL_RenderFillRect(renderer, &tex_rect); - } - SDL_RenderCopy(renderer, tex, NULL, &tex_rect); - - SDL_DestroyTexture(tex); - SDL_FreeSurface(surf); - va_end(args); -} - -/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/util.h b/src/util.h deleted file mode 100644 index 23a6388..0000000 --- a/src/util.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef UTIL_H -#define UTIL_H - -#include -#include - -/* Read binary data from stdin into buffer */ -size_t read_from_stdin(void **buffer); - -/* Creates a new SDL_Texture* containing a chequeboard texture */ -SDL_Texture *create_chequered(SDL_Renderer *renderer); - -/* Loads a font using SDL2_ttf given a spec in the format "name:size" */ -TTF_Font *load_font(const char *font_spec); - -void imv_printf(SDL_Renderer *renderer, TTF_Font *font, int x, int y, - SDL_Color *fg, SDL_Color *bg, const char *fmt, ...); - -#endif - - -/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/viewport.c b/src/viewport.c index 1ecbea4..e04648f 100644 --- a/src/viewport.c +++ b/src/viewport.c @@ -1,11 +1,14 @@ #include "viewport.h" struct imv_viewport { - SDL_Window *window; - SDL_Renderer *renderer; double scale; + struct { + int width, height; + } window; /* window dimensions */ + struct { + int width, height; + } buffer; /* rendering buffer dimensions */ int x, y; - int fullscreen; int redraw; int playing; int locked; @@ -13,20 +16,20 @@ struct imv_viewport { static void input_xy_to_render_xy(struct imv_viewport *view, int *x, int *y) { - int ww, wh, rw, rh; - SDL_GetWindowSize(view->window, &ww, &wh); - SDL_GetRendererOutputSize(view->renderer, &rw, &rh); - *x *= rw / ww; - *y *= rh / wh; + *x *= view->buffer.width / view->window.width; + *y *= view->buffer.height / view->window.height; } -struct imv_viewport *imv_viewport_create(SDL_Window *window, SDL_Renderer *renderer) +struct imv_viewport *imv_viewport_create(int window_width, int window_height, + int buffer_width, int buffer_height) { struct imv_viewport *view = malloc(sizeof *view); - view->window = window; - view->renderer = renderer; + view->window.width = window_width; + view->window.height = window_height; + view->buffer.width = buffer_width; + view->buffer.height = buffer_height; view->scale = 1; - view->x = view->y = view->fullscreen = view->redraw = 0; + view->x = view->y = view->redraw = 0; view->playing = 1; view->locked = 0; return view; @@ -37,17 +40,6 @@ void imv_viewport_free(struct imv_viewport *view) free(view); } -void imv_viewport_toggle_fullscreen(struct imv_viewport *view) -{ - if(view->fullscreen) { - SDL_SetWindowFullscreen(view->window, 0); - view->fullscreen = 0; - } else { - SDL_SetWindowFullscreen(view->window, SDL_WINDOW_FULLSCREEN_DESKTOP); - view->fullscreen = 1; - } -} - void imv_viewport_set_playing(struct imv_viewport *view, bool playing) { view->playing = playing; @@ -98,37 +90,34 @@ void imv_viewport_move(struct imv_viewport *view, int x, int y, view->locked = 1; int w = (int)(imv_image_width(image) * view->scale); int h = (int)(imv_image_height(image) * view->scale); - int ww, wh; - SDL_GetRendererOutputSize(view->renderer, &ww, &wh); if (view->x < -w) { view->x = -w; } - if (view->x > ww) { - view->x = ww; + if (view->x > view->buffer.width) { + view->x = view->buffer.width; } if (view->y < -h) { view->y = -h; } - if (view->y > wh) { - view->y = wh; + if (view->y > view->buffer.height) { + view->y = view->buffer.height; } } -void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image, enum imv_zoom_source src, int amount) +void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image, + enum imv_zoom_source src, int mouse_x, int mouse_y, int amount) { double prev_scale = view->scale; - int x, y, ww, wh; - SDL_GetRendererOutputSize(view->renderer, &ww, &wh); + int x, y; const int image_width = imv_image_width(image); const int image_height = imv_image_height(image); /* x and y cordinates are relative to the image */ if(src == IMV_ZOOM_MOUSE) { - SDL_GetMouseState(&x, &y); - input_xy_to_render_xy(view, &x, &y); - x -= view->x; - y -= view->y; + input_xy_to_render_xy(view, &mouse_x, &mouse_y); + x = mouse_x - view->x; + y = mouse_y - view->y; } else { x = view->scale * image_width / 2; y = view->scale * image_height / 2; @@ -138,10 +127,10 @@ void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image, const int scaled_height = image_height * view->scale; const int ic_x = view->x + scaled_width/2; const int ic_y = view->y + scaled_height/2; - const int wc_x = ww/2; - const int wc_y = wh/2; + const int wc_x = view->buffer.width/2; + const int wc_y = view->buffer.height/2; - double delta_scale = 0.04 * ww * amount / image_width; + double delta_scale = 0.04 * view->buffer.width * amount / image_width; view->scale += delta_scale; const double min_scale = 0.1; @@ -153,17 +142,17 @@ void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image, } if(view->scale < prev_scale) { - if(scaled_width < ww) { + if(scaled_width < view->buffer.width) { x = scaled_width/2 - (ic_x - wc_x)*2; } - if(scaled_height < wh) { + if(scaled_height < view->buffer.height) { y = scaled_height/2 - (ic_y - wc_y)*2; } } else { - if(scaled_width < ww) { + if(scaled_width < view->buffer.width) { x = scaled_width/2; } - if(scaled_height < wh) { + if(scaled_height < view->buffer.height) { y = scaled_height/2; } } @@ -180,14 +169,11 @@ void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image, void imv_viewport_center(struct imv_viewport *view, const struct imv_image *image) { - int ww, wh; - SDL_GetRendererOutputSize(view->renderer, &ww, &wh); - const int image_width = imv_image_width(image); const int image_height = imv_image_height(image); - view->x = (ww - image_width * view->scale) / 2; - view->y = (wh - image_height * view->scale) / 2; + view->x = (view->buffer.width - image_width * view->scale) / 2; + view->y = (view->buffer.height - image_height * view->scale) / 2; view->locked = 1; view->redraw = 1; @@ -195,20 +181,17 @@ void imv_viewport_center(struct imv_viewport *view, const struct imv_image *imag void imv_viewport_scale_to_window(struct imv_viewport *view, const struct imv_image *image) { - int ww, wh; - SDL_GetRendererOutputSize(view->renderer, &ww, &wh); - const int image_width = imv_image_width(image); const int image_height = imv_image_height(image); - const double window_aspect = (double)ww / (double)wh; + const double window_aspect = (double)view->buffer.width / (double)view->buffer.height; const double image_aspect = (double)image_width / (double)image_height; if(window_aspect > image_aspect) { /* Image will become too tall before it becomes too wide */ - view->scale = (double)wh / (double)image_height; + view->scale = (double)view->buffer.height / (double)image_height; } else { /* Image will become too wide before it becomes too tall */ - view->scale = (double)ww / (double)image_width; + view->scale = (double)view->buffer.width / (double)image_width; } imv_viewport_center(view, image); @@ -220,13 +203,16 @@ void imv_viewport_set_redraw(struct imv_viewport *view) view->redraw = 1; } -void imv_viewport_set_title(struct imv_viewport *view, char* title) +void imv_viewport_update(struct imv_viewport *view, + int window_width, int window_height, + int buffer_width, int buffer_height, + struct imv_image *image) { - SDL_SetWindowTitle(view->window, title); -} + view->window.width = window_width; + view->window.height = window_height; + view->buffer.width = buffer_width; + view->buffer.height = buffer_height; -void imv_viewport_update(struct imv_viewport *view, struct imv_image *image) -{ view->redraw = 1; if(view->locked) { return; diff --git a/src/viewport.h b/src/viewport.h index 471d2a5..32fc728 100644 --- a/src/viewport.h +++ b/src/viewport.h @@ -2,7 +2,6 @@ #define IMV_VIEWPORT_H #include -#include #include "image.h" struct imv_viewport; @@ -14,14 +13,12 @@ enum imv_zoom_source { }; /* Creates an instance of imv_viewport */ -struct imv_viewport *imv_viewport_create(SDL_Window *window, SDL_Renderer *renderer); +struct imv_viewport *imv_viewport_create(int window_width, int window_height, + int buffer_width, int buffer_height); /* Cleans up an imv_viewport instance */ void imv_viewport_free(struct imv_viewport *view); -/* Toggle their viewport's fullscreen mode. Triggers a redraw */ -void imv_viewport_toggle_fullscreen(struct imv_viewport *view); - /* Set playback of animated gifs */ void imv_viewport_set_playing(struct imv_viewport *view, bool playing); @@ -45,7 +42,7 @@ void imv_viewport_move(struct imv_viewport *view, int x, int y, /* Zoom the view by the given amount. imv_image* is used to get the image * dimensions */ void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image, - enum imv_zoom_source, int amount); + enum imv_zoom_source, int mouse_x, int mouse_y, int amount); /* Recenter the view to be in the middle of the image */ void imv_viewport_center(struct imv_viewport *view, @@ -62,11 +59,11 @@ void imv_viewport_scale_to_window(struct imv_viewport *view, /* Tell the viewport that it needs to be redrawn */ void imv_viewport_set_redraw(struct imv_viewport *view); -/* Set the title of the viewport */ -void imv_viewport_set_title(struct imv_viewport *view, char *title); - /* Tell the viewport the window or image has changed */ -void imv_viewport_update(struct imv_viewport *view, struct imv_image *image); +void imv_viewport_update(struct imv_viewport *view, + int window_width, int window_height, + int buffer_width, int buffer_height, + struct imv_image *image); /* Poll whether we need to redraw */ int imv_viewport_needs_redraw(struct imv_viewport *view); -- cgit v1.2.3