diff options
-rw-r--r-- | README.md | 13 | ||||
-rw-r--r-- | doc/imv.1 | 24 | ||||
-rw-r--r-- | src/loader.c | 116 | ||||
-rw-r--r-- | src/loader.h | 6 | ||||
-rw-r--r-- | src/main.c | 70 | ||||
-rw-r--r-- | src/navigator.c | 3 | ||||
-rw-r--r-- | src/util.c | 29 | ||||
-rw-r--r-- | src/util.h | 3 |
8 files changed, 192 insertions, 72 deletions
@@ -20,13 +20,16 @@ Usage imv image1.png another_image.jpeg yet_another.TIFF ### Opening images via stdin - find . "*.png" | imv - + find . "*.png" | imv ### Open an image fullscreen imv -f image.jpeg ### Viewing images in a random order - find . "*.png" | shuf | imv - + find . "*.png" | shuf | imv + +### Viewing images from stdin + curl http://somesi.te/img.png | imv - ### Image picker imv can be used to select images in a pipeline by using the `p` hotkey to print @@ -35,13 +38,13 @@ to list the remaining paths on exit for a "open set of images, close unwanted ones with `x`, then quit imv to pass the remaining images through" workflow. #### Picking a wallpaper - custom-set-wallpaper-script "$(find ./wallpaper -type f -name '*.jpg' | imv - | tail -n1)" + custom-set-wallpaper-script "$(find ./wallpaper -type f -name '*.jpg' | imv | tail -n1)" #### Deleting unwanted images - find -type f -name '*.jpg' | imv - | xargs rm -v + find -type f -name '*.jpg' | imv | xargs rm -v #### Choosing pictures to email - find ./holiday_pics -type f -name '*.jpg' | imv - | xargs cp -t ~/outbox + find ./holiday_pics -type f -name '*.jpg' | imv | xargs cp -t ~/outbox ### Slideshow @@ -21,12 +21,8 @@ It supports a wide variety of image file formats, including animated gif files. .Nm reloads the current image if it detects changes to the file. .Pp -When run with argument -.Sq - , .Nm -reads the list of images from standard input. -.Pp -The options are as follows: +accepts following flags: .Bl -tag -width Ds .It Fl a Default to showing images at actual size. @@ -66,6 +62,17 @@ Setting this to zero disables slideshow mode, which is the default. .It Fl u Use nearest neighbour resampling. Recommended for pixel art. .El +.Ss Reading from standard input +When run with argument +.Sq - , +.Nm +reads image from standard input. +Argument +.Sq - +may occur among other arguments, but only once. +.Pp +If no arguments supplied, reads list of files from standard input. +.Pp .Sh CONTROLS .Bl -tag -width Ds .It Aq Cm left mouse button @@ -111,6 +118,13 @@ Increase slideshow delay by one second .It Cm T Decrease slideshow delay by one second. When delay is zero, slideshow mode is disabled. +.Sh EXAMPLES +Load all files from directory +.Pa dir : +.Pp +.Dl $ ls dir | imv +or +.Dl $ ls dir | xargs imv .Sh LEGAL 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 diff --git a/src/loader.c b/src/loader.c index 883e857..1250289 100644 --- a/src/loader.c +++ b/src/loader.c @@ -17,10 +17,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "loader.h" #include "texture.h" +#include <limits.h> #include <stdlib.h> #include <pthread.h> #include <signal.h> +static void block_usr1_signal(void); +static int is_thread_cancelled(void); +static void *bg_new_img(void *data); +static void *bg_next_frame(void *data); +static void error_occurred(struct imv_loader *ldr); + static void block_usr1_signal(void) { sigset_t sigmask; @@ -61,10 +68,8 @@ void imv_destroy_loader(struct imv_loader *ldr) } } -static void *imv_loader_bg_new_img(void *data); -static void *imv_loader_bg_next_frame(void *data); - -void imv_loader_load_path(struct imv_loader *ldr, const char *path) +void imv_loader_load(struct imv_loader *ldr, const char *path, + const void *buffer, const size_t buffer_size) { /* cancel existing thread if already running */ if(ldr->bg_thread) { @@ -77,7 +82,13 @@ void imv_loader_load_path(struct imv_loader *ldr, const char *path) free(ldr->path); } ldr->path = strdup(path); - pthread_create(&ldr->bg_thread, NULL, &imv_loader_bg_new_img, ldr); + if (strncmp(path, "-", 2) == 0) { + ldr->buffer = (BYTE *)buffer; + ldr->buffer_size = buffer_size; + } else if (ldr->fi_buffer != NULL) { + FreeImage_CloseMemory(ldr->fi_buffer); + } + pthread_create(&ldr->bg_thread, NULL, &bg_new_img, ldr); pthread_mutex_unlock(&ldr->lock); } @@ -120,7 +131,7 @@ void imv_loader_load_next_frame(struct imv_loader *ldr) } /* kick off a new thread */ - pthread_create(&ldr->bg_thread, NULL, &imv_loader_bg_next_frame, ldr); + pthread_create(&ldr->bg_thread, NULL, &bg_next_frame, ldr); } void imv_loader_time_passed(struct imv_loader *ldr, double dt) @@ -140,33 +151,38 @@ void imv_loader_time_passed(struct imv_loader *ldr, double dt) } } -static void imv_loader_error_occurred(struct imv_loader *ldr) -{ - pthread_mutex_lock(&ldr->lock); - if(ldr->out_err) { - free(ldr->out_err); - } - ldr->out_err = strdup(ldr->path); - pthread_mutex_unlock(&ldr->lock); -} - -static void *imv_loader_bg_new_img(void *data) +static void *bg_new_img(void *data) { /* so we can poll for it */ block_usr1_signal(); struct imv_loader *ldr = data; + char path[PATH_MAX] = "-"; pthread_mutex_lock(&ldr->lock); - char *path = strdup(ldr->path); + int from_stdin = !strncmp(path, ldr->path, 2); + if(!from_stdin) { + (void)snprintf(path, PATH_MAX, "%s", ldr->path); + } pthread_mutex_unlock(&ldr->lock); - FREE_IMAGE_FORMAT fmt = FreeImage_GetFileType(path, 0); - + FREE_IMAGE_FORMAT fmt; + if (from_stdin) { + pthread_mutex_lock(&ldr->lock); + ldr->fi_buffer = FreeImage_OpenMemory(ldr->buffer, ldr->buffer_size); + fmt = FreeImage_GetFileTypeFromMemory(ldr->fi_buffer, 0); + pthread_mutex_unlock(&ldr->lock); + } else { + fmt = FreeImage_GetFileType(path, 0); + } if(fmt == FIF_UNKNOWN) { - imv_loader_error_occurred(ldr); - free(path); - return 0; + if (from_stdin) { + pthread_mutex_lock(&ldr->lock); + FreeImage_CloseMemory(ldr->fi_buffer); + pthread_mutex_unlock(&ldr->lock); + } + error_occurred(ldr); + return NULL; } int num_frames = 1; @@ -176,15 +192,21 @@ static void *imv_loader_bg_new_img(void *data) int raw_frame_time = 100; /* default to 100 */ if(fmt == FIF_GIF) { - mbmp = FreeImage_OpenMultiBitmap(FIF_GIF, path, + if(from_stdin) { + pthread_mutex_lock(&ldr->lock); + mbmp = FreeImage_LoadMultiBitmapFromMemory(FIF_GIF, ldr->fi_buffer, + GIF_LOAD256); + pthread_mutex_unlock(&ldr->lock); + } else { + mbmp = FreeImage_OpenMultiBitmap(FIF_GIF, path, /* don't create file */ 0, /* read only */ 1, /* keep in memory */ 1, /* flags */ GIF_LOAD256); - free(path); + } if(!mbmp) { - imv_loader_error_occurred(ldr); - return 0; + error_occurred(ldr); + return NULL; } num_frames = FreeImage_GetPageCount(mbmp); @@ -205,17 +227,27 @@ static void *imv_loader_bg_new_img(void *data) } else { /* Future TODO: If we load image line-by-line we could stop loading large * ones before wasting much more time/memory on them. */ - FIBITMAP *image = FreeImage_Load(fmt, path, 0); - free(path); + FIBITMAP *image; + if(from_stdin) { + pthread_mutex_lock(&ldr->lock); + image = FreeImage_LoadFromMemory(fmt, ldr->fi_buffer, 0); + pthread_mutex_unlock(&ldr->lock); + } else { + image = FreeImage_Load(fmt, path, 0); + } if(!image) { - imv_loader_error_occurred(ldr); - return 0; + error_occurred(ldr); + pthread_mutex_lock(&ldr->lock); + FreeImage_CloseMemory(ldr->fi_buffer); + ldr->fi_buffer = NULL; + pthread_mutex_unlock(&ldr->lock); + return NULL; } /* Check for cancellation before we convert pixel format */ if(is_thread_cancelled()) { FreeImage_Unload(image); - return 0; + return NULL; } width = FreeImage_GetWidth(bmp); @@ -236,7 +268,7 @@ static void *imv_loader_bg_new_img(void *data) FreeImage_Unload(bmp); } pthread_mutex_unlock(&ldr->lock); - return 0; + return NULL; } if(ldr->mbmp) { @@ -262,10 +294,10 @@ static void *imv_loader_bg_new_img(void *data) ldr->frame_time = (double)raw_frame_time * 0.0001; pthread_mutex_unlock(&ldr->lock); - return 0; + return NULL; } -static void *imv_loader_bg_next_frame(void *data) +static void *bg_next_frame(void *data) { struct imv_loader *ldr = data; @@ -273,7 +305,7 @@ static void *imv_loader_bg_next_frame(void *data) int num_frames = ldr->num_frames; if(num_frames < 2) { pthread_mutex_unlock(&ldr->lock); - return 0; + return NULL; } FITAG *tag = NULL; @@ -371,5 +403,15 @@ static void *imv_loader_bg_next_frame(void *data) ldr->out_is_new_image = 0; pthread_mutex_unlock(&ldr->lock); - return 0; + return NULL; +} + +static void error_occurred(struct imv_loader *ldr) +{ + pthread_mutex_lock(&ldr->lock); + if(ldr->out_err) { + free(ldr->out_err); + } + ldr->out_err = strdup(ldr->path); + pthread_mutex_unlock(&ldr->lock); } diff --git a/src/loader.h b/src/loader.h index 65c8689..6e20349 100644 --- a/src/loader.h +++ b/src/loader.h @@ -28,6 +28,9 @@ struct imv_loader { pthread_mutex_t lock; pthread_t bg_thread; char *path; + BYTE *buffer; + size_t buffer_size; + FIMEMORY *fi_buffer; FIBITMAP *out_bmp; int out_is_new_image; char *out_err; @@ -48,7 +51,8 @@ void imv_init_loader(struct imv_loader *img); void imv_destroy_loader(struct imv_loader *img); /* Asynchronously load the given file */ -void imv_loader_load_path(struct imv_loader *ldr, const char *path); +void imv_loader_load(struct imv_loader *ldr, const char *path, + const void *buffer, const size_t buffer_size); /* Returns 1 if image data is available. 0 if not. Caller is responsible for * cleaning up the data returned. Each image is only returned once. @@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include <FreeImage.h> #include <getopt.h> #include <ctype.h> +#include <errno.h> #include "loader.h" #include "texture.h" @@ -33,7 +34,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. struct { int fullscreen; - int sin; + int stdin_list; int recursive; int actual; int nearest_neighbour; @@ -49,7 +50,7 @@ struct { const char *font; } g_options = { .fullscreen = 0, - .sin = 0, + .stdin_list = 0, .recursive = 0, .actual = 0, .nearest_neighbour = 0, @@ -72,7 +73,7 @@ static void print_usage(const char* name) "Usage: %s [-rfaudlh] [-n <NUM|PATH>] [-b BG] [-e FONT:SIZE] [-t SECONDS] [-] [images...]\n" "\n" "Flags:\n" - " -: Read paths from stdin. One path per line.\n" + " -: Read image from stdin. One path per line.\n" " -r: Recursively search input paths.\n" " -f: Start in fullscreen mode\n" " -a: Default to images' actual size\n" @@ -137,8 +138,8 @@ static void parse_args(int argc, char** argv) switch(o) { case 'f': g_options.fullscreen = 1; break; case 'i': - g_options.sin = 1; - fprintf(stderr, "Warning: '-i' is deprecated. Just use '-' instead.\n"); + 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.actual = 1; break; @@ -193,31 +194,23 @@ static void parse_args(int argc, char** argv) int main(int argc, char** argv) { - if(argc < 2) { - print_usage(argv[0]); - exit(1); - } - struct imv_navigator nav; imv_navigator_init(&nav); /* parse any command line options given */ parse_args(argc, argv); - /* handle any image paths given as arguments */ - for(int i = optind; i < argc; ++i) { - /* special case: '-' is actually an option */ - if(!strcmp("-",argv[i])) { - g_options.sin = 1; - continue; - } - /* add the given path to the list to load */ - imv_navigator_add(&nav, argv[i], g_options.recursive); + argc -= optind; + argv += optind; + + /* if no names are given, expect them on stdin */ + if(argc == 0) { + g_options.stdin_list = 1; } /* if the user asked us to load paths from stdin, now is the time */ - if(g_options.sin) { - char buf[512]; + if(g_options.stdin_list) { + char buf[PATH_MAX]; while(fgets(buf, sizeof(buf), stdin)) { size_t len = strlen(buf); if(buf[len-1] == '\n') { @@ -229,6 +222,30 @@ int main(int argc, char** argv) } } + 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 */ + imv_navigator_add(&nav, argv[i], g_options.recursive); + } + /* 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"); @@ -435,6 +452,15 @@ int main(int argc, char** argv) 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); } @@ -451,7 +477,7 @@ int main(int argc, char** argv) nav.cur_path + 1, nav.num_paths, current_path); imv_viewport_set_title(&view, title); - imv_loader_load_path(&ldr, current_path); + imv_loader_load(&ldr, current_path, stdin_buffer, stdin_buffer_size); view.playing = 1; } diff --git a/src/navigator.c b/src/navigator.c index fb2a3cd..dd5124d 100644 --- a/src/navigator.c +++ b/src/navigator.c @@ -17,6 +17,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "navigator.h" +#include <limits.h> #include <sys/stat.h> #include <dirent.h> #include <stdlib.h> @@ -84,7 +85,7 @@ static void add_item(struct imv_navigator *nav, const char *path, void imv_navigator_add(struct imv_navigator *nav, const char *path, int recursive) { - char path_buf[512]; + char path_buf[PATH_MAX]; struct stat path_info; stat(path, &path_info); if(S_ISDIR(path_info.st_mode)) { @@ -16,9 +16,36 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #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 = 1024; /* Arbitrary value of 1 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)) <= 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; @@ -21,6 +21,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #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); |