aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarry Jeffery <harry@exec64.co.uk>2019-06-15 14:28:29 +0100
committerHarry Jeffery <harry@exec64.co.uk>2019-07-03 20:50:19 +0100
commit7c7dc660e587eac1aa3c8b3405eba95ba558e682 (patch)
tree81d12d560b60d397be23c7d132e32a5de30e409a
parent20e9d23b82f55a751c3cf1166cb59ef26775ee00 (diff)
downloadimv-7c7dc660e587eac1aa3c8b3405eba95ba558e682.tar.gz
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.
-rw-r--r--Makefile11
-rw-r--r--src/backend_freeimage.c29
-rw-r--r--src/backend_libjpeg.c17
-rw-r--r--src/backend_libpng.c19
-rw-r--r--src/backend_librsvg.c37
-rw-r--r--src/backend_libtiff.c17
-rw-r--r--src/binds.c64
-rw-r--r--src/binds.h4
-rw-r--r--src/canvas.c284
-rw-r--r--src/canvas.h51
-rw-r--r--src/console.c112
-rw-r--r--src/console.h47
-rw-r--r--src/image.c182
-rw-r--r--src/image.h18
-rw-r--r--src/imv.c1094
-rw-r--r--src/keyboard.c118
-rw-r--r--src/keyboard.h27
-rw-r--r--src/log.c59
-rw-r--r--src/log.h11
-rw-r--r--src/source.h14
-rw-r--r--src/util.c139
-rw-r--r--src/util.h22
-rw-r--r--src/viewport.c104
-rw-r--r--src/viewport.h17
24 files changed, 1465 insertions, 1032 deletions
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 <stdbool.h>
+#include <stdio.h>
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 <SDL2/SDL.h>
+#include <unistd.h>
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 <GLFW/glfw3.h>
+#include <assert.h>
+#include <cairo/cairo.h>
+#include <pango/pangocairo.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef IMV_BACKEND_LIBRSVG
+#include <librsvg/rsvg.h>
+#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 <stdlib.h>
+#include <string.h>
+
+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 <stdbool.h>
+
+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 <stdbool.h>
+#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 <SDL2/SDL.h>
+
+#ifdef IMV_BACKEND_LIBRSVG
+#include <librsvg/rsvg.h>
+#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 <unistd.h>
#include <wordexp.h>
-#include <SDL2/SDL.h>
-#include <SDL2/SDL_ttf.h>
+#define GLFW_EXPOSE_NATIVE_X11
+#define GLFW_EXPOSE_NATIVE_WAYLAND
+#include <GLFW/glfw3.h>
+#include <GLFW/glfw3native.h>
#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, "<Left>", "select_rel -1");
- add_bind(imv, "<LeftSquareBracket>", "select_rel -1");
+ add_bind(imv, "<bracketleft>", "select_rel -1");
add_bind(imv, "<Right>", "select_rel 1");
- add_bind(imv, "<RightSquareBracket>", "select_rel 1");
+ add_bind(imv, "<bracketright>", "select_rel 1");
add_bind(imv, "gg", "select_abs 0");
- add_bind(imv, "<Shift+g>", "select_abs -1");
+ add_bind(imv, "<Shift+G>", "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, "<Equals>", "zoom 1");
+ add_bind(imv, "<equal>", "zoom 1");
add_bind(imv, "<Up>", "zoom 1");
- add_bind(imv, "+", "zoom 1");
+ add_bind(imv, "<Shift+plus>", "zoom 1");
add_bind(imv, "i", "zoom 1");
add_bind(imv, "<Down>", "zoom -1");
- add_bind(imv, "-", "zoom -1");
+ add_bind(imv, "<minus>", "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, "<Space>", "toggle_playing");
+ add_bind(imv, "<period>", "next_frame");
+ add_bind(imv, "<space>", "toggle_playing");
add_bind(imv, "t", "slideshow_duration +1");
- add_bind(imv, "<Shift+t>", "slideshow_duration -1");
+ add_bind(imv, "<Shift+T>", "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 <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xkbcommon/xkbcommon.h>
+
+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 <stdbool.h>
+#include <unistd.h>
+
+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 <assert.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
-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 <pthread.h>
#include <stdbool.h>
-#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 <unistd.h>
-#include <stddef.h>
-#include <errno.h>
-#include <fontconfig/fontconfig.h>
-
-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 <SDL2/SDL.h>
-#include <SDL2/SDL_ttf.h>
-
-/* 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 <stdbool.h>
-#include <SDL2/SDL.h>
#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);