diff options
author | Harry Jeffery <harry@exec64.co.uk> | 2017-08-06 19:45:15 +0100 |
---|---|---|
committer | Harry Jeffery <harry@exec64.co.uk> | 2017-08-06 19:45:15 +0100 |
commit | 2ffd6edea17c1ec8fdb33d1135e27db0eb080625 (patch) | |
tree | 113e09b6386ad4b69ddb00fb8e6e41fe31261320 /src | |
parent | cee36733b5db5dcd0b479663000f889788468c80 (diff) | |
parent | 12450f38753699b0e606c3ad542892752da6aca8 (diff) | |
download | imv-2ffd6edea17c1ec8fdb33d1135e27db0eb080625.tar.gz |
Merge v3 changes into master
Diffstat (limited to 'src')
-rw-r--r-- | src/commands.c | 90 | ||||
-rw-r--r-- | src/commands.h | 35 | ||||
-rw-r--r-- | src/imv.c | 872 | ||||
-rw-r--r-- | src/imv.h | 36 | ||||
-rw-r--r-- | src/list.c | 111 | ||||
-rw-r--r-- | src/list.h | 48 | ||||
-rw-r--r-- | src/loader.c | 13 | ||||
-rw-r--r-- | src/loader.h | 8 | ||||
-rw-r--r-- | src/main.c | 692 | ||||
-rw-r--r-- | src/navigator.c | 26 | ||||
-rw-r--r-- | src/navigator.h | 12 | ||||
-rw-r--r-- | src/texture.c | 7 | ||||
-rw-r--r-- | src/texture.h | 8 | ||||
-rw-r--r-- | src/util.c | 3 | ||||
-rw-r--r-- | src/viewport.c | 9 | ||||
-rw-r--r-- | src/viewport.h | 8 |
16 files changed, 1263 insertions, 715 deletions
diff --git a/src/commands.c b/src/commands.c new file mode 100644 index 0000000..0f31bfc --- /dev/null +++ b/src/commands.c @@ -0,0 +1,90 @@ +/* Copyright (c) 2017 imv authors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "commands.h" +#include "list.h" + +struct command { + char* command; + void (*handler)(struct imv_list *args, void *data); + char* alias; +}; + +struct imv_commands *imv_commands_create(void) +{ + struct imv_commands *cmds = malloc(sizeof(struct imv_commands)); + cmds->command_list = imv_list_create(); + return cmds; +} + +void imv_commands_free(struct imv_commands *cmds) +{ + for(size_t i = 0; i < cmds->command_list->len; ++i) { + struct command *cmd = cmds->command_list->items[i]; + free(cmd->command); + if(cmd->alias) { + free(cmd->alias); + } + free(cmd); + } + imv_list_free(cmds->command_list); + free(cmds); +} + +void imv_command_register(struct imv_commands *cmds, const char *command, void (*handler)()) +{ + struct command *cmd = malloc(sizeof(struct command)); + cmd->command = strdup(command); + cmd->handler = handler; + cmd->alias = NULL; + imv_list_append(cmds->command_list, cmd); +} + +void imv_command_alias(struct imv_commands *cmds, const char *command, const char *alias) +{ + struct command *cmd = malloc(sizeof(struct command)); + cmd->command = strdup(command); + cmd->handler = NULL; + cmd->alias = strdup(alias); + imv_list_append(cmds->command_list, cmd); +} + +int imv_command_exec(struct imv_commands *cmds, const char *command, void *data) +{ + struct imv_list *args = imv_split_string(command, ' '); + int ret = 1; + + if(args->len > 0) { + for(size_t i = 0; i < cmds->command_list->len; ++i) { + struct command *cmd = cmds->command_list->items[i]; + if(!strcmp(cmd->command, args->items[0])) { + if(cmd->handler) { + cmd->handler(args, data); + ret = 0; + } else if(cmd->alias) { + ret = imv_command_exec(cmds, cmd->alias, data); + } + break; + } + } + } + + imv_list_deep_free(args); + return ret; +} + +/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000..2d106d9 --- /dev/null +++ b/src/commands.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2017 imv authors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef COMMANDS_H +#define COMMANDS_H + +struct imv_list; + +struct imv_commands { + struct imv_list *command_list; +}; + +struct imv_commands *imv_commands_create(void); +void imv_commands_free(struct imv_commands *cmds); +void imv_command_register(struct imv_commands *cmds, const char *command, void (*handler)()); +void imv_command_alias(struct imv_commands *cmds, const char *command, const char *alias); +int imv_command_exec(struct imv_commands *cmds, const char *command, void *data); + +#endif + +/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/imv.c b/src/imv.c new file mode 100644 index 0000000..368973b --- /dev/null +++ b/src/imv.c @@ -0,0 +1,872 @@ +/* Copyright (c) 2017 imv authors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imv.h" + +#include <getopt.h> +#include <limits.h> +#include <poll.h> +#include <stdlib.h> +#include <stdbool.h> +#include <SDL2/SDL.h> +#include <SDL2/SDL_ttf.h> +#include <unistd.h> + +#include "commands.h" +#include "list.h" +#include "loader.h" +#include "texture.h" +#include "navigator.h" +#include "viewport.h" +#include "util.h" + +enum scaling_mode { + SCALING_NONE, + SCALING_DOWN, + SCALING_FULL, + SCALING_MODE_COUNT +}; + +static const char *scaling_label[] = { + "actual size", + "shrink to fit", + "scale to fit" +}; + +enum background_type { + BACKGROUND_SOLID, + BACKGROUND_CHEQUERED, + BACKGROUND_TYPE_COUNT +}; + +struct imv { + bool quit; + bool fullscreen; + bool overlay_enabled; + bool nearest_neighbour; + bool need_redraw; + bool need_rescale; + bool recursive_load; + bool cycle_input; + bool list_at_exit; + bool paths_from_stdin; + enum scaling_mode scaling_mode; + enum background_type background_type; + struct { unsigned char r, g, b; } background_color; + unsigned long slideshow_image_duration; + unsigned long slideshow_time_elapsed; + char *font_name; + struct imv_navigator *navigator; + struct imv_loader *loader; + struct imv_commands *commands; + struct imv_texture *texture; + struct imv_viewport *view; + void *stdin_image_data; + size_t stdin_image_data_len; + char *input_buffer; + char *starting_path; + struct pollfd stdin_fd; + + SDL_Window *window; + SDL_Renderer *renderer; + TTF_Font *font; + SDL_Texture *background_texture; + bool sdl_init; + bool ttf_init; +}; + +void command_quit(struct imv_list *args, void *data); +void command_pan(struct imv_list *args, void *data); +void command_select_rel(struct imv_list *args, void *data); +void command_select_abs(struct imv_list *args, void *data); +void command_zoom(struct imv_list *args, void *data); +void command_remove(struct imv_list *args, void *data); +void command_fullscreen(struct imv_list *args, void *data); +void command_overlay(struct imv_list *args, void *data); + +static bool setup_window(struct imv *imv); +static void handle_event(struct imv *imv, SDL_Event *event); +static void render_window(struct imv *imv); + +struct imv *imv_create(void) +{ + struct imv *imv = malloc(sizeof(struct imv)); + imv->quit = false; + imv->fullscreen = false; + imv->overlay_enabled = false; + imv->nearest_neighbour = false; + imv->need_redraw = true; + imv->need_rescale = true; + imv->recursive_load = false; + imv->scaling_mode = SCALING_FULL; + imv->cycle_input = true; + imv->list_at_exit = false; + imv->paths_from_stdin = false; + imv->background_color.r = imv->background_color.g = imv->background_color.b = 0; + imv->slideshow_image_duration = 0; + imv->slideshow_time_elapsed = 0; + imv->font_name = strdup("Monospace:24"); + imv->navigator = imv_navigator_create(); + imv->loader = imv_loader_create(); + imv->commands = imv_commands_create(); + imv->stdin_image_data = NULL; + imv->stdin_image_data_len = 0; + imv->input_buffer = NULL; + imv->starting_path = NULL; + imv->window = NULL; + imv->renderer = NULL; + imv->font = NULL; + imv->background_texture = NULL; + imv->sdl_init = false; + imv->ttf_init = false; + + imv_command_register(imv->commands, "quit", &command_quit); + imv_command_register(imv->commands, "pan", &command_pan); + imv_command_register(imv->commands, "select_rel", &command_select_rel); + imv_command_register(imv->commands, "select_abs", &command_select_abs); + imv_command_register(imv->commands, "zoom", &command_zoom); + imv_command_register(imv->commands, "remove", &command_remove); + imv_command_register(imv->commands, "fullscreen", &command_fullscreen); + imv_command_register(imv->commands, "overlay", &command_overlay); + + imv_command_alias(imv->commands, "q", "quit"); + imv_command_alias(imv->commands, "next", "select_rel 1"); + imv_command_alias(imv->commands, "previous", "select_rel -1"); + imv_command_alias(imv->commands, "n", "select_rel 1"); + imv_command_alias(imv->commands, "p", "select_rel -1"); + + return imv; +} + +void imv_free(struct imv *imv) +{ + free(imv->font_name); + imv_navigator_free(imv->navigator); + imv_loader_free(imv->loader); + imv_commands_free(imv->commands); + 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_texture) { + SDL_DestroyTexture(imv->background_texture); + } + if(imv->font) { + TTF_CloseFont(imv->font); + } + if(imv->ttf_init) { + TTF_Quit(); + } + if(imv->sdl_init) { + SDL_Quit(); + } + free(imv); +} + +bool imv_parse_args(struct imv *imv, int argc, char **argv) +{ + /* Do not print getopt errors */ + opterr = 0; + + char *argp, *ep = *argv; + int o; + + while((o = getopt(argc, argv, "frasSudxhln:b:e:t:")) != -1) { + switch(o) { + case 'f': imv->fullscreen = true; break; + case 'r': imv->recursive_load = true; break; + case 'a': imv->scaling_mode = SCALING_NONE; break; + case 's': imv->scaling_mode = SCALING_DOWN; break; + case 'S': imv->scaling_mode = SCALING_FULL; break; + case 'u': imv->nearest_neighbour = true; break; + case 'd': imv->overlay_enabled = true; break; + case 'x': imv->cycle_input = false; break; + case 'l': imv->list_at_exit = true; break; + case 'n': imv->starting_path = optarg; break; + case 'e': imv->font_name = strdup(optarg); break; + case 'h': + fprintf(stdout, + "imv %s\n" + "See manual for usage information.\n" + "\n" + "Legal:\n" + "This program is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU General Public License\n" + "as published by the Free Software Foundation; either version 2\n" + "of the License, or (at your option) any later version.\n" + "\n" + "This software uses the FreeImage open source image library.\n" + "See http://freeimage.sourceforge.net for details.\n" + "FreeImage is used under the GNU GPLv2.\n" + , IMV_VERSION); + imv->quit = true; + return true; + case 'b': + if(strcmp("checks", optarg) == 0) { + imv->background_type = BACKGROUND_CHEQUERED; + } else { + imv->background_type = BACKGROUND_SOLID; + argp = (*optarg == '#') ? optarg + 1 : optarg; + uint32_t n = strtoul(argp, &ep, 16); + if(*ep != '\0' || ep - argp != 6 || n > 0xFFFFFF) { + fprintf(stderr, "Invalid hex color: '%s'\n", optarg); + return false; + } + imv->background_color.b = n & 0xFF; + imv->background_color.g = (n >> 8) & 0xFF; + imv->background_color.r = (n >> 16); + } + break; + case 't': + imv->slideshow_image_duration = strtoul(optarg, &argp, 10); + imv->slideshow_image_duration *= 1000; + if (*argp == '.') { + long delay = strtoul(++argp, &ep, 10); + for (int i = 3 - (ep - argp); 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) { + fprintf(stderr, "Wrong slideshow delay '%s'. Aborting.\n", optarg); + return false; + } + break; + case '?': + fprintf(stderr, "Unknown argument '%c'. Aborting.\n", optopt); + return false; + } + } + + argc -= optind; + argv += optind; + + /* if no paths are given as args, expect them from stdin */ + 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) { + + /* Special case: '-' denotes reading image data from stdin */ + if(!strcmp("-", argv[i])) { + if(imv->paths_from_stdin) { + fprintf(stderr, "Can't read paths AND image data from stdin. Aborting.\n"); + return false; + } else if(data_from_stdin) { + fprintf(stderr, "Can't read image data from stdin twice. Aborting.\n"); + return false; + } + data_from_stdin = true; + + imv->stdin_image_data_len = read_from_stdin(&imv->stdin_image_data); + } + + imv_add_path(imv, argv[i]); + } + } + + if(imv->paths_from_stdin) { + imv->stdin_fd.fd = STDIN_FILENO; + imv->stdin_fd.events = POLLIN; + fprintf(stderr, "Reading paths from stdin..."); + + char buf[PATH_MAX]; + char *stdin_ok; + while((stdin_ok = fgets(buf, sizeof(buf), stdin)) != NULL) { + size_t len = strlen(buf); + if(buf[len-1] == '\n') { + buf[--len] = 0; + } + if(len > 0) { + imv_add_path(imv, buf); + break; + } + } + if(!stdin_ok) { + fprintf(stderr, " no input!\n"); + return false; + } + fprintf(stderr, "\n"); + } + return true; +} + +static void check_stdin_for_paths(struct imv *imv) +{ + /* check stdin to see if we've been given any new paths to load */ + if(poll(&imv->stdin_fd, 1, 10) != 1 || imv->stdin_fd.revents & (POLLERR|POLLNVAL)) { + fprintf(stderr, "error polling stdin"); + imv->quit = true; + return; + } + + if(imv->stdin_fd.revents & (POLLIN|POLLHUP)) { + char buf[PATH_MAX]; + if(fgets(buf, sizeof(buf), stdin) == NULL && ferror(stdin)) { + clearerr(stdin); + return; + } + if(feof(stdin)) { + imv->paths_from_stdin = false; + fprintf(stderr, "done with stdin\n"); + return; + } + + size_t len = strlen(buf); + if(buf[len-1] == '\n') { + buf[--len] = 0; + } + if(len > 0) { + imv_add_path(imv, buf); + imv->need_redraw = true; + } + } +} + +void imv_add_path(struct imv *imv, const char *path) +{ + imv_navigator_add(imv->navigator, path, imv->recursive_load); +} + +bool imv_run(struct imv *imv) +{ + if(!setup_window(imv)) + return false; + + imv->quit = false; + + /* cache current image's dimensions */ + int iw = 0; + int ih = 0; + + /* time keeping */ + unsigned int last_time = SDL_GetTicks(); + unsigned int current_time; + + while(!imv->quit) { + + SDL_Event e; + while(!imv->quit && SDL_PollEvent(&e)) { + handle_event(imv, &e); + } + + /* if we're quitting, don't bother drawing any more images */ + if(imv->quit) { + break; + } + + /* check if an image failed to load, if so, remove it from our image list */ + char *err_path = imv_loader_get_error(imv->loader); + if(err_path) { + imv_navigator_remove(imv->navigator, err_path); + + /* special case: the image came from stdin */ + if(strncmp(err_path, "-", 2) == 0) { + if(imv->stdin_image_data) { + free(imv->stdin_image_data); + imv->stdin_image_data = NULL; + imv->stdin_image_data_len = 0; + } + fprintf(stderr, "Failed to load image from stdin.\n"); + } + free(err_path); + } + + /* Check if navigator wrapped around paths lists */ + if(!imv->cycle_input && imv_navigator_wrapped(imv->navigator)) { + break; + } + + /* if the user has changed image, start loading the new one */ + if(imv_navigator_poll_changed(imv->navigator)) { + const char *current_path = imv_navigator_selection(imv->navigator); + if(!current_path) { + if(!imv->paths_from_stdin) { + fprintf(stderr, "No input files left. Exiting.\n"); + imv->quit = true; + } + continue; + } + + char title[1024]; + snprintf(title, sizeof(title), "imv - [%i/%i] [LOADING] %s [%s]", + imv->navigator->cur_path + 1, imv->navigator->num_paths, current_path, + scaling_label[imv->scaling_mode]); + imv_viewport_set_title(imv->view, title); + + imv_loader_load(imv->loader, current_path, + imv->stdin_image_data, imv->stdin_image_data_len); + imv->view->playing = true; + } + + + /* check if a new image is available to display */ + FIBITMAP *bmp; + int is_new_image; + if(imv_loader_get_image(imv->loader, &bmp, &is_new_image)) { + imv_texture_set_image(imv->texture, bmp); + iw = FreeImage_GetWidth(bmp); + ih = FreeImage_GetHeight(bmp); + FreeImage_Unload(bmp); + imv->need_redraw = true; + imv->need_rescale += is_new_image; + } + + if(imv->need_rescale) { + int ww, wh; + SDL_GetWindowSize(imv->window, &ww, &wh); + + imv->need_rescale = false; + if(imv->scaling_mode == SCALING_NONE || + (imv->scaling_mode == SCALING_DOWN && ww > iw && wh > ih)) { + imv_viewport_scale_to_actual(imv->view, imv->texture); + } else { + imv_viewport_scale_to_window(imv->view, imv->texture); + } + } + + /* tell the loader time has passed (for gifs) */ + current_time = SDL_GetTicks(); + + /* if we're playing an animated gif, tell the loader how much time has + * passed */ + if(imv->view->playing) { + unsigned int dt = current_time - last_time; + /* We cap the delta-time to 100 ms so that if imv is asleep for several + * seconds or more (e.g. suspended), upon waking up it doesn't try to + * catch up all the time it missed by playing through the gif quickly. */ + if(dt > 100) { + dt = 100; + } + imv_loader_time_passed(imv->loader, dt / 1000.0); + } + + /* handle slideshow */ + if(imv->slideshow_image_duration != 0) { + unsigned int 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_navigator_select_rel(imv->navigator, 1); + imv->slideshow_time_elapsed = 0; + } + } + + last_time = current_time; + + + /* check if the viewport needs a redraw */ + if(imv_viewport_needs_redraw(imv->view)) { + imv->need_redraw = true; + } + + if(imv->need_redraw) { + render_window(imv); + SDL_RenderPresent(imv->renderer); + } + + if(imv->paths_from_stdin) { + /* check stdin for any more paths */ + check_stdin_for_paths(imv); + } else { + /* sleep a little bit so we don't waste CPU time */ + SDL_Delay(10); + } + } + + return false; +} + +static bool setup_window(struct imv *imv) +{ + if(SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "SDL Failed to Init: %s\n", SDL_GetError()); + return false; + } + imv->sdl_init = true; + + /* width and height arbitrarily chosen. Perhaps there's a smarter way to + * set this */ + const int width = 1280; + const int height = 720; + + imv->window = SDL_CreateWindow( + "imv", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + width, height, + SDL_WINDOW_RESIZABLE); + + if(!imv->window) { + fprintf(stderr, "SDL Failed to create window: %s\n", SDL_GetError()); + 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) { + fprintf(stderr, "SDL Failed to create renderer: %s\n", SDL_GetError()); + return false; + } + + /* use the appropriate resampling method */ + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, + imv->nearest_neighbour ? "0" : "1"); + + /* construct a chequered background texture */ + if(imv->background_type == BACKGROUND_CHEQUERED) { + imv->background_texture = create_chequered(imv->renderer); + } + + /* 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) { + fprintf(stderr, "Error loading font: %s\n", TTF_GetError()); + return false; + } + + imv->texture = imv_texture_create(imv->renderer); + imv->view = imv_viewport_create(imv->window); + + /* put us in fullscren mode to begin with if requested */ + if(imv->fullscreen) { + imv_viewport_toggle_fullscreen(imv->view); + } + + /* start outside of command mode */ + SDL_StopTextInput(); + + return true; +} + +static void handle_event(struct imv *imv, SDL_Event *event) +{ + const int command_buffer_len = 1024; + 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) { + imv_command_exec(imv->commands, imv->input_buffer, imv); + SDL_StopTextInput(); + 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; + } + + switch (event->key.keysym.sym) { + case SDLK_SEMICOLON: + if(event->key.keysym.mod & KMOD_SHIFT) { + SDL_StartTextInput(); + imv->input_buffer = malloc(command_buffer_len); + imv->input_buffer[0] = '\0'; + imv->need_redraw = true; + } + break; + case SDLK_q: + imv->quit = true; + imv_command_exec(imv->commands, "quit", imv); + break; + case SDLK_LEFTBRACKET: + case SDLK_LEFT: + imv_command_exec(imv->commands, "select_rel -1", imv); + break; + case SDLK_RIGHTBRACKET: + case SDLK_RIGHT: + imv_command_exec(imv->commands, "select_rel 1", imv); + break; + case SDLK_EQUALS: + case SDLK_PLUS: + case SDLK_i: + case SDLK_UP: + imv_viewport_zoom(imv->view, imv->texture, IMV_ZOOM_KEYBOARD, 1); + break; + case SDLK_MINUS: + case SDLK_o: + case SDLK_DOWN: + imv_viewport_zoom(imv->view, imv->texture, IMV_ZOOM_KEYBOARD, -1); + break; + case SDLK_s: + if(!event->key.repeat) { + imv->scaling_mode++; + imv->scaling_mode %= SCALING_MODE_COUNT; + } + /* FALLTHROUGH */ + case SDLK_r: + if(!event->key.repeat) { + imv->need_rescale = true; + imv->need_redraw = true; + } + break; + case SDLK_a: + if(!event->key.repeat) { + imv_viewport_scale_to_actual(imv->view, imv->texture); + } + break; + case SDLK_c: + if(!event->key.repeat) { + imv_viewport_center(imv->view, imv->texture); + } + break; + case SDLK_j: + imv_command_exec(imv->commands, "pan 0 -50", imv); + break; + case SDLK_k: + imv_command_exec(imv->commands, "pan 0 50", imv); + break; + case SDLK_h: + imv_command_exec(imv->commands, "pan 50 0", imv); + break; + case SDLK_l: + imv_command_exec(imv->commands, "pan -50 0", imv); + break; + case SDLK_x: + if(!event->key.repeat) { + imv_command_exec(imv->commands, "remove", imv); + } + break; + case SDLK_f: + if(!event->key.repeat) { + imv_command_exec(imv->commands, "fullscreen", imv); + } + break; + case SDLK_PERIOD: + imv_loader_load_next_frame(imv->loader); + break; + case SDLK_SPACE: + if(!event->key.repeat) { + imv_viewport_toggle_playing(imv->view); + } + break; + case SDLK_p: + if(!event->key.repeat) { + puts(imv_navigator_selection(imv->navigator)); + } + break; + case SDLK_d: + if(!event->key.repeat) { + imv_command_exec(imv->commands, "overlay", imv); + } + break; + case SDLK_t: + if(event->key.keysym.mod & (KMOD_SHIFT|KMOD_CAPS)) { + if(imv->slideshow_image_duration >= 1000) { + imv->slideshow_image_duration -= 1000; + } + } else { + imv->slideshow_image_duration += 1000; + } + imv->need_redraw = true; + break; + } + break; + case SDL_MOUSEWHEEL: + imv_viewport_zoom(imv->view, imv->texture, 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); + } + SDL_ShowCursor(SDL_ENABLE); + break; + case SDL_WINDOWEVENT: + imv_viewport_update(imv->view, imv->texture); + break; + } +} + +static void render_window(struct imv *imv) +{ + char title[1024]; + int ww, wh; + SDL_GetWindowSize(imv->window, &ww, &wh); + + /* update window title */ + const char *current_path = imv_navigator_selection(imv->navigator); + int len = snprintf(title, sizeof(title), "imv - [%i/%i] [%ix%i] [%.2f%%] %s [%s]", + imv->navigator->cur_path + 1, imv->navigator->num_paths, imv->texture->width, imv->texture->height, + 100.0 * imv->view->scale, + current_path, scaling_label[imv->scaling_mode]); + if(imv->slideshow_image_duration >= 1000) { + len += snprintf(title + len, sizeof(title) - len, "[%lu/%lus]", + imv->slideshow_time_elapsed / 1000 + 1, imv->slideshow_image_duration / 1000); + } + imv_viewport_set_title(imv->view, title); + + /* 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); + } else { + /* chequered background */ + int img_w, img_h; + SDL_QueryTexture(imv->background_texture, NULL, NULL, &img_w, &img_h); + /* tile the texture 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_texture, NULL, &dst_rect); + } + } + } + + /* draw our actual texture */ + imv_texture_draw(imv->texture, imv->view->x, imv->view->y, imv->view->scale); + + /* 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}; + imv_printf(imv->renderer, imv->font, 0, 0, &fg, &bg, "%s", + title + strlen("imv - ")); + } + + /* 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); + } + + /* redraw complete, unset the flag */ + imv->need_redraw = false; +} + +void command_quit(struct imv_list *args, void *data) +{ + (void)args; + struct imv *imv = data; + imv->quit = true; +} + +void command_pan(struct imv_list *args, void *data) +{ + struct imv *imv = data; + 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); +} + +void command_select_rel(struct imv_list *args, void *data) +{ + struct imv *imv = data; + 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; +} + +void command_select_abs(struct imv_list *args, void *data) +{ + (void)args; + (void)data; +} + +void command_zoom(struct imv_list *args, void *data) +{ + (void)args; + (void)data; +} + +void command_remove(struct imv_list *args, void *data) +{ + (void)args; + struct imv *imv = data; + char* path = strdup(imv_navigator_selection(imv->navigator)); + imv_navigator_remove(imv->navigator, path); + free(path); + + imv->slideshow_time_elapsed = 0; +} + +void command_fullscreen(struct imv_list *args, void *data) +{ + (void)args; + struct imv *imv = data; + imv_viewport_toggle_fullscreen(imv->view); +} + +void command_overlay(struct imv_list *args, void *data) +{ + (void)args; + struct imv *imv = data; + imv->overlay_enabled = !imv->overlay_enabled; + imv->need_redraw = true; +} + +/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/imv.h b/src/imv.h new file mode 100644 index 0000000..0945fe2 --- /dev/null +++ b/src/imv.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2017 imv authors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef IMV_H +#define IMV_H + +#include <stdbool.h> + +struct imv; + +struct imv *imv_create(void); +void imv_free(struct imv *imv); + +bool imv_parse_args(struct imv *imv, int argc, char **argv); + +void imv_add_path(struct imv *imv, const char *path); + +bool imv_run(struct imv *imv); + +#endif + +/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..03195e0 --- /dev/null +++ b/src/list.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2017 imv authors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "list.h" + +struct imv_list *imv_list_create(void) +{ + struct imv_list *list = malloc(sizeof(struct imv_list)); + list->len = 0; + list->cap = 64; + list->items = malloc(sizeof(void*) * list->cap); + return list; +} + +void imv_list_free(struct imv_list *list) +{ + free(list->items); +} + +void imv_list_deep_free(struct imv_list *list) +{ + for(size_t i = 0; i < list->len; ++i) { + free(list->items[i]); + } + imv_list_free(list); +} + +void imv_list_append(struct imv_list *list, void *item) +{ + imv_list_grow(list, list->len + 1); + list->items[list->len++] = item; +} + +void imv_list_grow(struct imv_list *list, size_t min_size) +{ + if(list->cap >= min_size) { + return; + } + + while(list->cap < min_size) { + list->cap *= 2; + } + + list->items = realloc(list->items, sizeof(void*) * list->cap); +} + +void imv_list_remove(struct imv_list *list, size_t index) +{ + if(index >= list->len) { + return; + } + + memmove(&list->items[index], &list->items[index + 1], list->len - index); + + list->len -= 1; +} + +void imv_list_insert(struct imv_list *list, size_t index, void *item) +{ + imv_list_grow(list, list->len + 1); + + if(index > list->len) { + index = list->len; + } + + memmove(&list->items[index + 1], &list->items[index], list->len - index); + list->items[index] = item; + list->len += 1; +} + +struct imv_list *imv_split_string(const char *string, char delim) +{ + struct imv_list *list = imv_list_create(); + + const char *base = string; + + while(*base) { + while(*base && *base == delim) { + ++base; + } + + const char *end = base; + while(*end && *end != delim) { + ++end; + } + + if(*base && base != end) { + char *item = strndup(base, end - base); + imv_list_append(list, item); + base = end; + } + } + + return list; +} + +/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..a329e6d --- /dev/null +++ b/src/list.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2017 imv authors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef LIST_H +#define LIST_H + +#include <stdlib.h> +#include <string.h> + +struct imv_list { + size_t len; + size_t cap; + void **items; +}; + +struct imv_list *imv_list_create(void); + +void imv_list_free(struct imv_list *list); + +void imv_list_deep_free(struct imv_list *list); + +void imv_list_append(struct imv_list *list, void *item); + +void imv_list_grow(struct imv_list *list, size_t min_size); + +void imv_list_remove(struct imv_list *list, size_t index); + +void imv_list_insert(struct imv_list *list, size_t index, void *item); + +struct imv_list *imv_split_string(const char *string, char delim); + +#endif + +/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/loader.c b/src/loader.c index d70b66d..b21b140 100644 --- a/src/loader.c +++ b/src/loader.c @@ -44,18 +44,22 @@ static int is_thread_cancelled(void) return sigismember(&sigmask, SIGUSR1); } -void imv_init_loader(struct imv_loader *ldr) +struct imv_loader *imv_loader_create(void) { + struct imv_loader *ldr = malloc(sizeof(struct imv_loader)); memset(ldr, 0, sizeof(struct imv_loader)); pthread_mutex_init(&ldr->lock, NULL); /* ignore this signal in case we accidentally receive it */ block_usr1_signal(); + return ldr; } -void imv_destroy_loader(struct imv_loader *ldr) +void imv_loader_free(struct imv_loader *ldr) { /* wait for any existing bg thread to finish */ - pthread_join(ldr->bg_thread, NULL); + if(ldr->bg_thread) { + pthread_join(ldr->bg_thread, NULL); + } pthread_mutex_destroy(&ldr->lock); if(ldr->bmp) { @@ -70,6 +74,7 @@ void imv_destroy_loader(struct imv_loader *ldr) if(ldr->path) { free(ldr->path); } + free(ldr); } void imv_loader_load(struct imv_loader *ldr, const char *path, @@ -93,6 +98,7 @@ void imv_loader_load(struct imv_loader *ldr, const char *path, ldr->buffer_size = buffer_size; } else if (ldr->fi_buffer != NULL) { FreeImage_CloseMemory(ldr->fi_buffer); + ldr->fi_buffer = NULL; } pthread_create(&ldr->bg_thread, NULL, &bg_new_img, ldr); pthread_mutex_unlock(&ldr->lock); @@ -186,6 +192,7 @@ static void *bg_new_img(void *data) if (from_stdin) { pthread_mutex_lock(&ldr->lock); FreeImage_CloseMemory(ldr->fi_buffer); + ldr->fi_buffer = NULL; pthread_mutex_unlock(&ldr->lock); } error_occurred(ldr); diff --git a/src/loader.h b/src/loader.h index d2418a2..04dc640 100644 --- a/src/loader.h +++ b/src/loader.h @@ -44,11 +44,11 @@ struct imv_loader { double frame_time; }; -/* Initialises an instance of imv_loader */ -void imv_init_loader(struct imv_loader *img); +/* Creates an instance of imv_loader */ +struct imv_loader *imv_loader_create(void); -/* Cleans up all resources owned by a imv_loader instance */ -void imv_destroy_loader(struct imv_loader *img); +/* Cleans up an imv_loader instance */ +void imv_loader_free(struct imv_loader *ldr); /* Asynchronously load the given file */ void imv_loader_load(struct imv_loader *ldr, const char *path, @@ -1,4 +1,4 @@ -/* Copyright (c) 2015 imv authors +/* Copyright (c) 2017 imv authors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -15,696 +15,26 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include <limits.h> -#include <stdio.h> -#include <stddef.h> -#include <stdint.h> -#include <unistd.h> -#include <SDL2/SDL.h> -#include <SDL2/SDL_ttf.h> -#include <FreeImage.h> -#include <getopt.h> -#include <ctype.h> -#include <poll.h> -#include <errno.h> - -#include "loader.h" -#include "texture.h" -#include "navigator.h" -#include "viewport.h" -#include "util.h" - -enum scaling_mode { - NONE, - DOWN, - FULL -}; - -static char *scaling_label[] = { - "actual size", - "best fit", - "perfect fit" -}; - -struct { - int fullscreen; - int stdin_list; - int recursive; - int scaling; - int nearest_neighbour; - int solid_bg; - int list; - unsigned long delay; - int cycle; - unsigned char bg_r; - unsigned char bg_g; - unsigned char bg_b; - int overlay; - const char *start_at; - const char *font; -} g_options = { - .fullscreen = 0, - .stdin_list = 0, - .recursive = 0, - .scaling = FULL, - .nearest_neighbour = 0, - .solid_bg = 1, - .list = 0, - .delay = 0, - .cycle = 1, - .bg_r = 0, - .bg_g = 0, - .bg_b = 0, - .overlay = 0, - .start_at = NULL, - .font = "Monospace:24", -}; - -static void print_usage(void) -{ - fprintf(stdout, - "imv %s\n" - "See manual for usage information.\n" - "\n" - "Legal:\n" - "This program is free software; you can redistribute it and/or\n" - "modify it under the terms of the GNU General Public License\n" - "as published by the Free Software Foundation; either version 2\n" - "of the License, or (at your option) any later version.\n" - "\n" - "This software uses the FreeImage open source image library.\n" - "See http://freeimage.sourceforge.net for details.\n" - "FreeImage is used under the GNU GPLv2.\n" - , IMV_VERSION); -} - -static void parse_args(int argc, char** argv) -{ - /* Do not print getopt errors */ - opterr = 0; - - char *argp, *ep = *argv; - int o; - - while((o = getopt(argc, argv, "firasSudxhln:b:e:t:")) != -1) { - switch(o) { - case 'f': g_options.fullscreen = 1; break; - case 'i': - g_options.stdin_list = 1; - fprintf(stderr, "Warning: '-i' is deprecated. No flag is needed.\n"); - break; - case 'r': g_options.recursive = 1; break; - case 'a': g_options.scaling = NONE; break; - case 's': g_options.scaling = DOWN; break; - case 'S': g_options.scaling = FULL; break; - case 'u': g_options.nearest_neighbour = 1; break; - case 'd': g_options.overlay = 1; break; - case 'x': g_options.cycle = 0; break; - case 'h': print_usage(); exit(0); break; - case 'l': g_options.list = 1; break; - case 'n': - g_options.start_at = optarg; - break; - case 'b': - if(strcmp("checks", optarg) == 0) { - g_options.solid_bg = 0; - } else { - g_options.solid_bg = 1; - argp = (*optarg == '#') ? optarg + 1 : optarg; - uint32_t n = strtoul(argp, &ep, 16); - if(*ep != '\0' || ep - argp != 6 || n > 0xFFFFFF) { - fprintf(stderr, "Invalid hex color: '%s'\n", optarg); - exit(1); - } - g_options.bg_b = n & 0xFF; - g_options.bg_g = (n >> 8) & 0xFF; - g_options.bg_r = (n >> 16); - } - break; - case 'e': - g_options.font = optarg; - break; - case 't': - g_options.delay = strtoul(optarg, &argp, 10); - g_options.delay *= 1000; - if (*argp == '.') { - long delay = strtoul(++argp, &ep, 10); - for (int i = 3 - (ep - argp); i; i--) { - delay *= 10; - } - if (delay < 1000) { - g_options.delay += delay; - } else { - g_options.delay = ULONG_MAX; - } - } - if (g_options.delay == ULONG_MAX) { - fprintf(stderr, "Wrong slideshow delay '%s'. Aborting.\n", optarg); - exit(1); - } - break; - case '?': - fprintf(stderr, "Unknown argument '%c'. Aborting.\n", optopt); - exit(1); - } - } -} +#include "imv.h" int main(int argc, char** argv) { - struct imv_navigator nav; - imv_navigator_init(&nav); - - /* parse any command line options given */ - parse_args(argc, argv); - - argc -= optind; - argv += optind; + struct imv *imv = imv_create(); - /* if no names are given, expect them on stdin */ - if(argc == 0) { - g_options.stdin_list = 1; + if(!imv) { + return 1; } - /* if the user asked to load paths from stdin, set up poll(2)ing and read - the first path */ - struct pollfd rfds[1]; - if(g_options.stdin_list) { - rfds[0].fd = STDIN_FILENO; - rfds[0].events = POLLIN; - fprintf(stderr, "Reading paths from stdin..."); - - char buf[PATH_MAX]; - char *stdin_ok; - while((stdin_ok = fgets(buf, sizeof(buf), stdin)) != NULL) { - size_t len = strlen(buf); - if(buf[len-1] == '\n') { - buf[--len] = 0; - } - if(len > 0) { - if(imv_navigator_add(&nav, buf, g_options.recursive) != 0) { - imv_navigator_destroy(&nav); - exit(1); - } - break; - } - } - if(!stdin_ok) { - fprintf(stderr, " no input!\n"); - return -1; - } - fprintf(stderr, "\n"); - } - - void *stdin_buffer = NULL; - size_t stdin_buffer_size = 0; - int stdin_error = 0; - - /* handle any image paths given as arguments */ - for(int i = 0; i < argc; ++i) { - /* special case: '-' is actually an option */ - if(!strcmp("-",argv[i])) { - if (stdin_buffer) { - fprintf(stderr, "Can't read from stdin twice\n"); - exit(1); - } - stdin_buffer_size = read_from_stdin(&stdin_buffer); - if (stdin_buffer_size == 0) { - perror(NULL); - continue; /* we can't recover from the freed buffer, just ignore it */ - } - stdin_error = errno; - errno = 0; /* clear errno */ - } - /* add the given path to the list to load */ - if(imv_navigator_add(&nav, argv[i], g_options.recursive) != 0) { - imv_navigator_destroy(&nav); - exit(1); - } - } - - /* if we weren't given any paths we have nothing to view. exit */ - if(!imv_navigator_selection(&nav)) { - fprintf(stderr, "No input files. Exiting.\n"); - exit(1); - } - - /* go to the chosen starting image if possible */ - if(g_options.start_at) { - int start_index = imv_navigator_find_path(&nav, g_options.start_at); - if(start_index < 0) { - start_index = strtol(g_options.start_at,NULL,10) - 1; - } - imv_navigator_select_str(&nav, start_index); - } - - /* we've got something to display, so create an SDL window */ - if(SDL_Init(SDL_INIT_VIDEO) != 0) { - fprintf(stderr, "SDL Failed to Init: %s\n", SDL_GetError()); - exit(1); - } - - /* width and height arbitrarily chosen. Perhaps there's a smarter way to - * set this */ - const int width = 1280; - const int height = 720; - - SDL_Window *window = SDL_CreateWindow( - "imv", - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - width, height, - SDL_WINDOW_RESIZABLE); - if(!window) { - fprintf(stderr, "SDL Failed to create window: %s\n", SDL_GetError()); - SDL_Quit(); - exit(1); - } - - /* we'll use SDL's built-in renderer, hardware accelerated if possible */ - SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0); - if(!renderer) { - fprintf(stderr, "SDL Failed to create renderer: %s\n", SDL_GetError()); - SDL_DestroyWindow(window); - SDL_Quit(); - exit(1); - } - - /* use the appropriate resampling method */ - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, - g_options.nearest_neighbour ? "0" : "1"); - - /* construct a chequered background texture */ - SDL_Texture *chequered_tex = NULL; - if(!g_options.solid_bg) { - chequered_tex = create_chequered(renderer); - } - - /* set up the required fonts and surfaces for displaying the overlay */ - TTF_Init(); - TTF_Font *font = load_font(g_options.font); - if(!font) { - fprintf(stderr, "Error loading font: %s\n", TTF_GetError()); - } - - /* create our main classes on the stack*/ - struct imv_loader ldr; - imv_init_loader(&ldr); - - struct imv_texture tex; - imv_init_texture(&tex, renderer); - - struct imv_viewport view; - imv_init_viewport(&view, window); - - /* put us in fullscren mode to begin with if requested */ - if(g_options.fullscreen) { - imv_viewport_toggle_fullscreen(&view); + if(!imv_parse_args(imv, argc, argv)) { + imv_free(imv); + return 1; } - /* help keeping track of time */ - unsigned int last_time = SDL_GetTicks(); - unsigned int current_time; - - /* keep file change polling rate under control */ - static uint8_t poll_countdown = UINT8_MAX; - - /* do we need to redraw the window? */ - int need_redraw = 1; - int need_rescale = 0; - - /* keep title buffer around for reuse */ - char title[256]; - - /* used to calculate when to skip to the next image in slideshow mode */ - unsigned long delay_msec = 0; - - /* initialize variables holding image dimentions */ - int iw = 0, ih = 0; - - int quit = 0; - while(!quit) { - /* handle any input/window events sent by SDL */ - SDL_Event e; - while(!quit && SDL_PollEvent(&e)) { - switch(e.type) { - case SDL_QUIT: - quit = 1; - break; - case SDL_KEYDOWN: - SDL_ShowCursor(SDL_DISABLE); - switch (e.key.keysym.sym) { - case SDLK_q: - quit = 1; - break; - case SDLK_LEFTBRACKET: - case SDLK_LEFT: - imv_navigator_select_rel(&nav, -1); - /* reset slideshow delay */ - delay_msec = 0; - break; - case SDLK_RIGHTBRACKET: - case SDLK_RIGHT: - imv_navigator_select_rel(&nav, 1); - /* reset slideshow delay */ - delay_msec = 0; - break; - case SDLK_EQUALS: - case SDLK_PLUS: - case SDLK_i: - case SDLK_UP: - imv_viewport_zoom(&view, &tex, IMV_ZOOM_KEYBOARD, 1); - break; - case SDLK_MINUS: - case SDLK_o: - case SDLK_DOWN: - imv_viewport_zoom(&view, &tex, IMV_ZOOM_KEYBOARD, -1); - break; - case SDLK_s: - if(!e.key.repeat) { - if((g_options.scaling += 1) > FULL) { - g_options.scaling = NONE; - } - } - /* FALLTHROUGH */ - case SDLK_r: - if(!e.key.repeat) { - need_rescale = 1; - need_redraw = 1; - } - break; - case SDLK_a: - if(!e.key.repeat) { - imv_viewport_scale_to_actual(&view, &tex); - } - break; - case SDLK_c: - if(!e.key.repeat) { - imv_viewport_center(&view, &tex); - } - break; - case SDLK_j: - imv_viewport_move(&view, 0, -50); - break; - case SDLK_k: - imv_viewport_move(&view, 0, 50); - break; - case SDLK_h: - imv_viewport_move(&view, 50, 0); - break; - case SDLK_l: - imv_viewport_move(&view, -50, 0); - break; - case SDLK_x: - if(!e.key.repeat) { - char* path = strdup(imv_navigator_selection(&nav)); - imv_navigator_remove(&nav, path); - - if (SDL_GetModState() & KMOD_SHIFT) { - if (remove(path)) { - fprintf(stderr, "Warning: can't remove %s from disk.\n", path); - } - } - - free(path); - - /* reset slideshow delay */ - delay_msec = 0; - } - break; - case SDLK_f: - if(!e.key.repeat) { - imv_viewport_toggle_fullscreen(&view); - } - break; - case SDLK_PERIOD: - imv_loader_load_next_frame(&ldr); - break; - case SDLK_SPACE: - if(!e.key.repeat) { - imv_viewport_toggle_playing(&view); - } - break; - case SDLK_p: - if(!e.key.repeat) { - puts(imv_navigator_selection(&nav)); - } - break; - case SDLK_d: - if(!e.key.repeat) { - g_options.overlay = !g_options.overlay; - need_redraw = 1; - } - break; - case SDLK_t: - if(e.key.keysym.mod & (KMOD_SHIFT|KMOD_CAPS)) { - if(g_options.delay >= 1000) { - g_options.delay -= 1000; - } - } else { - g_options.delay += 1000; - } - need_redraw = 1; - break; - } - break; - case SDL_MOUSEWHEEL: - imv_viewport_zoom(&view, &tex, IMV_ZOOM_MOUSE, e.wheel.y); - SDL_ShowCursor(SDL_ENABLE); - break; - case SDL_MOUSEMOTION: - if(e.motion.state & SDL_BUTTON_LMASK) { - imv_viewport_move(&view, e.motion.xrel, e.motion.yrel); - } - SDL_ShowCursor(SDL_ENABLE); - break; - case SDL_WINDOWEVENT: - imv_viewport_update(&view, &tex); - break; - } - } - - /* if we're quitting, don't bother drawing any more images */ - if(quit) { - break; - } - - /* check if an image failed to load, if so, remove it from our image list */ - char *err_path = imv_loader_get_error(&ldr); - if(err_path) { - imv_navigator_remove(&nav, err_path); - if (strncmp(err_path, "-", 2) == 0) { - free(stdin_buffer); - stdin_buffer_size = 0; - if (stdin_error != 0) { - errno = stdin_error; - perror("Failed to load image from standard input"); - errno = 0; - } - } - free(err_path); - } - - /* Check if navigator wrapped around paths lists */ - if(!g_options.cycle && imv_navigator_wrapped(&nav)) { - break; - } - - /* if the user has changed image, start loading the new one */ - if(imv_navigator_poll_changed(&nav, poll_countdown--)) { - const char *current_path = imv_navigator_selection(&nav); - if(!current_path) { - if(g_options.stdin_list) { - continue; - } - fprintf(stderr, "No input files left. Exiting.\n"); - exit(1); - } - - snprintf(title, sizeof(title), "imv - [%i/%i] [LOADING] %s [%s]", - nav.cur_path + 1, nav.num_paths, current_path, - scaling_label[g_options.scaling]); - imv_viewport_set_title(&view, title); - - imv_loader_load(&ldr, current_path, stdin_buffer, stdin_buffer_size); - view.playing = 1; - } - - /* get window height and width */ - int ww, wh; - SDL_GetWindowSize(window, &ww, &wh); - - /* check if a new image is available to display */ - FIBITMAP *bmp; - int is_new_image; - if(imv_loader_get_image(&ldr, &bmp, &is_new_image)) { - imv_texture_set_image(&tex, bmp); - iw = FreeImage_GetWidth(bmp); - ih = FreeImage_GetHeight(bmp); - FreeImage_Unload(bmp); - need_redraw = 1; - need_rescale += is_new_image; - } - - if(need_rescale) { - need_rescale = 0; - if(g_options.scaling == NONE || - (g_options.scaling == DOWN && ww > iw && wh > ih)) { - imv_viewport_scale_to_actual(&view, &tex); - } else { - imv_viewport_scale_to_window(&view, &tex); - } - } - - current_time = SDL_GetTicks(); + int ret = imv_run(imv); - /* if we're playing an animated gif, tell the loader how much time has - * passed */ - if(view.playing) { - unsigned int dt = current_time - last_time; - /* We cap the delta-time to 100 ms so that if imv is asleep for several - * seconds or more (e.g. suspended), upon waking up it doesn't try to - * catch up all the time it missed by playing through the gif quickly. */ - if(dt > 100) { - dt = 100; - } - imv_loader_time_passed(&ldr, dt / 1000.0); - } + imv_free(imv); - /* handle slideshow */ - if(g_options.delay) { - unsigned int dt = current_time - last_time; - - delay_msec += dt; - need_redraw = 1; - if(delay_msec >= g_options.delay) { - imv_navigator_select_rel(&nav, 1); - delay_msec = 0; - } - } - - last_time = current_time; - - /* check if the viewport needs a redraw */ - if(imv_viewport_needs_redraw(&view)) { - need_redraw = 1; - } - - /* only redraw when something's changed */ - if(need_redraw) { - /* update window title */ - int len; - const char *current_path = imv_navigator_selection(&nav); - len = snprintf(title, sizeof(title), "imv - [%i/%i] [%ix%i] [%.2f%%] %s [%s]", - nav.cur_path + 1, nav.num_paths, tex.width, tex.height, - 100.0 * view.scale, - current_path, scaling_label[g_options.scaling]); - if(g_options.delay >= 1000) { - len += snprintf(title + len, sizeof(title) - len, "[%lu/%lus]", - delay_msec / 1000 + 1, g_options.delay / 1000); - } - imv_viewport_set_title(&view, title); - - /* first we draw the background */ - if(g_options.solid_bg) { - /* solid background */ - SDL_SetRenderDrawColor(renderer, - g_options.bg_r, g_options.bg_g, g_options.bg_b, 255); - SDL_RenderClear(renderer); - } else { - /* chequered background */ - int img_w, img_h; - SDL_QueryTexture(chequered_tex, NULL, NULL, &img_w, &img_h); - /* tile the texture 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(renderer, chequered_tex, NULL, &dst_rect); - } - } - } - - /* draw our actual texture */ - imv_texture_draw(&tex, view.x, view.y, view.scale); - - /* if the overlay needs to be drawn, draw that too */ - if(g_options.overlay && font) { - SDL_Color fg = {255,255,255,255}; - SDL_Color bg = {0,0,0,160}; - imv_printf(renderer, font, 0, 0, &fg, &bg, "%s", - title + strlen("imv - ")); - } - - /* redraw complete, unset the flag */ - need_redraw = 0; - - /* reset poll countdown timer */ - poll_countdown = UINT8_MAX; - - /* tell SDL to show the newly drawn frame */ - SDL_RenderPresent(renderer); - } - - /* sleep a little bit so we don't waste CPU time */ - if(g_options.stdin_list) { - if(poll(rfds, 1, 10) != 1 || rfds[0].revents & (POLLERR|POLLNVAL)) { - fprintf(stderr, "error polling stdin"); - return 1; - } - if(rfds[0].revents & (POLLIN|POLLHUP)) { - char buf[PATH_MAX]; - if(fgets(buf, sizeof(buf), stdin) == NULL && ferror(stdin)) { - clearerr(stdin); - continue; - } - if(feof(stdin)) { - g_options.stdin_list = 0; - fprintf(stderr, "done with stdin\n"); - continue; - } - - size_t len = strlen(buf); - if(buf[len-1] == '\n') { - buf[--len] = 0; - } - if(len > 0) { - if(imv_navigator_add(&nav, buf, g_options.recursive)) { - break; - } - need_redraw = 1; - } - } - } else { - SDL_Delay(10); - } - } - while(g_options.list) { - const char *path = imv_navigator_selection(&nav); - if(!path) { - break; - } - fprintf(stdout, "%s\n", path); - imv_navigator_remove(&nav, path); - } - /* clean up our resources now that we're exiting */ - imv_destroy_loader(&ldr); - imv_destroy_texture(&tex); - imv_navigator_destroy(&nav); - imv_destroy_viewport(&view); - - if(font) { - TTF_CloseFont(font); - } - TTF_Quit(); - if(chequered_tex) { - SDL_DestroyTexture(chequered_tex); - } - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); - - return 0; + return ret; } - /* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/navigator.c b/src/navigator.c index 97772cd..ae7bccd 100644 --- a/src/navigator.c +++ b/src/navigator.c @@ -24,13 +24,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include <string.h> #include <stdio.h> -void imv_navigator_init(struct imv_navigator *nav) +struct imv_navigator *imv_navigator_create(void) { + struct imv_navigator *nav = malloc(sizeof(struct imv_navigator)); memset(nav, 0, sizeof(struct imv_navigator)); nav->last_move_direction = 1; + return nav; } -void imv_navigator_destroy(struct imv_navigator *nav) +void imv_navigator_free(struct imv_navigator *nav) { if(nav->paths) { for(int i = 0; i < nav->num_paths; ++i) { @@ -45,7 +47,11 @@ void imv_navigator_destroy(struct imv_navigator *nav) free(nav->mtimes); } - memset(nav, 0, sizeof(struct imv_navigator)); + if(nav->ctimes) { + free(nav->ctimes); + } + + free(nav); } static int add_item(struct imv_navigator *nav, const char *path, @@ -54,19 +60,23 @@ static int add_item(struct imv_navigator *nav, const char *path, if(nav->num_paths % BUFFER_SIZE == 0) { char **new_paths; time_t *new_mtimes; + time_t *new_ctimes; size_t new_size = nav->num_paths + BUFFER_SIZE; new_paths = realloc(nav->paths, sizeof(char*) * new_size); new_mtimes = realloc(nav->mtimes, sizeof(time_t) * new_size); - if (new_paths == NULL || new_mtimes == NULL) { + new_ctimes = realloc(nav->ctimes, sizeof(time_t) * new_size); + if (new_paths == NULL || new_mtimes == NULL || new_ctimes == NULL) { return 1; } nav->paths = new_paths; nav->mtimes = new_mtimes; + nav->ctimes = new_ctimes; } if((nav->paths[nav->num_paths] = strndup(path, PATH_MAX)) == NULL) { return 1; } nav->mtimes[nav->num_paths] = mtime; + nav->ctimes[nav->num_paths] = time(NULL); nav->num_paths += 1; if(nav->num_paths == 1) { nav->changed = 1; @@ -216,7 +226,7 @@ int imv_navigator_find_path(struct imv_navigator *nav, const char *path) return -1; } -int imv_navigator_poll_changed(struct imv_navigator *nav, const int nopoll) +int imv_navigator_poll_changed(struct imv_navigator *nav) { if(nav->changed) { nav->changed = 0; @@ -227,7 +237,11 @@ int imv_navigator_poll_changed(struct imv_navigator *nav, const int nopoll) return 0; }; - if(!nopoll) { + time_t cur_time = time(NULL); + /* limit polling to once per second */ + if(nav->ctimes[nav->cur_path] < cur_time - 1) { + nav->ctimes[nav->cur_path] = cur_time; + struct stat file_info; if(stat(nav->paths[nav->cur_path], &file_info) == -1) { return 0; diff --git a/src/navigator.h b/src/navigator.h index ac3cc59..0d56fa7 100644 --- a/src/navigator.h +++ b/src/navigator.h @@ -27,16 +27,18 @@ struct imv_navigator { int cur_path; char **paths; time_t *mtimes; + time_t *ctimes; int last_move_direction; int changed; int wrapped; + int poll_countdown; }; -/* Initialises an instance of imv_navigator */ -void imv_navigator_init(struct imv_navigator *nav); +/* Creates an instance of imv_navigator */ +struct imv_navigator *imv_navigator_create(void); -/* Cleans up all resources owned by a imv_navigator instance */ -void imv_navigator_destroy(struct imv_navigator *nav); +/* Cleans up an imv_navigator instance */ +void imv_navigator_free(struct imv_navigator *nav); /* Adds the given path to the navigator's internal list. * If a directory is given, all files within that directory are added. @@ -65,7 +67,7 @@ int imv_navigator_find_path(struct imv_navigator *nav, const char *path); /* Returns 1 if either the currently selected path or underlying file has * changed since last called */ -int imv_navigator_poll_changed(struct imv_navigator *nav, const int nopoll); +int imv_navigator_poll_changed(struct imv_navigator *nav); /* Check whether navigator wrapped around paths list */ int imv_navigator_wrapped(struct imv_navigator *nav); diff --git a/src/texture.c b/src/texture.c index 52ec521..bdfe91f 100644 --- a/src/texture.c +++ b/src/texture.c @@ -17,8 +17,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "texture.h" -void imv_init_texture(struct imv_texture *tex, SDL_Renderer *r) +struct imv_texture *imv_texture_create(SDL_Renderer *r) { + struct imv_texture *tex = malloc(sizeof(struct imv_texture)); memset(tex, 0, sizeof(struct imv_texture)); tex->renderer = r; @@ -26,9 +27,10 @@ void imv_init_texture(struct imv_texture *tex, SDL_Renderer *r) SDL_GetRendererInfo(r, &ri); tex->chunk_width = ri.max_texture_width != 0 ? ri.max_texture_width : 4096; tex->chunk_height = ri.max_texture_height != 0 ? ri.max_texture_height : 4096; + return tex; } -void imv_destroy_texture(struct imv_texture *tex) +void imv_texture_free(struct imv_texture *tex) { if(tex->num_chunks > 0) { for(int i = 0; i < tex->num_chunks; ++i) { @@ -39,6 +41,7 @@ void imv_destroy_texture(struct imv_texture *tex) tex->chunks = NULL; tex->renderer = NULL; } + free(tex); } int imv_texture_set_image(struct imv_texture *tex, FIBITMAP *image) diff --git a/src/texture.h b/src/texture.h index 4d2aea9..2931525 100644 --- a/src/texture.h +++ b/src/texture.h @@ -36,11 +36,11 @@ struct imv_texture { }; -/* Initialises an instance of imv_texture */ -void imv_init_texture(struct imv_texture *tex, SDL_Renderer *r); +/* Creates an instance of imv_texture */ +struct imv_texture *imv_texture_create(SDL_Renderer *r); -/* Cleans up all resources owned by a imv_texture instance */ -void imv_destroy_texture(struct imv_texture *tex); +/* Cleans up an imv_texture instance */ +void imv_texture_free(struct imv_texture *tex); /* Updates the texture to contain the data in the image parameter */ int imv_texture_set_image(struct imv_texture *tex, FIBITMAP *image); @@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. size_t read_from_stdin(void **buffer) { size_t len = 0; ssize_t r; - size_t step = 1024; /* Arbitrary value of 1 KiB */ + size_t step = 4096; /* Arbitrary value of 4 KiB */ void *p; errno = 0; /* clear errno */ @@ -153,5 +153,4 @@ void imv_printf(SDL_Renderer *renderer, TTF_Font *font, int x, int y, va_end(args); } - /* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/viewport.c b/src/viewport.c index e706643..d096a45 100644 --- a/src/viewport.c +++ b/src/viewport.c @@ -17,19 +17,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "viewport.h" -void imv_init_viewport(struct imv_viewport *view, SDL_Window *window) +struct imv_viewport *imv_viewport_create(SDL_Window *window) { + struct imv_viewport *view = malloc(sizeof(struct imv_viewport)); view->window = window; view->scale = 1; view->x = view->y = view->fullscreen = view->redraw = 0; view->playing = 1; view->locked = 0; + return view; } -void imv_destroy_viewport(struct imv_viewport *view) +void imv_viewport_free(struct imv_viewport *view) { - view->window = NULL; - return; + free(view); } void imv_viewport_toggle_fullscreen(struct imv_viewport *view) diff --git a/src/viewport.h b/src/viewport.h index 0f19b85..d1803d4 100644 --- a/src/viewport.h +++ b/src/viewport.h @@ -37,11 +37,11 @@ enum imv_zoom_source { IMV_ZOOM_KEYBOARD }; -/* Initialises an instance of imv_viewport */ -void imv_init_viewport(struct imv_viewport *view, SDL_Window *window); +/* Creates an instance of imv_viewport */ +struct imv_viewport *imv_viewport_create(SDL_Window *window); -/* Cleans up all resources owned by a imv_viewport instance */ -void imv_destroy_viewport(struct imv_viewport *view); +/* 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); |