summaryrefslogtreecommitdiff
path: root/src/imlib.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imlib.c')
-rw-r--r--src/imlib.c2943
1 files changed, 1762 insertions, 1181 deletions
diff --git a/src/imlib.c b/src/imlib.c
index 1b2ae12..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"
@@ -33,9 +35,20 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
-#include <jpeglib.h>
-#include "transupp.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;
@@ -51,1246 +64,1814 @@ Window root = 0;
XineramaScreenInfo *xinerama_screens = NULL;
int xinerama_screen;
int num_xinerama_screens;
-#endif /* HAVE_LIBXINERAMA */
+#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)
+void init_xinerama(void)
{
- if (opt.xinerama && XineramaIsActive(disp)) {
- int major, minor;
- xinerama_screen = 0;
- XineramaQueryVersion(disp, &major, &minor);
- xinerama_screens = XineramaQueryScreens(disp, &num_xinerama_screens);
- }
+ if (opt.xinerama && XineramaIsActive(disp)) {
+ 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 */
+#endif /* HAVE_LIBXINERAMA */
-void
-init_imlib_fonts(void)
+void init_imlib_fonts(void)
{
- D_ENTER(4);
+ /* Set up the font stuff */
+ imlib_add_path_to_font_path(".");
+ imlib_add_path_to_font_path(PREFIX "/share/feh/fonts");
- /* Set up the font stuff */
- imlib_add_path_to_font_path(".");
- imlib_add_path_to_font_path(PREFIX "/share/feh/fonts");
- imlib_add_path_to_font_path("./ttfonts");
-
- D_RETURN_(4);
+ return;
}
-void
-init_x_and_imlib(void)
+void init_x_and_imlib(void)
{
- D_ENTER(4);
-
- disp = XOpenDisplay(NULL);
- if (!disp)
- eprintf("Can't open X display. It *is* running, yeah?");
- vis = DefaultVisual(disp, DefaultScreen(disp));
- depth = DefaultDepth(disp, DefaultScreen(disp));
- cm = DefaultColormap(disp, DefaultScreen(disp));
- root = RootWindow(disp, DefaultScreen(disp));
- scr = ScreenOfDisplay(disp, DefaultScreen(disp));
- xid_context = XUniqueContext();
+ disp = XOpenDisplay(NULL);
+ if (!disp)
+ eprintf("Can't open X display. It *is* running, yeah?");
+ vis = DefaultVisual(disp, DefaultScreen(disp));
+ depth = DefaultDepth(disp, DefaultScreen(disp));
+ cm = DefaultColormap(disp, DefaultScreen(disp));
+ root = RootWindow(disp, DefaultScreen(disp));
+ scr = ScreenOfDisplay(disp, DefaultScreen(disp));
+ xid_context = XUniqueContext();
#ifdef HAVE_LIBXINERAMA
- init_xinerama();
-#endif /* HAVE_LIBXINERAMA */
+ init_xinerama();
+#endif /* HAVE_LIBXINERAMA */
- imlib_context_set_display(disp);
- imlib_context_set_visual(vis);
- imlib_context_set_colormap(cm);
- imlib_context_set_color_modifier(NULL);
- imlib_context_set_progress_function(NULL);
- imlib_context_set_operation(IMLIB_OP_COPY);
- wmDeleteWindow = XInternAtom(disp, "WM_DELETE_WINDOW", False);
+ imlib_context_set_display(disp);
+ imlib_context_set_visual(vis);
+ imlib_context_set_colormap(cm);
+ imlib_context_set_color_modifier(NULL);
+ imlib_context_set_progress_function(NULL);
+ 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);
- D_RETURN_(4);
+ return;
}
-int
-feh_load_image_char(Imlib_Image * im, char *filename)
+int feh_should_ignore_image(Imlib_Image * im)
{
- feh_file *file;
- int i;
-
- D_ENTER(4);
- file = feh_file_new(filename);
- i = feh_load_image(im, file);
- feh_file_free(file);
- D_RETURN(4, i);
+ 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(Imlib_Image * im, feh_file * file)
+int feh_load_image_char(Imlib_Image * im, char *filename)
{
- Imlib_Load_Error err;
-
- D_ENTER(4);
- D(3, ("filename is %s, image is %p\n", file->filename, im));
-
- if (!file || !file->filename)
- D_RETURN(4, 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)
- D_RETURN(4, 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 (!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;
- }
- /* Check error code */
- switch (err)
- {
- case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
- if (!opt.quiet)
- weprintf("%s - File does not exist", file->filename);
- break;
- case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY:
- if (!opt.quiet)
- weprintf("%s - Directory specified for image filename",
- file->filename);
- break;
- case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ:
- if (!opt.quiet)
- weprintf("%s - No read access to directory", file->filename);
- 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);
- break;
- case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
- if (!opt.quiet)
- weprintf("%s - Path specified is too long", file->filename);
- break;
- case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT:
- if (!opt.quiet)
- weprintf("%s - Path component does not exist", file->filename);
- break;
- case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY:
- if (!opt.quiet)
- weprintf("%s - Path component is not a directory",
- file->filename);
- break;
- case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE:
- if (!opt.quiet)
- weprintf("%s - Path points outside address space",
- file->filename);
- break;
- case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS:
- if (!opt.quiet)
- weprintf("%s - Too many levels of symbolic links",
- file->filename);
- 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);
- break;
- case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE:
- if (!opt.quiet)
- weprintf("%s - Cannot write to directory", file->filename);
- break;
- case IMLIB_LOAD_ERROR_OUT_OF_DISK_SPACE:
- if (!opt.quiet)
- weprintf("%s - Cannot write - out of disk space",
- file->filename);
- break;
- default:
- if (!opt.quiet)
- weprintf
- ("While loading %s - Unknown error (%d). Attempting to continue",
- file->filename, err);
- break;
- }
- D(3, ("Load *failed*\n"));
- D_RETURN(4, 0);
- }
-
- D(3, ("Loaded ok\n"));
- D_RETURN(4, 1);
+ feh_file *file;
+ int i;
+
+ file = feh_file_new(filename);
+ i = feh_load_image(im, file);
+ feh_file_free(file);
+ return(i);
}
-char *
-feh_http_load_image(char *url)
+void feh_print_load_error(char *file, winwidget w, Imlib_Load_Error err, enum feh_load_error feh_err)
{
- char *tmpname;
- char *tmpname_timestamper = NULL;
- char *basename;
- char *newurl = NULL;
- char randnum[20];
- int rnum;
- char *path = NULL;
-
- D_ENTER(4);
-
- if (opt.keep_http)
- {
- if (opt.output_dir)
- path = opt.output_dir;
- else
- path = "";
- }
- else
- path = "/tmp/";
-
- basename = strrchr(url, '/') + 1;
- tmpname = feh_unique_filename(path, basename);
-
- if (opt.wget_timestamp)
- {
- char cppid[10];
- pid_t ppid;
-
- ppid = getpid();
- snprintf(cppid, sizeof(cppid), "%06ld", (long)ppid);
- tmpname_timestamper =
- estrjoin("", "/tmp/feh_", cppid, "_", basename, NULL);
- newurl = estrdup(url);
- }
- else
- {
- rnum = rand();
- snprintf(randnum, sizeof(randnum), "%d", rnum);
- newurl = estrjoin("?", url, randnum, NULL);
- }
- D(3, ("newurl: %s\n", newurl));
-
- 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(4, ("using builtin http collection\n"));
- fp = fopen(tmpname, "w");
- if (!fp)
- {
- weprintf("couldn't write to file %s:", tmpname);
- free(tmpname);
- D_RETURN(4, NULL);
- }
-
- hostname = feh_strip_hostname(newurl);
- if (!hostname)
- {
- weprintf("couldn't work out hostname from %s:", newurl);
- free(tmpname);
- free(newurl);
- D_RETURN(4, NULL);
- }
-
- D(4, ("trying hostname %s\n", hostname));
-
- if (!(hptr = feh_gethostbyname(hostname)))
- {
- weprintf("error resolving host %s:", hostname);
- free(hostname);
- free(tmpname);
- free(newurl);
- D_RETURN(4, NULL);
- }
-
- /* Copy the address of the host to socket description. */
- memcpy(&addr.sin_addr, hptr->h_addr, hptr->h_length);
-
- /* Set port and protocol */
- addr.sin_family = AF_INET;
- addr.sin_port = htons(80);
-
- if ((sockno = socket(PF_INET, SOCK_STREAM, 0)) == -1)
- {
- weprintf("error opening socket:");
- free(tmpname);
- free(hostname);
- free(newurl);
- D_RETURN(4, NULL);
- }
- if (connect(sockno, (struct sockaddr *) &addr, sizeof(addr)) == -1)
- {
- weprintf("error connecting socket:");
- free(tmpname);
- free(hostname);
- free(newurl);
- D_RETURN(4, NULL);
- }
-
- get_url = strchr(newurl, '/') + 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);
- free(newurl);
- weprintf("error sending over socket:");
- D_RETURN(4, NULL);
- }
- free(get_string);
- free(host_string);
- free(query_string);
- free(hostname);
- free(newurl);
-
- 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
- */
-
- switch (body)
- {
-
- case IN_BODY:
- fwrite(buf + i, 1, size - i, fp);
- i = size;
- break;
-
- 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;
-
- 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;
-
- } /* switch */
- } /* for i */
- }
- } /* while read */
- close(sockno);
- fclose(fp);
- }
- else
- {
- int pid;
- int status;
-
- if ((pid = fork()) < 0)
- {
- weprintf("open url: fork failed:");
- free(tmpname);
- free(newurl);
- D_RETURN(4, NULL);
- }
- else if (pid == 0)
- {
- char *quiet = NULL;
-
- if (!opt.verbose)
- quiet = estrdup("-q");
-
- if (opt.wget_timestamp)
- {
- execlp("wget", "wget", "-N", "-O", tmpname_timestamper, newurl,
- quiet, (char*) NULL);
- }
- else
- {
- execlp("wget", "wget", "--cache=off", "-O", tmpname, newurl,
- quiet, NULL);
- }
- eprintf("url: exec failed: wget:");
- }
- else
- {
- waitpid(pid, &status, 0);
-
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
- {
- weprintf("url: wget failed to load URL %s\n", url);
- free(newurl);
- free(tmpname);
- D_RETURN(4, NULL);
- }
- if (opt.wget_timestamp)
- {
- char cmd[2048];
-
- snprintf(cmd, sizeof(cmd), "/bin/cp %s %s", tmpname_timestamper,
- tmpname);
- system(cmd);
- }
- free(newurl);
- }
- }
-
- D_RETURN(4, 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 (feh_err != LOAD_ERROR_IMLIB) {
+ return;
+ }
+ switch (err) {
+ case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
+ im_weprintf(w, "%s - File does not exist", file);
+ break;
+ case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY:
+ im_weprintf(w, "%s - Directory specified for image filename", file);
+ break;
+ case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ:
+ im_weprintf(w, "%s - No read access", file);
+ break;
+ case IMLIB_LOAD_ERROR_UNKNOWN:
+ case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
+ im_weprintf(w, "%s - No Imlib2 loader for that file format", file);
+ break;
+ case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
+ im_weprintf(w, "%s - Path specified is too long", file);
+ break;
+ case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT:
+ im_weprintf(w, "%s - Path component does not exist", file);
+ break;
+ case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY:
+ im_weprintf(w, "%s - Path component is not a directory", file);
+ break;
+ case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE:
+ im_weprintf(w, "%s - Path points outside address space", file);
+ break;
+ case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS:
+ im_weprintf(w, "%s - Too many levels of symbolic links", file);
+ break;
+ case IMLIB_LOAD_ERROR_OUT_OF_MEMORY:
+ im_weprintf(w, "While loading %s - Out of memory", file);
+ break;
+ case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE:
+ im_weprintf(w, "%s - Cannot write to directory", file);
+ break;
+ case IMLIB_LOAD_ERROR_OUT_OF_DISK_SPACE:
+ 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:
+ im_weprintf(w, "While loading %s - Unknown error (%d)",
+ file, err);
+ break;
+ }
+ }
}
-struct hostent *
-feh_gethostbyname(const char *name)
+#ifdef HAVE_LIBMAGIC
+void uninit_magic(void)
{
- struct hostent *hp;
- unsigned long addr;
-
- D_ENTER(3);
- addr = (unsigned long) inet_addr(name);
- if ((int) addr != -1)
- hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET);
- else
- hp = gethostbyname(name);
- D_RETURN(3, hp);
+ if (!magic) {
+ return;
+ }
+
+ magic_close(magic);
+ magic = NULL;
}
+void init_magic(void)
+{
+ if (getenv("FEH_SKIP_MAGIC")) {
+ return;
+ }
+
+ if (!(magic = magic_open(MAGIC_NONE))) {
+ weprintf("unable to initialize magic library\n");
+ return;
+ }
-char *
-feh_strip_hostname(char *url)
+ 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 *ret;
- char *start;
- char *finish;
- int len;
+ const char * mime_type = NULL;
- D_ENTER(3);
+ if (!magic) {
+ return 1;
+ }
- start = strchr(url, '/');
- if (!start)
- D_RETURN(3, NULL);
+ magic_setflags(magic, MAGIC_MIME_TYPE | MAGIC_SYMLINK | magic_flags);
+ mime_type = magic_file(magic, file->filename);
- start += 2;
+ if (!mime_type) {
+ return 0;
+ }
- finish = strchr(start, '/');
- if (!finish)
- D_RETURN(3, NULL);
+ D(("file %s has mime type: %s\n", file->filename, mime_type));
- len = finish - start;
+ 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);
+ }
- ret = emalloc(len + 1);
- strncpy(ret, start, len);
- ret[len] = '\0';
- D_RETURN(3, ret);
+ return 0;
}
+#else
+int feh_is_image(__attribute__((unused)) feh_file * file, __attribute__((unused)) int magic_flags)
+{
+ return 1;
+}
+#endif
-void
-feh_draw_zoom(winwidget w)
+int feh_load_image(Imlib_Image * im, feh_file * file)
{
- static Imlib_Font fn = NULL;
- int tw = 0, th = 0;
- Imlib_Image im = NULL;
- char buf[100];
- static DATA8 atab[256];
-
- D_ENTER(4);
-
- if (!w->im)
- D_RETURN_(4);
-
- if (!fn) {
- fn = gib_imlib_load_font(DEFAULT_FONT);
- memset(atab, 0, sizeof(atab));
- }
-
- if (!fn)
- {
- weprintf("Couldn't load font for zoom printing");
- D_RETURN_(4);
- }
-
- snprintf(buf, sizeof(buf), "%.0f%%, %dx%d", w->zoom * 100,
- (int) (w->im_w * w->zoom), (int) (w->im_h * w->zoom));
-
- /* Work out how high the font is */
- gib_imlib_get_text_size(fn, buf, NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT);
-
- tw += 3;
- th += 3;
- im = imlib_create_image(tw, 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, th,
- NULL, NULL, NULL, atab);
- gib_imlib_image_fill_rectangle(im, 0, 0, tw, th, 0, 0, 0, 0);
-
- 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);
- gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, w->h - th, 1, 1, 0);
- gib_imlib_free_image_and_decache(im);
- D_RETURN_(4);
+ 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;
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ if (tmpname) {
+ *im = imlib_load_image_with_error_return(tmpname, &err);
+ if (!err && im) {
+ real_filename = file->filename;
+ file->filename = tmpname;
+
+ /*
+ * 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 (!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);
+ } 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
+ }
+
+ if ((err) || (!im)) {
+ if (opt.verbose && !opt.quiet) {
+ fputs("\n", stderr);
+ reset_output = 1;
+ }
+ feh_print_load_error(file->filename, NULL, err, feh_err);
+ D(("Load *failed*\n"));
+ return(0);
+ }
+
+ /*
+ * 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);
+ }
+ }
+
+ 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
+
+ D(("Loaded ok\n"));
+ return(1);
}
-void
-feh_draw_filename(winwidget w)
+void feh_reload_image(winwidget w, int resize, int force_new)
{
- static Imlib_Font fn = NULL;
- int tw = 0, th = 0;
- Imlib_Image im = NULL;
- static DATA8 atab[256];
- char *s = NULL;
- int len = 0;
-
- D_ENTER(4);
-
- if ((!w->file) || (!FEH_FILE(w->file->data))
- || (!FEH_FILE(w->file->data)->filename))
- D_RETURN_(4);
-
- if (!fn)
- {
- memset(atab, 0, sizeof(atab));
- 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 filename printing");
- D_RETURN_(4);
- }
-
- /* 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);
-
- /* 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);
-
- 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 are in fullscreen and the
- * list has more than one element */
- if (w->full_screen && (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 */
- 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);
- }
-
- gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, 0, 1, 1, 0);
-
- gib_imlib_free_image_and_decache(im);
- D_RETURN_(4);
+ char *new_title;
+ int len;
+ Imlib_Image tmp;
+ int old_w, old_h;
+
+ 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;
}
-char *build_caption_filename(feh_file *file) {
- char *caption_filename;
- char *s, *dir;
- s = strrchr(file->filename, '/');
- if (s) {
- dir = estrdup(file->filename);
- s = strrchr(dir, '/');
- *s = '\0';
- } else {
- dir = estrdup(".");
- }
- caption_filename = estrjoin("",
- dir,
- "/",
- opt.caption_path,
- "/",
- file->name,
- ".txt",
- NULL);
- free(dir);
- return caption_filename;
+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 status;
+ do {
+ waitpid(childpid, &status, WUNTRACED);
+ if (WIFEXITED(status)) {
+ return !WEXITSTATUS(status);
+ }
+ } while (!WIFEXITED(status) && !WIFSIGNALED(status));
+ }
+
+ return 0;
}
-void
-feh_draw_caption(winwidget w)
+static char *feh_dcraw_load_image(char *filename)
{
- static Imlib_Font fn = NULL;
- 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;
- feh_file *file;
-
- D_ENTER(4);
-
- if (!w->file) {
- D_RETURN_(4);
- }
- file = FEH_FILE(w->file->data);
- if (!file->filename) {
- D_RETURN_(4);
- }
-
- if (!file->caption) {
- char *caption_filename;
- caption_filename = build_caption_filename(file);
- /* read caption from file */
- file->caption = ereadfile(caption_filename);
- free(caption_filename);
- }
-
- if (file->caption == NULL) {
- /* caption file is not there, we want to cache that, otherwise we'll stat
- * the damn file every time we render the image. Reloading an image will
- * always cause the caption to be reread though so we're safe to do so.
- * (Before this bit was added, when zooming a captionless image with
- * captions enabled, the captions file would be stat()d like 30 times a
- * second) - don't forget this function is called from
- * winwidget_render_image().
- */
- file->caption = estrdup("");
- D_RETURN_(4);
- }
-
- if (file->caption == '\0') {
- D_RETURN_(4);
- }
-
- if (!caption_style) {
- caption_style = gib_style_new("caption");
- caption_style->bits = gib_list_add_front(caption_style->bits,
- gib_style_bit_new(0,0,0,0,0,0));
- caption_style->bits = gib_list_add_front(caption_style->bits,
- gib_style_bit_new(1,1,0,0,0,255));
- }
-
- if (!fn)
- {
- memset(atab, 0, sizeof(atab));
- 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");
- D_RETURN_(4);
- }
-
- lines = feh_wrap_string(file->caption, w->w, w->h, fn, NULL);
- if (!lines)
- D_RETURN_(4);
-
- /* Work out how high/wide the caption is */
- l = lines;
- while (l) {
- p = (char *) l->data;
- gib_imlib_get_text_size(fn, p, caption_style, &ww, &hh, IMLIB_TEXT_TO_RIGHT);
- if (ww > tw)
- tw = ww;
- th += hh;
- if (l->next)
- th += 1; /* line spacing */
- l = l->next;
- }
-
- /* we don't want the caption overlay larger than our window */
- if (th > w->h)
- th = w->h;
- if (tw > w->w)
- tw = w->w;
-
- im = imlib_create_image(tw, 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, th,
- NULL, NULL, NULL, atab);
- gib_imlib_image_fill_rectangle(im, 0, 0, tw, th, 0, 0, 0, 0);
-
- l = lines;
- x = 0;
- y = 0;
- while (l) {
- p = (char *) l->data;
- gib_imlib_get_text_size(fn, p, caption_style, &ww, &hh, IMLIB_TEXT_TO_RIGHT);
- x = (tw - ww) / 2;
- if (w->caption_entry) {
- gib_imlib_text_draw(im, fn, caption_style, x, y, p, IMLIB_TEXT_TO_RIGHT,
- 255, 255, 0, 255);
- } else {
- gib_imlib_text_draw(im, fn, caption_style, x, y, p, IMLIB_TEXT_TO_RIGHT,
- 255, 255, 255, 255);
- }
-
- y += hh + 1; /* line spacing */
- l = l->next;
- }
-
- gib_imlib_render_image_on_drawable(w->bg_pmap, im, (w->w - tw) / 2, w->h - th, 1, 1, 0);
- gib_imlib_free_image_and_decache(im);
- gib_list_free_and_data(lines);
- D_RETURN_(4);
+ 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;
+ }
+
+ 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;
+ }
-unsigned char reset_output = 0;
+ basename = strrchr(filename, '/');
+
+ if (basename == NULL)
+ basename = filename;
+ else
+ basename++;
-void
-feh_display_status(char stat)
+ 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 {
+ 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 (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);
+ }
+
+ free(argv_fn);
+
+ if ((sfn != NULL) && opt.use_conversion_cache)
+ gib_hash_set(conversion_cache, filename, sfn);
+
+ return sfn;
+}
+
+#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
{
- static int i = 0;
- static int init_len = 0;
- int j = 0;
-
- D_ENTER(5);
-
- D(5, ("filelist %p, filelist->next %p\n", filelist, filelist->next));
-
- if (!init_len)
- init_len = gib_list_length(filelist);
-
- if (i)
- {
- if (reset_output)
- {
- /* There's just been an error message. Unfortunate ;) */
- for (j = 0; j < (((i % 50) + ((i % 50) / 10)) + 7); j++)
- fprintf(stdout, " ");
- }
-
- if (!(i % 50))
- {
- int len;
- char buf[50];
-
- len = gib_list_length(filelist);
- snprintf(buf, sizeof(buf), " %5d/%d (%d)\n[%3d%%] ", i, init_len,
- len, ((int) ((float) i / init_len * 100)));
- fprintf(stdout, buf);
- }
- else if ((!(i % 10)) && (!reset_output))
- fprintf(stdout, " ");
-
- reset_output = 0;
- }
- else
- fprintf(stdout, "[ 0%%] ");
-
- fprintf(stdout, "%c", stat);
- fflush(stdout);
- i++;
- D_RETURN_(5);
+ // 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;
}
-void feh_edit_inplace_orient(winwidget w, int orientation) {
- int ret;
- Imlib_Image old;
- D_ENTER(4);
- if(!w->file
- || !w->file->data
- || !FEH_FILE(w->file->data)->filename)
- D_RETURN_(4);
-
- if (!strcmp(gib_imlib_image_format(w->im), "jpeg")) {
- feh_edit_inplace_lossless_rotate(w, orientation);
- feh_reload_image(w, 1, 1);
- D_RETURN_(4);
- }
-
- 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);
- gib_imlib_free_image(old);
- feh_reload_image(w, 1, 1);
- } else {
- weprintf("failed to load image from disk to edit it in place\n");
- }
-
- D_RETURN_(4);
+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;
}
+#else /* HAVE_LIBCURL */
-/* TODO max_height is ignored... Could use a function which generates a
- * transparent text overlay image, with wrapping and all. Would be useful */
-gib_list *
-feh_wrap_string(char *text, int wrap_width, int max_height, Imlib_Font fn, gib_style * style)
+char *feh_http_load_image(char *url)
{
- gib_list *ll, *lines = NULL, *list = NULL, *words;
- gib_list *l = NULL;
- char delim[2] = { '\n', '\0' };
- int w, line_width;
- int tw, th;
- char *p, *pp;
- char *line = NULL;
- char *temp;
- int space_width = 0, m_width = 0, t_width = 0, new_width = 0;
-
- lines = gib_string_split(text, delim);
-
- if (wrap_width)
- {
- gib_imlib_get_text_size(fn, "M M", style, &t_width, NULL,
- IMLIB_TEXT_TO_RIGHT);
- gib_imlib_get_text_size(fn, "M", style, &m_width, NULL,
- IMLIB_TEXT_TO_RIGHT);
- space_width = t_width - (2 * m_width);
- w = wrap_width;
- l = lines;
- while (l)
- {
- line_width = 0;
- p = (char *) l->data;
- /* quick check to see if whole line fits okay */
- gib_imlib_get_text_size(fn, p, style, &tw, &th, IMLIB_TEXT_TO_RIGHT);
- if (tw <= w) {
- list = gib_list_add_end(list, estrdup(p));
- } else if (strlen(p) == 0) {
- list = gib_list_add_end(list, estrdup(""));
- } else if (!strcmp(p, " ")) {
- list = gib_list_add_end(list, estrdup(" "));
- } else {
- words = gib_string_split(p, " ");
- if (words) {
- ll = words;
- while (ll) {
- pp = (char *) ll->data;
- if (strcmp(pp, " ")) {
- gib_imlib_get_text_size(fn, pp, style, &tw, &th,
- IMLIB_TEXT_TO_RIGHT);
- if (line_width == 0)
- new_width = tw;
- else
- new_width = line_width + space_width + tw;
- if (new_width <= w) {
- /* add word to line */
- if (line) {
- int len;
-
- len = strlen(line) + strlen(pp) + 2;
- temp = emalloc(len);
- snprintf(temp, len, "%s %s", line, pp);
- free(line);
- line = temp;
- } else {
- line = estrdup(pp);
- }
- line_width = new_width;
- } else if (line_width == 0) {
- /* can't fit single word in :/
- increase width limit to width of word
- and jam the bastard in anyhow */
- w = tw;
- line = estrdup(pp);
- line_width = new_width;
- } else {
- /* finish this line, start next and add word there */
- if (line) {
- list = gib_list_add_end(list, estrdup(line));
- free(line);
- line = NULL;
- }
- line = estrdup(pp);
- line_width = tw;
- }
- }
- ll = ll->next;
- }
- if (line) {
- /* finish last line */
- list = gib_list_add_end(list, estrdup(line));
- free(line);
- line = NULL;
- line_width = 0;
- }
- gib_list_free_and_data(words);
- }
- }
- l = l->next;
- }
- gib_list_free_and_data(lines);
- lines = list;
- }
- return lines;
+ weprintf(
+ "Cannot load image %s\nPlease recompile feh with libcurl support",
+ url
+ );
+ return NULL;
+}
+
+#endif /* HAVE_LIBCURL */
+
+void feh_imlib_image_fill_text_bg(Imlib_Image im, int w, int h)
+{
+ gib_imlib_image_set_has_alpha(im, 1);
+
+ imlib_context_set_blend(0);
+
+ 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);
+
+ imlib_context_set_blend(1);
+}
+
+static Imlib_Font feh_load_font(winwidget w)
+{
+ static Imlib_Font fn = NULL;
+
+ if (opt.font)
+ fn = gib_imlib_load_font(opt.font);
+
+ if (!fn) {
+ if (w && w->full_screen)
+ fn = gib_imlib_load_font(DEFAULT_FONT_BIG);
+ else
+ fn = gib_imlib_load_font(DEFAULT_FONT);
+ }
+
+ if (!fn) {
+ eprintf("Couldn't load font to draw a message");
+ }
+
+ return fn;
}
-void feh_edit_inplace_lossless_rotate(winwidget w, int orientation) {
- FILE *input_file;
- FILE *output_file;
- struct jpeg_decompress_struct srcinfo;
- struct jpeg_compress_struct dstinfo;
- struct jpeg_error_mgr jsrcerr, jdsterr;
- jvirt_barray_ptr * src_coef_arrays;
- jvirt_barray_ptr * dst_coef_arrays;
- JCOPY_OPTION copyoption;
- jpeg_transform_info transformoption;
- int len;
- char *outfilename;
- char *infilename = FEH_FILE(w->file->data)->filename;
-
- copyoption = JCOPYOPT_ALL;
- transformoption.transform = JXFORM_NONE;
- transformoption.trim = FALSE;
- transformoption.force_grayscale = FALSE;
-
- if (orientation == 1) {
- transformoption.transform = JXFORM_ROT_90;
- } else if (orientation == 2) {
- transformoption.transform = JXFORM_ROT_180;
- } else {
- transformoption.transform = JXFORM_ROT_270;
- }
-
- if ((input_file = fopen(infilename, "rb")) == NULL) {
- weprintf("couldn't open file for reading: %s\n", infilename);
- D_RETURN_(4);
- }
- len = strlen(infilename) + sizeof(".tmp") + 1;
- outfilename = emalloc(len);
- snprintf(outfilename, len, "%s.tmp", infilename);
-
- if ((output_file = fopen(outfilename, "wb")) == NULL) {
- weprintf("couldn't open file for writing: %s\n", outfilename);
- free(outfilename);
- fclose(input_file);
- D_RETURN_(4);
- }
-
- /* Initialize the JPEG decompression object with default error handling. */
- srcinfo.err = jpeg_std_error(&jsrcerr);
- jpeg_create_decompress(&srcinfo);
-
- /* Initialize the JPEG compression object with default error handling. */
- dstinfo.err = jpeg_std_error(&jdsterr);
- jpeg_create_compress(&dstinfo);
- jsrcerr.trace_level = jdsterr.trace_level;
-
- /* Specify data source for decompression */
- jpeg_stdio_src(&srcinfo, input_file);
-
- /* Enable saving of extra markers that we want to copy */
- jcopy_markers_setup(&srcinfo, copyoption);
-
- /* Read file header */
- (void) jpeg_read_header(&srcinfo, TRUE);
-
- /* Any space needed by a transform option must be requested before
- * jpeg_read_coefficients so that memory allocation will be done right.
- */
- jtransform_request_workspace(&srcinfo, &transformoption);
-
- /* Read source file as DCT coefficients */
- src_coef_arrays = jpeg_read_coefficients(&srcinfo);
-
- /* Initialize destination compression parameters from source values */
- jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
-
- /* Adjust destination parameters if required by transform options;
- * also find out which set of coefficient arrays will hold the output.
- */
- dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo,
- src_coef_arrays,
- &transformoption);
-
- /* Specify data destination for compression */
- jpeg_stdio_dest(&dstinfo, output_file);
-
- /* Start compressor (note no image data is actually written here) */
- jpeg_write_coefficients(&dstinfo, dst_coef_arrays);
-
- /* Copy to the output file any extra markers that we want to preserve */
- jcopy_markers_execute(&srcinfo, &dstinfo, copyoption);
-
- /* Execute image transformation */
- jtransform_execute_transformation(&srcinfo, &dstinfo,
- src_coef_arrays,
- &transformoption);
-
- /* Finish compression and release memory */
- jpeg_finish_compress(&dstinfo);
- jpeg_destroy_compress(&dstinfo);
-
- (void) jpeg_finish_decompress(&srcinfo);
- jpeg_destroy_decompress(&srcinfo);
-
- fclose(input_file);
- fclose(output_file);
-
- /* TODO fix EXIF tags (orientation, width, height) */
-
- /* rename outfilename to infilename.. if it worked */
- if (jsrcerr.num_warnings > 0) {
- weprintf("got errors from libjpeg (%d), not replacing file\n", jsrcerr.num_warnings);
- } else {
- if (rename(outfilename, infilename)) {
- weprintf("failed to replace file %s with %s\n", infilename, outfilename);
- }
- }
- free(outfilename);
+
+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));
+
+ /* Work out how high the font is */
+ gib_imlib_get_text_size(fn, buf, NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT);
+
+ tw += 3;
+ th += 3;
+ im = imlib_create_image(tw, th);
+ if (!im)
+ eprintf("Couldn't create image. Out of memory?");
+
+ 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);
+ gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, w->h - th, 1, 1, 0);
+ gib_imlib_free_image_and_decache(im);
+ return;
}
-void
-feh_draw_actions(winwidget w)
+void im_weprintf(winwidget w, char *fmt, ...)
{
- static Imlib_Font fn = NULL;
- int tw = 0, th = 0;
- int max_tw = 0;
- int line_th = 0;
- Imlib_Image im = NULL;
- static DATA8 atab[256];
- int i = 0;
- int num_actions = 0;
-
- D_ENTER(4);
-
-// count the number of defined actions
- for (num_actions=0;opt.actions[num_actions];num_actions++)
- ;
- if (num_actions == 0)
- return;
-
- if ((!w->file) || (!FEH_FILE(w->file->data))
- || (!FEH_FILE(w->file->data)->filename))
- D_RETURN_(4);
-
- if (!fn)
- {
- memset(atab, 0, sizeof(atab));
- 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");
- D_RETURN_(4);
- }
-
-
- gib_imlib_get_text_size(fn, "defined actions:", NULL, &tw, &th,
- IMLIB_TEXT_TO_RIGHT);
-// Check for the widest line
- max_tw = tw;
- for (i=0;opt.actions[i];i++) {
- gib_imlib_get_text_size(fn, opt.actions[i], NULL, &tw, &th,
- IMLIB_TEXT_TO_RIGHT);
- if (tw>max_tw) {
- max_tw = tw;
- }
- }
-
- tw = max_tw;
- tw += 3;
- th += 3;
- line_th = th;
- th = (th*num_actions)+line_th;
-
- im = imlib_create_image(tw, 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, th,
- NULL, NULL, NULL, atab);
- gib_imlib_image_fill_rectangle(im, 0, 0, tw, th, 0, 0, 0, 0);
-
- gib_imlib_text_draw(im, fn, NULL, 1, 1, "defined actions:",
- IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255);
- gib_imlib_text_draw(im, fn, NULL, 2, 2, "defined actions:",
- IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255);
-
- for(i=0;i<num_actions;i++)
- {
-// compose a line containing an index, a colon followed by the
-// action.
- char index[1];
- char line[strlen(opt.actions[i])+5];
- sprintf(index, "%d", i);
- strcpy(line, index);
- strcat(line, ": ");
- strcat(line, opt.actions[i]);
-
- gib_imlib_text_draw(im, fn, NULL, 2, ((i+1)*line_th)+2, line,
- IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255);
- gib_imlib_text_draw(im, fn, NULL, 1, ((i+1)*line_th)+1, line,
- IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255);
- free(line);
- }
-
- gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, 0, 1, 1, 0);
-
- gib_imlib_free_image_and_decache(im);
- D_RETURN_(4);
+ 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;
+
+ 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;
+
+ if ((!w->file) || (!FEH_FILE(w->file->data))
+ || (!FEH_FILE(w->file->data)->filename))
+ return;
+
+ fn = feh_load_font(w);
+
+ /* 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 (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));
+
+ gib_imlib_get_text_size(fn, s, NULL, &nw, NULL, IMLIB_TEXT_TO_RIGHT);
+
+ if (nw > tw)
+ tw = nw;
+ }
+
+ tw += 3;
+ th += 3;
+ im = imlib_create_image(tw, 2 * th);
+ if (!im)
+ eprintf("Couldn't create image. Out of memory?");
+
+ 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);
+
+ 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);
+ }
+
+ gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, 0, 1, 1, 0);
+
+ gib_imlib_free_image_and_decache(im);
+ return;
+}
+
+#ifdef HAVE_LIBEXIF
+void feh_draw_exif(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;
+ 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;
+ }
+
+
+ buffer[0] = '\0';
+ exif_get_info(FEH_FILE(w->file->data)->ed, buffer, EXIF_MAX_DATA);
+
+ 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;
+ }
+
+ pos++;
+ pos2++;
+ }
+ info_line[pos2] = '\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++;
+ }
+ }
+
+ 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, 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) {
+ 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 < 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, 0,
+ w->h - height, 1, 1, 0);
+
+ gib_imlib_free_image_and_decache(im);
+ return;
+}
+
+char *build_caption_filename(feh_file * file, short create_dir)
+{
+ char *caption_filename;
+ char *s, *dir, *caption_dir;
+ struct stat cdir_stat;
+ s = strrchr(file->filename, '/');
+ if (s) {
+ dir = estrdup(file->filename);
+ s = strrchr(dir, '/');
+ *s = '\0';
+ } else {
+ dir = estrdup(".");
+ }
+
+ caption_dir = estrjoin("/", dir, opt.caption_path, NULL);
+
+ 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))
+ eprintf("Caption directory (%s) exists, but is not a directory.",
+ caption_dir);
+
+ free(caption_dir);
+
+ caption_filename = estrjoin("", dir, "/", opt.caption_path, "/", file->name, ".txt", NULL);
+ free(dir);
+ return caption_filename;
+}
+
+void feh_draw_caption(winwidget w)
+{
+ static Imlib_Font fn = NULL;
+ int tw = 0, th = 0, ww, hh;
+ int x, y;
+ Imlib_Image im = NULL;
+ char *p;
+ gib_list *lines, *l;
+ static gib_style *caption_style = NULL;
+ feh_file *file;
+
+ if (!w->file) {
+ return;
+ }
+ file = FEH_FILE(w->file->data);
+ if (!file->filename) {
+ return;
+ }
+
+ if (!file->caption) {
+ char *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);
+ }
+
+ if (file->caption == NULL) {
+ /* caption file is not there, we want to cache that, otherwise we'll stat
+ * the damn file every time we render the image. Reloading an image will
+ * always cause the caption to be reread though so we're safe to do so.
+ * (Before this bit was added, when zooming a captionless image with
+ * captions enabled, the captions file would be stat()d like 30 times a
+ * second) - don't forget this function is called from
+ * winwidget_render_image().
+ */
+ file->caption = estrdup("");
+ }
+
+ if (*(file->caption) == '\0' && !w->caption_entry)
+ return;
+
+ caption_style = gib_style_new("caption");
+ caption_style->bits = gib_list_add_front(caption_style->bits,
+ gib_style_bit_new(0, 0, 0, 0, 0, 0));
+ caption_style->bits = gib_list_add_front(caption_style->bits,
+ gib_style_bit_new(1, 1, 0, 0, 0, 255));
+
+ fn = feh_load_font(w);
+
+ if (*(file->caption) == '\0') {
+ p = estrdup("Caption entry mode - Hit ESC to cancel");
+ lines = feh_wrap_string(p, w->w, fn, NULL);
+ free(p);
+ } else
+ lines = feh_wrap_string(file->caption, w->w, fn, NULL);
+
+ if (!lines)
+ return;
+
+ /* Work out how high/wide the caption is */
+ l = lines;
+ while (l) {
+ p = (char *) l->data;
+ gib_imlib_get_text_size(fn, p, caption_style, &ww, &hh, IMLIB_TEXT_TO_RIGHT);
+ if (ww > tw)
+ tw = ww;
+ th += hh;
+ if (l->next)
+ th += 1; /* line spacing */
+ l = l->next;
+ }
+
+ /* we don't want the caption overlay larger than our window */
+ if (th > w->h)
+ th = w->h;
+ if (tw > w->w)
+ tw = w->w;
+
+ im = imlib_create_image(tw, th);
+ if (!im)
+ eprintf("Couldn't create image. Out of memory?");
+
+ feh_imlib_image_fill_text_bg(im, tw, th);
+
+ l = lines;
+ y = 0;
+ while (l) {
+ p = (char *) l->data;
+ gib_imlib_get_text_size(fn, p, caption_style, &ww, &hh, IMLIB_TEXT_TO_RIGHT);
+ x = (tw - ww) / 2;
+ if (w->caption_entry && (*(file->caption) == '\0'))
+ gib_imlib_text_draw(im, fn, caption_style, x, y, p,
+ IMLIB_TEXT_TO_RIGHT, 255, 255, 127, 255);
+ else if (w->caption_entry)
+ gib_imlib_text_draw(im, fn, caption_style, x, y, p,
+ IMLIB_TEXT_TO_RIGHT, 255, 255, 0, 255);
+ else
+ gib_imlib_text_draw(im, fn, caption_style, x, y, p,
+ IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255);
+
+ y += hh + 1; /* line spacing */
+ l = l->next;
+ }
+
+ gib_imlib_render_image_on_drawable(w->bg_pmap, im, (w->w - tw) / 2, w->h - th, 1, 1, 0);
+ gib_imlib_free_image_and_decache(im);
+ gib_list_free_and_data(lines);
+ return;
+}
+
+unsigned char reset_output = 0;
+
+void feh_display_status(char stat)
+{
+ static int i = 0;
+ static int init_len = 0;
+ int j = 0;
+
+ 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);
+
+ if (i) {
+ if (reset_output) {
+ /* There's just been an error message. Unfortunate ;) */
+ for (j = 0; j < (((i % 50) + ((i % 50) / 10)) + 7); j++)
+ putc(' ', stderr);
+ }
+
+ if (!(i % 50)) {
+ int len = gib_list_length(filelist);
+
+ fprintf(stderr, " %5d/%d (%d)\n[%3d%%] ",
+ i, init_len, len, ((int) ((float) i / init_len * 100)));
+
+ } else if ((!(i % 10)) && (!reset_output))
+ putc(' ', stderr);
+
+ reset_output = 0;
+ } else
+ fputs("[ 0%] ", stderr);
+
+ fprintf(stderr, "%c", stat);
+ fflush(stderr);
+ i++;
+ return;
+}
+
+void feh_edit_inplace(winwidget w, int op)
+{
+ 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 (!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;
+ }
+
+ 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 {
+ /*
+ * 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;
+}
+
+gib_list *feh_wrap_string(char *text, int wrap_width, Imlib_Font fn, gib_style * style)
+{
+ gib_list *ll, *lines = NULL, *list = NULL, *words;
+ gib_list *l = NULL;
+ char delim[2] = { '\n', '\0' };
+ int w, line_width;
+ int tw, th;
+ char *p, *pp;
+ char *line = NULL;
+ char *temp;
+ int space_width = 0, m_width = 0, t_width = 0, new_width = 0;
+
+ lines = gib_string_split(text, delim);
+
+ if (wrap_width) {
+ gib_imlib_get_text_size(fn, "M M", style, &t_width, NULL, IMLIB_TEXT_TO_RIGHT);
+ gib_imlib_get_text_size(fn, "M", style, &m_width, NULL, IMLIB_TEXT_TO_RIGHT);
+ space_width = t_width - (2 * m_width);
+ w = wrap_width;
+ l = lines;
+ while (l) {
+ line_width = 0;
+ p = (char *) l->data;
+ /* quick check to see if whole line fits okay */
+ gib_imlib_get_text_size(fn, p, style, &tw, &th, IMLIB_TEXT_TO_RIGHT);
+ if (tw <= w) {
+ list = gib_list_add_end(list, estrdup(p));
+ } else if (strlen(p) == 0) {
+ list = gib_list_add_end(list, estrdup(""));
+ } else if (!strcmp(p, " ")) {
+ list = gib_list_add_end(list, estrdup(" "));
+ } else {
+ words = gib_string_split(p, " ");
+ if (words) {
+ ll = words;
+ while (ll) {
+ pp = (char *) ll->data;
+ if (strcmp(pp, " ")) {
+ gib_imlib_get_text_size
+ (fn, pp, style, &tw, &th, IMLIB_TEXT_TO_RIGHT);
+ if (line_width == 0)
+ new_width = tw;
+ else
+ new_width = line_width + space_width + tw;
+ if (new_width <= w) {
+ /* add word to line */
+ if (line) {
+ int len;
+
+ len = strlen(line)
+ + strlen(pp)
+ + 2;
+ temp = emalloc(len);
+ snprintf(temp, len, "%s %s", line, pp);
+ free(line);
+ line = temp;
+ } else {
+ line = estrdup(pp);
+ }
+ line_width = new_width;
+ } else if (line_width == 0) {
+ /* can't fit single word in :/
+ increase width limit to width of word
+ and jam the bastard in anyhow */
+ w = tw;
+ line = estrdup(pp);
+ line_width = new_width;
+ } else {
+ /* finish this line, start next and add word there */
+ if (line) {
+ list = gib_list_add_end(list, estrdup(line));
+ free(line);
+ line = NULL;
+ }
+ line = estrdup(pp);
+ line_width = tw;
+ }
+ }
+ ll = ll->next;
+ }
+ if (line) {
+ /* finish last line */
+ list = gib_list_add_end(list, estrdup(line));
+ free(line);
+ line = NULL;
+ }
+ gib_list_free_and_data(words);
+ }
+ }
+ l = l->next;
+ }
+ gib_list_free_and_data(lines);
+ lines = list;
+ }
+ return lines;
+}
+
+void feh_edit_inplace_lossless(winwidget w, int op)
+{
+ char *filename = FEH_FILE(w->file->data)->filename;
+ int len = strlen(filename) + 1;
+ char *file_str = emalloc(len);
+ 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(file_str, len, "%s", filename);
+
+ if ((pid = fork()) < 0) {
+ im_weprintf(w, "lossless %s: fork failed:", op_name);
+ free(file_str);
+ return;
+ }
+ else if (pid == 0) {
+
+ execlp("jpegtran", "jpegtran", "-copy", "all", op_op, op_value,
+ "-outfile", file_str, file_str, NULL);
+
+ weprintf("lossless %s: Is 'jpegtran' installed? Failed to exec:", op_name);
+ _exit(1);
+ }
+ else {
+ waitpid(pid, &status, 0);
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ 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)
+{
+ static Imlib_Font fn = NULL;
+ int tw = 0, th = 0;
+ int th_offset = 0;
+ int max_tw = 0;
+ int line_th = 0;
+ Imlib_Image im = NULL;
+ int i = 0;
+ int num_actions = 0;
+ int cur_action = 0;
+ char index[3];
+ char *line;
+
+ /* Count number of defined actions. This method sucks a bit since it needs
+ * to be changed if the number of actions changes, but at least it doesn't
+ * miss actions 2 to 9 if action1 isn't defined
+ */
+ for (i = 0; i < 10; i++) {
+ if (opt.actions[i])
+ num_actions++;
+ }
+
+ if (num_actions == 0)
+ return;
+
+ if ((!w->file) || (!FEH_FILE(w->file->data))
+ || (!FEH_FILE(w->file->data)->filename))
+ return;
+
+ 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 */
+ max_tw = tw;
+
+ for (i = 0; i < 10; i++) {
+ if (opt.actions[i]) {
+ line = emalloc(strlen(opt.action_titles[i]) + 5);
+ strcpy(line, "0: ");
+ 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)
+ max_tw = tw;
+ }
+ }
+
+ tw = max_tw;
+ tw += 3;
+ th += 3;
+ line_th = th;
+ th = (th * num_actions) + line_th;
+
+ /* This depends on feh_draw_filename internals...
+ * should be fixed some time
+ */
+ if (opt.draw_filename)
+ th_offset = line_th * 2;
+
+ im = imlib_create_image(tw, th);
+ if (!im)
+ eprintf("Couldn't create image. Out of memory?");
+
+ 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.action_titles[i]) {
+ cur_action++;
+ line = emalloc(strlen(opt.action_titles[i]) + 5);
+ sprintf(index, "%d", i);
+ strcpy(line, index);
+ strcat(line, ": ");
+ strcat(line, opt.action_titles[i]);
+
+ gib_imlib_text_draw(im, fn, NULL, 2,
+ (cur_action * line_th) + 2, line,
+ IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255);
+ gib_imlib_text_draw(im, fn, NULL, 1,
+ (cur_action * line_th) + 1, line,
+ IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255);
+ free(line);
+ }
+ }
+
+ gib_imlib_render_image_on_drawable(w->bg_pmap, im, 0, 0 + th_offset, 1, 1, 0);
+
+ gib_imlib_free_image_and_decache(im);
+ return;
}