diff options
Diffstat (limited to 'src/imlib.c')
-rw-r--r-- | src/imlib.c | 1678 |
1 files changed, 1194 insertions, 484 deletions
diff --git a/src/imlib.c b/src/imlib.c index 8196340..d2352fd 100644 --- a/src/imlib.c +++ b/src/imlib.c @@ -1,6 +1,7 @@ /* imlib.c Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2024 Birte Kristina Friesel. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to @@ -25,6 +26,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "feh.h" #include "filelist.h" +#include "signals.h" #include "winwidget.h" #include "options.h" @@ -34,6 +36,20 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include <arpa/inet.h> #include <netdb.h> +#ifdef HAVE_LIBCURL +#include <curl/curl.h> +#endif + +#ifdef HAVE_LIBEXIF +#include "exif.h" +#endif + +#ifdef HAVE_LIBMAGIC +#include <magic.h> + +magic_t magic = NULL; +#endif + Display *disp = NULL; Visual *vis = NULL; Screen *scr = NULL; @@ -50,17 +66,45 @@ int xinerama_screen; int num_xinerama_screens; #endif /* HAVE_LIBXINERAMA */ +gib_hash* conversion_cache = NULL; + +int childpid = 0; + +static int feh_file_is_raw(char *filename); +static char *feh_http_load_image(char *url); +static char *feh_dcraw_load_image(char *filename); +static char *feh_magick_load_image(char *filename); + #ifdef HAVE_LIBXINERAMA void init_xinerama(void) { if (opt.xinerama && XineramaIsActive(disp)) { - int major, minor; - if (getenv("XINERAMA_SCREEN")) - xinerama_screen = atoi(getenv("XINERAMA_SCREEN")); - else - xinerama_screen = 0; + int major, minor, px, py, i; + + /* discarded */ + Window dw; + int di; + unsigned int du; + XineramaQueryVersion(disp, &major, &minor); xinerama_screens = XineramaQueryScreens(disp, &num_xinerama_screens); + + if (opt.xinerama_index >= 0) + xinerama_screen = opt.xinerama_index; + else { + xinerama_screen = 0; + XQueryPointer(disp, root, &dw, &dw, &px, &py, &di, &di, &du); + for (i = 0; i < num_xinerama_screens; i++) { + if (XY_IN_RECT(px, py, + xinerama_screens[i].x_org, + xinerama_screens[i].y_org, + xinerama_screens[i].width, + xinerama_screens[i].height)) { + xinerama_screen = i; + break; + } + } + } } } #endif /* HAVE_LIBXINERAMA */ @@ -98,12 +142,23 @@ void init_x_and_imlib(void) imlib_context_set_operation(IMLIB_OP_COPY); wmDeleteWindow = XInternAtom(disp, "WM_DELETE_WINDOW", False); - /* Initialise random numbers */ - srand(getpid() * time(NULL) % ((unsigned int) -1)); + imlib_set_cache_size(opt.cache_size * 1024 * 1024); return; } +int feh_should_ignore_image(Imlib_Image * im) +{ + if (opt.filter_by_dimensions) { + unsigned int w = gib_imlib_image_get_width(im); + unsigned int h = gib_imlib_image_get_height(im); + if (w < opt.min_width || w > opt.max_width || h < opt.min_height || h > opt.max_height) { + return 1; + } + } + return 0; +} + int feh_load_image_char(Imlib_Image * im, char *filename) { feh_file *file; @@ -115,421 +170,852 @@ int feh_load_image_char(Imlib_Image * im, char *filename) return(i); } -int feh_load_image(Imlib_Image * im, feh_file * file) +void feh_print_load_error(char *file, winwidget w, Imlib_Load_Error err, enum feh_load_error feh_err) { - Imlib_Load_Error err; - - D(("filename is %s, image is %p\n", file->filename, im)); - - if (!file || !file->filename) - return(0); - - /* Handle URLs */ - if ((!strncmp(file->filename, "http://", 7)) || (!strncmp(file->filename, "https://", 8)) - || (!strncmp(file->filename, "ftp://", 6))) { - char *tmpname = NULL; - char *tempcpy; - - tmpname = feh_http_load_image(file->filename); - if (tmpname == NULL) - return(0); - *im = imlib_load_image_with_error_return(tmpname, &err); - if (im) { - /* load the info now, in case it's needed after we delete the - temporary image file */ - tempcpy = file->filename; - file->filename = tmpname; - feh_file_info_load(file, *im); - file->filename = tempcpy; - } - if ((opt.slideshow) && (opt.reload == 0)) { - /* Http, no reload, slideshow. Let's keep this image on hand... */ - free(file->filename); - file->filename = estrdup(tmpname); - } else { - /* Don't cache the image if we're doing reload + http (webcams etc) */ - if (!opt.keep_http) - unlink(tmpname); + if (err == IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS) + eprintf("%s - Out of file descriptors while loading", file); + else if (!opt.quiet || w) { + switch (feh_err) { + case LOAD_ERROR_IMLIB: + // handled in the next switch/case statement + break; + case LOAD_ERROR_IMAGEMAGICK: + im_weprintf(w, "%s - No ImageMagick loader for that file format", file); + break; + case LOAD_ERROR_CURL: + im_weprintf(w, "%s - libcurl was unable to retrieve the file", file); + break; + case LOAD_ERROR_DCRAW: + im_weprintf(w, "%s - Unable to open preview via dcraw", file); + break; + case LOAD_ERROR_MAGICBYTES: + im_weprintf(w, "%s - Does not look like an image (magic bytes missing)", file); + break; } - if (!opt.keep_http) - add_file_to_rm_filelist(tmpname); - free(tmpname); - } else { - *im = imlib_load_image_with_error_return(file->filename, &err); - } - - if ((err) || (!im)) { - if (opt.verbose && !opt.quiet) { - fprintf(stdout, "\n"); - reset_output = 1; + if (feh_err != LOAD_ERROR_IMLIB) { + return; } - /* Check error code */ switch (err) { case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST: - if (!opt.quiet) - weprintf("%s - File does not exist", file->filename); + im_weprintf(w, "%s - File does not exist", file); break; case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY: - if (!opt.quiet) - weprintf("%s - Directory specified for image filename", file->filename); + im_weprintf(w, "%s - Directory specified for image filename", file); break; case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ: - if (!opt.quiet) - weprintf("%s - No read access to directory", file->filename); + im_weprintf(w, "%s - No read access", file); break; case IMLIB_LOAD_ERROR_UNKNOWN: case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT: - if (!opt.quiet) - weprintf("%s - No Imlib2 loader for that file format", file->filename); + im_weprintf(w, "%s - No Imlib2 loader for that file format", file); break; case IMLIB_LOAD_ERROR_PATH_TOO_LONG: - if (!opt.quiet) - weprintf("%s - Path specified is too long", file->filename); + im_weprintf(w, "%s - Path specified is too long", file); break; case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT: - if (!opt.quiet) - weprintf("%s - Path component does not exist", file->filename); + im_weprintf(w, "%s - Path component does not exist", file); break; case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY: - if (!opt.quiet) - weprintf("%s - Path component is not a directory", file->filename); + im_weprintf(w, "%s - Path component is not a directory", file); break; case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE: - if (!opt.quiet) - weprintf("%s - Path points outside address space", file->filename); + im_weprintf(w, "%s - Path points outside address space", file); break; case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS: - if (!opt.quiet) - weprintf("%s - Too many levels of symbolic links", file->filename); + im_weprintf(w, "%s - Too many levels of symbolic links", file); break; case IMLIB_LOAD_ERROR_OUT_OF_MEMORY: - if (!opt.quiet) - weprintf("While loading %s - Out of memory", file->filename); - break; - case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS: - eprintf("While loading %s - Out of file descriptors", file->filename); + im_weprintf(w, "While loading %s - Out of memory", file); break; case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE: - if (!opt.quiet) - weprintf("%s - Cannot write to directory", file->filename); + im_weprintf(w, "%s - Cannot write to directory", file); break; case IMLIB_LOAD_ERROR_OUT_OF_DISK_SPACE: - if (!opt.quiet) - weprintf("%s - Cannot write - out of disk space", file->filename); + im_weprintf(w, "%s - Cannot write - out of disk space", file); break; +#if defined(IMLIB2_VERSION_MAJOR) && defined(IMLIB2_VERSION_MINOR) && (IMLIB2_VERSION_MAJOR > 1 || IMLIB2_VERSION_MINOR > 7) + case IMLIB_LOAD_ERROR_IMAGE_READ: + im_weprintf(w, "%s - Invalid image file", file); + break; + case IMLIB_LOAD_ERROR_IMAGE_FRAME: + im_weprintf(w, "%s - Requested frame not in image", file); + break; +#endif default: - if (!opt.quiet) - weprintf("While loading %s - Unknown error (%d). Attempting to continue", - file->filename, err); + im_weprintf(w, "While loading %s - Unknown error (%d)", + file, err); break; } - D(("Load *failed*\n")); - return(0); } +} - D(("Loaded ok\n")); - return(1); +#ifdef HAVE_LIBMAGIC +void uninit_magic(void) +{ + if (!magic) { + return; + } + + magic_close(magic); + magic = NULL; } +void init_magic(void) +{ + if (getenv("FEH_SKIP_MAGIC")) { + return; + } -char *feh_http_load_image(char *url) + if (!(magic = magic_open(MAGIC_NONE))) { + weprintf("unable to initialize magic library\n"); + return; + } + + if (magic_load(magic, NULL) != 0) { + weprintf("cannot load magic database: %s\n", magic_error(magic)); + uninit_magic(); + } +} + +/* + * This is a workaround for an Imlib2 regression, causing unloadable image + * detection to be excessively slow (and, thus, causing feh to hang for a while + * when encountering an unloadable image). We use magic byte detection to + * avoid calling Imlib2 for files it probably cannot handle. See + * <https://phab.enlightenment.org/T8739> and + * <https://github.com/derf/feh/issues/505>. + */ +int feh_is_image(feh_file * file, int magic_flags) { - char *tmpname; - char *basename; - char *path = NULL; + const char * mime_type = NULL; - if (opt.keep_http) { - if (opt.output_dir) - path = opt.output_dir; - else - path = ""; - } else - path = "/tmp/"; + if (!magic) { + return 1; + } - basename = strrchr(url, '/') + 1; - tmpname = feh_unique_filename(path, basename); - - if (opt.builtin_http) { - /* state for HTTP header parser */ -#define SAW_NONE 1 -#define SAW_ONE_CM 2 -#define SAW_ONE_CJ 3 -#define SAW_TWO_CM 4 -#define IN_BODY 5 - -#define OUR_BUF_SIZE 1024 -#define EOL "\015\012" - - int sockno = 0; - int size; - int body = SAW_NONE; - struct sockaddr_in addr; - struct hostent *hptr; - char *hostname; - char *get_string; - char *host_string; - char *query_string; - char *get_url; - static char buf[OUR_BUF_SIZE]; - char ua_string[] = "User-Agent: feh image viewer"; - char accept_string[] = "Accept: image/*"; - FILE *fp; - - D(("using builtin http collection\n")); - fp = fopen(tmpname, "w"); - if (!fp) { - weprintf("couldn't write to file %s:", tmpname); - free(tmpname); - return(NULL); - } + magic_setflags(magic, MAGIC_MIME_TYPE | MAGIC_SYMLINK | magic_flags); + mime_type = magic_file(magic, file->filename); - hostname = feh_strip_hostname(url); - if (!hostname) { - weprintf("couldn't work out hostname from %s:", url); - fclose(fp); - unlink(tmpname); - free(tmpname); - return(NULL); - } + if (!mime_type) { + return 0; + } - D(("trying hostname %s\n", hostname)); + D(("file %s has mime type: %s\n", file->filename, mime_type)); - if (!(hptr = feh_gethostbyname(hostname))) { - weprintf("error resolving host %s:", hostname); - fclose(fp); - unlink(tmpname); - free(hostname); - free(tmpname); - return(NULL); + if (strncmp(mime_type, "image/", 6) == 0) { + return 1; + } + + /* no infinite loop on compressed content, please */ + if (magic_flags) { + return 0; + } + + /* imlib2 supports loading compressed images, let's have a look inside */ + if (strcmp(mime_type, "application/gzip") == 0 || + strcmp(mime_type, "application/x-bzip2") == 0 || + strcmp(mime_type, "application/x-xz") == 0) { + return feh_is_image(file, MAGIC_COMPRESS); + } + + return 0; +} +#else +int feh_is_image(__attribute__((unused)) feh_file * file, __attribute__((unused)) int magic_flags) +{ + return 1; +} +#endif + +int feh_load_image(Imlib_Image * im, feh_file * file) +{ + Imlib_Load_Error err = IMLIB_LOAD_ERROR_NONE; + enum feh_load_error feh_err = LOAD_ERROR_IMLIB; + enum { SRC_IMLIB, SRC_HTTP, SRC_MAGICK, SRC_DCRAW } image_source = SRC_IMLIB; + char *tmpname = NULL; + char *real_filename = NULL; + + D(("filename is %s, image is %p\n", file->filename, im)); + + if (!file || !file->filename) + return 0; + + if (path_is_url(file->filename)) { + image_source = SRC_HTTP; + + if ((tmpname = feh_http_load_image(file->filename)) == NULL) { + feh_err = LOAD_ERROR_CURL; + err = IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST; + } + } + else { + if (feh_is_image(file, 0)) { + *im = imlib_load_image_with_error_return(file->filename, &err); + } else { + feh_err = LOAD_ERROR_MAGICBYTES; + err = IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT; } + } - /* Copy the address of the host to socket description. */ - memcpy(&addr.sin_addr, hptr->h_addr, hptr->h_length); + if (opt.conversion_timeout >= 0 && ( + (err == IMLIB_LOAD_ERROR_UNKNOWN) || + (err == IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT))) { + if (feh_file_is_raw(file->filename)) { + image_source = SRC_DCRAW; + tmpname = feh_dcraw_load_image(file->filename); + if (!tmpname) { + feh_err = LOAD_ERROR_DCRAW; + } + } else { + image_source = SRC_MAGICK; + feh_err = LOAD_ERROR_IMLIB; + tmpname = feh_magick_load_image(file->filename); + if (!tmpname) { + feh_err = LOAD_ERROR_IMAGEMAGICK; + } + } + } - /* Set port and protocol */ - addr.sin_family = AF_INET; - addr.sin_port = htons(80); + if (tmpname) { + *im = imlib_load_image_with_error_return(tmpname, &err); + if (!err && im) { + real_filename = file->filename; + file->filename = tmpname; - if ((sockno = socket(PF_INET, SOCK_STREAM, 0)) == -1) { - weprintf("error opening socket:"); - fclose(fp); - unlink(tmpname); - free(tmpname); - free(hostname); - return(NULL); + /* + * feh does not associate a non-native image with its temporary + * filename and may delete the temporary file right after loading. + * To ensure that it is still aware of image size, dimensions, etc., + * file_info is preloaded here. To avoid a memory leak when loading + * a non-native file multiple times in a slideshow, the file_info + * struct is freed first. If file->info is not set, feh_file_info_free + * is a no-op. + */ + feh_file_info_free(file->info); + feh_file_info_load(file, *im); + + file->filename = real_filename; +#ifdef HAVE_LIBEXIF + /* + * if we're called from within feh_reload_image, file->ed is already + * populated. + */ + if (file->ed) { + exif_data_unref(file->ed); + } + file->ed = exif_data_new_from_file(tmpname); +#endif } - if (connect(sockno, (struct sockaddr *) &addr, sizeof(addr)) == -1) { - weprintf("error connecting socket:"); - fclose(fp); + if (!opt.use_conversion_cache && ((image_source != SRC_HTTP) || !opt.keep_http)) unlink(tmpname); + // keep_http already performs an add_file_to_rm_filelist call + else if (opt.use_conversion_cache && !opt.keep_http) + // add_file_to_rm_filelist duplicates tmpname + add_file_to_rm_filelist(tmpname); + + if (!opt.use_conversion_cache) free(tmpname); - free(hostname); - return(NULL); + } else if (im) { +#ifdef HAVE_LIBEXIF + /* + * if we're called from within feh_reload_image, file->ed is already + * populated. + */ + if (file->ed) { + exif_data_unref(file->ed); } + file->ed = exif_data_new_from_file(file->filename); +#endif + } - get_url = strchr(url, '/') + 2; - get_url = strchr(get_url, '/'); - - get_string = estrjoin(" ", "GET", get_url, "HTTP/1.0", NULL); - host_string = estrjoin(" ", "Host:", hostname, NULL); - query_string = estrjoin(EOL, get_string, host_string, accept_string, ua_string, "", "", NULL); - /* At this point query_string looks something like - ** - ** GET /dir/foo.jpg?123456 HTTP/1.0^M^J - ** Host: www.example.com^M^J - ** Accept: image/ *^M^J - ** User-Agent: feh image viewer^M^J - ** ^M^J - ** - ** Host: is required by HTTP/1.1 and very important for some sites, - ** even with HTTP/1.0 - ** - ** -- BEG - */ - if ((send(sockno, query_string, strlen(query_string), 0)) == -1) { - free(get_string); - free(host_string); - free(query_string); - free(tmpname); - free(hostname); - weprintf("error sending over socket:"); - return(NULL); + if ((err) || (!im)) { + if (opt.verbose && !opt.quiet) { + fputs("\n", stderr); + reset_output = 1; } - free(get_string); - free(host_string); - free(query_string); - free(hostname); - - while ((size = read(sockno, &buf, OUR_BUF_SIZE))) { - if (body == IN_BODY) { - fwrite(buf, 1, size, fp); - } else { - int i; - - for (i = 0; i < size; i++) { - /* We are looking for ^M^J^M^J, but will accept - ** ^J^J from broken servers. Stray ^Ms will be - ** ignored. - ** - ** TODO: - ** Checking the headers for a - ** Content-Type: image/ * - ** header would help detect problems with results. - ** Maybe look at the response code too? But there is - ** no fundamental reason why a 4xx or 5xx response - ** could not return an image, it is just the 3xx - ** series we need to worry about. - ** - ** Also, grabbing the size from the Content-Length - ** header and killing the connection after that - ** many bytes where read would speed up closing the - ** socket. - ** -- BEG - */ + feh_print_load_error(file->filename, NULL, err, feh_err); + D(("Load *failed*\n")); + return(0); + } - switch (body) { + /* + * By default, Imlib2 unconditionally loads a cached file without checking + * if it was modified on disk. However, feh (or rather its users) should + * expect image changes to appear at the next reload. So we tell Imlib2 to + * always check the file modification time and only use a cached image if + * the mtime was not changed. The performance penalty is usually negligible. + */ + imlib_context_set_image(*im); + imlib_image_set_changes_on_disk(); + +#ifdef HAVE_LIBEXIF + int orientation = 0; + if (file->ed) { + ExifByteOrder byteOrder = exif_data_get_byte_order(file->ed); + ExifEntry *exifEntry = exif_data_get_entry(file->ed, EXIF_TAG_ORIENTATION); + if (exifEntry && opt.auto_rotate) { + orientation = exif_get_short(exifEntry->data, byteOrder); + } + } - case IN_BODY: - fwrite(buf + i, 1, size - i, fp); - i = size; - break; + if (orientation == 2) + gib_imlib_image_flip_horizontal(*im); + else if (orientation == 3) + gib_imlib_image_orientate(*im, 2); + else if (orientation == 4) + gib_imlib_image_flip_vertical(*im); + else if (orientation == 5) { + gib_imlib_image_orientate(*im, 3); + gib_imlib_image_flip_vertical(*im); + } + else if (orientation == 6) + gib_imlib_image_orientate(*im, 1); + else if (orientation == 7) { + gib_imlib_image_orientate(*im, 3); + gib_imlib_image_flip_horizontal(*im); + } + else if (orientation == 8) + gib_imlib_image_orientate(*im, 3); +#endif - case SAW_ONE_CM: - if (buf[i] == '\012') { - body = SAW_ONE_CJ; - } else { - body = SAW_NONE; - } - break; - - case SAW_ONE_CJ: - if (buf[i] == '\015') { - body = SAW_TWO_CM; - } else { - if (buf[i] == '\012') { - body = IN_BODY; - } else { - body = SAW_NONE; - } - } - break; + D(("Loaded ok\n")); + return(1); +} - case SAW_TWO_CM: - if (buf[i] == '\012') { - body = IN_BODY; - } else { - body = SAW_NONE; - } - break; - - case SAW_NONE: - if (buf[i] == '\015') { - body = SAW_ONE_CM; - } else { - if (buf[i] == '\012') { - body = SAW_ONE_CJ; - } - } - break; +void feh_reload_image(winwidget w, int resize, int force_new) +{ + char *new_title; + int len; + Imlib_Image tmp; + int old_w, old_h; - } /* switch */ - } /* for i */ - } - } /* while read */ - close(sockno); - fclose(fp); + if (!w->file) { + im_weprintf(w, "couldn't reload, this image has no file associated with it."); + winwidget_render_image(w, 0, 0); + return; + } + + D(("resize %d, force_new %d\n", resize, force_new)); + + free(FEH_FILE(w->file->data)->caption); + FEH_FILE(w->file->data)->caption = NULL; + + len = strlen(w->name) + sizeof("Reloading: ") + 1; + new_title = emalloc(len); + snprintf(new_title, len, "Reloading: %s", w->name); + winwidget_rename(w, new_title); + free(new_title); + + old_w = gib_imlib_image_get_width(w->im); + old_h = gib_imlib_image_get_height(w->im); + + /* + * If we don't free the old image before loading the new one, Imlib2's + * caching will get in our way. + * However, if --reload is used (force_new == 0), we want to continue if + * the new image cannot be loaded, so we must not free the old image yet. + */ + if (force_new) + winwidget_free_image(w); + + // if it's an external image, our own cache will also get in your way + char *sfn; + if (opt.use_conversion_cache && conversion_cache && (sfn = gib_hash_get(conversion_cache, FEH_FILE(w->file->data)->filename)) != NULL) { + free(sfn); + gib_hash_set(conversion_cache, FEH_FILE(w->file->data)->filename, NULL); + } + + if ((feh_load_image(&tmp, FEH_FILE(w->file->data))) == 0) { + if (force_new) + eprintf("failed to reload image\n"); + else { + im_weprintf(w, "Couldn't reload image. Is it still there?"); + winwidget_render_image(w, 0, 0); + } + return; + } + + if (!resize && ((old_w != gib_imlib_image_get_width(tmp)) || + (old_h != gib_imlib_image_get_height(tmp)))) + resize = 1; + + if (!force_new) + winwidget_free_image(w); + + w->im = tmp; + winwidget_reset_image(w); + + w->mode = MODE_NORMAL; + if ((w->im_w != gib_imlib_image_get_width(w->im)) + || (w->im_h != gib_imlib_image_get_height(w->im))) + w->had_resize = 1; + if (w->has_rotated) { + Imlib_Image temp; + + temp = gib_imlib_create_rotated_image(w->im, 0.0); + w->im_w = gib_imlib_image_get_width(temp); + w->im_h = gib_imlib_image_get_height(temp); + gib_imlib_free_image_and_decache(temp); + } else { + w->im_w = gib_imlib_image_get_width(w->im); + w->im_h = gib_imlib_image_get_height(w->im); + } + winwidget_render_image(w, resize, 0); + + return; +} + +static int feh_file_is_raw(char *filename) +{ + childpid = fork(); + if (childpid == -1) { + perror("fork"); + return 0; + } + + if (childpid == 0) { + int devnull = open("/dev/null", O_WRONLY); + dup2(devnull, 1); + dup2(devnull, 2); + execlp("dcraw", "dcraw", "-i", filename, NULL); + _exit(1); } else { - int pid; int status; + do { + waitpid(childpid, &status, WUNTRACED); + if (WIFEXITED(status)) { + return !WEXITSTATUS(status); + } + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } - if ((pid = fork()) < 0) { - weprintf("open url: fork failed:"); - free(tmpname); - return(NULL); - } else if (pid == 0) { - char *quiet = NULL; + return 0; +} - if (!opt.verbose) - quiet = estrdup("-q"); +static char *feh_dcraw_load_image(char *filename) +{ + char *basename; + char *tmpname; + char *sfn; + int fd = -1; + + if (opt.use_conversion_cache) { + if (!conversion_cache) + conversion_cache = gib_hash_new(); + if ((sfn = gib_hash_get(conversion_cache, filename)) != NULL) + return sfn; + } - execlp("wget", "wget", "--cache=off", "-O", tmpname, url, quiet, NULL); - eprintf("url: Is 'wget' installed? Failed to exec wget:"); + basename = strrchr(filename, '/'); + + if (basename == NULL) + basename = filename; + else + basename++; + + tmpname = feh_unique_filename("/tmp/", basename); + + if (strlen(tmpname) > (NAME_MAX-6)) + tmpname[NAME_MAX-7] = '\0'; + + sfn = estrjoin("_", tmpname, "XXXXXX", NULL); + free(tmpname); + + fd = mkstemp(sfn); + + if (fd == -1) { + free(sfn); + return NULL; + } + + childpid = fork(); + if (childpid == -1) { + weprintf("%s: Can't load with dcraw. Fork failed:", filename); + unlink(sfn); + free(sfn); + close(fd); + return NULL; + } else if (childpid == 0) { + dup2(fd, STDOUT_FILENO); + close(fd); + + alarm(opt.conversion_timeout); + execlp("dcraw", "dcraw", "-c", "-e", filename, NULL); + _exit(1); + } + + int status; + waitpid(-1, &status, 0); + if (WIFSIGNALED(status)) { + unlink(sfn); + free(sfn); + sfn = NULL; + if (!opt.quiet) + weprintf("%s - Conversion took too long, skipping", filename); + } + + if ((sfn != NULL) && opt.use_conversion_cache) + gib_hash_set(conversion_cache, filename, sfn); + + return sfn; +} + +static char *feh_magick_load_image(char *filename) +{ + char *argv_fn; + char *basename; + char *tmpname; + char *sfn; + char tempdir[] = "/tmp/.feh-magick-tmp-XXXXXX"; + int fd = -1, devnull = -1; + int status; + char created_tempdir = 0; + + if (opt.use_conversion_cache) { + if (!conversion_cache) + conversion_cache = gib_hash_new(); + if ((sfn = gib_hash_get(conversion_cache, filename)) != NULL) + return sfn; + } + + basename = strrchr(filename, '/'); + + if (basename == NULL) + basename = filename; + else + basename++; + + tmpname = feh_unique_filename("/tmp/", basename); + + if (strlen(tmpname) > (NAME_MAX-6)) + tmpname[NAME_MAX-7] = '\0'; + + sfn = estrjoin("_", tmpname, "XXXXXX", NULL); + free(tmpname); + + fd = mkstemp(sfn); + + if (fd == -1) { + free(sfn); + return NULL; + } + + /* + * We could use png:fd:(whatever mkstemp returned) as target filename + * for convert, but this seems to be broken in some ImageMagick versions. + * So we resort to png:(sfn) instead. + */ + argv_fn = estrjoin(":", "png", sfn, NULL); + + /* + * By default, ImageMagick saves (occasionally lots of) temporary files + * in /tmp. It doesn't remove them if it runs into a timeout and is killed + * by us, no matter whether we use SIGINT, SIGTERM or SIGKILL. So, unless + * MAGICK_TMPDIR has already been set by the user, we create our own + * temporary directory for ImageMagick and remove its contents at the end of + * this function. + */ + if (getenv("MAGICK_TMPDIR") == NULL) { + if (mkdtemp(tempdir) == NULL) { + weprintf("%s: ImageMagick may leave temporary files in /tmp. mkdtemp failed:", filename); } else { - waitpid(pid, &status, 0); + created_tempdir = 1; + } + } + + if ((childpid = fork()) < 0) { + weprintf("%s: Can't load with imagemagick. Fork failed:", filename); + unlink(sfn); + free(sfn); + sfn = NULL; + } + else if (childpid == 0) { + + devnull = open("/dev/null", O_WRONLY); + dup2(devnull, 0); + if (opt.quiet) { + /* discard convert output */ + dup2(devnull, 1); + dup2(devnull, 2); + } + + /* + * convert only accepts SIGINT via killpg, a normal kill doesn't work + */ + setpgid(0, 0); + + if (created_tempdir) { + // no error checking - this is a best-effort code path + setenv("MAGICK_TMPDIR", tempdir, 0); + } + + execlp("convert", "convert", filename, argv_fn, NULL); + _exit(1); + } + else { + alarm(opt.conversion_timeout); + waitpid(childpid, &status, 0); + kill(childpid, SIGKILL); + if (opt.conversion_timeout > 0 && !alarm(0)) { + unlink(sfn); + free(sfn); + sfn = NULL; + + if (!opt.quiet) { + weprintf("%s: Conversion took too long, skipping", filename); + } + } + close(fd); + childpid = 0; + } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - weprintf("url: wget failed to load URL %s\n", url); - unlink(tmpname); - free(tmpname); - return(NULL); + if (created_tempdir) { + DIR *dir; + struct dirent *de; + if ((dir = opendir(tempdir)) == NULL) { + weprintf("%s: Cannot remove temporary ImageMagick files from %s:", filename, tempdir); + } else { + while ((de = readdir(dir)) != NULL) { + if (de->d_name[0] != '.') { + char *temporary_file_name = estrjoin("/", tempdir, de->d_name, NULL); + /* + * We assume that ImageMagick only creates temporary files and + * not directories. + */ + if (unlink(temporary_file_name) == -1) { + weprintf("unlink %s:", temporary_file_name); + } + free(temporary_file_name); + } + } + if (rmdir(tempdir) == -1) { + weprintf("rmdir %s:", tempdir); } } + closedir(dir); } - return(tmpname); + free(argv_fn); + + if ((sfn != NULL) && opt.use_conversion_cache) + gib_hash_set(conversion_cache, filename, sfn); + + return sfn; } -struct hostent *feh_gethostbyname(const char *name) +#ifdef HAVE_LIBCURL + +#if LIBCURL_VERSION_NUM >= 0x072000 /* 07.32.0 */ +static int curl_quit_function(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +#else +static int curl_quit_function(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) +#endif { - struct hostent *hp; - unsigned long addr; + // ignore "unused parameter" warnings + (void)clientp; + (void)dltotal; + (void)dlnow; + (void)ultotal; + (void)ulnow; + if (sig_exit) { + /* + * The user wants to quit feh. Tell libcurl to abort the transfer and + * return control to the main loop, where we can quit gracefully. + */ + return 1; + } + return 0; +} - addr = (unsigned long) inet_addr(name); - if ((int) addr != -1) - hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET); - else - hp = gethostbyname(name); - return(hp); +static char *feh_http_load_image(char *url) +{ + CURL *curl; + CURLcode res; + char *sfn; + FILE *sfp; + int fd = -1; + char *ebuff; + char *tmpname; + char *basename; + char *path = NULL; + + if (opt.use_conversion_cache) { + if (!conversion_cache) + conversion_cache = gib_hash_new(); + if ((sfn = gib_hash_get(conversion_cache, url)) != NULL) + return sfn; + } + + if (opt.keep_http) { + if (opt.output_dir) + path = opt.output_dir; + else + path = ""; + } else + path = "/tmp/"; + + curl = curl_easy_init(); + if (!curl) { + weprintf("open url: libcurl initialization failure"); + return NULL; + } + + basename = strrchr(url, '/') + 1; + +#ifdef HAVE_MKSTEMPS + tmpname = estrjoin("_", "feh_curl_XXXXXX", basename, NULL); + + if (strlen(tmpname) > NAME_MAX) { + tmpname[NAME_MAX] = '\0'; + } +#else + if (strlen(basename) > NAME_MAX-7) { + tmpname = estrdup("feh_curl_XXXXXX"); + } else { + tmpname = estrjoin("_", "feh_curl", basename, "XXXXXX", NULL); + } +#endif + + sfn = estrjoin("", path, tmpname, NULL); + free(tmpname); + + D(("sfn is %s\n", sfn)) + +#ifdef HAVE_MKSTEMPS + fd = mkstemps(sfn, strlen(basename) + 1); +#else + fd = mkstemp(sfn); +#endif + + if (fd != -1) { + sfp = fdopen(fd, "w+"); + if (sfp != NULL) { +#ifdef DEBUG + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); +#endif + /* + * Do not allow requests to take longer than 30 minutes. + * This should be sufficiently high to accommodate use cases with + * unusually high latencies, while at the same time avoiding + * feh hanging indefinitely in unattended slideshows. + */ + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1800); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, sfp); + ebuff = emalloc(CURL_ERROR_SIZE); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, ebuff); + curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE "/" VERSION); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); +#if LIBCURL_VERSION_NUM >= 0x072000 /* 07.32.0 */ + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, curl_quit_function); +#else + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curl_quit_function); +#endif + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + if (opt.insecure_ssl) { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + } else if (getenv("CURL_CA_BUNDLE") != NULL) { + // Allow the user to specify custom CA certificates. + curl_easy_setopt(curl, CURLOPT_CAINFO, + getenv("CURL_CA_BUNDLE")); + } + + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + if (res != CURLE_OK) { + if (res != CURLE_ABORTED_BY_CALLBACK) { + weprintf("open url: %s", ebuff); + } + unlink(sfn); + close(fd); + free(sfn); + sfn = NULL; + } + + free(ebuff); + fclose(sfp); + if (opt.use_conversion_cache) + gib_hash_set(conversion_cache, url, sfn); + return sfn; + } else { + weprintf("open url: fdopen failed:"); + unlink(sfn); + free(sfn); + close(fd); + } + } else { +#ifdef HAVE_MKSTEMPS + weprintf("open url: mkstemps failed:"); +#else + weprintf("open url: mkstemp failed:"); +#endif + free(sfn); + } + curl_easy_cleanup(curl); + return NULL; } -char *feh_strip_hostname(char *url) +#else /* HAVE_LIBCURL */ + +char *feh_http_load_image(char *url) { - char *ret; - char *start; - char *finish; - int len; + weprintf( + "Cannot load image %s\nPlease recompile feh with libcurl support", + url + ); + return NULL; +} - start = strchr(url, '/'); - if (!start) - return(NULL); +#endif /* HAVE_LIBCURL */ - start += 2; +void feh_imlib_image_fill_text_bg(Imlib_Image im, int w, int h) +{ + gib_imlib_image_set_has_alpha(im, 1); - finish = strchr(start, '/'); - if (!finish) - return(NULL); + imlib_context_set_blend(0); - len = finish - start; + if (opt.text_bg == TEXT_BG_CLEAR) + gib_imlib_image_fill_rectangle(im, 0, 0, w, h, 0, 0, 0, 0); + else + gib_imlib_image_fill_rectangle(im, 0, 0, w, h, 0, 0, 0, 127); - ret = emalloc(len + 1); - strncpy(ret, start, len); - ret[len] = '\0'; - return(ret); + imlib_context_set_blend(1); } -void feh_draw_zoom(winwidget w) +static Imlib_Font feh_load_font(winwidget w) { static Imlib_Font fn = NULL; - int tw = 0, th = 0; - Imlib_Image im = NULL; - char buf[100]; - static DATA8 atab[256]; - - if (!w->im) - return; if (opt.font) fn = gib_imlib_load_font(opt.font); if (!fn) { - fn = gib_imlib_load_font(DEFAULT_FONT); + if (w && w->full_screen) + fn = gib_imlib_load_font(DEFAULT_FONT_BIG); + else + fn = gib_imlib_load_font(DEFAULT_FONT); } if (!fn) { - weprintf("Couldn't load font for zoom printing"); - return; + eprintf("Couldn't load font to draw a message"); } - memset(atab, 0, sizeof(atab)); + return fn; +} + + +void feh_draw_zoom(winwidget w) +{ + static Imlib_Font fn = NULL; + int tw = 0, th = 0; + Imlib_Image im = NULL; + char buf[100]; + + if (!w->im) + return; + + fn = feh_load_font(w); snprintf(buf, sizeof(buf), "%.0f%%, %dx%d", w->zoom * 100, (int) (w->im_w * w->zoom), (int) (w->im_h * w->zoom)); @@ -543,9 +1029,7 @@ void feh_draw_zoom(winwidget w) if (!im) eprintf("Couldn't create image. Out of memory?"); - gib_imlib_image_set_has_alpha(im, 1); - gib_imlib_apply_color_modifier_to_rectangle(im, 0, 0, tw, th, NULL, NULL, NULL, atab); - gib_imlib_image_fill_rectangle(im, 0, 0, tw, th, 0, 0, 0, 0); + feh_imlib_image_fill_text_bg(im, tw, th); gib_imlib_text_draw(im, fn, NULL, 2, 2, buf, IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); gib_imlib_text_draw(im, fn, NULL, 1, 1, buf, IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); @@ -554,12 +1038,65 @@ void feh_draw_zoom(winwidget w) return; } -void feh_draw_filename(winwidget w) +void im_weprintf(winwidget w, char *fmt, ...) +{ + va_list args; + char *errstr = emalloc(1024); + + fflush(stdout); + fputs(PACKAGE " WARNING: ", stderr); + + va_start(args, fmt); + vsnprintf(errstr, 1024, fmt, args); + va_end(args); + + if (w) + w->errstr = errstr; + + fputs(errstr, stderr); + if (fmt[0] != '\0' && fmt[strlen(fmt) - 1] == ':') + fprintf(stderr, " %s", strerror(errno)); + fputs("\n", stderr); + if (!w) + free(errstr); +} + + +void feh_draw_errstr(winwidget w) { static Imlib_Font fn = NULL; int tw = 0, th = 0; Imlib_Image im = NULL; - static DATA8 atab[256]; + + if (!w->im) + return; + + fn = feh_load_font(NULL); + + /* Work out how high the font is */ + gib_imlib_get_text_size(fn, w->errstr, NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT); + + tw += 3; + th += 3; + im = imlib_create_image(tw, th); + if (!im) + eprintf("Couldn't create errstr image. Out of memory?"); + + feh_imlib_image_fill_text_bg(im, tw, th); + + gib_imlib_text_draw(im, fn, NULL, 2, 2, w->errstr, IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); + gib_imlib_text_draw(im, fn, NULL, 1, 1, w->errstr, IMLIB_TEXT_TO_RIGHT, 255, 0, 0, 255); + free(w->errstr); + w->errstr = NULL; + gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, w->h - th, 1, 1, 0); + gib_imlib_free_image_and_decache(im); +} + +void feh_draw_filename(winwidget w) +{ + static Imlib_Font fn = NULL; + int tw = 0, th = 0, nw = 0; + Imlib_Image im = NULL; char *s = NULL; int len = 0; @@ -567,49 +1104,43 @@ void feh_draw_filename(winwidget w) || (!FEH_FILE(w->file->data)->filename)) return; - if (opt.font) - fn = gib_imlib_load_font(opt.font); + fn = feh_load_font(w); - if (!fn) { - if (w->full_screen) - fn = gib_imlib_load_font(DEFAULT_FONT_BIG); - else - fn = gib_imlib_load_font(DEFAULT_FONT); - } + /* Work out how high the font is */ + gib_imlib_get_text_size(fn, FEH_FILE(w->file->data)->filename, NULL, &tw, + &th, IMLIB_TEXT_TO_RIGHT); - if (!fn) { - weprintf("Couldn't load font for filename printing"); - return; - } + if (gib_list_length(filelist) > 1) { + len = snprintf(NULL, 0, "%d of %d", gib_list_length(filelist), + gib_list_length(filelist)) + 1; + s = emalloc(len); + if (w->file) + snprintf(s, len, "%d of %d", gib_list_num(filelist, w->file) + + 1, gib_list_length(filelist)); + else + snprintf(s, len, "%d of %d", gib_list_num(filelist, current_file) + + 1, gib_list_length(filelist)); - memset(atab, 0, sizeof(atab)); + gib_imlib_get_text_size(fn, s, NULL, &nw, NULL, IMLIB_TEXT_TO_RIGHT); - /* Work out how high the font is */ - gib_imlib_get_text_size(fn, FEH_FILE(w->file->data)->filename, NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT); + if (nw > tw) + tw = nw; + } - /* tw is no longer correct, if the filename is shorter than - * the string "%d of %d" used below in fullscreen mode */ tw += 3; th += 3; im = imlib_create_image(tw, 2 * th); if (!im) eprintf("Couldn't create image. Out of memory?"); - gib_imlib_image_set_has_alpha(im, 1); - gib_imlib_apply_color_modifier_to_rectangle(im, 0, 0, tw, 2 * th, NULL, NULL, NULL, atab); - gib_imlib_image_fill_rectangle(im, 0, 0, tw, 2 * th, 0, 0, 0, 0); + feh_imlib_image_fill_text_bg(im, tw, 2 * th); gib_imlib_text_draw(im, fn, NULL, 2, 2, FEH_FILE(w->file->data)->filename, IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); gib_imlib_text_draw(im, fn, NULL, 1, 1, FEH_FILE(w->file->data)->filename, IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); - /* Print the position in the filelist, if we have >=2 files */ - if (gib_list_length(filelist) > 1) { - /* sic! */ - len = snprintf(NULL, 0, "%d of %d", gib_list_length(filelist), gib_list_length(filelist)) + 1; - s = emalloc(len); - snprintf(s, len, "%d of %d", gib_list_num(filelist, current_file) + 1, gib_list_length(filelist)); - /* This should somehow be right-aligned */ + + if (s) { gib_imlib_text_draw(im, fn, NULL, 2, th + 1, s, IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); gib_imlib_text_draw(im, fn, NULL, 1, th, s, IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); free(s); @@ -621,81 +1152,188 @@ void feh_draw_filename(winwidget w) return; } -void feh_draw_info(winwidget w) +#ifdef HAVE_LIBEXIF +void feh_draw_exif(winwidget w) { static Imlib_Font fn = NULL; - int tw = 0, th = 0; + int width = 0, height = 0, line_width = 0, line_height = 0; Imlib_Image im = NULL; - static DATA8 atab[256]; - int no_lines = 0; - char *info_cmd; - char info_buf[256]; - FILE *info_pipe; - - if ((!w->file) || (!FEH_FILE(w->file->data)) - || (!FEH_FILE(w->file->data)->filename)) + int no_lines = 0, i; + int pos = 0; + int pos2 = 0; + char info_line[256]; + char *info_buf[128]; + char buffer[EXIF_MAX_DATA]; + + if ( (!w->file) || (!FEH_FILE(w->file->data)) + || (!FEH_FILE(w->file->data)->filename) ) + { return; + } - if (opt.font) - fn = gib_imlib_load_font(opt.font); - if (!fn) { - if (w->full_screen) - fn = gib_imlib_load_font(DEFAULT_FONT_BIG); - else - fn = gib_imlib_load_font(DEFAULT_FONT); - } + buffer[0] = '\0'; + exif_get_info(FEH_FILE(w->file->data)->ed, buffer, EXIF_MAX_DATA); - if (!fn) { - weprintf("Couldn't load font for filename printing"); - return; + fn = feh_load_font(w); + + if (buffer[0] == '\0') + { + snprintf(buffer, EXIF_MAX_DATA, "%s", "Failed to run exif command"); + gib_imlib_get_text_size(fn, buffer, NULL, &width, &height, IMLIB_TEXT_TO_RIGHT); + info_buf[no_lines] = estrdup(buffer); + no_lines++; } + else + { + + while ( (no_lines < 128) && (pos < EXIF_MAX_DATA) ) + { + /* max 128 lines */ + pos2 = 0; + while ( pos2 < 255 ) /* max 255 chars + 1 null byte per line */ + { + if ( (buffer[pos] != '\n') + && (buffer[pos] != '\0') ) + { + info_line[pos2] = buffer[pos]; + } + else if ( buffer[pos] == '\0' ) + { + pos = EXIF_MAX_DATA; /* all data seen */ + break; + } + else + { + pos++; /* line finished, continue with next line*/ + break; + } - info_cmd = feh_printf(opt.info_cmd, FEH_FILE(w->file->data)); + pos++; + pos2++; + } + info_line[pos2] = '\0'; - memset(atab, 0, sizeof(atab)); + gib_imlib_get_text_size(fn, info_line, NULL, &line_width, + &line_height, IMLIB_TEXT_TO_RIGHT); - gib_imlib_get_text_size(fn, "w", NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT); + if (line_height > height) + height = line_height; + if (line_width > width) + width = line_width; + info_buf[no_lines] = estrdup(info_line); - info_pipe = popen(info_cmd, "r"); + no_lines++; + } + } + + if (no_lines == 0) + return; - im = imlib_create_image(290 * tw, 20 * th); + height *= no_lines; + width += 4; + + im = imlib_create_image(width, height); if (!im) + { eprintf("Couldn't create image. Out of memory?"); + } - gib_imlib_image_set_has_alpha(im, 1); - gib_imlib_apply_color_modifier_to_rectangle(im, 0, 0, 290 * tw, 20 * th, NULL, NULL, NULL, atab); - gib_imlib_image_fill_rectangle(im, 0, 0, 290 * tw, 20 * th, 0, 0, 0, 0); + feh_imlib_image_fill_text_bg(im, width, height); + + for (i = 0; i < no_lines; i++) + { + gib_imlib_text_draw(im, fn, NULL, 2, (i * line_height) + 2, + info_buf[i], IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); + gib_imlib_text_draw(im, fn, NULL, 1, (i * line_height) + 1, + info_buf[i], IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + free(info_buf[i]); + + } + + gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, w->h - height, 1, 1, 0); + + gib_imlib_free_image_and_decache(im); + return; + +} +#endif + +void feh_draw_info(winwidget w) +{ + static Imlib_Font fn = NULL; + int width = 0, height = 0, line_width = 0, line_height = 0; + Imlib_Image im = NULL; + int no_lines = 0, i; + char *info_cmd; + char info_line[256]; + char *info_buf[128]; + FILE *info_pipe; + + if ((!w->file) || (!FEH_FILE(w->file->data)) + || (!FEH_FILE(w->file->data)->filename)) + return; + + fn = feh_load_font(w); + + info_cmd = feh_printf(opt.info_cmd, FEH_FILE(w->file->data), w); + + info_pipe = popen(info_cmd, "r"); if (!info_pipe) { - gib_imlib_text_draw(im, fn, NULL, 2, 2, - "Error runnig info command", IMLIB_TEXT_TO_RIGHT, - 255, 0, 0, 255); - gib_imlib_get_text_size(fn, "Error running info command", NULL, &tw, &th, - IMLIB_TEXT_TO_RIGHT); + info_buf[0] = estrdup("Failed to run info command"); + gib_imlib_get_text_size(fn, info_buf[0], NULL, &width, &height, IMLIB_TEXT_TO_RIGHT); no_lines = 1; } else { - while ((no_lines < 20) && fgets(info_buf, 256, info_pipe)) { - info_buf[strlen(info_buf)-1] = '\0'; - gib_imlib_text_draw(im, fn, NULL, 2, (no_lines*th)+2, info_buf, - IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); - gib_imlib_text_draw(im, fn, NULL, 1, (no_lines*th)+1, info_buf, - IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + while ((no_lines < 128) && fgets(info_line, 256, info_pipe)) { + if (info_line[strlen(info_line)-1] == '\n') + info_line[strlen(info_line)-1] = '\0'; + + gib_imlib_get_text_size(fn, info_line, NULL, &line_width, + &line_height, IMLIB_TEXT_TO_RIGHT); + + if (line_height > height) + height = line_height; + if (line_width > width) + width = line_width; + + info_buf[no_lines] = estrdup(info_line); + no_lines++; } pclose(info_pipe); } + if (no_lines == 0) + return; + + height *= no_lines; + width += 4; + + im = imlib_create_image(width, height); + if (!im) + eprintf("Couldn't create image. Out of memory?"); + + feh_imlib_image_fill_text_bg(im, width, height); + + for (i = 0; i < no_lines; i++) { + gib_imlib_text_draw(im, fn, NULL, 2, (i * line_height) + 2, + info_buf[i], IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); + gib_imlib_text_draw(im, fn, NULL, 1, (i * line_height) + 1, + info_buf[i], IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + + free(info_buf[i]); + } - gib_imlib_render_image_on_drawable(w->bg_pmap, im, 2, - w->h - (th * no_lines) - 2, 1, 1, 0); + gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, + w->h - height, 1, 1, 0); gib_imlib_free_image_and_decache(im); return; } -char *build_caption_filename(feh_file * file) +char *build_caption_filename(feh_file * file, short create_dir) { char *caption_filename; char *s, *dir, *caption_dir; @@ -714,6 +1352,8 @@ char *build_caption_filename(feh_file * file) D(("dir %s, cp %s, cdir %s\n", dir, opt.caption_path, caption_dir)) if (stat(caption_dir, &cdir_stat) == -1) { + if (!create_dir) + return NULL; if (mkdir(caption_dir, 0755) == -1) eprintf("Failed to create caption directory %s:", caption_dir); } else if (!S_ISDIR(cdir_stat.st_mode)) @@ -733,7 +1373,6 @@ void feh_draw_caption(winwidget w) int tw = 0, th = 0, ww, hh; int x, y; Imlib_Image im = NULL; - static DATA8 atab[256]; char *p; gib_list *lines, *l; static gib_style *caption_style = NULL; @@ -749,9 +1388,12 @@ void feh_draw_caption(winwidget w) if (!file->caption) { char *caption_filename; - caption_filename = build_caption_filename(file); - /* read caption from file */ - file->caption = ereadfile(caption_filename); + caption_filename = build_caption_filename(file, 0); + if (caption_filename) + /* read caption from file */ + file->caption = ereadfile(caption_filename); + else + file->caption = estrdup(""); free(caption_filename); } @@ -776,22 +1418,7 @@ void feh_draw_caption(winwidget w) caption_style->bits = gib_list_add_front(caption_style->bits, gib_style_bit_new(1, 1, 0, 0, 0, 255)); - if (opt.font) - fn = gib_imlib_load_font(opt.font); - - if (!fn) { - if (w->full_screen) - fn = gib_imlib_load_font(DEFAULT_FONT_BIG); - else - fn = gib_imlib_load_font(DEFAULT_FONT); - } - - if (!fn) { - weprintf("Couldn't load font for caption printing"); - return; - } - - memset(atab, 0, sizeof(atab)); + fn = feh_load_font(w); if (*(file->caption) == '\0') { p = estrdup("Caption entry mode - Hit ESC to cancel"); @@ -826,12 +1453,9 @@ void feh_draw_caption(winwidget w) if (!im) eprintf("Couldn't create image. Out of memory?"); - gib_imlib_image_set_has_alpha(im, 1); - gib_imlib_apply_color_modifier_to_rectangle(im, 0, 0, tw, th, NULL, NULL, NULL, atab); - gib_imlib_image_fill_rectangle(im, 0, 0, tw, th, 0, 0, 0, 0); + feh_imlib_image_fill_text_bg(im, tw, th); l = lines; - x = 0; y = 0; while (l) { p = (char *) l->data; @@ -867,6 +1491,13 @@ void feh_display_status(char stat) D(("filelist %p, filelist->next %p\n", filelist, filelist->next)); + if (!stat) { + putc('\n', stderr); + init_len = 0; + i = 0; + return; + } + if (!init_len) init_len = gib_list_length(filelist); @@ -874,49 +1505,106 @@ void feh_display_status(char stat) if (reset_output) { /* There's just been an error message. Unfortunate ;) */ for (j = 0; j < (((i % 50) + ((i % 50) / 10)) + 7); j++) - fprintf(stdout, " "); + putc(' ', stderr); } if (!(i % 50)) { int len = gib_list_length(filelist); - fprintf(stdout, " %5d/%d (%d)\n[%3d%%] ", + fprintf(stderr, " %5d/%d (%d)\n[%3d%%] ", i, init_len, len, ((int) ((float) i / init_len * 100))); } else if ((!(i % 10)) && (!reset_output)) - fprintf(stdout, " "); + putc(' ', stderr); reset_output = 0; } else - fprintf(stdout, "[ 0%%] "); + fputs("[ 0%] ", stderr); - fprintf(stdout, "%c", stat); - fflush(stdout); + fprintf(stderr, "%c", stat); + fflush(stderr); i++; return; } -void feh_edit_inplace_orient(winwidget w, int orientation) +void feh_edit_inplace(winwidget w, int op) { - int ret; - Imlib_Image old; + int tmp; + Imlib_Image old = NULL; + Imlib_Load_Error err = IMLIB_LOAD_ERROR_NONE; if (!w->file || !w->file->data || !FEH_FILE(w->file->data)->filename) return; - if (!strcmp(gib_imlib_image_format(w->im), "jpeg")) { - feh_edit_inplace_lossless_rotate(w, orientation); + if (!opt.edit) { + imlib_context_set_image(w->im); + if (op == INPLACE_EDIT_FLIP) + imlib_image_flip_vertical(); + else if (op == INPLACE_EDIT_MIRROR) + imlib_image_flip_horizontal(); + else { + imlib_image_orientate(op); + if(op != 2) { + tmp = w->im_w; + w->im_w = w->im_h; + w->im_h = tmp; + } + if (FEH_FILE(w->file->data)->info) { + FEH_FILE(w->file->data)->info->width = w->im_w; + FEH_FILE(w->file->data)->info->height = w->im_h; + } + } + winwidget_render_image(w, 1, 0); + return; + } + + // Imlib2 <= 1.5 returns "jpeg", Imlib2 >= 1.6 uses "jpg" + if ((!strcmp(gib_imlib_image_format(w->im), "jpeg") + || !strcmp(gib_imlib_image_format(w->im), "jpg")) + && !path_is_url(FEH_FILE(w->file->data)->filename)) { + feh_edit_inplace_lossless(w, op); feh_reload_image(w, 1, 1); return; } - ret = feh_load_image(&old, FEH_FILE(w->file->data)); - if (ret) { - gib_imlib_image_orientate(old, orientation); - gib_imlib_save_image(old, FEH_FILE(w->file->data)->filename); + old = imlib_load_image_with_error_return(FEH_FILE(w->file->data)->filename, &err); + + if ((old != NULL) && (err == IMLIB_LOAD_ERROR_NONE)) { + imlib_context_set_image(old); + if (op == INPLACE_EDIT_FLIP) + imlib_image_flip_vertical(); + else if (op == INPLACE_EDIT_MIRROR) + imlib_image_flip_horizontal(); + else + imlib_image_orientate(op); + gib_imlib_save_image_with_error_return(old, + FEH_FILE(w->file->data)->filename, &err); gib_imlib_free_image(old); + if (err) + feh_print_load_error(FEH_FILE(w->file->data)->filename, + w, err, LOAD_ERROR_IMLIB); feh_reload_image(w, 1, 1); } else { - weprintf("failed to load image from disk to edit it in place\n"); + /* + * Image was opened using curl/magick or has been deleted after + * opening it + */ + imlib_context_set_image(w->im); + if (op == INPLACE_EDIT_FLIP) + imlib_image_flip_vertical(); + else if (op == INPLACE_EDIT_MIRROR) + imlib_image_flip_horizontal(); + else { + imlib_image_orientate(op); + tmp = w->im_w; + w->im_w = w->im_h; + w->im_h = tmp; + if (FEH_FILE(w->file->data)->info) { + FEH_FILE(w->file->data)->info->width = w->im_w; + FEH_FILE(w->file->data)->info->height = w->im_h; + } + } + im_weprintf(w, "unable to edit in place. Changes have not been saved."); + winwidget_render_image(w, 1, 0); } return; @@ -1007,7 +1695,6 @@ gib_list *feh_wrap_string(char *text, int wrap_width, Imlib_Font fn, gib_style * list = gib_list_add_end(list, estrdup(line)); free(line); line = NULL; - line_width = 0; } gib_list_free_and_data(words); } @@ -1020,38 +1707,79 @@ gib_list *feh_wrap_string(char *text, int wrap_width, Imlib_Font fn, gib_style * return lines; } -void feh_edit_inplace_lossless_rotate(winwidget w, int orientation) +void feh_edit_inplace_lossless(winwidget w, int op) { char *filename = FEH_FILE(w->file->data)->filename; - char rotate_str[4]; int len = strlen(filename) + 1; char *file_str = emalloc(len); - int rotatearg = 90 * orientation; int pid, status; + int devnull = -1; + char op_name[] = "rotate"; /* message */ + char op_op[] = "-rotate"; /* jpegtran option */ + char op_value[] = "horizontal"; /* jpegtran option's value */ + + if (op == INPLACE_EDIT_FLIP) { + sprintf(op_name, "flip"); + sprintf(op_op, "-flip"); + sprintf(op_value, "vertical"); + } else if (op == INPLACE_EDIT_MIRROR) { + sprintf(op_name, "mirror"); + sprintf(op_op, "-flip"); + } else + snprintf(op_value, 4, "%d", 90 * op); - snprintf(rotate_str, 4, "%d", rotatearg); snprintf(file_str, len, "%s", filename); if ((pid = fork()) < 0) { - weprintf("lossless rotate: fork failed:"); + im_weprintf(w, "lossless %s: fork failed:", op_name); + free(file_str); return; - } else if (pid == 0) { + } + else if (pid == 0) { - execlp("jpegtran", "jpegtran", "-copy", "all", "-rotate", - rotate_str, "-outfile", file_str, file_str, NULL); + execlp("jpegtran", "jpegtran", "-copy", "all", op_op, op_value, + "-outfile", file_str, file_str, NULL); - eprintf("lossless rotate: Is 'jpegtran' installed? Failed to exec:"); - } else { + weprintf("lossless %s: Is 'jpegtran' installed? Failed to exec:", op_name); + _exit(1); + } + else { waitpid(pid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - weprintf("lossless rotate: Got exitcode %d from jpegtran." - " Commandline was:\n" - "jpegtran -copy all -rotate %d -outfile %s %s\n", - status >> 8, rotate_str, file_str, file_str); + im_weprintf(w, + "lossless %s: Got exitcode %d from jpegtran." + " Commandline was: " + "jpegtran -copy all %s %s -outfile %s %s", + op_name, status >> 8, op_op, op_value, file_str, file_str); + free(file_str); return; } } + if ((pid = fork()) < 0) { + im_weprintf(w, "lossless %s: fork failed while updating EXIF tags:", op_name); + } + else if (pid == 0) { + + /* discard normal output */ + devnull = open("/dev/null", O_WRONLY); + dup2(devnull, 1); + + execlp("jpegexiforient", "jpegexiforient", "-1", file_str, NULL); + weprintf("lossless %s: Failed to exec jpegexiforient:", op_name); + _exit(1); + } + else { + waitpid(pid, &status, 0); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + im_weprintf(w, + "lossless %s: Failed to update EXIF orientation tag:" + " jpegexiforient returned %d", + op_name, status >> 8); + } + } + free(file_str); } void feh_draw_actions(winwidget w) @@ -1062,11 +1790,10 @@ void feh_draw_actions(winwidget w) int max_tw = 0; int line_th = 0; Imlib_Image im = NULL; - static DATA8 atab[256]; int i = 0; int num_actions = 0; int cur_action = 0; - char index[2]; + char index[3]; char *line; /* Count number of defined actions. This method sucks a bit since it needs @@ -1085,22 +1812,7 @@ void feh_draw_actions(winwidget w) || (!FEH_FILE(w->file->data)->filename)) return; - if (opt.font) - fn = gib_imlib_load_font(opt.font); - - if (!fn) { - if (w->full_screen) - fn = gib_imlib_load_font(DEFAULT_FONT_BIG); - else - fn = gib_imlib_load_font(DEFAULT_FONT); - } - - if (!fn) { - weprintf("Couldn't load font for actions printing"); - return; - } - - memset(atab, 0, sizeof(atab)); + fn = feh_load_font(w); gib_imlib_get_text_size(fn, "defined actions:", NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT); /* Check for the widest line */ @@ -1108,9 +1820,9 @@ void feh_draw_actions(winwidget w) for (i = 0; i < 10; i++) { if (opt.actions[i]) { - line = emalloc(strlen(opt.actions[i]) + 5); + line = emalloc(strlen(opt.action_titles[i]) + 5); strcpy(line, "0: "); - line = strcat(line, opt.actions[i]); + line = strcat(line, opt.action_titles[i]); gib_imlib_get_text_size(fn, line, NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT); free(line); if (tw > max_tw) @@ -1134,21 +1846,19 @@ void feh_draw_actions(winwidget w) if (!im) eprintf("Couldn't create image. Out of memory?"); - gib_imlib_image_set_has_alpha(im, 1); - gib_imlib_apply_color_modifier_to_rectangle(im, 0, 0, tw, th, NULL, NULL, NULL, atab); - gib_imlib_image_fill_rectangle(im, 0, 0, tw, th, 0, 0, 0, 0); + feh_imlib_image_fill_text_bg(im, tw, th); gib_imlib_text_draw(im, fn, NULL, 2, 2, "defined actions:", IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); gib_imlib_text_draw(im, fn, NULL, 1, 1, "defined actions:", IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); for (i = 0; i < 10; i++) { - if (opt.actions[i]) { + if (opt.action_titles[i]) { cur_action++; - line = emalloc(strlen(opt.actions[i]) + 5); + line = emalloc(strlen(opt.action_titles[i]) + 5); sprintf(index, "%d", i); strcpy(line, index); strcat(line, ": "); - strcat(line, opt.actions[i]); + strcat(line, opt.action_titles[i]); gib_imlib_text_draw(im, fn, NULL, 2, (cur_action * line_th) + 2, line, |