diff options
-rw-r--r-- | .github/workflows/c.yml | 38 | ||||
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .mailmap | 4 | ||||
-rw-r--r-- | AUTHORS | 51 | ||||
-rw-r--r-- | COPYING | 1 | ||||
-rw-r--r-- | ChangeLog | 1152 | ||||
-rw-r--r-- | Makefile | 91 | ||||
-rw-r--r-- | README | 54 | ||||
-rw-r--r-- | README.md | 158 | ||||
-rw-r--r-- | TODO | 40 | ||||
-rw-r--r-- | cam/ChangeLog | 14 | ||||
-rw-r--r-- | cam/README | 45 | ||||
-rw-r--r-- | cam/cam_bookmarks | 2 | ||||
-rwxr-xr-x | cam/feh-cam | 192 | ||||
-rwxr-xr-x | cam/gen-cam-menu | 41 | ||||
-rw-r--r-- | config.mk | 100 | ||||
-rw-r--r-- | data/images/about.png | bin | 61171 -> 0 bytes | |||
-rw-r--r-- | data/images/logo.svg | 162 | ||||
-rw-r--r-- | data/images/menubg_aluminium.png | bin | 1862 -> 0 bytes | |||
-rw-r--r-- | data/images/menubg_aqua.png | bin | 5325 -> 0 bytes | |||
-rw-r--r-- | data/images/menubg_black.png | bin | 1716 -> 0 bytes | |||
-rw-r--r-- | data/images/menubg_brushed.png | bin | 8649 -> 0 bytes | |||
-rw-r--r-- | data/images/menubg_default.png | bin | 209 -> 0 bytes | |||
-rw-r--r-- | data/images/menubg_sky.png | bin | 1928 -> 0 bytes | |||
-rw-r--r-- | examples/buttons | 17 | ||||
-rwxr-xr-x | examples/find-lowres | 32 | ||||
-rw-r--r-- | examples/keys | 45 | ||||
-rw-r--r-- | examples/themes (renamed from src/fehrc.raw) | 37 | ||||
-rw-r--r-- | man/Makefile | 8 | ||||
-rw-r--r-- | man/feh-cam.pre | 48 | ||||
-rw-r--r-- | man/feh.pre | 2219 | ||||
-rw-r--r-- | man/gen-cam-menu.pre | 24 | ||||
-rwxr-xr-x | scripts/checkkeys.pl | 91 | ||||
-rwxr-xr-x | scripts/checkopts.pl | 2 | ||||
-rwxr-xr-x | scripts/lskeys.pl | 20 | ||||
-rwxr-xr-x | scripts/update-todo.sh | 4 | ||||
-rw-r--r-- | share/applications/Makefile | 24 | ||||
-rw-r--r-- | share/applications/feh.pre | 13 | ||||
-rw-r--r-- | share/fonts/black.style (renamed from data/fonts/black.style) | 1 | ||||
-rw-r--r-- | share/fonts/menu.style (renamed from data/fonts/menu.style) | 1 | ||||
-rw-r--r-- | share/fonts/yudit.ttf (renamed from data/fonts/yudit.ttf) | bin | 57708 -> 57708 bytes | |||
-rw-r--r-- | share/images/feh.png | bin | 0 -> 1028 bytes | |||
-rw-r--r-- | share/images/feh.svg | 107 | ||||
-rw-r--r-- | share/images/menubg_default.png | bin | 0 -> 148 bytes | |||
-rw-r--r-- | src/Makefile | 41 | ||||
-rw-r--r-- | src/collage.c | 210 | ||||
-rw-r--r-- | src/debug.h | 1 | ||||
-rw-r--r-- | src/deps.mk | 41 | ||||
-rw-r--r-- | src/events.c | 552 | ||||
-rw-r--r-- | src/events.h | 2 | ||||
-rw-r--r-- | src/exif.c | 439 | ||||
-rw-r--r-- | src/exif.h | 47 | ||||
-rw-r--r-- | src/exif_canon.c | 61 | ||||
-rw-r--r-- | src/exif_canon.h | 34 | ||||
-rw-r--r-- | src/exif_cfg.h | 71 | ||||
-rw-r--r-- | src/exif_nikon.c | 542 | ||||
-rw-r--r-- | src/exif_nikon.h | 34 | ||||
-rw-r--r-- | src/feh.h | 85 | ||||
-rw-r--r-- | src/feh_png.c | 40 | ||||
-rw-r--r-- | src/feh_png.h | 6 | ||||
-rw-r--r-- | src/filelist.c | 431 | ||||
-rw-r--r-- | src/filelist.h | 43 | ||||
-rw-r--r-- | src/getopt.c | 949 | ||||
-rw-r--r-- | src/getopt.h | 130 | ||||
-rw-r--r-- | src/getopt1.c | 173 | ||||
-rw-r--r-- | src/gib_hash.c | 149 | ||||
-rw-r--r-- | src/gib_hash.h | 75 | ||||
-rw-r--r-- | src/gib_imlib.c | 732 | ||||
-rw-r--r-- | src/gib_imlib.h | 194 | ||||
-rw-r--r-- | src/gib_list.c | 579 | ||||
-rw-r--r-- | src/gib_list.h | 105 | ||||
-rw-r--r-- | src/gib_style.c | 114 | ||||
-rw-r--r-- | src/gib_style.h | 69 | ||||
-rw-r--r-- | src/help.raw | 175 | ||||
-rw-r--r-- | src/imlib.c | 1678 | ||||
-rw-r--r-- | src/index.c | 423 | ||||
-rw-r--r-- | src/index.h | 35 | ||||
-rw-r--r-- | src/keyevents.c | 856 | ||||
-rw-r--r-- | src/list.c | 48 | ||||
-rw-r--r-- | src/main.c | 153 | ||||
-rw-r--r-- | src/menu.c | 578 | ||||
-rw-r--r-- | src/menu.h | 16 | ||||
-rw-r--r-- | src/multiwindow.c | 22 | ||||
-rw-r--r-- | src/options.c | 1068 | ||||
-rw-r--r-- | src/options.h | 270 | ||||
-rw-r--r-- | src/signals.c | 52 | ||||
-rw-r--r-- | src/signals.h | 7 | ||||
-rw-r--r-- | src/slideshow.c | 539 | ||||
-rw-r--r-- | src/structs.h | 3 | ||||
-rw-r--r-- | src/strverscmp.c | 57 | ||||
-rw-r--r-- | src/thumbnail.c | 673 | ||||
-rw-r--r-- | src/thumbnail.h | 18 | ||||
-rw-r--r-- | src/timers.c | 37 | ||||
-rw-r--r-- | src/timers.h | 2 | ||||
-rw-r--r-- | src/utils.c | 85 | ||||
-rw-r--r-- | src/utils.h | 12 | ||||
-rw-r--r-- | src/wallpaper.c (renamed from src/support.c) | 599 | ||||
-rw-r--r-- | src/wallpaper.h (renamed from src/support.h) | 14 | ||||
-rw-r--r-- | src/winwidget.c | 619 | ||||
-rw-r--r-- | src/winwidget.h | 35 | ||||
-rw-r--r-- | test/bg/exact/h/fill | bin | 15089 -> 9290 bytes | |||
-rw-r--r-- | test/bg/exact/w/fill | bin | 18419 -> 11586 bytes | |||
-rw-r--r-- | test/config/keys/feh/keys | 17 | ||||
-rw-r--r-- | test/config/themes/feh/themes | 5 | ||||
-rwxr-xr-x | test/feh-bg-i.t (renamed from test/feh-bg.i) | 40 | ||||
-rwxr-xr-x | test/feh-i.t | 463 | ||||
-rwxr-xr-x | test/feh-scr-i.t | 326 | ||||
-rwxr-xr-x | test/feh-scr.i | 285 | ||||
-rwxr-xr-x | test/feh.i | 343 | ||||
-rw-r--r-- | test/feh.t | 181 | ||||
-rw-r--r-- | test/imlib2-bug-notice | 11 | ||||
-rw-r--r-- | test/list/custom | 8 | ||||
-rw-r--r-- | test/list/default | 10 | ||||
-rw-r--r-- | test/list/filename_recursive | 7 | ||||
-rw-r--r-- | test/list/format_reverse | 10 | ||||
-rw-r--r-- | test/list/size | 10 | ||||
-rw-r--r-- | test/list_imlib2_1.6/custom | 4 | ||||
-rw-r--r-- | test/list_imlib2_1.6/default | 5 | ||||
l--------- | test/list_imlib2_1.6/filename | 1 | ||||
-rw-r--r-- | test/list_imlib2_1.6/filename_recursive | 7 | ||||
l--------- | test/list_imlib2_1.6/format | 1 | ||||
-rw-r--r-- | test/list_imlib2_1.6/format_reverse | 5 | ||||
l--------- | test/list_imlib2_1.6/height | 1 | ||||
l--------- | test/list_imlib2_1.6/name | 1 | ||||
l--------- | test/list_imlib2_1.6/pixels | 1 | ||||
-rw-r--r-- | test/list_imlib2_1.6/size | 5 | ||||
l--------- | test/list_imlib2_1.6/width | 1 | ||||
-rwxr-xr-x | test/mandoc.t | 21 | ||||
-rw-r--r-- | test/no-loadable-files | 2 | ||||
-rw-r--r-- | test/no-loadable-files.help | 2 | ||||
-rw-r--r-- | test/nx_action/loadable_action | 8 | ||||
-rw-r--r-- | test/nx_action/loadable_naction | 8 | ||||
-rw-r--r-- | test/nx_action/unloadable_action | 8 | ||||
-rw-r--r-- | test/nx_action/unloadable_naction | 8 | ||||
-rw-r--r-- | test/ok/jpg_exif | bin | 0 -> 9821 bytes | |||
-rw-r--r-- | test/ok/recursive/png | bin | 0 -> 403 bytes | |||
-rwxr-xr-x | test/run-interactive | 6 | ||||
-rw-r--r-- | test/scr/caption_done | bin | 9669 -> 6830 bytes | |||
-rw-r--r-- | test/scr/caption_new | bin | 19333 -> 13437 bytes | |||
-rw-r--r-- | test/scr/caption_none | bin | 6525 -> 4532 bytes | |||
-rw-r--r-- | test/scr/caption_while | bin | 21458 -> 14923 bytes | |||
-rw-r--r-- | test/scr/draw_action | bin | 8315 -> 5869 bytes | |||
-rw-r--r-- | test/scr/draw_action_tinted | bin | 0 -> 6211 bytes | |||
-rw-r--r-- | test/scr/draw_all_multi | bin | 11416 -> 7658 bytes | |||
-rw-r--r-- | test/scr/draw_all_one | bin | 11113 -> 7422 bytes | |||
-rw-r--r-- | test/scr/draw_filename | bin | 7796 -> 5552 bytes | |||
-rw-r--r-- | test/scr/draw_filename_action | bin | 9549 -> 6462 bytes | |||
-rw-r--r-- | test/scr/draw_filename_action_tinted | bin | 0 -> 6953 bytes | |||
-rw-r--r-- | test/scr/draw_filename_tinted | bin | 0 -> 5777 bytes | |||
-rw-r--r-- | test/scr/draw_info | bin | 0 -> 5874 bytes | |||
-rw-r--r-- | test/scr/draw_info_tinted | bin | 0 -> 6152 bytes | |||
-rw-r--r-- | test/scr/draw_nothing | bin | 6525 -> 4532 bytes | |||
-rw-r--r-- | test/scr/feh_full_lwi | bin | 17898 -> 11374 bytes | |||
-rw-r--r-- | test/scr/feh_ibg_black | bin | 9521 -> 3346 bytes | |||
-rw-r--r-- | test/scr/feh_ibg_white | bin | 8635 -> 3883 bytes | |||
-rw-r--r-- | test/scr/feh_lhi | bin | 13552 -> 10743 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_i | bin | 34338 -> 24321 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_ii | bin | 71938 -> 49181 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_iir | bin | 15536 -> 12745 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_iirr | bin | 15431 -> 12748 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_iirri | bin | 64012 -> 42802 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_iirrio | bin | 72552 -> 49288 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_o | bin | 120797 -> 14900 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_oo | bin | 131624 -> 22116 bytes | |||
-rw-r--r-- | test/scr/feh_lhi_ooo | bin | 141170 -> 97221 bytes | |||
-rw-r--r-- | test/scr/feh_lwi | bin | 19070 -> 15333 bytes | |||
-rw-r--r-- | test/scr/feh_lwi_scroll_r | bin | 19072 -> 15268 bytes | |||
-rw-r--r-- | test/scr/feh_lwi_scroll_rd | bin | 19081 -> 15287 bytes | |||
-rw-r--r-- | test/scr/feh_lwi_scroll_rdr | bin | 19075 -> 15383 bytes | |||
-rw-r--r-- | test/scr/feh_lwi_scroll_rdru | bin | 19066 -> 15330 bytes | |||
-rw-r--r-- | test/scr/feh_lwi_scroll_rdrul | bin | 19072 -> 15268 bytes | |||
-rw-r--r-- | test/scr/feh_scaledown_lwi | bin | 19203 -> 12407 bytes | |||
-rw-r--r-- | test/scr/geometry_offset_only | bin | 0 -> 628 bytes | |||
-rw-r--r-- | test/scr/index_full_h400 | bin | 0 -> 3357 bytes | |||
-rw-r--r-- | test/scr/index_full_w400 | bin | 0 -> 2252 bytes | |||
-rw-r--r-- | test/scr/index_h400 | bin | 0 -> 1794 bytes | |||
-rw-r--r-- | test/scr/index_w400 | bin | 0 -> 1199 bytes | |||
-rw-r--r-- | test/scr/thumbnail_default | bin | 1712 -> 1199 bytes | |||
-rw-r--r-- | test/status | 401 | ||||
-rw-r--r-- | test/tiny.pbm | 4 |
180 files changed, 14454 insertions, 6627 deletions
diff --git a/.github/workflows/c.yml b/.github/workflows/c.yml new file mode 100644 index 0000000..5477164 --- /dev/null +++ b/.github/workflows/c.yml @@ -0,0 +1,38 @@ +name: linux + +on: + push: + branches: + - '*' + pull_request: + branches: + - '*' + +jobs: + c: + + runs-on: ubuntu-latest + + strategy: + matrix: + curl: [0, 1] + exif: [0, 1] + xinerama: [0, 1] + + steps: + - uses: actions/checkout@v2 + - name: APT + run: sudo apt-get -y update + - name: Install Dependencies + run: sudo apt-get -y install build-essential libx11-dev libxt-dev libimlib2-dev libtest-command-perl libtest-simple-perl + - name: Install libcurl + if: matrix.curl + run: sudo apt-get -y install libcurl4-openssl-dev + - name: Install libexif + if: matrix.exif + run: sudo apt-get -y install libexif-dev + - name: Install Xinerama + if: matrix.xinerama + run: sudo apt-get -y install libxinerama-dev + - name: Build and Test + run: for inotify in 0 1; do for verscmp in 0 1; do make curl=${{ matrix.curl }} exif=${{ matrix.exif }} inotify=$inotify verscmp=$verscmp xinerama=${{ matrix.xinerama }} && make test && make clean; done; done @@ -1,4 +1,8 @@ +/share/applications/*.desktop +/src/deps.mk /src/*.o /src/*.inc /src/feh /man/*.1 +*~ +core diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..888b47c --- /dev/null +++ b/.mailmap @@ -0,0 +1,4 @@ +Birte Kristina Friesel <derf@finalrewind.org> +Birte Kristina Friesel <derf@chaosdorf.de> +Birte Kristina Friesel <ghub@derf.homelinux.org> +Birte Kristina Friesel <derf@derf.homelinux.org> @@ -1,61 +1,70 @@ Principle author. -Tom Gilbert (gilbrit, giblet, grubby) <gilbertt@linuxbrit.co.uk> +Tom Gilbert (gilbrit, giblet, grubby) <gilbertt (at) linuxbrit.co.uk> -Paul Duncan (pabs) <pabs@pablotron.org> +Paul Duncan (pabs) - pabs (at) pablotron (dot) org - cam, wget fixups, mouse-button swapping options, rotation, xinerama support, menu backgrounds, and more. -Richard Lowe (richlowe) <richlowe@btinternet.com> +Richard Lowe (richlowe) - richlowe (at) btinternet (dot) com - background setting code, thumbnail patches. -Benjamin Elijah Griffin <bgriffin@cddb.com> +Benjamin Elijah Griffin bgriffin (at) cddb (dot) com - made builtin http work. -Mrinal Kalakrishnan <mrinal@india.com> +Mrinal Kalakrishnan - mrinal (at) india (dot) com - auto-zoom mode. -Phil Morris (marmot) <marmot@vennercs.com> +Phil Morris (marmot) - marmot (at) vennercs (dot) com - about.png. -Carlos Puchol <cpg@puchol.com> +Carlos Puchol - cpg (at) puchol (dot) com - configure.in fix for nonstandard imlib2 locations. -Eric Dorland <dorland@lords.com> +Eric Dorland - dorland (at) lords (dot) com - the --title option. -Omar Harriott <omar.harriott@csun.edu> +Omar Harriott - omar.harriott (at) csun (dot) edu - fix filelist handling for filenames with spaces. -Panagiotis Issaris <takis.issaris@skynet.be> +Panagiotis Issaris - takis.issaris (at) skynet (dot) be - keypad buttonpresses -Jon Bernard <jbernard@roanoke.edu> +Jon Bernard - jbernard (at) roanoke (dot) edu - xinerama bugfix -Maciej Kalisiak <mac@dgp.toronto.edu> +Maciej Kalisiak - mac (at) dgp (dot) toronto (dot) edu - large image/zoom bugfix -Claes Nasten <pekdon@pekdon.net> +Claes Nasten - pekdon (at) pekdon (dot) net - Thumbnail caching, fixes -JC <jcm314@yahoo.com> +JC - jcm314 (at) yahoo (dot) com - multiple --action* options -Ulrich Spoerlein <q@uni.de> +Ulrich Spoerlein - q (at) uni (dot) de - fullscreen image count display -Daniel Friesel <derf@finalrewind.org> +Yu-Jie Lin + - Various patches + +Dennis Real + - patch for builtin exif support (exif=1) + +Birte Kristina Friesel - derf (at) finalrewind (dot) org - Maintenance since 2010 Thanks to: -Michael Jennings <mej@eterm.org> +Michael Jennings - mej (at) eterm (dot) org - Makefile Fixes, moral support -Alistair Sutton <metallica@freenet.co.uk> + +Alistair Sutton - metallica (at) freenet (dot) co (dot) uk - rpm spec file for redhat. -Laurence J. Lane <ljlane@debian.org> + +Laurence J. Lane - ljlane (at) debian (dot) org - debian assimilation. -The Rasterman <raster@rasterman.com> + +The Rasterman - raster (at) rasterman (dot) com - Imlib2, initial zooming code example. Enlightenment. + Stephen Levine - Great help debugging under E17 - @@ -1,4 +1,5 @@ Copyright (C) 1999,2000 Tom Gilbert. +Copyright (C) 2010-2025 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 @@ -1,11 +1,1106 @@ -git HEAD +Fri, 29 Aug 2025 19:14:26 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.11.1 + * Correctly center images in fullscreen mode. This fixes a regression + introduced in v3.11. + +Wed, 27 Aug 2025 21:39:43 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.11 + * New option: --tap-zones enables support for simple prev/next tap zones + (patch by Finn Teegen, thanks!) + * Fix --scale-down not scaling down the first image in floating i3 windows + and similar setups (patch by Awal Garg, thanks!) + +Sun, 30 Jun 2024 11:47:16 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.10.3 + * Fix feh not respecting aspect ratio of thumbnails that are smaller than + --thumb-width and --thumb-height + * Fix --no-recursive behaving like --recursive (Patch by GitHub user + wwsmiff, thanks!) + * Fix rotation by 180° corrupting images (Patch by GitHub user wwsmiff, + thanks!) + * Speed up --sort=size and --sort=mtime by caching stat(2) calls + (Patch by Naïm Favier, thanks!) + +Mon, 04 Dec 2023 21:25:49 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.10.2 + * Fix crash in right-click / menu rendering code on some distributions + (patch by Ametov Imil) + +Mon, 02 Oct 2023 04:27:56 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.10.1 + * Do not call signal-unsafe functions within signal handlers + +Thu, 06 Apr 2023 16:19:16 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.10 + * Add --sort=none option to un-set sort modes specified earlier + * Improve error messages for Imlib2 ≥ 1.8 + * Fix build with clang 16. (Patch by orbea) + * Fix tests when building with magic=1 + +Mon, 22 Aug 2022 17:49:11 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.9.1 + * Set libcurl user agent to "feh/3.9.1". Previously, feh did not send a + user agent. + +Sun, 12 Jun 2022 13:12:00 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.9 + * Fix compilation on macOS by defining _DARWIN_C_SOURCE for + mkdtemp and mkstemps. (Patch by Ryan Schmidt) + * Remove magic byte check before passing an image to Imlib2. + This check was introduced in feh 3.6 to work around an issue with + Imlib2 being slow to determine image loadability in some cases + <https://phab.enlightenment.org/T8739>. By now, an Imlib2 version without + this issue should be available in all recent distributions, so the check + in feh is no longer required. For a slight speed-up, or to use feh with + an affected Imlib2 version (1.6.x or 1.7.0), compile feh with 'magic=1'. + In this case, feh will use libmagic to determine whether a file is an + image, and only pass images to Imlib2. (Patch by Christian Hesse) + * Fix crash (segmentation fault due to null pointer dereference) when + toggling fullscreen mode while moving the cursor. + * Note that feh no longer supplies its own getopt_long function. + getopt_long is provided by a wide range of libc implementations, so + there is no need for feh to duplicate it. (Patch by Guilherme Janczak) + +Mon, 03 Jan 2022 11:29:03 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.8 + * Add button ID "0" to cursor bindings. This pseudo-button is triggered + whenever feh observes a cursor movement. It does not have a default + binding. + * Support hexadecimal IDs in --window-id + * Disable --auto-rotate in feh builds compiled with Imlib2 1.7.5 or later. + Imlib2 1.7.5 introduces transparent EXIF-based image orientation + adjustment, so --auto-rotate is no longer needed (and would cause + mis-orientation of images, as Imlib2 has already adjusted the + orientation). + +Sat, 25 Sep 2021 09:21:25 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.7.2 + * Fix crash when running feh without stdin file descriptor + +Sat, 24 Jul 2021 22:30:12 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.7.1 + * Support JPEG XL files when using imlib2-jxl (patch by Alistair) + * Fix support for images smaller than 16 bytes (patch by David Buchanan) + * Fix some out of bounds reads (patches by Tobias Stoeckmann) + +Sun, 09 May 2021 11:31:55 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.7 + * Use compact representation instead of key-value pairs for common EXIF data + (only applies when compiling feh with exif=1) + * Add --window-id <windowid> option (draw to an existing window) + * Add --zoom-step <percent> option (specify zoom step size) + * Pass gopher:// and gophers:// URLs to libcurl + * Fix --reload / --auto-reload reloading the wrong directory when + using --start-at and no file arguments or filelists have been + specified + * Fix Ctrl+key causing unintended behaviour when controlling feh via stdin + * Fix high CPU usage when closing stdin after starting feh from a terminal + +Mon, 25 Jan 2021 17:46:57 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.6.3 + * Fix --start-at not handling URL-encoded file:/// paths properly. Notably, + this also fixes feh not displaying images with spaces or unicode + elements in their path when opened from a file manager. + +Sat, 09 Jan 2021 12:28:06 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.6.2 + * Fix save_filelist not respecting --output-dir + * Fix file descriptor leak when attempting to load truncated image files. + The issue was introduced in v3.6. + +Sun, 06 Dec 2020 08:34:15 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.6.1 + * Fix excessive memory consumption when showing long-running slideshows + with thousands to tens of thousands of images and feh has been compiled + with exif=1 (see https://github.com/derf/feh/issues/553) + * Fix memory leak when showing long-running slideshows with relatively few + images and feh has been compiled with exif=1 (ibid.) + * Fix memory leak when reloading an image and feh has been compiled with + exif=1 + * Fix memory leak in --draw-exif + * Fix memory leak when reloading HTTP files with --no-conversion-cache + +Mon, 30 Nov 2020 19:44:47 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.6 + * Add flip and rotate options to the menu + * Improve unloadable image detection time (e.g. for large video files) by + checking a file's header before passing it to Imlib2. For rarely used + image formats, there is a very small chance that an image which could be + loaded by feh 3.5 is reported as unloadable by feh 3.6 due to this + change. Set FEH_SKIP_MAGIC=1 to bypass the header check in this case. See + <https://phab.enlightenment.org/T8739> and + <https://github.com/derf/feh/issues/505> for details. + + +Sat, 29 Aug 2020 08:49:08 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.5 + * Enable --version-sort on systems without strverscmp support. This + works by bundling the strverscmp of musl libc with feh and using it + when feh is compiled without the verscmp flag (i.e., when the system + libc does not provide the verscmp function). Patch by Tim van der Molen + * Add %a format specifier (slideshow state: "playing" / "paused") + * Fix crashes when combining --reload and --multiwindow + +Fri, 29 May 2020 23:52:35 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.4.1 + * Fix lossless rotate not being lossless when using feh with Imlib2 v1.6 + or later + +Sat, 11 Apr 2020 09:51:01 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.4 + * Images loaded via HTTPS/curl, ImageMagick, and dcraw are now cached + by default to decrease image load time on subsequent slideshow passes. + Caching is disabled when using `--reload` and can also be disabled with + the new `--no-conversion-cache` option. Suggestion and initial patch by + Awal Garg. + * Handle SIGINT/SIGTERM/SIGQUIT signals while loading images using libcurl + < v7.32. Patch by <https://github.com/c99pedant>. + * "feh --start-at URL" now loads a single-image slideshow displaying URL. + This allows feh.desktop to handle URLs as well as ordinary files. + file:/// URLs are treated as local files, so "feh --start-at file:///..." + without filelist arguments behaves just like "feh --start-at ..." + (i.e., feh will load the entire directory and start the slideshow at ...) + * Fix a memory leak when repeatedly cycling through slideshows containing + images loaded via libcurl, ImageMagick or dcraw. + +Tue, 03 Dec 2019 17:27:46 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.3 + * New option --class allows setting the X11 class hint per feh instance + (patch by Olof-Joachim Frahm) + * Improve handling of NULL returns from Imlib2 calls (patch by Ben Boeckel) + * Fix compilation with libcurl < v7.32 + +Mon, 22 Jul 2019 20:17:03 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.2.1 + * Use --no-fehbg option in ~/.fehbg. This fixes cases where an X11 + setup change may inadvertently alter the commandline stored in .fehbg. + * Fix insufficient error handling when updating ~/.fehbg. Previously, + a stat() error may have caused .fehbg to be update with excessive file + permissions (patch by Tobias Stoeckmann) + * Fix TOCTTOU when setting the file mode of ~/.fehbg + (patch by Tobias Stoeckmann) + +Wed, 10 Jul 2019 17:40:29 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.2 + * Build feh with inotify=1 to automatically reload changed files in + slideshow mode. Introduces the inotify build flag and the --auto-reload + option. + * Reload current image(s) when receiving SIGUSR1 or SIGUSR2 in single-image + slideshows or in multiwindow mode + +Sun, 17 Feb 2019 08:41:53 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Releasev v3.1.3 + * Fix missing filename in ~/.fehbg when using --no-xinerama on a feh + binary compiled with xinerama=1. This issue was introduced in v3.1.2. + +Mon, 11 Feb 2019 17:24:13 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.1.2 + * Fix missing filenames in ~/.fehbg when using --bg-* on directories and/or + with --randomize + * Fix repeated --slideshow-delay/-D option not properly overriding the + 'start paused' flag + * Fix repeated --info option not properly overriding the 'draw info' flag + +Fri, 07 Dec 2018 22:51:15 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.1.1 + * Decrease libcurl timeout from indefinite to 30 minutes. This should + be sufficient even for use cases with unusually high timeouts (just in + case anyone wants to do interplanetary slideshows), while at the same + time avoiding stalls in unattended slideshows when encountering + temporary network issues. + * Handle SIGINT/SIGTERM/SIGQUIT signals while loading images using libcurl. + Previously, signals were ignored during a (possibly slow) libcurl + network transfer, which was not intended behaviour. + +Wed, 21 Nov 2018 19:37:34 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.1 + * Running "feh --start-at .../file.jpg" without specifying images, + directories or filelists to load is now equivalent to running + "feh --start-at .../file.jpg $(dirname .../file.jpg)". This way, it is + possible to view a specific file and browse all other files in the + corresponding directory. This is especially useful when starting feh + from file managers. + * Introduce fuzzy matching in --start-at: If the specified path is not + found in the file list, feh now resorts to comparing basenames (i.e., + file names without the directory components). This allows calls + like "feh --start-at cat.jpg ~/Pictures", which led to a file not found + error in previous versions. + * Respect -j / --output-dir when using save_image or save_filelist actions. + +Fri, 09 Nov 2018 17:17:15 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v3.0 + * Remove deprecated webcam scripts (feh-cam and gen-cam-menu) + * Remove deprecated collage mode (-c/--collage) + * Remove deprecated option --cycle-once (use --on-last-slide=quit instead) + * Remove deprecated option --menu-bg + * Change default save_filelist key from "f" to "L" (mnemonic: fileList) + * Change default toggle_fullscreen key from "v" to "f" as this is also + used by mplayer, mpv and similar + * flip and rotation (keys "<", ">", "|", and "_") no longer change the + underlying file. This leaves delete ("Ctrl+Delete") as the only + destructive action which is enabled by default + * Add option --edit, which makes flip and rotation change the underlying + file as well as the displayed image. This was the default behaviour in + feh 1.x and 2.x + +Sat, 27 Oct 2018 19:46:48 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.28.1 + * Do not ignore quit signals (SIGTERM, SIGINT, SIGQUIT) during preload + * Add missing EXIF orientations 2, 4, 5, and 7 (when built with exif=1, + patch by Olof-Joachim Frahm) + * Improve randomness on non-glibc systems + +Mon, 17 Sep 2018 21:17:04 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.28 + * Decrease loading time for RAW images by utilizing dcraw to display the + embedded JPEG preview (patch by <https://github.com/ulteq>) + * Rename --magick-timeout to --conversion-timeout. --magick-timeout is + now deprecated and will be removed in a future release + * Fix unintened aliasing in rotated images whose rotation is not a + multiple of 90 degrees (patch by <https://github.com/ulteq>) + * New option: --on-last-slide=hold|quit|resume. + hold will cause feh to stop advancing beyond the last slide (patch by + <https://github.com/ulteq>), quit replaces --cycle-once (which is now + deprecated), and resume is the default (continue at the first image). + +Tue, 17 Jul 2018 17:33:10 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.27.1 + * Fix feh occasionally becoming unresponsive when asked to terminate + via SIGINT/SIGQUIT/SIGTERM (based on a patch by + <https://github.com/giladogit>) + * Fix --keep-zoom-vp issues introduced in 2.27 + (patch by <https://github.com/ulteq>) + +Thu, 28 Jun 2018 17:26:54 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.27 + * Fix size_to_image ("w") command when both --scale-down and --keep-zoom-vp + are enabled + * Fix --auto-zoom not being triggered on window resize events when + --scale-down is enabled + * Fix --auto-zoom conflicting with manual zoom + * Fix feh_draw_checks not taking the zoom level into account properly + * Prevent --zoom <percent> from blocking --scale-down in fullscreen / fixed + geometry mode + * Prevent --keep-zoom-vp from blocking the dynamic window resizing + mechanism + * Prevent automatic recalculation of the zoom ratio when --keep_zoom_vp + is enabled + * All patches provided by <https://github.com/ulteq>. Thanks a lot! + +Tue, 26 Jun 2018 10:33:04 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.26.4 + * Correctly save --bg-max in ~/.fehbg (patch by Sebastian Bickerle) + +Fri, 18 May 2018 22:58:02 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.26.3 + * Properly escape --image-bg argument in ~/.fehbg (broken in 2.26.1) + +Sat, 12 May 2018 16:33:56 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.26.2 + * Show correct filelist position in windows opened from thumbnail mode. + Note that navigation is still not supported in those windows + * Improve support for key input from stdin + * Do not push menus off the screen when hitting screen limits + +Fri, 11 May 2018 15:11:17 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.26.1 + * Restore pre-2.21 ~/.fehbg behaviour. This fixes nondeterministic + wallpaper setting when using --bg-* --randomize, issues when specifying + --theme both in ~/.fehbg and on the commandline, and possibly other + edge cases + * Fix /tmp being cluttered with temporary ImageMagick files when using + --magick-timeout and a conversion takes longer than allowed + +Thu, 19 Apr 2018 21:43:12 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.26 + * Save absolute file paths in ~/.fehbg, similar to the behaviour prior to + feh 2.21 + * Add %g (window dimensions) and %Z (precise zoom level) format specifiers + * Improve -z/--randomize randomness + +Wed, 07 Mar 2018 17:49:52 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.25.1 + * Fix compilation issues when using CFLAGS=-m64 on some gcc versions + * Re-render current image when toggle_fixed_geometry is input + +Sun, 04 Mar 2018 08:53:50 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.25 + * Add --version-sort option to enable natural sorting of file and directory + names. This requires a libc with strverscmp support, which is a + non-POSIX GNU extension. Use the new build flag `verscmp=0` to disable + this feature on systems which do not ship strverscmp + (patch by ulteq) + * Allow arbitrary X11 colors as -B/--image-bg argument (patch by ulteq) + * Improve --image-bg support and transparency handling in --bg-* mode + * Respect --geometry settings in --bg-fill mode + * Add keybinding toggle_auto_zoom (default "Z") to toggle --auto-zoom + * Fix filelists specified by -f/--filelist not being reloaded when using + --reload + +Mon, 26 Feb 2018 21:41:38 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.24 + * Improve performance when using --{max,min}-dimension in slideshow mode + (patch by ulteq) + * Fix crash when using %m format specifier in slideshow mode + (introduced in feh 2.23.1) + + +Mon, 12 Feb 2018 22:11:55 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.23.2 + * Fix support for nested quotes in .confeg/feh/themes + +Wed, 31 Jan 2018 17:38:25 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.23.1 + * The Makefile no longer honors CPPFLAGS and instead consistently uses + CFLAGS for user-provided include paths + * Fix %u format specifier in multiwindow and list modes (patch by ulteq) + * Minor performance improvements (patches by ulteq) + * Stability improvements when using --magick-timeout (patch by ulteq) + +Thu, 28 Dec 2017 19:26:29 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.23 + * Fix broken thumbnail/index windows when using --scale-down + * Use Imlib2 in-memory image cache (default cache size: 4MiB). This allows + for significant performance improvements especially in small slideshows + * Add --cache-size option to set Imlib2 image cache size + +Tue, 07 Nov 2017 17:36:26 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.22.2 + * Fix HTTPS certificate errors on some systems (broken in 2.22) +Tue, 07 Nov 2017 07:51:48 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.22.1 + * Allow ~/.fehbg to be sourced (instead of executed) from other shell + scripts again (broken in 2.22) + +Sat, 04 Nov 2017 14:55:38 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.22 + * Add support for CURL_CA_BUNDLE environment variable when loading images + via HTTPS + * Fix ~/.fehbg not being updated when setting a wallpaper via menu + (broken in 2.21) + +Sat, 07 Oct 2017 12:14:17 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.21 + * Add toggle_fixed_geometry ('g') key binding to toggle window auto-resize + * Improve control via terminal input + * Fix crash (segmentation fault) when using feh -O in non-index mode + * Fix --force-aliasing (and possibly other options) missing from ~/.fehbg + when using them for background setting + +Thu, 07 Sep 2017 20:20:11 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.20 + * Fix clang/gcc warnings (Patches by orbea) + * Add support for control via terminal input. feh will read key presses + from the controlling terminal and handle them like X11 key presses + inside the feh window. Note that at the moment, only lower / upper case + ASCII letters and a very small set of additional keys are supported. + * Fix broken ImageMagick support (see --magick-timeout) when using some + ImageMagick versions <https://github.com/derf/feh/issues/323> + * Remove images from the filelist if they were removed by executing a + user-defined action <https://github.com/derf/feh/issues/322> + +Mon, 21 Aug 2017 19:04:00 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.19.3 + * Save geometry data in .fehbg when setting a wallpaper with --geometry + * Fix Imlib2 developer warning and improve out-of-memory error message + when using --thumbnails / --index on large directories + +Sat, 12 Aug 2017 05:05:24 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.19.2 + * Show ImageMagick loader errors unless --quiet is specified + * Fix crash when handling certain media keys (introduced in 2.19.1) + +Tue, 25 Jul 2017 18:40:33 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.19.1 + * Fix Shift modifier not being recognized for tab, space and similar keys. + This lead to keybindings like Shift+Tab or Shift+Space being parsed as if + the Shift modifier had not been specified + +Tue, 06 Jun 2017 20:40:00 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.19 + * Follow the freedesktop.org Thumbnail Managing Standard by saving + thumbnails in ${XDG_CACHE_HOME}/thumbnails (defaulting to + ~/.cache/thumbnails) instead of ~/.thumbnails + (patch by Olof-Joachim Frahm) + * Install app icons with the correct permissions of 644 + * Documentation improvements + +Tue, 04 Apr 2017 21:22:16 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.18.3 + * Fix double-free/OOB-write in E17 IPC. This only affects the + background setting options and requires a malicious X11 app to run + alongside feh and pretend to be an E17 window manager. + (patch by Tobias Stoeckmann) + * Fix image-specific format specifiers not being updated correctly in + thumbnail mode window titles + * Fix memory leak when closing images opened from thumbnail mode + * Fix a possible out of bounds read caused by an unterminated string when + using --output to save images in long paths. (patch by Tobias Stoeckmann) + * Fix out of bounds read/write when handling empty or broken caption files. + (patch by Tobias Stoeckmann) + * Fix memory leak when saving a filelist or image whose target filename + already exists. (patch by Tobias Stoeckmann) + +Thu, 16 Feb 2017 23:05:39 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.18.2 + * Fix crash when using both --thumbnails and --title. This bug was + introduced in v2.18.1. + +Sun, 22 Jan 2017 19:11:32 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.18.1 + * Fix image-specific format specifiers not being updated correctly + (e.g. %z not displaying the correct zoom value after zooming in / out) + +Tue, 01 Nov 2016 10:55:04 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.18 + * Move README to README.md + * New key binding: ! / zoom_fill (zoom to fill window, may cut off image + parts) + * Only for builds with exif=1: Disable EXIF-based auto rotation by + default, add --auto-rotate option to enable it (Patch by Elliot Wolk) + +Wed, 31 Aug 2016 20:27:20 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.17.1 + * Fix compilation on systems where HOST_NAME_MAX is not defined, such as + FreeBSD (patch by Niclas Zeising) + +Sun, 28 Aug 2016 21:26:54 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.17 + * Install feh icon (both 48x48 and scalable SVG) to /usr/share/icons + when running "make install app=1" + * Fix --sort not being respected after the first reload when used in + conjunction with --reload + * All key actions can now also be bound to a button by specifying them + in .config/feh/buttons. However, note that button actions can not be + bound to keys. + * Rename "menu" key action to "toggle_menu", "prev" to "prev_img" and + "next" to "next_img". The old names are still supported, but no longer + documented. + * feh now also sets the X11 _NET_WM_PID and WM_CLIENT_MACHINE window + properties + +Sun, 31 Jul 2016 16:59:07 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.16.2 + * Also support in-place editing for images loaded via libcurl or + imagemagick. Results will not be written back to disk in this case. + +Fri, 24 Jun 2016 00:31:56 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.16.1 + * Fix crash when trying to rotate a JPEG image without having + jpegtran / jpegexiforient installed + * Handle failing fork() calls gracefully + +Thu, 09 Jun 2016 08:59:35 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.16 + * Fix invalid key/button definitions mis-assigning keys/buttons to other + actions + * Add sort mode --sort dirname to sort images by directory instead + of by name. For example, where a normal recursive run will show images in + the order foo/a.jpg -> foo/bar/baz.jpg -> foo/fnord.jpg, a dirname sort + will result in foo/a.jpg -> foo/fnord.jpg -> foo/bar/baz.jpg (Patch by + Sung Pae) + * Add navigation keys next_dir (]) and prev_dir ([) to jump to the first + image of the nex/previous directory (Patch by Sung Pae) + +Fri, 27 May 2016 13:15:49 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.15.4 + * Fix toggle_filenames key displaying wrong file numbers in multiwindow + mode + +Thu, 28 Apr 2016 11:41:04 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.15.3 + * Rescale image when resizing a window and --scale-down or --geometry is + active. This largely fixes the --scale-down issues introduced in + 2.15. However, note that --scale-down still introduces a fixed window + size which will not be updated when changing images (as was the case in + feh < 2.15). This may or may not be fixed in the future. + +Sat, 16 Apr 2016 18:32:38 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.15.2 + * Fix --keep-zoom-vp not keeping the viewport x/y offsets (broken by 2.15) + +Fri, 15 Apr 2016 10:18:37 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.15.1 + * Fix w (size_to_image) key not updating window size when --scale-down + or --geometry is active + +Sat, 09 Apr 2016 20:42:23 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.15 + * Patch by William Woodruff: Add --insecure option to disable HTTPS + certificate checks + * Patch by guraga: Add --no-recursive option to disable recursive directory + expansion. Note that --no-recursive is the default behaviour of feh. + This option is mostly useful to override a --recursive set in a theme or + shell alias + * Patch by Richard Molitor: Improve --scale-down in tiling environments. + This fixes flickering when changing images at the cost of slightly + less apaptive scale-down behaviour: Window size changes are now only + processed when the active image is changed + * --action and --action[1..9] now support action titles + (e.g. --action '[some title]some-command %F'), which are displayed + instead of the specified shell command. Note that the title must not + start with a space. Titles starting with a space are treated as part of + of the command so that actions like '[ -L %F ] && foo' still work + +Thu, 18 Feb 2016 20:40:19 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.14.2 + * make test: Ignore results on arm and mips since they expose a bug in + Imlib2 1.4.7 and/or giflib 5.1.2. Note that due to this bug, feh may be + unable to display gif images. x86 and amd64 are also affected. + Again, see <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=813729> + for more information + * -f / --filelist: Do not print useless error message when a correct + filelist file is specified + * -f / --filelist: Fix bug in "-" / "/dev/stdin" handling affecting feh + running in ksh and possibly other environments + +Thu, 04 Feb 2016 20:31:38 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.14.1 + * Skip a small set of build tests on Debian and derivatives, since they + trigger a Debian/Imlib2 bug. See + <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=812657> and + <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=813729> for more + information + +Sun, 04 Oct 2015 10:01:20 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.14 + * Add --xinerama-index option for background setting + (patch by James Knight) + * When removing the last image in slidsehow mode, stay on the last + (previously second-to-last) image (patch by Lior Shiponi) + * Allow --sort and --randomize to override each other (most recently + specified option wins) instead of always preferring --sort + * Thumbnail mode: Mark image as processed when executing an action + (--action) by clicking on an image + * It is now possible to override feh's idea of the active xinerama screen + using the --xinerama-index option + * Remove (undocumented) feature allowing to override feh's idea of the + active xinerama screen by setting the XINERAMA_SCREEN environment + variable + +Sun, 24 May 2015 11:45:18 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.13.1 + * Fix --scale-down breaking image centering in fullscreen mode + +Sun, 17 May 2015 20:40:36 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.13 + * print --verbose output to stderr + * Show progress when using --verbose and --loadable / --unloadable + * ~/.fehbg is now a shell script and can be executed directly + (sourcing it still works) + * --max-dimension: ignore width/height limit of 0 pixels + * Do not re-render images when toggling keep_zoom_vp + * feh/themes: Support quoting with '' too (previously, only "" worked) + * Fix potential out of bounds array access in EXIF code + (when built with exif=1) + +Wed, 08 Apr 2015 11:18:41 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.12.1 + * Handle missing HOME in environment + * Fix memory leak when a slideshow contains many unloadable images + * Fix memory leak when --prelaod removes files from the filelist + +Thu, 15 May 2014 23:41:07 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.12 + * feh-cam and gen-cam-menu are no longer installed by default. Use + 'make install cam=1' to install them or 'make uninstall cam=1 && make + install cam=0' to remove them permanently + * feh no longer depends on giblib. Instead, the relevant parts of the + giblib source were imported into the feh source. + Rationale: giblib is unmaintained and, as far as I know, only used by + three projects (one of which is feh). There is at least one known bug + in it, and as I do not have the time to take over giblib development, + importing the library seems to be the best solution. + * Fix/improve --randomize for short filelists (closes #151) + * Fix a buffer overflow in the printf implementation when handling unknown + format specifiers (affects --action, --customlist, --index-info, --info, + --thumb-title and --title) + * Update help (when built with help=1) + +Sun, 27 Apr 2014 20:28:02 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.11 + * Patch by Michael Vorburger: Fix erroneous free() in case of failed + scandir (closes #140, #147) + * Patch by rangerer: --randomize: re-randomize after list is through + (closes #154) + * When setting a wallpaper from a URL, do not try to store it as + absolute path in .fehbg (closes #153) + * Add --scroll-step <px> option to change scroll_{up,left,down,right} + scroll offset in pixels + * feh(1): Escape %V (interpreted as mdoc macro) + (closes debian #745467) + * Respect --image-bg=checks in fullscreen mode (default remains black) + (closes #156) + +Fri, 28 Feb 2014 18:20:25 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.10 + * Allow non-centered wallpapers using the --geometry option + (Patch by Joel Bradshaw) + * Add ; flag to --info (as in "--info ';echo foo'") to disable info + display on startup + * Partially fix off-by-one pixel error when warping the pointer in the + bottom/right window border + * thumbnail mode: If --action is set, run specified command instead of + opening image on click. + * feh.desktop: Use feh %F since we support multiple files + * Fix --borderless not working on some 64bit systems + (Patch by Brian Mattern) + * Always use absolute paths in .fehbg + +Tue, 11 Jun 2013 08:27:24 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.9.3 + * Patch by David Gowers: Add %L format code (temporary copy of filelist) + * Fix buffer overflows in printf implementation (affects --action, + --customlist, --index-info, --info, --thumb-title, --title) + * Fix tests failing when compiled with help=1 + +Fri, 03 May 2013 21:16:59 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.9.2 + * Fix -F --zoom 100 not working in Gnome+Unity when compiling feh with + gcc and enabled optimizations (not -O0) + * EXIF updates by Dennis Real + +Thu, 14 Feb 2013 12:52:02 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.9.1 + * Set correct window dimensions on any Xinerama screen, not just the + first one (active screen is determined by current pointer location) + +Wed, 13 Feb 2013 01:46:56 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.9 + * Add --keep-zoom-vp option to keep zoom and offsets when switching + images (patch by sdaau). Press 'k' to toggle it. + * Add --sort mtime option (patch by guns) + * Add a desktop file (installed to share/applications/feh.desktop) + * Use "feh -" to read image from stdin + * Fix Imlib2 and X11 warnings when opening a URL that returned an HTTP + error + * Add button bindings to zoom in / out (patch by sdaau) + +Mon, 24 Dec 2012 15:45:54 +0100 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.8 + * Do not apply --scale-down to the thumbnail window. It will be applied + to windows opened from this, though. + <https://github.com/derf/feh/issues/106> + * Patch by Rob Cornish: Respect --image-bg when setting a wallpaper + (bg-center and bg-max) + <https://github.com/derf/feh/pull/105> + * Add %V (feh process ID) format specifier + <https://github.com/derf/feh/issues/109> + * Fix delete not working on last image with --cycle-once + <https://github.com/derf/feh/issues/107> + * Treat quick, low-offset drags (1px or 2px move in <1 second) as clicks + to improve graphics tablet support + <https://github.com/derf/feh/issues/113> + * Respect --start-at in thumbnail mode + <https://github.com/derf/feh/issues/116> + * Make 'z' (jump_random) work in thumbnail mode as well, fix thumbnail + selection roll-over <https://github.com/derf/feh/issues/115> + +Tue, 16 Oct 2012 06:29:58 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.7 + * Add --min-dim and --max-dim options to only process images with certain + dimensions + +Thu, 27 Sep 2012 16:48:48 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.6.3 + * Fix segfault when doing lossless mirror/rotate and jpegexiforient is + not present <https://github.com/derf/feh/issues/100> + +Wed, 26 Sep 2012 17:06:50 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.6.2 + * Set EXIF orientation tag to 1 ("0,0 is top left" aka normal) after + running jpegtran. Previously, when doing a lossless rotate, the image + was rotated but the corresponding EXIF tag not updated, resulting in + wrong image display in programs aware of this EXIF tag. + * Fix spelling in feh(1) + * Compile debug builds with -O0 + +Thu, 13 Sep 2012 12:00:06 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.6.1 + * Fix freedesktop.org Thumbnail Managing Standard implementation: + when running feh on the current working directory, '/path/to/./image.png' + was used for thumbnail name generation. Now it is always + '/path/to/image.png' + * Show error message if lossless rotate / flip failed on non-JPEG image + * Show error message if -O / -o failed to save image + * Show error message if load failed and magick is disabled (was broken + by feh 2.4) + +Tue, 28 Aug 2012 11:46:19 +0200 Birte Friesel <derf+feh@finalrewind.org> + +* Release v2.6 + * Dennis Real: EXIF mode fixes, support for more camera models + * feh(1): Document behaviour for duplicate key bindings + <https://github.com/derf/feh/issues/91> + * menu: simpler background, remove drop shadows + * Remove --menu-style support (deprecated in 1.12) + * Allow --slideshow-delay and --reload to be used together + * New format specifiers: + ** %o x,y image offset relative to top-left window corner + ** %r image rotation + ** %z image zoom + * Always use file URL as filename when opening http files. + (consequence: lossless rotate no longer works for such files) + * --reload now accepts non-decimal values + * Fix --filelist trying to load .txt files with imagemagick + * imagemagick support is now disabled by default, to make feh behaviour + less confusing. Set --magick-timeout to a non-negative value to enable + it (--magick-timeout 5 for feh-2.5 behaviour) + +Sun, 25 Mar 2012 13:13:26 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v2.5 + * Add R, * and / bindings for <keypad begin>, <keypad *> and <keypad /> + * Try to convert unloadable files with imagemagick for up to 5 seconds + * Add --magick-timeout option to set imagemagick conversion timeout or + disable it altogether + * Clean up temporary / to-delete files when receiving SIG{INT,TERM,QUIT} + * Do not scroll past image borders when using key bindings + * --loadable / --unloadable: indicate result in exit status + +Tue, 06 Mar 2012 13:13:35 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v2.4 + * exif-support fixes by Dennis Real + * format specifier %S now uses the appropriate suffix (B/kB/MB) + * format specifier %P now prints the number of pixels with k/M suffix, + like %S. Printing the program name ("feh") is no longer supported + * feh --list now uses %S/%P to print image size and amount of pixels + * make --quiet and --verbose behave more like their documented way, never + ignore out of memory errors + * Use ImageMagick (convert) as loader for unsupported file formats + +Thu, 02 Feb 2012 21:04:06 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v2.3 + * Add %F and %N format specifiers, containing an escaped version of %f/%n. + Example: %F for foo'bar".jpg will return 'foo'"'"'bar".jpg' + <https://github.com/derf/feh/issues/77> + * Mention URL support in the SYNOPSIS + <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=655431> + * Do not refer to --help unless help is compiled in + <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=657171> + * Do not overwrite filelist file if it actually is an image + (-f and -F may get mixed up) + <https://github.com/derf/feh/issues/79> + * Patch by Dennis Real: Add optional EXIF tag view support (make exif=1) + * Accept offset-only arguments for --geometry + <https://github.com/derf/feh/issues/73> + +Mon, 02 Jan 2012 11:54:01 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v2.2 + * Add --no-fehbg option to disable ~/.fehbg creation (patch by Felix Crux) + * Pause slideshow when editing captions + * Makefile: Respect CPPFLAGS when creating deps.mk + <https://github.com/derf/feh/issues/74> + * Fix --montage mode + <https://github.com/derf/feh/issues/76> + * Follow HTTP redirects + <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=653689> + +Wed, 02 Nov 2011 10:56:10 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v2.1 + * Experimental --scale-down and --auto-zoom tiling support + (--scale-down is now re-applied every time the image is resized) + <https://github.com/derf/feh/issues/54> + * Fix http image load for long image names + <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=646421> + +Mon, 10 Oct 2011 12:25:00 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v2.0 + * The --bg-options now accept multiple filenames, one per Xinerama screen + * Fix --info bug when the command wrote no lines to stdout + * The button-options -0 through -9 are no longer supported. + Use .config/feh/buttons instead (see feh manpage) + * New format specifier: %S (size in kB) + * The --index-{dim,name,size} options are no longer supported. + Use --index-info <string> with format specifiers instead + * The thumbnail index now supports actions on the currently selected + thumbnail (patch by Olof-Joachim Frahm) + * feh now supports 'make stat64=1' to access CIFS shares mounted from 64bit + hosts if the local system is 32bit (for 32<->32 and 64<->64, this works + anyways) + +Mon, 26 Sep 2011 09:35:41 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.16.2 + * Fix useless memory use when using feh --reload on HTTP URLs + (fun fact: strictly speaking, this was not a memory leak) + <https://github.com/derf/feh/issues/62> + * "--image-bg default" was renamed to "--image-bg checks" + * Fix --title-font fallback behaviour + * Fix delayed title display when using --title-font + +Sun, 11 Sep 2011 12:46:50 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.16.1 + * Fix reload after image rotation and similar (broken by 1.16) + <https://github.com/derf/feh/issues/63> + +Mon, 05 Sep 2011 10:56:58 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.16 + * Reload image after executing an action with the hold-action flag set + <https://github.com/derf/feh/issues/59> + * Fix bug in --info (used to swallow the last output character in some + cases) + * Add --draw-tinted option to make overlay text (filename, caption etc.) + better readable + <https://github.com/derf/feh/issues/60> + * The --collage option (aka collage mode) is now deprecated + * The feh-cam and gen-cam-menu tools are deprecated as well + (but will remain in this distribution for at least 1 year) + * The --filelist option now supports /dev/stdin (or "-" as shortcut) + * Several Xinerama fixes, only --fullscreen on screen != 0 is still broken + <https://github.com/derf/feh/issues/5> + * Fix segfault when reloading no longer loadable (but still existing) images. + feh will now display a warning and try to reload ad infinitum + * Fix memory leak in reload functionality for directories + <https://github.com/derf/feh/issues/62> + * When using --no-menus and clicking the menu button, feh will now ignore + it instead of quitting (which was undocumented behaviour anyways) + +Tue, 16 Aug 2011 22:48:06 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.15.1 + * Fix segfault when selecting menu items (broken by 1.15) + <https://github.com/derf/feh/issues/58>, + <https://bugs.archlinux.org/task/25612> + +Mon, 15 Aug 2011 11:12:34 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.15 + * Respect --image-bg option in full-screen mode + * Disable blur / rotation in thumbnail viewer window + * Fix --reloaed behaviour when combined with --fullscreen / --geometry + <https://github.com/derf/feh/issues/57> + + [Patches by Yu-Jie Lin] + + * Add scroll by page actions + * Allow combination of multiple key modifiers + * Fix option pair bug in theme config + <https://github.com/derf/feh/issues/49> + * Allow Shift modifier in key config + <https://github.com/derf/feh/issues/30> + * New actions: lossless flip (key _) and mirror (key |) + <https://github.com/derf/feh/issues/53> + * Fix save_image key for *.JPG and similar filenames + <https://github.com/derf/feh/issues/51> + * Show correct file number after deleting image + <https://github.com/derf/feh/issues/46> + * Add experimental reload functionality for directories + <https://github.com/derf/feh/issues/14> + +Mon, 04 Jul 2011 14:46:36 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.14.2 + * Fix --draw-filename "x of y" being cut off by short filenames + <https://github.com/derf/feh/issues/45> + * Use --zoom 100 to show all images at 100% in --fullscreen mode + <https://github.com/derf/feh/issues/47> + * Add toggle_info key to switch --info display on/off (defaults to 'i') + <https://github.com/derf/feh/issues/48> + * Fix minor documentation bugs + * Fix minor memleak in the recursive file loader for directories + +Thu, 19 May 2011 22:32:42 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.14.1 + * Fix compilation with curl=0 + * Make zoom_default key work properly with --geometry + +Wed, 11 May 2011 11:37:32 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.14 + * Only create caption directory when actually writing out a caption. + <http://github.com/derf/feh/issues/42> + * The --menu-bg option has been deprecated. It will be removed along with + --menu-style by the end of 2012. + <http://github.com/derf/feh/issues/27> + * read directory contents sorted by filename instead of 'randomly' + (as returned by readdir) by default. Thanks talisein! + <https://github.com/derf/feh/pull/20> + * Show certain warnings in the image window as well as on the commandline + <http://github.com/derf/feh/issues/43> + * Since the manual is way better structured and more detailed than the + --help output, it now simply refers to the manual. To include the old + help text, build feh with 'help=1' + * You can now use the next/prev/jump keys to navigate thumbnails. Use the + render key to open the currently selected thumbnail. + <http://github.com/derf/feh/issues/26> + * Change a patch for NETWM fullscreen support to only apply to fullscreen + windows. This fixes the moving windows bug in fluxbox (since fluxbox + doesn't report its window border width). + <http://github.com/derf/feh/issues/22> + <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=570903> + * Minor manpage fixes. + <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=625683> + * Fix --auto-zoom / --zoom max/fill documentation, the "Auto-Zoom" menu + option is now always checked when these options are used + * Set _NET_WM_NAME and _NET_WM_ICON_NAME properties + <http://github.com/derf/feh/issues/44> + * The 'A' key (toggle_aliasing) now actually changes the current window, + and not just the default for new windows + * The zoom_default key now works fine with --scale-down + <http://github.com/derf/feh/issues/41> + * Fix access of uninitialized memory / malloc/realloc clash in continued + theme definition handling. Having a theme line with just one + option/value pair used to produce undefined behaviour + +Sat, 23 Apr 2011 22:00:27 +0200 Birte Friesel <derf@finalrewind.org> + +* Release v1.13 + * Fix segfault upon unloadable images when image-related format specifiers + (e.g. %h) are used in --title + * Show images in current directory when invoked without file arguments + * Option to disable antialiasing, either global (--force-aliasing) or per + image (press 'A' to toggle, keybinding toggle_aliasing) + * Use SIGUSR1/SIGUSR2 to reload all images in multiwindow mode + * Fix Imlib2 caching bug in reload (only worked after the second try) + * The --bg options are now Xinerama-aware. That is, they set the image in + the respective mode (scale/fill/max/center) on each Xinerama screen. Use + --no-xinerama to disable this. + +Sat, 12 Mar 2011 22:49:53 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v1.12 + * Add --zoom fill as equivalent for --auto-zoom + * Add --zoom max (zooming like in --bg-max) + * --menu-style is now deprecated + * http images are now viewed using libcurl, not wget (thanks to talisein) + This adds libcurl as dependency, and removes the wget recommendation + * Slight build system change: make now has flags, e.g. + "make xinerama=0 debug=1". By default feh is compiled with xinerama and + libcurl support enabled, see README. + * Remove builtin http client (--builtin) + * Fix compilation issues with libpng 1.5.1 + +Wed, 09 Feb 2011 20:11:26 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v1.11.2 + * Use wget --no-clobber to prevent TOCTTOU-based hole allowing a + well-informed attacker to rewrite arbitrary user files with images. + The attacker needs to know feh's PID and the URL the user gave it. + It is still possible for an attacker to _create_ arbitrary files via the + same hole. + +Wed, 26 Jan 2011 21:07:19 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v1.11.1 + * Show correct image dimensions in for cached thumbnails + * Allow commandline options to override those set in a theme + * Remove support for FEH_OPTIONS (was deprecated >5 years ago) + * Restrict available modifiers to Control/Mod1/Mod4 + +Sat, 22 Jan 2011 11:48:33 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v1.11 + * Patch by Pascal Bleser: Use getaddrinfo for builtin http client, + this enables IPv6 support + * Fix zooming when --scale-down is used + * The themes are now read from ~/.config/feh/themes (BC for .fehrc exists) + * Key bindings can now be configured via ~/.config/feh/keys + * Removes --rcpath, use XDG_CONFIG_HOME instead + * Increase movement steps for Ctrl+Left etc. + * Make in/out zoom use equal zoom ratio + +Fri, 03 Dec 2010 19:41:45 +0100 Birte Friesel <derf@finalrewind.org> + +* Release v1.10.1 * Partially fix --scale-down behaviour (zooming is still broken) * Fix buffer overflow when using --draw-actions * Fix segfaults after trying to load several unloadable images * Fix fehrc created by feh (contined escape errors) -Thu, 07 Oct 2010 20:15:12 +0200 Daniel Friesel <derf@finalrewind.org> +Thu, 07 Oct 2010 20:15:12 +0200 Birte Friesel <derf@finalrewind.org> * Release v1.10 (aka "2.0 will come real soon now") * Patch by Stefan Mark: Add --bg-max (scaled with borders) @@ -21,7 +1116,7 @@ Thu, 07 Oct 2010 20:15:12 +0200 Daniel Friesel <derf@finalrewind.org> * Zoom button: Zoom to 100% on button release, not button click * --draw-filename: Always show position in filelist -Tue, 24 Aug 2010 19:23:36 +0200 Daniel Friesel <derf@chaosdorf.de> +Tue, 24 Aug 2010 19:23:36 +0200 Birte Friesel <derf@chaosdorf.de> * Release v1.9 * Add --fullscreen option, --full-screen is now deprecated @@ -36,7 +1131,7 @@ Tue, 24 Aug 2010 19:23:36 +0200 Daniel Friesel <derf@chaosdorf.de> * When zooming via keyboard: Always zoom around the center of the window * The image can now be panned with Ctrl + arrow keys -Fri, 25 Jun 2010 16:07:20 +0200 Daniel Friesel <derf@chaosdorf.de> +Fri, 25 Jun 2010 16:07:20 +0200 Birte Friesel <derf@chaosdorf.de> * Release v1.8 * support LDLIBS in Makefile/config.mk @@ -55,9 +1150,9 @@ Fri, 25 Jun 2010 16:07:20 +0200 Daniel Friesel <derf@chaosdorf.de> malicious URLs containing shell metacharacters (but only if those URLs led to a valid file) * Don't add ?randomnumber to URLs when downloading them, it confuses some - servers and is not really neccessary in general + servers and is not really necessary in general -Thu Jun 10 12:12:04 CEST 2010 Daniel Friesel <derf@chaosdorf.de> +Thu Jun 10 12:12:04 CEST 2010 Birte Friesel <derf@chaosdorf.de> * Release v1.7 * Fix segfault in Thumbnail mode when trying to open a no longer @@ -75,14 +1170,14 @@ Thu Jun 10 12:12:04 CEST 2010 Daniel Friesel <derf@chaosdorf.de> * caption mode: Automatically create caption directory if it doesn't exist * Slideshow mode: SIGUSR1 = next image, SIGUSR2 = previous image -Sat Jun 5 21:35:25 CEST 2010 Daniel Friesel <derf@chaosdorf.de +Sat Jun 5 21:35:25 CEST 2010 Birte Friesel <derf@chaosdorf.de * Release v1.6.1 * Fix omitted image borders at high zoom levels * Re-add getopt_long files (possibly relevant for non-glibc systems) * Do not require a running X server for -L, -u, -U options -Tue Jun 1 10:21:19 CEST 2010 Daniel Friesel <derf@chaosdorf.de> +Tue Jun 1 10:21:19 CEST 2010 Birte Friesel <derf@chaosdorf.de> * Release v1.6 * Patch by aaptel: Support numpad keys for actions @@ -98,7 +1193,7 @@ Tue Jun 1 10:21:19 CEST 2010 Daniel Friesel <derf@chaosdorf.de> * Fix "make uninstall". You do NOT want to call this for feh versions 1.4.2 to 1.5 -Thu May 6 08:34:39 CEST 2010 Daniel Friesel <derf@chaosdorf.de> +Thu May 6 08:34:39 CEST 2010 Birte Friesel <derf@chaosdorf.de> * Release v1.5 * Rewrite parts of the menu code & fix a memory leak while there @@ -106,7 +1201,7 @@ Thu May 6 08:34:39 CEST 2010 Daniel Friesel <derf@chaosdorf.de> * Add keybinding to toggle pointer visibility (see --hide-pointer) * Sort manual a bit -Thu Apr 22 22:28:09 CEST 2010 Daniel Friesel <derf@chaosdorf.de> +Thu Apr 22 22:28:09 CEST 2010 Birte Friesel <derf@chaosdorf.de> * Release v1.4.3 * Warp the pointer when reaching a window border in pan mode @@ -116,7 +1211,7 @@ Thu Apr 22 22:28:09 CEST 2010 Daniel Friesel <derf@chaosdorf.de> is. The zoom happen around at that pixel. * Manpage review -Fri Apr 2 16:20:55 CEST 2010 Daniel Friesel <derf@chaosdorf.de> +Fri Apr 2 16:20:55 CEST 2010 Birte Friesel <derf@chaosdorf.de> * Release v1.4.2 * Replace autoconf by config.mk @@ -125,7 +1220,7 @@ Fri Apr 2 16:20:55 CEST 2010 Daniel Friesel <derf@chaosdorf.de> * patch by dylan: Remove temporary files if url opening fails * Fix problems with unexpectedly empty filelists -Tue Mar 16 07:56:36 CET 2010 Daniel Friesel <derf@chaosdorf.de> +Tue Mar 16 07:56:36 CET 2010 Birte Friesel <derf@chaosdorf.de> * Release v1.4.1 * Fix chrome theme in the default .fehrc @@ -133,7 +1228,7 @@ Tue Mar 16 07:56:36 CET 2010 Daniel Friesel <derf@chaosdorf.de> * Add manual for feh-cam and gen-cam-menu (from Debian) * Fix lossless rotate for filenames with spaces etc. -Thu Mar 4 14:55:02 CET 2010 Daniel Friesel <derf@chaosdorf.de> +Thu Mar 4 14:55:02 CET 2010 Birte Friesel <derf@chaosdorf.de> * Release v1.4 * Lots of documentation fixes/improvements @@ -147,7 +1242,7 @@ Thu Mar 4 14:55:02 CET 2010 Daniel Friesel <derf@chaosdorf.de> * Use jpegtran binary instead of libjpeg for lossless rotation * Add --bg-fill option (patch by Anonymous) -Mon Feb 8 21:47:56 CET 2010 Daniel Friesel <derf@chaosdorf.de> +Mon Feb 8 21:47:56 CET 2010 Birte Friesel <derf@chaosdorf.de> * Release v1.3.5 * Import various Debian patches @@ -227,8 +1322,8 @@ Mon Mar 07 23:56:03 GMT 2005 Tom Gilbert <tom@linuxbrit.co.uk> > In short, it enables the user to use feh as an image viewer used by a > file manager like ROX-Filer or Nautilus when invoked with the --fmmode > option. The file manager passes the file that the user wants to view to - > feh. My function then reads the directory in which the file resides and - > first passes the current image, then alphabetically all the following + > feh. My function then reads the directory in which the file resides and + > first passes the current image, then alphabetically all the following > images and at last the images that are alphabetically before the current > file to the 'filelist'. > Afaik that's the default behaviour of gqview and gthumb. @@ -274,7 +1369,7 @@ Fri Sep 03 13:40:48 BST 2004 Tom Gilbert <tom@linuxbrit.co.uk> Sat Jul 24 14:52:19 BST 2004 Tom Gilbert <tom@linuxbrit.co.uk> - * Various warning fixes from Claes Nasten <pekdon@pekdon.net> + * Various warning fixes from Claes Nasten <pekdon@pekdon.net> Thu Jun 10 23:14:36 BST 2004 Tom Gilbert <tom@linuxbrit.co.uk> @@ -405,13 +1500,13 @@ Sun Oct 20 20:12:23 2002 EDT, Paul Duncan <pabs@pablotron.org> * AUTHORS: added Jon Bernard Sun Oct 20 14:49:46 2002 EDT, Paul Duncan <pabs@pablotron.org> - + * New beveled, off-white background image for menus. I made it the default after consulting with the boss (eg Sue Gilbert). Tom thinks it's okay too. Sun Oct 20 05:48:40 2002 EDT Paul Duncan <pabs@pablotron.org> - + * Added Xinerama support. Currently defaults to fullscreen on the first head (this can be fixed with a little work). * Added winwidget_{move,get_geometry}(); @@ -972,7 +2067,7 @@ Fri Jun 23 11:37:56 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> Thu Jun 22 23:57:33 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> * Optimisations to thumb hilites. Only show hilite when you can - click to open an image, don't show when over emtpy space, and + click to open an image, don't show when over emtpy space, and don't re-render if the selection hasn't changed from the last mouseover. Speeds it up a lot. @@ -1075,7 +2170,7 @@ Sun Jun 18 03:47:58 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> Sun Jun 18 01:29:16 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> - * The first part of some background setting code from richlowe + * The first part of some background setting code from richlowe <richlowe@btinternet.com> - more to come, it'll be sweet. * Doesn't work yet btw, so don't get excited ;) @@ -1097,7 +2192,7 @@ Sat Jun 17 23:46:32 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> bg. Same for --theme chrome. Sat Jun 17 12:57:49 PDT 2000 Paul Duncan <pabs@pablotron.org> - + * wget is now quiet by default (wget -q). it's verbose if the -V or --verbose flag is passed to feh. Your terminal will thank you. @@ -1172,7 +2267,7 @@ Sun Jun 4 15:44:05 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> the singleton context (accidentally leaving context_antialias on, not blending when I should be etc), and to fix them I was adding lines and lines of context_set this, context_set that. I've - wrapped the imlib calls in an imlib1 + wrapped the imlib calls in an imlib1 lots-of-params-per-function-call stylee, and now it's much more readable and harder to break. * In the process, made big speedups by not antialiasing when I @@ -1511,7 +2606,7 @@ Fri Mar 24 19:22:51 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> * If ~/.fehrc exists, or if not, but /etc/fehrc exists, feh will look in it for name/options pairs. An example entry would be: imagemap -rVq --thumb-width 40 --thumb-height 30 - * You can use the theme in two ways. Either + * You can use the theme in two ways. Either feh -C themename [images] or you can create a symbolic link to feh with the name of the options you want it to use. So from the example above: @@ -1524,7 +2619,7 @@ Fri Mar 24 19:22:51 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> create an index.jpg in the current directory. I just run: $ mkindex. * An example.fehrc is provided with a couple of cool examples. - + Fri Mar 24 19:17:27 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> * Slightly increased the default index mode font size. @@ -1760,11 +2855,11 @@ Fri Mar 3 22:44:25 PST 2000 Michael Jennings <mej@eterm.org> tired right now to try to make sense out of gilbertt's callback logic. :-) I'll try to fix it this weekend if he doesn't beat me to it. - + Wed Mar 1 15:13:04 PST 2000 Michael Jennings <mej@eterm.org> * Math lib - + Sun Feb 20 15:22:00 2000 Tom Gilbert <gilbertt@linuxbrit.co.uk> * Remove some crufty duplication. @@ -2067,7 +3162,7 @@ Sun Dec 19 22:06:43 1999 Tom Gilbert <gilbertt@linuxbrit.co.uk> Sun Dec 19 20:29:33 1999 Tom Gilbert <gilbertt@linuxbrit.co.uk> * Added a .spec file for feh. Contributed by Alistair Sutton - <metallica@freenet.co.uk>, (who is obviously a metallica fan ;), + <metallica@freenet.co.uk>, (who is obviously a metallica fan ;), and has written specs for me before ::) Thanks dude :) * Urm. I haven't actually tested this yet :) @@ -2177,4 +3272,3 @@ Thu Dec 16 22:58:21 1999 Tom Gilbert <gilbertt@linuxbrit.co.uk> Thu Dec 16 22:10:50 1999 Tom Gilbert <gilbertt@linuxbrit.co.uk> * Initial import. Feh is currently at 0.5.0 release level. - @@ -1,6 +1,6 @@ include config.mk -all: build-src build-man +all: build-src build-man build-applications build-src: @${MAKE} -C src @@ -8,61 +8,110 @@ build-src: build-man: @${MAKE} -C man +build-applications: + @${MAKE} -C share/applications + test: all - @PACKAGE=${PACKAGE} VERSION=${VERSION} prove test + @if ! uname -m | grep -q -e arm -e mips; then \ + PACKAGE=${PACKAGE} prove test/feh.t test/mandoc.t; \ + else \ + PACKAGE=${PACKAGE} prove test/feh.t test/mandoc.t || cat test/imlib2-bug-notice; \ + fi test-x11: all test/run-interactive - prove test/feh-bg.i + prove test/feh-bg-i.t install: install-man install-doc install-bin install-font install-img +install: install-icon install-examples install-applications -install-man: +install-man: man/feh.1 @echo installing manuals to ${man_dir} @mkdir -p ${man_dir}/man1 - @cp man/*.1 ${man_dir}/man1 - @chmod 644 ${man_dir}/man1/feh.1 ${man_dir}/man1/feh-cam.1 \ - ${man_dir}/man1/gen-cam-menu.1 + @cp man/feh.1 ${man_dir}/man1 + @chmod 644 ${man_dir}/man1/feh.1 -install-doc: +install-doc: AUTHORS ChangeLog README.md TODO @echo installing docs to ${doc_dir} @mkdir -p ${doc_dir} - @cp AUTHORS ChangeLog README TODO ${doc_dir} - @chmod 644 ${doc_dir}/* + @cp AUTHORS ChangeLog README.md TODO ${doc_dir} + @chmod 644 ${doc_dir}/AUTHORS ${doc_dir}/ChangeLog ${doc_dir}/README.md \ + ${doc_dir}/TODO -install-bin: +install-bin: src/feh @echo installing executables to ${bin_dir} @mkdir -p ${bin_dir} - @cp src/feh cam/feh-cam cam/gen-cam-menu ${bin_dir} - @chmod 755 ${bin_dir}/feh ${bin_dir}/feh-cam \ - ${bin_dir}/gen-cam-menu + @cp src/feh ${bin_dir}/feh.tmp + @mv ${bin_dir}/feh.tmp ${bin_dir}/feh + @chmod 755 ${bin_dir}/feh install-font: @echo installing fonts to ${font_dir} @mkdir -p ${font_dir} - @cp data/fonts/* ${font_dir} + @chmod 755 ${font_dir} + @cp share/fonts/* ${font_dir} @chmod 644 ${font_dir}/* install-img: @echo installing images to ${image_dir} @mkdir -p ${image_dir} - @cp data/images/* ${image_dir} + @chmod 755 ${image_dir} + @cp share/images/* ${image_dir} @chmod 644 ${image_dir}/* +install-icon: + @echo installing icon to ${48_icon_dir} + @mkdir -p ${48_icon_dir} + @cp share/images/feh.png ${48_icon_dir} + @chmod 644 ${48_icon_dir}/feh.png + @echo installing icon to ${scalable_icon_dir} + @mkdir -p ${scalable_icon_dir} + @cp share/images/feh.svg ${scalable_icon_dir} + @chmod 644 ${scalable_icon_dir}/feh.svg + @if test "${app}" = 1 && which gtk-update-icon-cache > /dev/null 2>&1; then \ + gtk-update-icon-cache ${icon_dir}; \ + fi + +install-examples: + @echo installing examples to ${example_dir} + @mkdir -p ${example_dir} + @cp examples/* ${example_dir} + @chmod 644 ${example_dir}/* + +install-applications: share/applications/feh.desktop + @echo installing desktop file to ${desktop_dir} + @mkdir -p ${desktop_dir} + @cp share/applications/feh.desktop ${desktop_dir} + @chmod 644 ${desktop_dir}/feh.desktop + uninstall: - rm -f ${man_dir}/man1/feh.1 ${man_dir}/man1/feh-cam.1 - rm -f ${man_dir}/man1/gen-cam-menu.1 + rm -f ${man_dir}/man1/feh.1 rm -rf ${doc_dir} - rm -f ${bin_dir}/feh ${bin_dir}/feh-cam ${bin_dir}/gen-cam-menu + rm -f ${bin_dir}/feh + rm -f ${desktop_dir}/feh.desktop rm -rf ${font_dir} rm -rf ${image_dir} + @if test -e ${48_icon_dir}/feh.png; then \ + echo rm -f ${48_icon_dir}/feh.png; \ + rm -f ${48_icon_dir}/feh.png; \ + fi + @if test -e ${scalable_icon_dir}/feh.svg; then \ + echo rm -f ${scalable_icon_dir}/feh.svg; \ + rm -f ${scalable_icon_dir}/feh.svg; \ + fi + @if which gtk-update-icon-cache > /dev/null 2>&1; then \ + gtk-update-icon-cache ${icon_dir}; \ + fi dist: mkdir /tmp/feh-${VERSION} git --work-tree=/tmp/feh-${VERSION} checkout -f + cp src/deps.mk /tmp/feh-${VERSION}/src/deps.mk sed -i 's/^VERSION ?= .*$$/VERSION ?= ${VERSION}/' \ /tmp/feh-${VERSION}/config.mk + sed -i 's/^MAN_DATE ?= .*$$/MAN_DATE ?= ${MAN_DATE}/' \ + /tmp/feh-${VERSION}/config.mk tar -C /tmp -cjf ../feh-${VERSION}.tar.bz2 feh-${VERSION} rm -r /tmp/feh-${VERSION} @@ -77,6 +126,8 @@ disttest: dist clean: @${MAKE} -C src clean @${MAKE} -C man clean + @${MAKE} -C share/applications clean .PHONY: all test test-x11 install uninstall clean install-man install-doc \ - install-bin install-font install-img dist + install-bin install-font install-img install-examples \ + install-applications dist @@ -1,54 +0,0 @@ -feh - Imlib2 based image viewer - - * <https://derf.homelinux.org/p/feh/> - * <http://linuxbrit.co.uk/feh/> - - -Dependencies: - - * giblib - * Imlib2 - * libpng - * libX11 - - -Recommended: - - * jpegtran (supplied by the jpeg library, for lossless image rotation) - * wget (for http/ftp support) - - -Installation: - -$ make -$ sudo make install - -If compilation does not work or you want to customize stuff (like disable -Xinerama support), edit config.mk first. - -Note: config.mk is designed so that in most cases, you can set environment -variables instead of editing it. E.g.: -CFLAGS='-g -Os' make -export DESTDIR=/tmp/feh PREFIX=/usr; make && make install - - -Testing (non-X): - -$ make test - -Requires perl >= 5.10 with Test::Command. The tests are non-interactive and -work without X, so they can safely be run even on a headless buildserver. - - -Testing (X): - -Requires - * import (usually supplied by imagemagick) - * perl >= 5.10 with GD, Test::More and X11::GUITest - * twm - * Xnest - -$ make test-x11 - -Be aware that this is quite experimental, so far the X-tests have only been -run on one machine. So they may or may not work for you. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b30b88 --- /dev/null +++ b/README.md @@ -0,0 +1,158 @@ +# feh - Image Viewer and Cataloguer + +**feh** is a light-weight, configurable and versatile image viewer. +It is aimed at command line users, but can also be started from graphical file +managers. Apart from viewing images, it can compile text and thumbnail +listings, show (un)loadable files, set X11 backgrounds, and more. + +Features include filelists, various image sorting modes, custom action scripts, +and image captions. feh can be controlled by configurable keyboard and mouse +shortcuts, terminal input and signals. When no file arguments or filelists are +specified, feh displays all files in the current directory. + +This README focuses on installation and contribution instructions. See the +[feh homepage](https://feh.finalrewind.org/) and the +[feh(1) manual](https://man.finalrewind.org/1/feh/) for usage instructions. + +## Dependencies + + * Imlib2 + * libcurl (disable with `curl=0`) + * libpng + * libX11 + * libXt + * libXinerama (disable with `xinerama=0`) + +Only when building with `exif=1`: + + * libexif-dev + * libexif12 + +Only when building with `magic=1`: + + * libmagic + +## Build Process + +feh has been packaged for a variety of distributions, including +[Arch Linux](https://archlinux.org/packages/extra/x86_64/feh/), +[Debian](https://packages.debian.org/search?keywords=feh&exact=1), +[FreeBSD](https://www.freshports.org/graphics/feh), and +[Ubuntu](https://packages.ubuntu.com/search?keywords=feh&exact=1). +You can configure, compile and install a custom version as follows. + +### Configuration + +feh's build process uses make flags to enable/disable optional features and +fine-tune the build and installation process. It uses (hopefully) reasonable +defaults, so you can skip this section if you like. + +Make flags can be passed as **make** arguments or set as environment variables, +like so: + +```bash +$ make flag=bool +$ make install flag=bool +``` +or +```bash +$ export flag=bool +$ make && make install +``` + +The following flags are respected by the makefile. A default value of **1** +indicates that the corresponding feature is enabled by default. + +| Flag | Default value | Description | +| :--- | :---: | :--- | +| app | 0 | install icons to /usr/share, regardless of `DESTDIR` and `PREFIX`, and call gtk-update-icon-cache afterwards | +| curl | 1 | use libcurl to view https:// and similar images | +| debug | 0 | debug build, enables `--debug` | +| exif | 0 | Builtin EXIF tag display support | +| help | 0 | include help text (refers to the manpage otherwise) | +| inotify | 0 | enable inotify, needed for `--auto-reload` | +| stat64 | 0 | Support CIFS shares from 64bit hosts on 32bit machines | +| magic | 0 | Use libmagic to filter unsupported file formats | +| mkstemps | 1 | Whether your libc provides `mkstemps()`. If set to 0, feh will be unable to load gif images via libcurl | +| verscmp | 1 | Whether your libc provides `strvercmp()`. If set to 0, feh will use an internal implementation. | +| xinerama | 1 | Support Xinerama/XRandR multiscreen setups | + +For example, `make xinerama=0 debug=1` will disable Xinerama support and +produce a debug build; libcurl and natural sorting support will remain enabled. + +Additionally, it supports the standard variables `CFLAGS`, `LDLIBS`, `PREFIX`, +and `DESTDIR`. + +**PREFIX _(default: /usr/local)_** controls where the application and its data files +will be installed. It must be set both during `make` and `make install`. + +**DESTDIR _(default: empty)_** sets the installation root during "make install". It +is mostly useful for package maintainers. + +**Note:** Defaults are specified in `config.mk`. It is designed so that in most +cases, you can set environment variables instead of editing it. E.g.: + +```bash +$ CFLAGS='-g -Os' make +``` +```bash +$ export DESTDIR=/tmp/feh PREFIX=/usr +$ make && make install +``` + +Builtin EXIF support is maintained by Dennis Real, [here](https://github.com/reald/feh). + +### Installation + +Add your own make flags to the following examples as needed. + +**For end users:** +```bash +$ make +$ sudo make install app=1 +``` + +**For package maintainers and users who do not want feh to install its +icons into /usr/share:** +```bash +$ make +$ sudo make install +``` + +**Explanation:** feh ships some icons and an X11 desktop entry, which allow it to +be used from file managers, desktop menus and similar. However, installing +icons to /usr/local/share/... does not seem to work reliably. +Because of this, when using "make install app=1", feh will install its icons +to /usr/share/..., even though they technically belong into /usr/local. + +[ZSH completion for +feh](https://git.finalrewind.org/zsh/plain/etc/completions/_feh) is also +available. + +## Testing (non-X11) + +The non-X11 parts of feh can be automatically tested by running + +```bash +$ make test +``` +This requires **perl >= 5.10** and the perl module `Test::Command`. Tests are +non-interactive and do not require a running X11, so they can safely be run on +a headless buildserver. + +## Contributing + +Bugfixes are always welcome, just open a pull request :) + +Before proposing a new feature, please consider the scope of feh: It is an +image viewer and cataloguer, not an image editor or similar. Also, its option +list is already pretty long. Please discuss your ideas in a feature request +before opening a pull request in this case. Also, keep in mind that feh is +developed as a hobby project and that there is absolutely no obligation for +anyone to implement requested features or review merge requests. + +Please keep in mind that feh's options, key bindings and format specifiers are +documented in two different places: The manual (man/feh.pre) and the help text +(src/help.raw). Although the help is not compiled in by default, it should be +kept up-to-date. On space-constrained embedded systems, it may be more useful +than the (significantly larger) man page. @@ -1,39 +1 @@ -See also: <http://github.com/derf/feh/issues> - -key/option/signal/timeout to totally reload filelist (rescan directories etc) - -<keypad begin> antialieses the image, but doing that automatically upon -key release would be better. However, I couldn't find a way to do that so far. -Even when holding down a key, everytime the eventloop runs I get a -KeyRelease event. - ---thumb-title covers a very specific case, would be cool to make this more -general (usable in index mode, as image info in thumbnail mode, etc.) - ---reload-button might be useless (wtf is button 0) - -Control thumbnail mode (image selection, mainly) with keys. - -Xinerama doesn't really work when not on screen 0. -On screen 1, it will work if fullscreen is turned off, the window is rendered -and moved so that its top corner is only on screen 1, and then fullscreen is -turned on (after that, it'll work for all images in the slideshow). -Maybe I'll someday find out how to fix that :> - -The thumbnail mode draws an image which has to be re-rendered as new -thumbnails are created, becoming quite slow for large filelists. -A workaround (--thumb-redraw) exists, but at some point a rewrite with a -GUI-toolkit or similar (or maybe writing a completely new tool for that and -removing the feature from feh) would be due. - -Saving the filelist from thumbnail mode is not possible. I wonder if this -matters enough to be fixed. %u is borked there for the same reason. - -Maybe: Zoom mode like --bg-fill? - -Configurable key bindings - -Maybe remove some unneccessary stuff? - * Builtin HTTP client - * Support for custom menu backgrounds/themes - * output-dir (cd $dir; feh is good enough) +See <https://github.com/derf/feh/issues> diff --git a/cam/ChangeLog b/cam/ChangeLog deleted file mode 100644 index 4a562bd..0000000 --- a/cam/ChangeLog +++ /dev/null @@ -1,14 +0,0 @@ -0.3 ---- -- added this changelog.. - -0.4 ---- -- added richlowe's patch. since he pretty much rewrote everything, he's -an author, not a contributor. :) -- uses "use strict" now -- uses POD for documentation -- added title support in cam bookmarks file -- cmd line flag for different bookmars file -- getoppt_long use for command-line flags -- rmeoved richlow's damn nonstandard Pod::Usage stuff diff --git a/cam/README b/cam/README deleted file mode 100644 index e67d60e..0000000 --- a/cam/README +++ /dev/null @@ -1,45 +0,0 @@ -Cam 0.4 README -============== -This document was last updated on 20010223-1620-PST. -Please see the file COPYING for licensing information. - -Description -=========== -feh-cam is a Perl wrapper for feh that simlifies viewing webcams. It uses -keyed bookmarks. Type "feh-cam --help" at the command line for usage -information. - -Webcam Information -================== -All webcam images are the property of their respective owners. If you -enjoy the cam, you should check out the page too! The initial list of -cam bookmarks (in the cam_bookmarks file) is a combination of webcams -scoured from the #E People Page (http://bma.debian.net/~bma/e-irc/), -my favorite webcams from the Stile Project's cam pages -(http://www.stileproject.com/), and additional #e people cams that -have come online since cam's original release (0.1). - -Installation -============ -- Copy the "feh-cam" script to a location in your PATH (ex /usr/bin, - /usr/local/bin, or $HOME/bin), and make sure hte execute bit on the - script is set (type "chmod a+x $HOME/.cam_bookmarks"). -- Copy the "cam_bookmarks" file to $HOME/.cam_bookmarks and make sure - cam can see it by typing "feh-cam --list". -- Optionally, modify the options inside the feh-cam script. You can pass - feh any options before the image by adjusting the $PRE variable, - and any options after the image by adjustin the $POST variable. - For example, I like to keep all cam images by default; you can enable - this behavior with the -k command line option, or enable it permanently - by adding it to the $PRE flags in feh-cam. - -About the Authors -================= -Paul Duncan <pabs@pablotron.org>, pabs on #e -http://www.pablotron.org/ - -Richard Lowe <richlowe@btinternet.com>, richlowe on #e -http://www.richlowe.btinternet.co.uk/ - -...but the real credit goes to Tom Gilbert for making feh, an awesome -image viewer. :) diff --git a/cam/cam_bookmarks b/cam/cam_bookmarks deleted file mode 100644 index 8d45413..0000000 --- a/cam/cam_bookmarks +++ /dev/null @@ -1,2 +0,0 @@ -nasa_shuttle=60,http://science.ksc.nasa.gov/shuttle/countdown/video/chan2large.jpg - diff --git a/cam/feh-cam b/cam/feh-cam deleted file mode 100755 index 014b22b..0000000 --- a/cam/feh-cam +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use Getopt::Long; - -############################################# -############# CAM RUN-TIME OPTIONS ########## -############################################# -my $feh = "feh"; - -# additional feh cmdline options -my $PRE = " -q -G -Twebcam -1 0 -0 1 "; -my $POST = ""; -############################################# -############################################# - -# Options -my $help = ''; -my $fullscreen = ''; -my $geometry = ''; -my $list = ''; -my $verbose = ''; -my $add = ''; -my $keep = ''; -my $deftitle = '%cCAM - %u'; -my $title = ''; -my $bp = $ENV{HOME}."/.cam_bookmarks"; -my $DEBUG = 0; - -# check args -&print_usage_and_exit unless (@ARGV); - - -# Url, Refresh, and bookmarks -my $url = ""; -my $ref = ""; -my %bms = (); - -GetOptions('help|?|h' => \$help, - 'full-screen|f|giblets-mom' => \$fullscreen, - 'list|l' => \$list, - 'geometry|g=s' => \$geometry, - 'verbose|v' => \$verbose, - 'add|a' => \$add, - 'keep-images|k|save-pr0n' => \$keep, - 'title|t=s' => \$title, - 'debug|d' => \$DEBUG, - 'bookmarks|b=s' => \$bp - ); - -my $key = shift @ARGV; - -&print_usage_and_exit if ($help); - -if ($verbose) { - $PRE .= " -V "; -} - -if ($fullscreen) { - $PRE =~ s/-w//; - $PRE .= " --full-screen --auto-zoom "; -} - -if ($geometry) { - $PRE .= " --geometry $geometry "; -} - -# if requested, add a key/url pair to bookmarks file -if ($add) { - my $mytitle = ''; - ($url,$ref,$mytitle) = @ARGV; - die "Bad key syntax\n" unless ($key && $url && $ref); - - $mytitle="" unless ($mytitle); - - open(BMF, ">>$bp") or die "Couldn't open bookmarks file \"$bp\": $!\n"; - print BMF "$key=$ref,$url \"$mytitle\"\n"; - close BMF; - print "Added URL key \"$key\" = $url, $ref.\n"; # its useful to have this even if you arent debugging --richlowe - exit 0; -} - -if ($keep) { - $PRE .= " -k "; -} - - - -# load bookmarks -open(BMF, "$bp") or die "Couldn't open bookmarks file \"$bp\": $!\n"; -foreach (<BMF>) { - next unless /^(.*?)=(.*)$/; - $bms{$1} = $2; - print "key=$1, url=$2\n" if ($DEBUG); -} -close BMF; - -# if requested, dump a list of key/url pair values -if ($list) { - foreach (sort keys %bms) { - my $t = $bms{$_}; - - $t =~ s/^(.+?),(.+?)(^ "(.*)"|)?$/$2/; - chomp $t; - print "$_ = $t, $1, $3\n"; - } - exit 0; -} - -# main loop -MAIN: { - do { - $title=""; - $url = $bms{$key}; - die "Couldn't find URL key \"$key.\"\n" unless($url); - - $url =~ s/^(.+?),(.+?)( "(.*)")?$/$2/; - $ref = $1; - - if ($4) { - $title = $4; - } else { - $title = $deftitle; - } - - if ($title) { - $title =~ s/\%c/$key/g; - $title =~ s/\%u/$url/g; - $title =~ s/\%r/$ref/g; - $title =~ s/\%\%/\%/g; - $title = " --title \"$title\" "; - } - - my $cmd = "$feh $PRE $title -T".$key."cam -R $ref $url $POST"; - print "$cmd\n" if ($DEBUG); - FORK: { - my $pid; - if ($pid = fork) { - # We're a daddy! :) - } elsif (defined $pid) { - # child - exec "$cmd" or die "Couldnt exec() $feh: $!\n"; - } elsif ( $! =~ /No more process/) { - sleep 5; - redo FORK; - } else { - # wtf? - die "Unrecoverable fork() error: $!\n"; - } - } - } while ($key = shift @ARGV); -} - - -sub print_usage_and_exit() { - print <<END_USAGE; -$0 0.4 -by Paul Duncan <pabs\@pablotron.org>, and - Richard Lowe <richlowe\@btinternet.com> - -Description: - A convenient webcam wrapper for feh. - -Usage: - $0 <keys> - Load the urls specified by the given keys. - key : a url key stored in the bookmarks file (\"$bp\"). - $0 <-a|--add> key url refresh - Add a key to the bookmarks file. - key : short key (ex \"jenni\"), - url : url (ex \"http://www.jennicam.org/webcam/cam.jpg\"), - refresh : refresh, in seconds (ex 120) - $0 <-l|--list> - List each url key in the bookmarks file (\"$bp\"). - $0 [-f|--full-screen] keys - Start feh in full-screen mode (aka --giblets-mom \"viewing\" mode). - $0 [-k|--keep-images] keys - Save each image in the current directory (aka --save-pr0n). - $0 [-g|--geometry] xxx - Use window geometry xxx (e.g. 640x480). - $0 [-v|--verbose] options - Start feh in verbose mode (feh -V). - $0 <-h|-?|--help> - Display usage information (this screen). - -Notes: -Thanks to giblet for feh, an awesome image and webcam viewing program, -and raster for Imlib2. -END_USAGE - exit(-1); -} - diff --git a/cam/gen-cam-menu b/cam/gen-cam-menu deleted file mode 100755 index 198c74c..0000000 --- a/cam/gen-cam-menu +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -###################################################################### -# gen_cam_menu.sh 0.1 # -# by Paul Duncan <pabs@pablotron.org> # -# # -# This script will generate a menu of cam bookmarks for # -# Enlightenment DR0.16.x. TYou can safely run this script more than # -# once; it won't add an another entry to the left-click menu if it's # -# already been run once. It doesn't delete any existing menu # -# entries, and it backs up your existing menu files as well. (just # -# in case I screwed up.. hehe). THe two variables below allow you # -# rename the left-click menuitem, and the menu title. # -# # -# # -MENU_ITEM="Webcams"; # -MENU_TITLE="Webcam List"; # -BMARKS=$HOME"/.cam_bookmarks"; # -# # -###################################################################### - - -C_MENUFILE="webcam.menu"; -F_MENUFILE="file.menu"; -C_MENU=$HOME"/.enlightenment/"$C_MENUFILE; -F_MENU=$HOME"/.enlightenment/"$F_MENUFILE; - -# make backups, just in case -cp -f $C_MENU $C_MENU"-cam_menu.backup" -cp -f $F_MENU $F_MENU"-cam_menu.backup" - -# generate cam menu -echo "Generating \""$C_MENU"\"."; -echo "\"$TITLE\"" > "$C_MENU"; -cat $BMARKS | perl -e "while (<>) { /(.*?)=/; \$keys{\$1}=\"1\"; } foreach(sort keys %keys) { /(.)(.*$)/; print \"\\\"\".uc(\$1).\"\$2\\\" NULL exec \\\"feh-cam \$1\$2\\\"\\n\"; }">> $C_MENU; - -# add entry to file menu if there isn't one -echo "Generating \""$F_MENU"\"."; -perl -i -e "\$already_there=0; while (<>) { \$already_there++ if (/$MENU_ITEM/); print \"\\\"$MENU_ITEM\\\" NULL menu \\\"$C_MENUFILE\\\"\\n\" if (!\$already_there&&/Restart/); print; }" $F_MENU; - -echo "Done."; @@ -1,8 +1,26 @@ PACKAGE ?= feh -VERSION ?= ${shell git describe} +VERSION ?= ${shell git describe --dirty} + +app ?= 0 +curl ?= 1 +debug ?= 0 +exif ?= 0 +help ?= 0 +magic ?= 0 +mkstemps ?= 1 +verscmp ?= 1 +xinerama ?= 1 # Prefix for all installed files PREFIX ?= /usr/local +ICON_PREFIX ?= ${DESTDIR}${PREFIX}/share/icons + +# icons in /usr/share/local/icons (and other prefixes != /usr) are not +# generally supported. So ignore PREFIX and always install icons into +# /usr/share/icons if the user wants to install feh on their local machine. +ifeq (${app},1) + ICON_PREFIX = /usr/share/icons +endif # Directories for manuals, executables, docs, data, etc. main_dir = ${DESTDIR}${PREFIX} @@ -11,23 +29,87 @@ bin_dir = ${main_dir}/bin doc_dir = ${main_dir}/share/doc/feh image_dir = ${main_dir}/share/feh/images font_dir = ${main_dir}/share/feh/fonts +example_dir = ${main_dir}/share/doc/feh/examples +desktop_dir = ${main_dir}/share/applications +icon_dir = ${ICON_PREFIX}/hicolor +48_icon_dir = ${icon_dir}/48x48/apps +scalable_icon_dir = ${icon_dir}/scalable/apps # default CFLAGS CFLAGS ?= -g -O2 CFLAGS += -Wall -Wextra -pedantic -# Comment these out if you don't have libxinerama -xinerama = -DHAVE_LIBXINERAMA -xinerama_ld = -lXinerama +# Settings for glibc >= 2.19 - may need to be adjusted for other systems +CFLAGS += -std=c11 -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -D_DARWIN_C_SOURCE + +ifeq (${curl},1) + CFLAGS += -DHAVE_LIBCURL + LDLIBS += -lcurl + MAN_CURL = enabled +else + MAN_CURL = disabled +endif + +ifeq (${debug},1) + CFLAGS += -DDEBUG -O0 + MAN_DEBUG = This is a debug build. +else + MAN_DEBUG = . +endif + +ifeq (${help},1) + CFLAGS += -DINCLUDE_HELP +endif + +ifeq (${stat64},1) + CFLAGS += -D_FILE_OFFSET_BITS=64 +endif + +ifeq (${mkstemps},1) + CFLAGS += -DHAVE_MKSTEMPS +endif + +ifeq (${magic},1) + CFLAGS += -DHAVE_LIBMAGIC + LDLIBS += -lmagic + MAN_MAGIC = enabled +else + MAN_MAGIC = disabled +endif + +ifeq (${verscmp},1) + CFLAGS += -DHAVE_STRVERSCMP +endif + +ifeq (${xinerama},1) + CFLAGS += -DHAVE_LIBXINERAMA + LDLIBS += -lXinerama + MAN_XINERAMA = enabled +else + MAN_XINERAMA = disabled +endif + +ifeq (${exif},1) + CFLAGS += -DHAVE_LIBEXIF + LDLIBS += -lexif + MAN_EXIF = available +else + MAN_EXIF = not available +endif + +ifeq (${inotify},1) + CFLAGS += -DHAVE_INOTIFY + MAN_INOTIFY = enabled +else + MAN_INOTIFY = disabled +endif -# Uncomment this for debug mode -# (Use feh -+ or feh --debug to see debug output) -#CFLAGS += -DDEBUG +MAN_DATE ?= ${shell date '+%B %d, %Y'} # Uncomment this to use dmalloc #CFLAGS += -DWITH_DMALLOC -CFLAGS += ${xinerama} -DPREFIX=\"${PREFIX}\" \ +CFLAGS += -DPREFIX=\"${PREFIX}\" \ -DPACKAGE=\"${PACKAGE}\" -DVERSION=\"${VERSION}\" -LDLIBS += -lm -lpng -lX11 -lImlib2 -lgiblib ${xinerama_ld} +LDLIBS += -lm -lpng -lX11 -lImlib2 diff --git a/data/images/about.png b/data/images/about.png Binary files differdeleted file mode 100644 index 5aaaf17..0000000 --- a/data/images/about.png +++ /dev/null diff --git a/data/images/logo.svg b/data/images/logo.svg deleted file mode 100644 index e07b95d..0000000 --- a/data/images/logo.svg +++ /dev/null @@ -1,162 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="200" - height="50" - id="svg2" - version="1.1" - inkscape:version="0.47 r22583" - sodipodi:docname="New document 1"> - <defs - id="defs4"> - <linearGradient - id="linearGradient3659" - inkscape:collect="always"> - <stop - id="stop3661" - offset="0" - style="stop-color:#cd022f;stop-opacity:1;" /> - <stop - id="stop3663" - offset="1" - style="stop-color:#cd022f;stop-opacity:0;" /> - </linearGradient> - <linearGradient - id="linearGradient3619"> - <stop - style="stop-color:#b60000;stop-opacity:0.80392158;" - offset="0" - id="stop3621" /> - <stop - style="stop-color:#b60000;stop-opacity:0;" - offset="1" - id="stop3623" /> - </linearGradient> - <inkscape:perspective - sodipodi:type="inkscape:persp3d" - inkscape:vp_x="0 : 526.18109 : 1" - inkscape:vp_y="0 : 1000 : 0" - inkscape:vp_z="744.09448 : 526.18109 : 1" - inkscape:persp3d-origin="372.04724 : 350.78739 : 1" - id="perspective10" /> - <linearGradient - inkscape:collect="always" - xlink:href="#linearGradient3659" - id="linearGradient3667" - x1="60.296875" - y1="1026.6521" - x2="141.11719" - y2="1026.6521" - gradientUnits="userSpaceOnUse" /> - <filter - id="filter3859" - inkscape:label="Drop shadow" - width="1.5" - height="1.5" - x="-.25" - y="-.25"> - <feGaussianBlur - id="feGaussianBlur3861" - in="SourceAlpha" - stdDeviation="1.000000" - result="blur" /> - <feColorMatrix - id="feColorMatrix3863" - result="bluralpha" - type="matrix" - values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.900000 0 " /> - <feOffset - id="feOffset3865" - in="bluralpha" - dx="2.000000" - dy="2.000000" - result="offsetBlur" /> - <feMerge - id="feMerge3867"> - <feMergeNode - id="feMergeNode3869" - in="offsetBlur" /> - <feMergeNode - id="feMergeNode3871" - in="SourceGraphic" /> - </feMerge> - </filter> - </defs> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="2" - inkscape:cx="96.605459" - inkscape:cy="39.75" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1022" - inkscape:window-height="583" - inkscape:window-x="0" - inkscape:window-y="0" - inkscape:window-maximized="0" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(0,-1002.3622)"> - <g - style="fill-opacity:1;fill-rule:nonzero;fill:#000000;opacity:1;stroke:none;stroke-opacity:1;filter:url(#filter3859)" - id="g3669"> - <path - id="path3671" - style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:Catharsis Bedouin Bold;fill-rule:nonzero;fill:#000000;stroke-opacity:1" - d="m 60.296875,1012.072 20.292969,0 0,5.6836 -12.773438,0 0,5.4297 12.011719,0 0,5.6836 -12.011719,0 0,12.3633 -7.519531,0 0,-29.1602" /> - <path - id="path3673" - style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:Catharsis Bedouin Bold;fill-rule:nonzero;fill:#000000;stroke-opacity:1" - d="m 87.640625,1012.072 20.292965,0 0,5.6836 -12.77343,0 0,5.4297 12.01172,0 0,5.6836 -12.01172,0 0,6.6797 13.20312,0 0,5.6836 -20.722655,0 0,-29.1602" /> - <path - id="path3675" - style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:Catharsis Bedouin Bold;fill-rule:nonzero;fill:#000000;stroke-opacity:1" - d="m 114.98438,1012.072 7.51953,0 0,11.1133 11.09375,0 0,-11.1133 7.51953,0 0,29.1602 -7.51953,0 0,-12.3633 -11.09375,0 0,12.3633 -7.51953,0 0,-29.1602" /> - </g> - <g - id="g2834" - style="fill-opacity:1;fill-rule:nonzero;fill:url(#linearGradient3667);opacity:1;stroke:none;stroke-opacity:1"> - <path - d="m 60.296875,1012.072 20.292969,0 0,5.6836 -12.773438,0 0,5.4297 12.011719,0 0,5.6836 -12.011719,0 0,12.3633 -7.519531,0 0,-29.1602" - style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:Catharsis Bedouin Bold;fill-rule:nonzero;fill:url(#linearGradient3667);stroke-opacity:1" - id="path2825" /> - <path - d="m 87.640625,1012.072 20.292965,0 0,5.6836 -12.77343,0 0,5.4297 12.01172,0 0,5.6836 -12.01172,0 0,6.6797 13.20312,0 0,5.6836 -20.722655,0 0,-29.1602" - style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:Catharsis Bedouin Bold;fill-rule:nonzero;fill:url(#linearGradient3667);stroke-opacity:1" - id="path2827" /> - <path - d="m 114.98438,1012.072 7.51953,0 0,11.1133 11.09375,0 0,-11.1133 7.51953,0 0,29.1602 -7.51953,0 0,-12.3633 -11.09375,0 0,12.3633 -7.51953,0 0,-29.1602" - style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:Catharsis Bedouin Bold;fill-rule:nonzero;fill:url(#linearGradient3667);stroke-opacity:1" - id="path2829" /> - </g> - </g> -</svg> diff --git a/data/images/menubg_aluminium.png b/data/images/menubg_aluminium.png Binary files differdeleted file mode 100644 index eed00f1..0000000 --- a/data/images/menubg_aluminium.png +++ /dev/null diff --git a/data/images/menubg_aqua.png b/data/images/menubg_aqua.png Binary files differdeleted file mode 100644 index 3a72590..0000000 --- a/data/images/menubg_aqua.png +++ /dev/null diff --git a/data/images/menubg_black.png b/data/images/menubg_black.png Binary files differdeleted file mode 100644 index 08b4c2b..0000000 --- a/data/images/menubg_black.png +++ /dev/null diff --git a/data/images/menubg_brushed.png b/data/images/menubg_brushed.png Binary files differdeleted file mode 100644 index 32fad47..0000000 --- a/data/images/menubg_brushed.png +++ /dev/null diff --git a/data/images/menubg_default.png b/data/images/menubg_default.png Binary files differdeleted file mode 100644 index dd21188..0000000 --- a/data/images/menubg_default.png +++ /dev/null diff --git a/data/images/menubg_sky.png b/data/images/menubg_sky.png Binary files differdeleted file mode 100644 index e0be8ca..0000000 --- a/data/images/menubg_sky.png +++ /dev/null diff --git a/examples/buttons b/examples/buttons new file mode 100644 index 0000000..be6ce39 --- /dev/null +++ b/examples/buttons @@ -0,0 +1,17 @@ +# feh button configuration. +# Comments start with a # sign, do not use them mid-line. +# Each line must be blank, a comment, or a button definition. +# +# button definition: <action name> <button> +# +# A <button> is a button number with optional modifier. +# For instance, C-1 would be Ctrl + Left click, 4-2 Mod4 + Middle click, +# et cetera. + +# Switch menu and zoom keys, useful for two-button touchpads +menu 2 +zoom 3 + +# make scroll wheel (mousewheel up and down) zoom, instead of flipping images +zoom_in 4 +zoom_out 5 diff --git a/examples/find-lowres b/examples/find-lowres new file mode 100755 index 0000000..ac77e7b --- /dev/null +++ b/examples/find-lowres @@ -0,0 +1,32 @@ +#!/bin/sh +# Recursively find images below a certain resolution +# +# Usage: find-lowres [-r] [directory [dimension]] +# +# directory defaults to . (the current working directory), +# dimension defaults to 1000x800 pixels +# +# With -r: removes images instead of just listing them. Use at your own risk. + +remove=0 + +while true +do + case $1 in + -r) remove=1 ;; + -*) echo "option \"$1\" ignored" ;; + -|--) shift; break ;; + *) break ;; + esac + shift +done + +dir=${1:-.} +dimension=${2:-1000x800} + +if [ "$remove" = "1" ] +then + feh --action 'rm %F' -rlV --max-dim "${dimension}" "${dir}" +else + feh -rlV --max-dim "${dimension}" "${dir}" +fi diff --git a/examples/keys b/examples/keys new file mode 100644 index 0000000..c1bb091 --- /dev/null +++ b/examples/keys @@ -0,0 +1,45 @@ +# feh key configuration. +# Comments start with a # sign, do not use them mid-line. +# Each line must be blank, a comment, or a key definition. +# +# key definition: <action name> <key1> [<key2> [<key3>]] +# +# Each <key> is an X11 keysym (as output by xev) with optional modifier. +# For instance, C-x would be Ctrl+X, or 4-space Mod4+Space + +# Examples for vim-like menu bindings on a qwerty keyboard: +menu_parent h Left +menu_child l Right +menu_down j Down +menu_up k Up +menu_select space Return + +# Same for image navigation ... +next_img j Right space +prev_img k Left BackSpace + +# and image movement +scroll_up J C-Up +scroll_down K C-Down +scroll_left H C-Left +scroll_right L C-Right + +# File deletion +remove d Delete +delete C-d C-Delete + +# remove now conflicts with toggle_filenames, so change that +toggle_filenames f + +# zooming +zoom_in C-Up f +zoom_out C-Down a +zoom_default d +zoom_fit s + +# I only hit these accidentally +save_image +save_filelist + +# This leaves some conflicts with existing default bindings, but you should +# get the idea. And I'm not gonna fix the conflicts, I don't use qwerty ;-) diff --git a/src/fehrc.raw b/examples/themes index 154a6e1..2d77770 100644 --- a/src/fehrc.raw +++ b/examples/themes @@ -1,8 +1,6 @@ -# Feh configuration file. -# Lines starting with # are comments. Don't use comments mid-line. - -# Feh expects to find this as ~/.fehrc or /etc/fehrc -# If both are available, ~/.fehrc will be used +# Feh themes configuration file. +# Lines starting with # are comments. Midline comments are not supported. +# Place this as either ~/.config/feh/themes or /etc/feh/themes # Options are defined in theme_name/options pairs. # Separate themename and options by whitespace. @@ -14,8 +12,8 @@ # mkindex theme. # Multiple options can of course be used. If they are too long for one line, -# you can (usually) use a \\ to make them continue on the next one: -# imagemap -rV --quiet -W 400 -H 300 \\ +# you can use a \ to make them continue on the next one, but not mid-option. +# imagemap -rV --quiet -W 400 -H 300 \ # --thumb-width 40 --thumb-height 30 # ==================== @@ -31,7 +29,7 @@ webcam --multiwindow --reload 20 mkindex -iVO index.jpg . # More ambitious version... -imgidx --index --output-only .fehindex.jpg --limit-width 1024 \\ +imgidx --index --output-only .fehindex.jpg --limit-width 1024 \ --thumb-width 128 --thumb-height 128 --verbose --quiet # Show a presentation @@ -43,32 +41,21 @@ booth --full-screen --hide-pointer --slideshow-delay 20 # Screw xscreensaver, use feh =) screensave --recursive --full-screen --randomize --slideshow-delay 10 --hide-pointer -# Add <img> tags to your html with ease :-) -newimg -q -L \"<img src=\\\"%f\\\" alt=\\\"%n\\\" border=\\\"0\\\" width=\\\"%w\\\" height=\\\"%h\\\">\" - -# Different menus -brushed --menu-bg " PREFIX "/share/feh/images/menubg_brushed.png -aluminium --menu-bg " PREFIX "/share/feh/images/menubg_aluminium.png -aqua --menu-bg " PREFIX "/share/feh/images/menubg_aqua.png -sky --menu-bg " PREFIX "/share/feh/images/menubg_sky.png -black --menu-bg " PREFIX "/share/feh/images/menubg_black.png \\ - --menu-style " PREFIX "/share/feh/fonts/black.style - # Some more examples, used by the feh developer rfs --full-screen --hide-pointer --auto-zoom --randomize fs --full-screen --hide-pointer --auto-zoom --sort filename -thumb_s --thumbnails --cache-thumbnails --thumb-width 128 --thumb-height 128 \\ - --limit-width 1024 --sort filename \\ +thumb_s --thumbnails --cache-thumbnails --thumb-width 128 --thumb-height 128 \ + --limit-width 1024 --sort filename \ --fontpath /usr/share/fonts/truetype/ttf-dejavu/ --font DejaVuSans/8 -thumb_b --thumbnails --cache-thumbnails --thumb-width 256 --thumb-height 256 \\ - --limit-width 1024 --sort filename \\ +thumb_b --thumbnails --cache-thumbnails --thumb-width 256 --thumb-height 256 \ + --limit-width 1024 --sort filename \ --fontpath /usr/share/fonts/truetype/ttf-dejavu/ --font DejaVuSans/8 -thumb_s_nt --thumbnails --cache-thumbnails --thumb-width 128 --thumb-height 128 \\ +thumb_s_nt --thumbnails --cache-thumbnails --thumb-width 128 --thumb-height 128 \ --limit-width 1024 --sort filename --index-name 0 -thumb_b_nt --thumbnails --cache-thumbnails --thumb-width 256 --thumb-height 256 \\ +thumb_b_nt --thumbnails --cache-thumbnails --thumb-width 256 --thumb-height 256 \ --limit-width 1024 --sort filename --index-name 0 diff --git a/man/Makefile b/man/Makefile index 293d2b7..8fe2cbd 100644 --- a/man/Makefile +++ b/man/Makefile @@ -8,7 +8,13 @@ all: ${TARGETS} .pre.1: sed \ -e 's/\$$VERSION\$$/${VERSION}/g' \ - -e 's/\$$DATE\$$/'"$$(date '+%B %d, %Y')"/g \ + -e 's/\$$DATE\$$/${MAN_DATE}/g' \ + -e 's/\$$MAN_CURL\$$/${MAN_CURL}/' \ + -e 's/\$$MAN_DEBUG\$$/${MAN_DEBUG}/' \ + -e 's/\$$MAN_EXIF\$$/${MAN_EXIF}/' \ + -e 's/\$$MAN_INOTIFY\$$/${MAN_INOTIFY}/' \ + -e 's/\$$MAN_MAGIC\$$/${MAN_MAGIC}/' \ + -e 's/\$$MAN_XINERAMA\$$/${MAN_XINERAMA}/' \ < ${@:.1=.pre} > $@ clean: diff --git a/man/feh-cam.pre b/man/feh-cam.pre deleted file mode 100644 index 40e87a7..0000000 --- a/man/feh-cam.pre +++ /dev/null @@ -1,48 +0,0 @@ -.Dd $DATE$ -.Dt FEH-CAM 1 -.Os -. -.Sh NAME -.Nm feh-cam -.Nd utility for viewing live webcam images -.Sh SYNOPSIS -.Nm -.Op Ar options -.Ar keys -. -.Sh VERSION -This manual documents feh-cam, shipped with feh version $VERSION$ -. -.Sh DESCRIPTION -.Nm -is a perl wrapper for feh which simplifies viewing webcams using keyed -bookmarks. It helps manage viewing your favourite webcam sites with feh. -. -.Sh OPTIONS -.Bl -tag -width indent -.It Cm -a , --add Ar key url refresh -Add -.Ar url -as -.Ar key -to the bookmarks file; when viewing, reload it every -.Ar refresh -seconds -.It Cm -l , --list -List each url / key pair in the bookmarks file -.It Cm -f , --full-screen -Start feh in full-screen mode -.It Cm -k , --keep-images -Save each webcam image in the current directory -.It Cm -g , --geometry Ar width No x Ar height -Use window geometry -.Ar width No x Ar height -.It Cm -v , --verbose -Start feh in verbose mode -.El -. -.Sh FILES -The bookmarks are stored in -.Pa ~/.cam_bookmarks -.Sh SEE ALSO -.Xr feh 1 diff --git a/man/feh.pre b/man/feh.pre index 1c45f64..ca64d37 100644 --- a/man/feh.pre +++ b/man/feh.pre @@ -4,626 +4,1436 @@ . . .Sh NAME +. .Nm feh .Nd image viewer and cataloguer . . .Sh SYNOPSIS +. .Nm .Op Ar options -.Ar files or directories ... +.Op Cm -- +.Op Ar files | Ar directories | Ar URLs ... . . .Sh VERSION -This manual documents feh $VERSION$ +. +This manual documents +.Nm +$VERSION$ +. +.Pp +. +Compile-time switches in this build: +. +.Bl -bullet -compact +. +.It +libcurl remote file support $MAN_CURL$ +. +.It +Xinerama multi-monitor support $MAN_XINERAMA$ +. +.It +libexif builtin EXIF reader $MAN_EXIF$ +. +.It +inotify-based auto-reload of changed files $MAN_INOTIFY$ +. +.It +libmagic $MAN_MAGIC$ +. +.El +. +$MAN_DEBUG$ . . .Sh DESCRIPTION +. .Nm -is a mode-based image viewer. It is especially aimed at commandline users who -need a fast image viewer without huge GUI dependencies, though it can also be -started by -.Pq graphical -file managers to view an image. +is a light-weight, configurable and versatile image viewer. +It is aimed at command line users, but can also be started from graphical file +managers. +Apart from viewing images, it can compile text and thumbnail +listings, show (un)loadable files, set X11 backgrounds, and more. +. +.Pp +. +Features include filelists, various image sorting modes, custom action scripts, +and image captions. +.Nm +can be controlled by configurable keyboard and mouse shortcuts, terminal +input and signals. +When no file arguments or filelists are specified and +.Cm --start-at +is not used, +.Nm +displays all files in the current directory. +. .Pp +. +EXIF tags are supported either using exiv2 / exifgrep via +.Cm --info +.Pq see the Sx USAGE EXAMPLES No section , +or as a builtin feature by compiling .Nm -supports filelists, various image sorting modes, image captions and more. -Control happens via keyboard shortcuts; the mouse can also be used to control -it, but is only required for very few actions. +with exif=1. In this build of +.Nm , +builtin EXIF support is $MAN_EXIF$. . . .Sh MODES +. .Nm -is based on various modes, which are selected at startup by comandline +is based on various modes, which are selected at startup by command line options. +. .Pp -Slideshow mode is the default. It opens one window and displays the first -image in it, the keyboard and mouse can be used to change slides -.Pq images . +. +Slideshow mode is the default. +It opens a window and displays the first image in it; +the slideshow position can be advanced +.Pq or otherwise changed +using keyboard and mouse shortcuts. In slideshow mode, images can be deleted either from the filelist or from the -disk, the new filelist can then be saved to the disk and reopened at a later +disk, a changed filelist can also be saved to the disk and reopened at a later time. +An image can also be read from stdin via +.Qq feh - . +. .Pp -Montage mode forms a montage from the filelist. The resulting image can be -viewed or saved, and its size can be limited by height, width or both. -.Pp -Collage mode is very similar to montage mode, except the images are distributed -randomly and may overlap each other. +. +Montage mode forms a montage from the filelist. +The resulting image can be viewed or saved, +and its size can be limited by height, width or both. +. .Pp -Index mode forms an index print from the filelist. Image thumbnails are shown -along with the filename, filesize and pixel size, printed using a truetype -font of your choice. The resulting image can be viewed or saved, and its size +. +Index mode forms an index print from the filelist. +Image thumbnails are shown along with the filename, +size and dimensions, printed using a truetype +font of your choice. +The resulting image can be viewed or saved, and its size can be limited by height, width or both. +. .Pp +. Thumbnail mode is like index mode, but the mini-images are clickable and open the selected image in a new window. +. .Pp +. Multiwindow mode shows images in multiple windows, instead of as a slideshow -in one window. Don't use with a large filelist ;) +in one window. +Don't use with a large filelist ;) +. .Pp -List mode doesn't display images. Outputs an -.Cm ls - No style -listing of the files in the filelist, including image info such as size, -pixels, type, etc. Customlist mode will display whatever image info you want, -in the format you choose. +. +List mode doesn't display images. +Instead, it outputs an +.Cm ls Ns No - Ns style +listing of the files in the filelist, including image info such as size, number +of pixels, type, etc. +There is also a Customlist mode which prints image info +in a custom format specified by a printf-like format string. +. .Pp +. .Nm can also list either all the loadable files in a filelist or all the -unloadable files. Useful for preening a directory. +unloadable files. +This is useful for preening a directory. +. +. +.Sh SUPPORTED FORMATS +. +.Nm +can open any format supported by imlib2, most notably jpeg, png, +pnm, tiff, and bmp. +The gif format is also supported, but only for static images. +In case of animations, only the first frame will be shown. +. +.Pp +. +When invoked with +.Cm --conversion-timeout Ar timeout +.Po +and a non-negative +.Ar timeout +value +.Pc , +.Nm +also has limited support for various other file types by means of external +conversion programs. +If the dcraw binary is available, +.Nm +will use it to display the thumbnails embedded into RAW files provided by +digital cameras and similar. +If the ImageMagick convert binary is available, +.Nm +will use it to load file types such as svg, xcf, and otf. . +.Pp +. +.Pq optional feature, $MAN_MAGIC$ in this build +.Nm +can use libmagic to only pass image files to Imlib2. +When using +.Nm +with lots of non-image files +.Pq especially with Imlib2 version 1.6.x or 1.7.0 , +this can speed up the detection of non-image files significantly. +If you think that Imlib2 can load a file which +.Nm +has determined to be likely not an image, set the environment variable +.Qq FEH_SKIP_MAGIC +to pass all files directly to Imlib2, bypassing this check. +The environment variable's value does not matter, it just needs to be set. . .Sh OPTIONS +. .Bl -tag -width indent . -.It Cm -A , --action Oo Ar flag Oc Ns Ar action -Specify a string as an action to perform on the image. In slideshow or -multiwindow modes, the action will be run when the enter key is pressed, in -list mode, the action will be run for each file listed. In -loadables/unloadables mode, the action will be run for each -loadable/unloadable file, respectively. +.It Cm -A , --action Oo Ar flag Oc Ns Oo [ Ar title ] Oc Ns Ar action +. +Specify a shell command as an action to perform on the image. +In slideshow or multiwindow mode, the action will be run when +the action_0 key is pressed, in list mode, it will be run for each file. +In loadable/unloadable mode, it will be run for each loadable/unloadable +file, respectively. +In thumbnail mode, clicking on an image will cause the action to run instead +of opening the image. +. .Pp +. If .Ar flag is .Qq \&; , .Nm -will not switch to the next image after executing the action. +will reload the current image instead of switching to the next one +.Pq slideshow mode +or closing the window +.Pq multiwindow mode +after +executing the action. +. +If +.Ar [ title ] +is specified +.Pq note the literal Qo \&[ Qc and Qo ] Qc , +.Cm --draw-actions +will display +.Ar title +instead of +.Ar action +in the action list. +Note that +.Ar title +must not start with a space. +If it does, the action is handled as if it did not have a title. +This special case exists for backwards compatibility reasons +and makes sure that actions like +.Qq \&[ -L %F \&] && foo +still work. +. +. .Pp -The action will be executed by /bin/sh. Use format specifiers to refer to -image info. See +. +The action will be executed by /bin/sh. +Use format specifiers to refer to image info, see .Sx FORMAT SPECIFIERS -for examples. E.g. -.Qq feh -A "mv ~/images/%n" * . -In slideshow mode, the next image will be shown after running the action, in -multiwindow mode, the window will be closed. +for details. +Example usage: +.Qq feh -A Qo mv %F ~/images/%N Qc * . +. +.It Cm --action1 No .. Cm --action9 Oo Ar flag Oc Ns Oo [ Ar title ] Oc Ns Ar action +. +Extra actions which can be set and triggered using the appropriate number key. +. +.It Cm --auto-reload +. +.Pq optional feature, $MAN_INOTIFY$ in this build +automatically reload image when the underlying file changes. +Note that auto-reload +.Pq if enabled in the build +is on by default. +This option is only useful to re-enable auto-reload after it has been +disabled by a preceding +.Cm --reload=0 +option. +. +.Pp . -.It Cm --action1 No .. Cm --action9 -Extra actions which can be set and triggered using the appropiate number key. +Automatic reload is not supported in montage, index, or thumbnail mode. +. +.It Cm --auto-rotate +. +.Pq optional feature, $MAN_EXIF$ in this build +.Pq deprecated in favor of Imlib2's auto-orientation support +Automatically rotate images based on EXIF data. +Does not alter the image files. +. +.Pp +. +Note that Imlib2 version 1.7.5+ performs auto-rotation by itself, so this option is obsolete on systems with Imlib2 version 1.7.5 or later. +.Nm +currently cannot detect this at runtime. . .It Cm -Z , --auto-zoom -Zoom pictures to screen size in fullscreen -.Pq affected by Cm --stretch No and Cm --ignore-aspect . +. +Zoom pictures to screen size in fullscreen / fixed geometry mode. . .It Cm -x , --borderless +. Create borderless windows. . -.It Cm -Q , --builtin -Use builtin HTTP client to grab remote files instead of -.Xr wget 1 . +.It Cm --cache-size Ar size +. +Set imlib2 in-memory cache to +.Ar size +MiB. +A higher cache size can significantly improve performance especially for small +slide shows, however at the cost of increased memory consumption. +.Ar size +must be between 0 and 2048 MiB and defaults to 4. . .It Cm -P , --cache-thumbnails -Enable (experimental) thumbnail caching in -.Pa ~/.thumbnails . -Only works with thumbnails <= 256x256 pixels. +. +Enable thumbnail caching. +Thumbnails are saved in +.Pa $XDG_CACHE_HOME/thumbnails , +which defaults to +.Pa ~/.cache/thumbnails . +Note that thumbnails are only cached if the configured thumbnail size does +not exceed 256x256 pixels. . .It Cm -K , --caption-path Ar path -Path to directory containing image captions. This turns on caption viewing, -and if captions are found in +. +Path to directory containing image captions. +This turns on caption viewing, and if captions are found in .Ar path , which is relative to the directory of each image, they are overlayed on the -displayed image. E.g. with caption path +displayed image. +E.g. with caption path .Qq captions/ , and viewing image .Qq images/foo.jpg , the caption will be looked for in .Qq images/captions/foo.jpg.txt . . -.It Cm -c , --collage -Enable collage mode. Collage mode is very similar to montage mode, except -the images are distributed randomly. -When using collage mode, you should also specify -.Cm --limit-width -and -.Cm --limit-height . +.It Cm --conversion-timeout Ar timeout +. +.Nm +can use ImageMagick to try converting unloadable files into a supported +file format. +As this can take a long time, it is disabled by default. +Set +.Ar timeout +to a non-negative value to enable it. +A positive value +specifies after how many seconds conversion attempts should be aborted, +zero causes +.Nm +to try indefinitely. +Negative values restore the default by disabling conversion altogether. +. +.It Cm --class Ar class +. +Set the X11 class hint to +.Ar class . +. +Default: feh . .It Cm -L , --customlist Ar format -Use +. +Don't display images, print image info according to .Ar format -.Pq printf-like string containing image info specifiers -for list output. See +instead. +See .Sx FORMAT SPECIFIERS . . -.It Cm --cycle-once -Exit feh after one loop through the slideshow. -. .It Cm -G , --draw-actions +. Draw the defined actions and what they do at the top-left of the image. . +.It Cm --draw-exif +. +.Pq optional feature, $MAN_EXIF$ in this build +display some EXIF information in the bottom left corner, similar to using +.Cm --info +with exiv2 / exifgrep. +. .It Cm -d , --draw-filename -Draw the filename at the top-left of the image. +. +Draw the file name at the top-left of the image. +. +.It Cm --draw-tinted +. +Show overlay texts +.Pq as created by Cm --draw-filename No et al +on a semi-transparent background to improve their readability. +. +.It Cm --edit +. +Enable basic editing of files. +This makes rotation and mirroring +.Pq bound to Qo < Qc , Qo > Qc , Qo | Qc , and Qo _ Qc by default +change the underlying file and not just its displayed content. . .It Cm -f , --filelist Ar file -This option is similar to the playlists used by music software. If +. +This option is similar to the playlists used by music software. +If .Ar file exists, it will be read for a list of files to load, in the order they appear. -The format is a list of image filenames, absolute or relative to the current -directory, one filename per line. If +The format is a list of image file names, absolute or relative to the current +directory, one file name per line. +. +.Pp +. +If .Ar file doesn't exist, it will be created from the internal filelist at the end of a -viewing session. This is best used to store the results of complex sorts +viewing session. +This is best used to store the results of complex sorts .Pq Cm -Spixels No for example -for later viewing. Any changes to the internal filelist +for later viewing. +. +.Pp +. +Any changes to the internal filelist .Pq such as deleting a file or it being pruned for being unloadable will be saved to .Ar file when .Nm -exits. You can add files to filelists by specifying them on the commandline +exits. +You can add files to filelists by specifying them on the command line when also specifying the list. . +.Pp +. +If +.Ar file +is +.Qq - , +.Nm +will read the filelist from its standard input. +. .It Cm -e , --font Ar font -Set global font. Should be a truetype font, resident in the current directory -or the font directory, and should be defined in the form fontname/points, like -.Qq myfont/12 . +. +Set global font. +Should be a truetype font, resident in the current directory or the font +directory, and should be defined in the form fontname/size, like +.Qq yudit/12 +.Pq which is the default . . .It Cm -C , --fontpath Ar path +. Specify .Ar path as extra directory in which to search for fonts; can be used multiple times to add multiple paths. . +.It Cm --tap-zones +. +Enable tap zones for previous/next file in slide show mode +. +.It Cm --force-aliasing +. +Disable anti-aliasing for zooming, background setting etc. +. .It Cm -I , --fullindex -Same as Index mode, but you also get image size and dimensions printed -below each thumbnail. +. +Same as index mode, but with additional information below the thumbnails. +Works just like +.Qq feh --index --index-info \&"\&%n\en\&%S\en\&%wx\&%h\&" . +Enables +.Sx MONTAGE MODE OPTIONS . +. +.Pp +. +Note: This option needs to load all images to calculate the dimensions of the +.Nm +window, so when using it with many files it will take a while before a +.Nm +window is visible. +Use +.Cm --preload +to get a progress bar. . .It Cm -F , --fullscreen +. Make the window fullscreen. +Note that in this mode, large images will always be scaled down to fit the +screen, and +.Cm --zoom Ar zoom +only affects smaller images and never scales larger than necessary to fit the +screen size. +The only exception is a +.Ar zoom +of 100, in which case images will always be shown at 100% zoom. . -.It Cm -g , --geometry Ar width No x Ar height -Limit (and don't change) the window size. Takes an X-style geometry -.Ar string -like 640x480. -Note that larger images will be zoomed out to fit but you can see them at 1:1 -by clicking the zoom button. +.Pp +. +When combined with +.Cm --thumbnails , +this option only affects images opened from the thumbnail overview. +The thumbnail list itself will still be windowed. +. +.It Cm -g , --geometry Ar width Cm x Ar height | Cm + Ar x Cm + Ar y | Ar width Cm x Ar height Cm + Ar x Cm + Ar y . -.It Cm -h , --help -display help output and exit. +Use a fixed window size as specified in the X-style geometry +.Ar string , +e.g. 640x480. +An optional +x+y window offset can be specified. +Combine with +.Cm --scale-down +to scale down larger images like in fullscreen mode. +. +Note that this option does not enforce the window size; changing it by a tiling +WM or manually is still possible. +However, auto-resize remains disabled. . .It Cm -Y , --hide-pointer +. Hide the pointer -.Pq useful for slideshows etc . +.Pq useful for slideshows . . .It Cm -B , --image-bg Ar style -Use style as background for transparent image parts and the like. -Accepted values: white, black, default. . -.It Cm -i , --index -Enable Index mode. Index mode is similar to montage mode, and accepts the -same options. It creates an index print of thumbails, printing the image -name beneath each thumbnail. Index mode enables certain other options, see -.Sx INDEX MODE OPTIONS . +Use +.Ar style +as background for transparent image parts and the like. +Accepted values: default, checks, or an XColor +.Pq e.g. Qo black Qc or Qo #428bdd Qc . +Note that some shells treat the hash symbol as a special character, so you +may need to quote or escape it for the XColor code to work. +. +In windowed mode, the default is checks +.Pq a checkered background so transparent image parts are easy to see . +In fullscreen and background setting mode, +.Cm checks +is not accepted and the default is black. . -.It Cm --index-dim Ar bool -Toggle showing image dimensions in thumbnail/index mode. +.It Cm -i , --index . -.It Cm --index-name Ar bool -Toggle showing the filename in thumbnail/index mode. +Enable Index mode. +Index mode is similar to montage mode, and accepts the same options. +It creates an index print of thumbnails, printing the image name beneath +each thumbnail. +Index mode enables certain other options, see +.Sx INDEX AND THUMBNAIL MODE OPTIONS +and +.Sx MONTAGE MODE OPTIONS . . -.It Cm --index-size Ar bool -Toggle showing the filesize in thumbnail/index mode. +.It Cm --info Oo Ar flag Oc Ns Ar command_line . -.It Cm --info Ar commandline Execute -.Ar commandline -and display its output in the bottom left corner of the image. Can be used to -display e.g. image dimensions or EXIF information. Supports +.Ar command_line +and display its output in the bottom left corner of the image. +Can be used to display e.g. image dimensions or EXIF information. +Supports .Sx FORMAT SPECIFIERS . . +If +.Ar flag +is set to +.Qo ; Qc , +the output will not be displayed by default, but has to be enabled by the +toggle_info key. +. +.It Cm --insecure +. +When viewing files with HTTPS, this option disables all certificate checks. +It allows images on sites with self-signed or expired certificates to be +opened, but is no more secure than plain HTTP. +. .It Cm -k , --keep-http +. When viewing files using HTTP, .Nm normally deletes the local copies after viewing, or, if caching, on exit. -This option prevents this so that you get to keep the local copies. -They will be in -.Pa /tmp -with -.Qq Nm -in the name. +This option permanently stores them on disk, either in the directory +specified by +.Cm --output-dir , +or in the current working directory. +. +.It Cm --keep-zoom-vp +. +When switching images, keep zoom and viewport settings +.Pq zoom level and X, Y offsets . .It Cm -l , --list -Don't display images. Analyse them and display an +. +Don't display images. +Analyze them and display an .Xr ls 1 - No style -listing. Useful in scripts to hunt out images of a certain -size/resolution/type etc. +listing. +Useful in scripts to hunt out images of a certain size/resolution/type etc. . .It Cm -U , --loadable -Don't display images. Just print out their names if imlib2 can successfully -load them. . -.It Cm -) , --menu-bg Ar file -Use -.Ar file -as background image in menus. +Don't display images. +Just print out their names if imlib2 can successfully load them. +Returns false if at least one image failed to load. +When combined with +.Cm --action , +the specified action will be run for each loadable image. +. +.It Cm --max-dimension Ar width No x Ar height +. +Only show images with width <= +.Ar width +and height <= +.Ar height . +If you only care about one parameter, set the other to 0 +.Pq or a negative value . . .It Cm -M , --menu-font Ar font +. Use .Ar font .Pq truetype, with size, like Qq yudit/12 as menu font. . -.It Cm --menu-style Ar file -Read -.Ar file -to determine menu style. +.It Cm --min-dimension Ar width No x Ar height +. +Only show images with width >= +.Ar width +and height >= +.Ar height . +If you only care about one parameter, set the other to 0. . .It Cm -m , --montage -Enable montage mode. Montage mode creates a new image consisting of a grid of -thumbnails of the images in the filelist. When montage mode is selected, -certain other options become available. See +. +Enable montage mode. +Montage mode creates a new image consisting of a grid of thumbnails of the +images in the filelist. +When montage mode is selected, certain other options become available. +See .Sx MONTAGE MODE OPTIONS . . .It Cm -w , --multiwindow -Disable slideshow mode. With this setting, instead of opening multiple files -in slideshow mode, multiple windows will be opened; one per file. +. +Disable slideshow mode. +With this setting, instead of opening multiple files in slideshow mode, +multiple windows will be opened; one per file. +. +.It Cm --no-conversion-cache +. +When loading images via HTTP, ImageMagick or dcraw, +.Nm +will only load/convert them once and re-use the cached file on subsequent +slideshow passes. +This option disables the cache. +It is also disabled when +.Cm --reload +is used. +Use it if you rely on frequently changing files loaded via one of these +sources. +Note that it will impair performance. . .It Cm --no-jump-on-resort +. Don't jump to the first image after resorting the filelist. . .It Cm -N , --no-menus +. Don't load or show any menus. . .It Cm --no-screen-clip -By default, window sizes are limited to the screen size. With this option, -windows will have the size of the image inside them. Note that they may -become very large this way, making them unmanageable in certain window -managers. +. +By default, window sizes are limited to the screen size. +With this option, windows will have the size of the image inside them. +Note that they may become very large this way, making them unmanageable +in certain window managers. . .It Cm --no-xinerama -Disable Xinerama support. Only makes sense when you have Xinerama support -compiled in. +. +.Pq optional feature, $MAN_XINERAMA$ in this build +Disable Xinerama support. +. +.It Cm --on-last-slide Cm hold | Cm quit | Cm resume +. +Select behaviour when trying to select the next image on the last slide +.Pq or the previous image on the first slide +in a slide show. +. +.Pp +. +With +.Cm hold , +.Nm +will stop advancing images in this case and continue displaying the first/last +image, respectively. +This is intended for linear slide shows. +Behaviour is unspecified when using other navigation commands than previous +and next image. +. +.Pp +. +.Cm quit +will cause +.Nm +to quit when trying to advance past the last image in the slide show. This is +the behavior of the obsolete +.Cm --cycle-once +option. +. +.Pp +. +.Cm resume +is the default behaviour: On the last +.Pq first +image, +.Nm +will wrap around to the first +.Pq last +image. . .It Cm -j , --output-dir Ar directory +. Save files to .Ar directory -.Pq only useful with -k +when using +.Cm --keep-http +or the save_image or save_filelist command. +By default, files are saved in the current working directory. . .It Cm -p , --preload -Preload images. This doesn't mean hold them in RAM, it means run through -them and eliminate unloadable images first. Otherwise they will be removed -as you flick through. This also analyses the images to get data for use in -sorting, such as pixel size, type etc. A preload run will be automatically -performed if you specify one of these sort modes. +. +Preload images. +This doesn't mean hold them in RAM, it means run through them and eliminate +unloadable images first. +Otherwise they will be removed as you flick through. +This also analyses the images to get data for use in sorting, such as pixel +size, type etc. +A preload run will be automatically performed if you specify one of these +sort modes. . .It Cm -q , --quiet -Don't report non-fatal errors for failed loads. Verbose and quiet modes are -not mutually exclusive, the first controls informational messages, the second -only errors. +. +Don't report non-fatal errors for failed loads. +Verbose and quiet modes are not mutually exclusive, the first controls +informational messages, the second only errors. . .It Cm -z , --randomize +. When viewing multiple files in a slideshow, randomize the file list before displaying. -. -.It Cm -_ , --rcfile Ar file -Use -.Ar file -to parse themes and options from, instead of the default -.Pa ~/.fehrc , /etc/fehrc -files. +The list is re-randomized whenever the slideshow cycles (that is, transitions +from last to first image). . .It Cm -r , --recursive -Recursively expand any directories in the commandline arguments +. +Recursively expand any directories in the command line arguments to the content of those directories, all the way down to the bottom level. . +.It Cm --no-recursive +. +Don't recursively expand any directories. +This is the default, but this option is useful to override themes containing +.Cm --recursive . +. .It Cm -R , --reload Ar int -Reload images after +. +Reload filelist and current image after .Ar int -seconds. Mainly useful when viewing webcams via http. +seconds. +Useful for viewing HTTP webcams or frequently changing directories. +.Pq Note that filelist reloading is still experimental. +Set to zero to disable any kind of automatic reloading. +. +.Pp +. +If an image is removed, +.Nm +will either show the next one or quit. +However, if an image still exists, but can no longer be loaded, +.Nm +will continue to try loading it. +. +.Pp +. +Setting this option causes inotify-based auto-reload to be disabled. +Reload is not supported in montage, index, or thumbnail mode. . .It Cm -n , --reverse -Reverse the sort order. Use this to invert the order of the filelist. +. +Reverse the sort order. +Use this to invert the order of the filelist. E.g. to sort in reverse width order, use .Cm -nSwidth . . .It Cm -. , --scale-down -When not in fullscreen: Scale images to screen size if they are too big. +. +Scale images to fit window geometry (defaults to screen size when no geometry +was specified). +Note that the window geometry is not updated when changing images at the moment. +This option is recommended for tiling window managers. +. +This option is ignored when in fullscreen and thumbnail list mode. +. +.Pp +. +In tiling environments, this also causes the image to be centered in the window. +. +.It Cm --scroll-step Ar count +. +Scroll +.Ar count +pixels whenever scroll_up, scroll_down, scroll_left or scroll_right is pressed. +Note that this option accepts negative numbers in case you need to reverse the +scroll direction. +See +.Sx KEYS CONFIG SYNTAX +for how to reverse it permanently. +Default: 20 . .It Cm -D , --slideshow-delay Ar float +. For slideshow mode, wait .Ar float -seconds between automatically changing slides. Useful for presentations. +seconds between automatically changing slides. +Useful for presentations. Specify a negative number to set the delay .Pq which will then be Ar float No * (-1) , -but start feh in paused mode. +but start +.Nm +in paused mode. . .It Cm -S , --sort Ar sort_type -The file list may be sorted according to image parameters. Allowed sort -types are: name, filename, width, height, pixels, size, format. For sort -modes other than name or filename, a preload run will be necessary, -causing a delay proportional to the number of images in the list. . +Sort file list according to image parameters. +Allowed sort types are: +.Cm name , none , filename , dirname , mtime , width , height , pixels , size , format . +For sort modes other than +.Cm name , none , filename , dirname , +or +.Cm mtime , +a preload run is +necessary, causing a delay proportional to the number of images in the list. +. +.Pp +. +.Cm mtime +starts with the most recently modified image. +.Cm width , height , pixels +and +.Cm size +start with the smallest. +Use +.Cm --reverse +to sort by oldest or largest first. +. +.Pp +. +For +.Cm name , filename , +and +.Cm dirname +you can use +.Cm --version-sort +to sort numbers naturally, so that e.g. 10.jpg comes after 2.jpg. +. +.Pp +. +.Cm none +is the default; you can specify it explicitly to discard a sort mode that has +been specified at an earlier point in the command line arguments. .It Cm -| , --start-at Ar filename +. Start the filelist at .Ar filename . +If no other files or filelists were specified on the command line, +.Nm +will first load all files from the directory in which +.Ar filename +resides. +This way, it's possible to look at a specific image and use the next / prev +keys to browse through the directory. See -.Sx USAGE EXAMPLES . +.Sx USAGE EXAMPLES +for examples. +If +.Ar filename +is a remote URL and no files or filelists were specified, +.Nm +will show +.Ar filename +and not attempt to load additional files or directories. +. +.Pp +. +Note: If you use relative paths in your filelist, +.Ar filename +should also be a relative path. +If you use absolute paths, it should also be an absolute path. +. +If +.Nm +cannot find an exact match, it will compare basenames +.Pq filenames without the directory suffix . +This may lead to mismatches if several files in your filelist +have the same basename. . .It Cm -T , --theme Ar theme +. Load options from config file with name .Ar theme - see -.Sx CONFIG FILE SYNTAX -for more info. Note that options from the theme file always override -commandline options. +.Sx THEMES CONFIG SYNTAX +for more info. +Note that command line options always override theme options. +The theme can also be set via the program name +.Pq e.g. with symlinks , +so by default +.Nm +will look for a +.Qq Nm +theme. . .It Cm -t , --thumbnails +. Same as Index mode, but the thumbnails are clickable image launchers. Note that .Cm --fullscreen -does not affect the thumbnail window. It does, however, work for the image -windows launched from thumbnail mode. +and +.Cm --scale-down +do not affect the thumbnail window. +They do, however, work for image windows launched from thumbnail mode. +Also supports +.Sx INDEX AND THUMBNAIL MODE OPTIONS +as well as +.Sx MONTAGE MODE OPTIONS . . .It Cm -~ , --thumb-title Ar string +. Set .Ar title -for windows opened from thumbnail mode. See also +for windows opened from thumbnail mode. +See also .Sx FORMAT SPECIFIERS . . .It Cm -^ , --title Ar title -Set window title. Applies to all windows except those opened from thumbnail -mode. See -.Sx FORMAT SPECIFIERS +. +Set window title. +Applies to all windows except those opened from thumbnail mode. +See +.Sx FORMAT SPECIFIERS . . .It Cm -u , --unloadable -Don't display images. Just print out their names if imlib2 can NOT -successfully load them. +. +Don't display images. +Just print out their names if imlib2 can NOT successfully load them. +Returns false if at least one image was loadable. +When combined with +.Cm --action , +the specified action will be run for each unloadable file. . .It Cm -V , --verbose +. output useful information, progress bars, etc. . .It Cm -v , --version +. output version information and exit. . -.It Cm --zoom Ar percent -Zoom images by -.Ar percent -when in full screen mode or when window geometry is fixed. When combined with -.Cm --auto-zoom , -zooming will be limited to the specified -.Ar percent . -.El +.It Cm --version-sort +. +When combined with +.Cm --sort name , --sort filename , +or +.Cm --sort dirname : +use natural sorting for file and directory names. +In this mode, filenames are sorted as an ordinary human would expect, e.g. +.Qq 2.jpg +comes before +.Qq 10.jpg . +Note that this option only has an effect when a sort mode is set using +.Cm --sort . +. +.It Cm --window-id Ar windowid +. +Draw to an existing X11 window by its ID +.Ar windowid . +This option is intended for use with software such as xcreensaver or +xsecurelock, which provide a window for other applications to draw into. +Unexpected things will happen if you specify a window belonging to software +which does not expect +.Nm +to draw into it or attempt to use options or keybindings which affect window +attributes, such as full-screen mode. . +.It Cm --xinerama-index Ar monitor . -.Sh BUTTON OPTIONS -.Bl -tag -width indent +.Pq optional feature, $MAN_XINERAMA$ in this build +Override +.Nm Ns No 's +idea of the active Xinerama monitor. +May be useful in certain circumstances where the window manager places the feh +window on Xinerama monitor A while +.Nm +assumes that it will be placed on monitor B. . -.It Cm -0 , --reload-button Ar int -Set button to reload the image -.Pq default: 0 . +.Pp . -.It Cm -1 , --pan-button Ar int -Set button to pan the image -.Pq hold button down and move mouse to move the image . -When the mouse is not moved, advances to the next image in slideshow mode. -.Pq default: 1 , usually the left button . +In background setting mode: When used with any option other than +.Cm --bg-tile : +Only set wallpaper on +.Ar monitor . +All other monitors will be filled black/white. . -.It Cm -2 , --zoom-button Ar int -Set button to enable zoom mode -.Pq default: 2 , usually the middle button . +This is most useful in a Xinerama configuration with overlapping monitors. +For instance, assume you have two overlapping displays (index 0 and 1), +where index 0 is smaller. +To center a background on the display with index 0 and fill the extra space +on index 1 black/white, use +.Qq --xinerama-index 0 +when setting the wallpaper. . -.It Cm -3 , --menu-button Ar int -Set button to activate the menu. -.Pq default: 3 , usually the right button . +.Pp . -.It Cm --menu-ctrl-mask -Require CTRL+Button for menu activation. +Use +.Cm xrandr --listmonitors +to determine how Xinerama monitor IDs map to screens/monitors in your setup. . -.It Cm -4 , --prev-button Ar int -Set button to switch to the previous image in slideshow mode -.Pq default: 4 , usually Aq mousewheel up . . -.It Cm -5 , --next-button Ar int -Set button to switch to the next image in slideshow mode -.Pq default: 5 , usually Aq mousewheel down . +.It Cm --zoom Ar percent | Cm max | Cm fill . -.It Cm -8 , --rotate-button Ar int -Use CTRL+Button to rotate the current image -.Pq default : 2 . +Zoom images by +.Ar percent +when in full screen mode or when window geometry is fixed. +When combined with +.Cm --auto-zoom , +zooming will be limited to the specified +.Ar percent . +Specifying +.Cm max +is like setting +.Cm --auto-zoom , +using +.Cm fill +makes +.Nm +zoom the image like the +.Cm --bg-fill +mode. . -.It Cm --no-rotate-ctrl-mask -Don't require CTRL+Button for rotation - just use the button. +.It Cm --zoom-step Ar percent . -.It Cm -9 , --blur-button Ar int -Use CTRL+Button for blurring -.Pq default : 1 . +Zoom images in and out by +.Ar percent +.Pq default: 25 +when using the zoom keys and buttons. . -.It Cm --no-blur-ctrl-mask -Don't require CTRL+Button for blurring - just use the button. .El . -. .Sh MONTAGE MODE OPTIONS . -These additional options can be used for montage, collage and (partially) -thumbnail modes. +These additional options can be used for index, montage and +.Pq partially +thumbnail mode. . .Bl -tag -width indent . .It Cm -a , --alpha Ar int +. When drawing thumbnails onto the background, set their transparency level to .Ar int .Pq 0 - 255 . . -.It Cm -b , --bg Ar file No | Cm trans +.It Cm -b , --bg Ar file | Cm trans +. Use .Ar file -as background for your montage. With this option specified, the montage size -will default to the size of +as background for your montage. +With this option specified, the montage size will default to the size of .Ar file -if no size restrictions were specified. Alternatively, if +if no size restrictions were specified. +Alternatively, if .Ar file is .Qq trans , the background will be made transparent. . .It Cm -X , --ignore-aspect +. By default, the montage thumbnails will retain their aspect ratios, while -fitting into thumb-width/-height. This options forces them to be the size set -by +fitting into thumb-width/-height. +This options forces them to be the size set by .Cm --thumb-width No and Cm --thumb-height . This will prevent any empty space in the final montage. . .It Cm -H , --limit-height Ar pixels -Limit the height of the montage. These options can be used together to define -the image size exactly, or separately. If only one is specified, the other is -calculated from the number of files specified and the size of the thumbnails. -The default is to limit width to 800 pixels and calculate the height as -necessary. +. +Limit the height of the montage. . .It Cm -W , --limit-width Ar pixels -Limit the width of the montage. +. +Limit the width of the montage, defaults to 800 pixels. +. +.Pp +. +If both +.Cm --limit-width No and Cm --limit-height +are specified, the montage will be exactly +.Ar width No x Ar height +pixels in dimensions. . .It Cm -o , --output Ar file +. Save the created montage to .Ar file . . .It Cm -O , --output-only Ar file +. Just save the created montage to .Ar file without displaying it. . .It Cm -s , --stretch +. Normally, if an image is smaller than the specified thumbnail size, it will -not be enlarged. If this option is set, the image will be scaled up to fit -the thumnail size. Aspect ratio will be maintained unles +not be enlarged. +If this option is set, the image will be scaled up to fit the thumbnail size. +Aspect ratio will be maintained unless .Cm --ignore-aspect is specified. . .It Cm -E , --thumb-height Ar pixels +. Set thumbnail height. . .It Cm -y , --thumb-width Ar pixels +. Set thumbnail width. . -.It Cm -J , --thumb-redraw Ar n -Only relevant for -.Cm --thumbnails : -Redraw thumbnail window every -.Ar n -images. In -.Nm -<= 1.5, the thumbnail image used to be redrawn after every computed thumbnail -.Pq so, it updated immediately . -However, since the redrawing takes quite long -.Pq especially for thumbnail mode on a large filelist , -this turned out to be a major performance penalty. -As a workaround, the thumbnail image is redrawn every 10th image now by -default. Set -.Ar n No = 1 -to get the old behaviour, -.Ar n No = 0 -will only redraw once all thumbnails are loaded. .El . -.Sh INDEX MODE OPTIONS +. +.Sh INDEX AND THUMBNAIL MODE OPTIONS +. +In addition to +.Sx MONTAGE MODE OPTIONS +.Cm --alpha , --bg , --limit-height , --limit-width , --output , --output-only , +.Cm --thumb-height , --thumb-width , +the following options can be used. +. .Bl -tag -width indent . +.It Cm --index-info Ar format +. +Show image information based on +.Ar format +below thumbnails in index / thumbnail mode. +See +.Sx FORMAT SPECIFIERS . +May contain newlines. +. +Use +.Qq --index-info '' +to display thumbnails without any info text +. +.Pp +. +Note: If you specify image-related formats +.Pq such as \&%w or \&%s , +.Nm +needs to load all images to calculate the dimensions of its own window. +So when using them with many files, it will take a while before a +.Nm +window becomes visible. +Use +.Cm --preload +to get a progress bar. +. .It Cm -@ , --title-font Ar font +. Set font to print a title on the index, if no font is specified, no title will be printed. +. +.It Cm -J , --thumb-redraw Ar n +. +Redraw thumbnail window every +.Ar n +images while generating thumbnails. +Redrawing takes quite long, so the default is 10. +Set +.Ar n No = 1 +to update the thumbnail window immediately. +With +.Ar n No = 0 , +there will only be one redraw once all thumbnails are loaded. +. .El . +. .Sh BACKGROUND SETTING +. +In many desktop environments, .Nm -can also be used as a background setter. It will store the command line -necessary to set the background in -.Pa ~/.fehbg , -so to have your background restored everytime you start X, you can add -.Qq `cat ~/.fehbg` +can also be used as a background setter. +Unless you pass the +.Cm --no-fehbg +option, it will write a script to set the current background to +.Pa ~/.fehbg . +So to have your background restored every time you start X, you can add +.Qq ~/.fehbg & to your X startup script -.Pq like Pa ~/.xinitrc . +.Pq such as Pa ~/.xinitrc . +Note that the commandline written to +.Pa ~/.fehbg +always includes the +.Cm --no-fehbg +option to ensure that it is not inadvertently changed by differences in +X11 screen layout or similar. +. .Pp -The following options control how exactly the background is set. Each of them -takes exactly one file as argument. +. +Note that +.Nm +does not support setting the wallpaper of GNOME shell desktops. +In this environment, you can use +.Qq gsettings set org.gnome.desktop.background picture-uri file:/// Ns Ar path +instead. +. +.Pp +. +For +.Cm --bg-center , --bg-fill , +and +.Cm --bg-max , +you can use +.Cm --geometry +to specify an offset from one side of the monitor instead of centering the image. +Positive values will offset from the left/top side, negative values from the +bottom/right. ++0 and -0 are both valid and distinct values. +. +.Pp +. +Note that all options except +.Cm --bg-tile +support Xinerama. +For instance, if you have multiple monitors connected and use e.g. +.Cm --bg-center , +.Nm +will center or appropriately offset the image on each monitor. +You may even specify more than one file, in that case, the first file is set +on monitor 0, the second on monitor 1, and so on. +Use +.Cm xrandr --listmonitors +to determine how Xinerama monitor IDs map to screens / monitors in your setup. +. +.Pp +. +Use +.Cm --no-xinerama +to treat the whole X display as one monitor when setting wallpapers. +You may also use +.Cm --xinerama-index +to use +.Nm +as a background setter for a specific monitor. +. .Bl -tag -width indent . .It Cm --bg-center -Center the file on the background. If it is too small, it will be surrounded -by a black border +. +Center the file on the background. +If it is too small, it will be surrounded by a border as specified by +.Cm --image-bg . . .It Cm --bg-fill +. Like .Cm --bg-scale , -but preserves aspect ratio by zooming the image until it fits. Either a -horizontal or a vertical part of the image will be cut off +but preserves aspect ratio by zooming the image until it fits. +Either a horizontal or a vertical part of the image will be cut off . .It Cm --bg-max +. Like .Cm --bg-fill , -but scale the image to the maximum size that fits the screen with black borders on one side. +but scale the image to the maximum size that fits the screen with borders on one side. +The border color can be set using +.Cm --image-bg . . .It Cm --bg-scale +. Fit the file into the background without repeating it, cutting off stuff or -using borders. But the aspect ratio is not preserved either +using borders. +But the aspect ratio is not preserved either . .It Cm --bg-tile +. Tile .Pq repeat the image in case it is too small for the screen . +.It Cm --no-fehbg +. +Do not write a +.Pa ~/.fehbg +file +. .El . . .Sh FORMAT SPECIFIERS +. .Bl -tag -width indent +. +.It %a +. +Information about slideshow state (playing/paused) +. .It %f +. Image path/filename +. +.It %F +. +Escaped image path/filename +.Pq for use in shell commands +. +.It %g +. +w,h window dimensions in pixels (mnemonic: geometry) +. .It %h +. Image height +. .It %l +. Total number of files in filelist +. +.It %L +. +Temporary copy of filelist. +Multiple uses of %L within the same format string will return the same copy. +. .It %m +. Current mode +. .It %n +. Image name +. +.It \&%N +. +Escaped image name +. +.It %o +. +x,y offset of top-left image corner to window corner in pixels +. .It %p +. Number of image pixels +. .It \&%P -.Nm +. +Number of image pixels in human-readable format with k/M +.Pq kilopixels / megapixels +suffix +. +.It %r +. +Image rotation. +A half right turn equals pi. +. .It %s +. Image size in bytes +. +.It %S +. +Human-readable image size +.Pq kB / MB +. .It %t +. Image format +. .It %u +. Number of current file +. .It %w +. Image width +. +.\" .It %W +.\" . +.\" Window dimensions and offset as WxH+x+y +.\" .Pq X11 geometry format . +.\" Note that this is currently only properly updated when changing images; +.\" offsets for the first image after starting feh and after changing window +.\" geometry may be bogus. +. .It %v +. .Nm version +. +.It \&%V +. +Process ID +. +.It %z +. +Current image zoom, rounded to two decimal places +. +.It %Z +. +Current image zoom, higher precision +. +.It %% +. +A literal % +. .El . -.Sh CONFIG FILE SYNTAX -The config file allows the naming of option groups, called themes. -If -.Pa ~/.fehrc No or Pa /etc/fehrc -exist, -.Nm -will look in them for name/options pairs. -If neither of them exist, +. +.Sh CONFIGURATION +. .Nm -will create a default one in -.Pa ~/.fehrc . +has three config files: +.Pa themes +for theme definitions, +.Pa keys +for key bindings and +.Pa buttons +for mouse button bindings. +It will try to read them from +.Pa $XDG_CONFIG_HOME/feh/ , +which +.Pq when XDG_CONFIG_HOME is unset +defaults to +.Pa ~/.config/feh/ . +If the files are not found in that directory, it will also try +.Pa /etc/feh/ . +.Pp +All config files treat lines starting with a +.Qq # +character as comments. +Comments at the end of a line are not supported. +. +. +.Sh THEMES CONFIG SYNTAX +. +.Pa .config/feh/themes +allows the naming of option groups, called themes. +. .Pp +. It takes entries of the form .Qq Ar theme options ... , where @@ -631,18 +1441,43 @@ where is the name of the entry and .Ar options are the options which will be applied when the theme is used. +. .Pp -An example entry would be -.Qq imagemap -rVq --thumb-width 40 --thumb-height 30 . +. +Note that the option parser does not behave like a normal shell: filename +expansion and backslash escape sequences are not supported and passed to +feh's option parser as-is. +However, quoting of arguments is respected and can be used for arguments +with whitespace. +. +So, the sequence +.Qq --info Qq foo bar +works as intended +.Pq that is, it display the string Qq foo bar , +whereas the option string +.Qq --info foo\e bar +will only display +.Qq foo\e +and complain about the file bar not existing. +Please keep this in mind when writing theme files. +. .Pp -You cane use this theme in two ways. Either call +. +An example entry is +.Qq imagemap -rVq --thumb-width 40 --thumb-height 30 --index-info \&'%n\en\&%wx\&%h\&' . +. +.Pp +. +You can use this theme in two ways. +Either call .Qo .Nm -Timagemap *.jpg -.Qc +.Qc , or create a symbolic link to .Nm -with the name of the theme you want it to use. So from the example above: +with the name of the theme you want it to use. +For the example above, this would be .Qo ln -s `which .Nm @@ -651,278 +1486,804 @@ ln -s `which Now just run .Qq imagemap *.jpg to use these options. +. .Pp +. Note that you can split a theme over several lines by placing a backslash at -the end of a line, like in the shell. +the end of an unfinished line. +A single option-argument-pair must not span multiple lines. +A single line must not be longer than 1023 characters, but there's no upper +limit for the length of a theme. +. +.Pp +. +Command line options always override theme options. +. +. +.Sh KEYS CONFIG SYNTAX +. +.Pa .config/feh/keys +defines key bindings. +It has entries of the form +.Qq Ar action Op Ar key1 Op Ar key2 Op Ar key3 . +. .Pp -You can combine these themes with commandline options. An example .fehrc is -provided with a couple of cool example themes. +. +Each +.Ar key +is an X11 keysym name as shown by +.Xr xev 1 , +like +.Qq Delete . +It may optionally start with modifiers for things like Control, in which case +.Ar key +looks like +.Ar mod Ns No - Ns Ar keysym +.Po +for example +.Qq C-Delete +for Ctrl+Delete or +.Qq C-1-Delete +for Ctrl+Alt+Delete +.Pc +. +.Pp +. +Available modifiers are +.Ar C No for Control , +.Ar S No for Shift and +.Ar 1 , 4 No for Mod1 and Mod4 . +To match an uppercase letter like +.Qq S +instead of +.Qq s , +the Shift modifier is not required. +. +.Pp +. +Specifying an +.Ar action +without any keys unbinds it (i.e. the default bindings are removed). +. +.Pp +. +.Em Note:\& +Do not use the same keybinding for multiple actions. +When binding an action to a new key +.Pq or mouse button , +make sure to unbind it from its previous action, if present. +.Nm +does not check for conflicting bindings, so their behaviour is undefined. +. +.Pp +. +For a list of the +.Ar action +names, see +.Sx KEYS . +Note that not all of the key names used there correspond to X11 keysyms. +Most notably, page up +.Pq Prior / Page_Up , +page down +.Pq Next / Page_Down +and the keypad keys +.Pq KP_* +do not. +. . .Sh KEYS -In an image window, the following keys may be used: +. +The following actions and default key bindings can be used in an image window. +.Pq The strings in Bo square brackets Bc are the config action names . +. +If +.Nm +is running inside a terminal and its standard input is not used for images or +filelists, key input from the terminal is also accepted. +However, terminal input support is currently limited to most alphanumeric +characters +.Pq 0-9 a-z A-Z and some more , +arrow keys, return and backspace. +The Alt +.Pq Mod1 +modifier is also supported. +. .Bl -tag -width indent -.It a +. +.It a Bq toggle_actions +. Toggle actions display .Pq see Cm --draw-actions -.It c -Caption entry mode. If +. +.It A Bq toggle_aliasing +. +Enable/Disable anti-aliasing +. +.It c Bq toggle_caption +. +Caption entry mode. +If .Cm --caption-path -has been specified, then this enables caption editing. The caption at the -bottom of the screen will turn yellow and can be edited. Hit return to confirm -and save the caption, or escape to cancel editing. Note that you can insert -an actual newline into the caption using -.Aq CTRL+return . -.It d +has been specified, then this enables caption editing. +The caption at the bottom of the screen will turn yellow and can be edited. +Hit return to confirm and save the caption, or escape to cancel editing. +Note that you can insert an actual newline into the caption using +.Aq Ctrl+return . +. +.It d Bq toggle_filenames +. Toggle filename display .Pq see Cm --draw-filename -.It f -Save the current filelist to a unique filename -.It h -Pause/Continue the slideshow. When it is paused, it will not automatically -change slides based on +. +.It e Bq toggle_exif +. +.Pq optional feature, $MAN_EXIF$ in this build +Toggle EXIF tag display +. +.It f Bq toggle_fullscreen +. +Toggle fullscreen +. +.It g Bq toggle_fixed_geometry +. +Enable/Disable automatic window resize when changing images. +. +.It h Bq toggle_pause +. +Pause/Continue the slideshow. +When it is paused, it will not automatically change slides based on .Cm --slideshow-delay . -.It m -Show menu. Use the arrow keys and return to select items, +. +.It i Bq toggle_info +. +Toggle info display +.Pq see Cm --info +. +.It k Bq toggle_keep_vp +. +Toggle zoom and viewport keeping. +When enabled, +.Nm +will keep zoom and X, Y offset when switching images. +. +.It L Bq save_filelist +. +Save the current filelist as +.Qq feh_PID_ID_filelist . +It is saved in the directory specified by +.Cm --output-dir , +if set, and in the current working directory otherwise. +. +.It m Bq toggle_menu +. +Show menu. +Use the arrow keys and return to select items, and .Aq escape to close the menu. -.It n , Ao Space Ac , Aq Right -Show next image -.It o +. +.It n , Ao space Ac , Ao Right Ac Bq next_img +. +Show next image. +Selects the next image in thumbnail mode. +. +.It o Bq toggle_pointer +. Toggle pointer visibility -.It p , Ao Backspace Ac , Aq Left -Show previous image -.It q -Quit feh -.It r -Reload current image. Useful for webcams -.It s -Save the current image to a unique filename -.It v -Toggle fullscreen -.It w +. +.It p , Ao BackSpace Ac , Ao Left Ac Bq prev_img +. +Show previous image. +Selects the previous image in thumbnail mode. +. +.It q , Ao Escape Ac Bq quit +. +Quit +.Nm +. +.It r Bq reload_image +. +Reload current image. +Useful for webcams +. +.It s Bq save_image +. +Save the current image as +.Qq feh_PID_ID_FILENAME . +It is saved in the directory specified by +.Cm --output-dir , +if set, and in the current working directory otherwise. +. +.It w Bq size_to_image +. Change window size to fit current image size -.It x +.Pq plus/minus zoom, if set . +In scale-down and fixed-geometry mode, this also updates the window size limits. +. +.It x Bq close +. Close current window -.It z +. +.It z Bq jump_random +. Jump to a random position in the current filelist -.It < , > -In place editing - rotate the images 90 degrees (counter)clockwise. -The rotation is lossless, but may create artifacts in some image corners when -used with JPEG images. Rotating in the reverse direction will make them go -away. See +. +.It Z Bq toggle_auto_zoom +. +Toggle auto-zoom. +. +.It \&[, \&] Bq prev_dir, next_dir +. +Jump to the first image of the previous or next sequence of images sharing +a directory name in the current filelist. +Use --sort dirname if you would like to ensure that all images in a directory +are grouped together. +. +.It < , > Bq orient_3 , orient_1 +. +rotate the image 90 degrees (counter)clockwise. +. +.Pp +. +When +.Cm --edit +is used, this also rotates the image in the underlying file. +Rotation is lossless, but may create artifacts in some image corners when +used with JPEG images. +Rotating in the reverse direction will make them go away. +See .Xr jpegtran 1 for more about lossless JPEG rotation. -.It 0 .. 9 +. +.Em Note:\& +.Nm +assumes that this feature is used to normalize image orientation. +For JPEG images, it will unconditionally set the EXIF orientation +tag to 1 +.Pq Qq 0,0 is top left +after every rotation. +See +.Xr jpegexiforient 1 +for details on how to change this flag. +. +.It _ Bq flip +. +Vertically flip image. +When +.Cm --edit +is used, this also flips the image in the underlying file +.Pq see above . +. +.It | Bq mirror +. +Horizontally flip image. +When +.Cm --edit +is used, this also flips the image in the underlying file +.Pq see above . +. +.It 0 .. 9 Bq action_0 .. action_9 +. Execute the corresponding action .Pq 0 = Cm --action , No 1 = Cm --action1 No etc. -.It Aq Return +. +.It Ao Return Ac Bq action_0 +. Run the command defined by .Cm --action -.It Aq home +. +.It Ao Home Ac Bq jump_first +. Show first image -.It Aq end +. +.It Ao End Ac Bq jump_last +. Show last image -.It Aq page up +. +.It Ao page up Ac Bq jump_fwd +. Go forward ~5% of the filelist -.It Aq page down +. +.It Ao page down Ac Bq jump_back +. Go backward ~5% of the filelist -.It Aq escape -Quit the slideshow -.It + -Increase reload delay -.It - -Decrease reload delay -.It Aq delete +. +.It + Bq reload_plus +. +Increase reload delay by 1 second +. +.It - Bq reload_minus +. +Decrease reload delay by 1 second +. +.It Ao Delete Ac Bq remove +. Remove current file from filelist -.It Aq CTRL+delete +. +.It Ao Ctrl+Delete Ac Bq delete +. Remove current file from filelist and delete it -.It Ao keypad left Ac , Ao Ctrl+Left Ac +. +.It Ao keypad Left Ac , Ao Ctrl+Left Ac Bq scroll_left +. Scroll to the left -.It Ao keypad right Ac , Ao Ctrl+Right Ac +. +.It Ao keypad Right Ac , Ao Ctrl+Right Ac Bq scroll_right +. Scroll to the right -.It Ao keypad up Ac , Ao Ctrl+Up Ac +. +.It Ao keypad up Ac , Ao Ctrl+Up Ac Bq scroll_up +. Scroll up -.It Ao keypad down Ac , Ao Ctrl+Down Ac -Scroll down -.It Aq keypad begin -Antialias the image -.It Ao keypad + Ac , Ao Up Ac +. +.It Ao keypad Down Ac , Ao Ctrl+Down Ac Bq scroll_down +. +Scroll down. +Note that the scroll keys work without anti-aliasing for performance reasons; +hit the render key after scrolling to anti-alias the image. +. +.It Aq Alt+Left Bq scroll_left_page +. +Scroll to the left by one page +. +.It Aq Alt+Right Bq scroll_right_page +. +Scroll to the right by one page +. +.It Aq Alt+Up Bq scroll_up_page +. +Scroll up by one page +. +.It Aq Alt+Down Bq scroll_down_page +. +Scroll down by one page +. +.It R, Ao keypad begin Ac Bq render +. +Anti-alias the image. +Opens the currently selected image in thumbnail mode. +. +.It Ao keypad + Ac , Ao Up Ac Bq zoom_in +. Zoom in -.It Ao keypad - Ac , Ao Down Ac +. +.It Ao keypad - Ac , Ao Down Ac Bq zoom_out +. Zoom out -.It Aq keypad * +. +.It *, Ao keypad * Ac Bq zoom_default +. Zoom to 100% -.It Aq keypad / +. +.It /, Ao keypad / Ac Bq zoom_fit +. Zoom to fit the window size +. +.It ! Bq zoom_fill +. +Zoom to fill the window size like +.Cm --bg-fill +. +.El +. +.Ss MENU KEYS +. +The following keys bindings are used for the +.Nm +menu: +. +.Bl -tag -width indent +. +.It Ao Escape Ac Bq menu_close +. +Close the menu +. +.It Ao Up Ac Bq menu_up +. +Highlight previous menu item +. +.It Ao Down Ac Bq menu_down +. +Highlight next menu item +. +.It Ao Left Ac Bq menu_parent +. +Highlight parent menu item +. +.It Ao Right Ac Bq menu_child +. +Highlight child menu item +. +.It Ao Return Ac , Ao space Ac Bq menu_select +. +Select highlighted menu item +. +.El +. +. +.Sh BUTTONS CONFIG SYNTAX +. +.Pa .config/feh/buttons . +This works like the keys config file: the entries are of the form +.Qq Ar action Op Ar binding . +. +.Pp +. +Each +.Ar binding +is a button number. +It may optionally start with modifiers for things like Control, in which case +.Ar binding +looks like +.Ar mod Ns No - Ns Ar button +.Pq for example "C-1" for Ctrl + Left button . +. +.Pp +. +.Em Note:\& +Do not use the same button for multiple actions. +.Nm +does not check for conflicting bindings, so their behaviour is undefined. +. +Either unbind the unwanted action, or bind it to another unused button. +. +The order in which you bind / unbind does not matter, though. +. +.Pp +. +For the available modifiers, see +.Sx KEYS CONFIGURATION SYNTAX . +. +. +.Ss BUTTONS +. +In an image window, the following buttons may be used +.Pq The strings in Bo square brackets Bc are the config action names . +Additionally, all actions specified in the +.Sx KEYS +section can also be bound to a button. +. +.Bl -tag -width indent +. +.It unbound Bq reload +. +Reload current image +. +.It 0 Ao cursor movement while not panning, zooming, or similar Ac +. +Does not have a default binding. +By binding it to +.Cm quit , +you can turn feh into a simple screensaver. +. +.It 1 Ao left mouse button Ac Bq pan +. +pan the current image +. +.It 2 Ao middle mouse button Ac Bq zoom +. +Zoom the current image +. +.It 3 Ao right mouse button Ac Bq toggle_menu +. +Toggle menu +. +.It 4 Ao mousewheel down Ac Bq prev_img +. +Show previous image +. +.It 5 Ao mousewheel up Ac Bq next_img +. +Show next image +. +.It Ctrl+1 Bq blur +. +Blur current image +. +.It Ctrl+2 Bq rotate +. +Rotate current image +. +.It unbound Bq zoom_in +. +Zoom in +. +.It unbound Bq zoom_out +. +Zoom out +. +. .El . +. .Sh MOUSE ACTIONS -When viewing an image, by default mouse button 1 pans -.Pq moves the image around +. +Default Bindings: +When viewing an image, mouse button 1 pans the image +.Pq moves it around or, when only clicked, moves to the next image -.Pq slideshow mode only ; -button 2 zooms +.Pq slideshow mode only . +Quick drags with less than 2px of movement per axis will be treated as clicks +to aid graphics tablet users. +. +Mouse button 2 zooms .Po click and drag left->right to zoom in, right->left to zoom out, click once to restore zoom to 100% -.Pc ; +.Pc and mouse button 3 opens the menu. +. .Pp -CTRL+Button 1 blurs or sharpens the image +. +Ctrl+Button 1 blurs or sharpens the image .Pq drag left to blur, right to sharpen ; -CTRL+Button 2 rotates the image around the center point. +Ctrl+Button 2 rotates the image around the center point. +. .Pp +. A note about pan and zoom modes: In pan mode, if you reach a window border but haven't yet panned to the end of the image, .Nm will warp your cursor to the opposite border so you can continue panning. +. .Pp +. When clicking the zoom button and immediately releasing it, the image will be -back at 100% zoom. When clicking it and moving the mouse while holding the -button down, the zoom will be continued at the previous zoom level. The zoom -will always happen so that the pixel on which you entered the zoom mode -remains stationary. So, to enlarge a specific part of an image, click the -zoom button on that part. +back at 100% zoom. +When clicking it and moving the mouse while holding the button down, the zoom +will be continued at the previous zoom level. +The zoom will always happen so that the pixel on which you entered the zoom mode +remains stationary. +So, to enlarge a specific part of an image, click the zoom button on that part. +. . .Sh SIGNALS -In slideshow mode, +. +In slideshow and multiwindow mode, .Nm handles the following signals: +. .Bl -tag -width indent +. .It Dv SIGUSR1 -Switch to next image +. +Slideshow mode: switch to next image; +reload current image if the slideshow consists of a single file. +Multiwindow mode: reload all images. +. .It Dv SIGUSR2 -Switch to previous image +. +Slideshow mode: switch to previous image; +reload current image if the slideshow consists of a single file. +Multiwindow mode: reload all images. +. .El . +. .Sh USAGE EXAMPLES -Here are some examples of useful option combinations +. +Here are some examples of useful option combinations. +See also: +.Aq https://feh.finalrewind.org/examples/ +. .Bl -tag -width indent . -.It feh /opt/images -Show all images in /opt/images +.It feh ~/Pictures +. +Show all images in ~/Pictures +. +.It feh -r ~/Pictures . -.It feh -r /opt/images -Recursively show all images found in /opt/images and subdirectories +Recursively show all images found in ~/Pictures and subdirectories . -.It feh -rSfilename /opt/images -Same as above, but sort by filename. By default, feh will show files in the -order it finds them on the hard disk, which is usually somewhat random. +.It feh -rSfilename --version-sort ~/Pictures +. +Same as above, but sort naturally. +By default, feh will show files in the string order of their names, meaning e.g. +.Qq foo 10.jpg +will come before +.Qq foo 2.jpg . +In this case, they are instead ordered as a human would expect. +. +.It feh -t -Sfilename -E 128 -y 128 -W 1024 ~/Pictures . -.It feh -t -Sfilename -E 128 -y 128 -W 1024 /opt/images Show 128x128 pixel thumbnails, limit window width to 1024 pixels. . -.It feh -t -Sfilename -E 128 -y 128 -W 1024 -P -C /usr/share/fonts/truetype/ttf-dejavu/ -e DejaVuSans/8 /opt/images -Same as above, but enable thumbnail caching in ~/.thumbnails and use a smaller -font. +.It feh -t -Sfilename -E 128 -y 128 -W 1024 -P -C /usr/share/fonts/truetype/ttf-dejavu/ -e DejaVuSans/8 ~/Pictures +. +Same as above, but enable thumbnail caching and use a smaller font. +. +.It feh -irFarial/14 -O index.jpg ~/Pictures +. +Make an index print of ~/Pictures and all directories below it, using 14 point +Arial to write the image info under each thumbnail. +Save the image as index.jpg and don't display it, just exit. +Note that this even works without a running X server +. +.It feh --unloadable -r ~/Pictures . -.It feh -irFarial/14 -O index.jpg /opt/images -Make an index print of /opt/images and all directories below it, using 14 point -Arial to write the image info under each thumbnail. Save the image as -index.jpg and don't display it, just exit. Note that this even works without -a running X server +Print all unloadable images in ~/Pictures, recursively . -.It feh --unloadable -r /opt/images -Print all unloadable images in /opt/images, recursively +.It feh -f by_width -S width --reverse --list \&. . -.It feh -w /opt/images/holidays -Open each image in /opt/images/holidays in its own window +Write a list of all images in the directory to by_width, sorted by width +.Pq widest images first +. +.It feh -w ~/Pictures/holidays +. +Open each image in ~/Pictures/holidays in its own window +. +.It feh -FD5 -Sname ~/Pictures/presentation . -.It feh -FD5 -Sname /opt/images/presentation Show the images in .../presentation, sorted by name, in fullscreen, automatically change to the next image after 5 seconds . -.It feh -rSwidth -A Qo mv '%f' ~/images/'%n' Qc /opt/images -View all images in /opt/images and below, sorted by width, move an image to +.It feh -rSwidth -A Qo mv %F ~/images/\&%N Qc ~/Pictures +. +View all images in ~/Pictures and below, sorted by width, move an image to ~/image/image_name when enter is pressed . -.It feh --start-at ./foo.jpg \&. -View all images in the current directory, starting with foo.jpg. All other -images are still in the slideshow and can be viewed normally +.It feh --start-at ~/Pictures/foo.jpg +. +View all images in ~/Pictures, starting with foo.jpg. +All other images are still in the slideshow and can be viewed normally . -.It feh --start-at foo.jpg * -Same as above +.It feh --start-at ~/Pictures/foo.jpg ~/Pictures +. +Same as above. +. +.It feh --info \&"exifgrep '\&(Model\&|DateTimeOriginal\&|FNumber\&|ISO\&|Flash\&)' %F \&| cut -d \&. -f 4-\&" \&. . -.It feh --info \&"exifgrep '\&(Model\&|DateTimeOriginal\&|FNumber\&|ISO\&|Flash\&)' '%f' \&| cut -d \&. -f 4-\&" \&. Show some EXIF information, extracted by exifprobe/exifgrep +. +.It feh --action 'rm %F' -rl --max-dimension 1000x800 +. +Recursively remove all images with dimensions below or equal to 1000x800 pixels +from the current directory. +. +.It feh -L '%w %h %f' \&| awk '{ if \&($1 > $2\&) { print $0 } }' \&| cut -d ' ' -f 3- \&| feh -f - +. +Show landscape pictures +.Pq image width greater than height +in the current directory. +. .El . . .Sh DEPENDENCIES +. +When +.Cm --edit +is used, .Nm -requires the +needs the .Cm jpegtran -binary +and +.Cm jpegexiforient +binaries .Pq usually distributed in Qo libjpeg-progs Qc or similar -for lossless rotation. +for lossless JPEG rotation. +. +.Pp +. +To view images from URLs such as "http://", +.Nm +must be compiled with libcurl support. +It is $MAN_CURL$ in this build. +. +. +.Sh KNOWN BUGS +. +Imlib2 releases prior to 1.7.4 are unable to load gif, tiff, and webp images +if the filename does not end with gif, tiff, or webp, respectively. +Notably, this means that feh is unable to show gif, tiff, and webp images from +stdin or network URLs on systems using an Imlib2 version older than 1.7.4. +. .Pp -To view images from URLs such as http://, you need to have -.Cm wget -available or use the builtin HTTP client -.Pq see Cm --builtin . . -.Sh BUGS -Xinerama support does not really work on Xinerama screens != 0. +Imlib2 releases 1.7.5+ automatically rotate images based on their EXIF tags. +When combined with the +.Cm --auto-rotate +option, this causes images to be rotated twice and end up in an incorrect orientation. +As a workaround, do not use +.Cm --auto-rotate +on systems with Imlib2 version 1.7.5 or later. .Pp +. +On systems with giflib 5.1.2, +.Nm +may be unable to load gif images. +For affected mips, mipsel and arm devices, gif support is completely +broken, while on x86 / x86_64 gifs can usually only be loaded if they are +the first image in the filelist. +This appears to be a bug in giflib, +see +.Aq https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=813729 +for details. +Workaround: Use +.Cm --conversion-timeout 5 +.Pq or some other positive value +to load gifs with imagemagick instead, or downgrade to giflib 5.1.1, or +upgrade to giflib 5.1.4. +. +.Pp +. +While loading images using libcurl, +.Nm +will not react to key or mouse actions. +. +.Pp +. Thumbnail mode is somewhat inefficient, and because of that not nearly as fast as it could be. +. .Pp +. .Cm --scale-down -does not support zooming. +does not take window decorations into account and may therefore make the +window slightly too large. . .Ss REPORTING BUGS +. If you find a bug, please report it to -.Aq derf@finalrewind.org +.Aq derf+feh@finalrewind.org or via -.Aq http://github.com/derf/feh/issues +.Aq https://github.com/derf/feh/issues . +. .Pp +. Please include the feh version .Aq the output of Qq feh --version , -steps to reproduce the bug and (if necessary), images to reproduce it. -. -.Sh FUTURE PLANS -Plans for the following releases: -.Bl -bullet -compact -. -.It -Switch to real config file from fehrc theme definitions +steps to reproduce the bug and, if necessary, images to reproduce it. +Note that +.Nm +is a hobby project, so bug reports may be addressed with significant delays. . -.It -Add config options to rebind keys + proper mouse action rebinding . -.It -Make zoom options more intuitive -. -.El +.Sh LICENSE . +Copyright (C) 1999, 2000 by Paul Duncan. +Copyright (C) 1999, 2000 by Tom Gilbert and contributors. +Copyright (C) 2010-2025 by Birte Kristina Friesel and contributors. . -.Sh LICENSE -Copyright (C) 1999, 2000 by Tom Gilbert (and various contributors). -Copyright (C) 2010 by Daniel Friesel (and even more contributors). .Pp +. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +. .Pp +. The above copyright notice and this permission notice shall be included in all copies of the Software and its documentation and acknowledgment shall be given in the documentation and software packages that this Software was used. +. .Pp +. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +. .Pp -Current developer: Daniel Friesel +. +Current developer: Birte Friesel .Aq derf@finalrewind.org +. .Pp +. Original author .Pq no longer developing : Tom Gilbert .Aq feh_sucks@linuxbrit.co.uk +. +.Pp +. +Website: +https://feh.finalrewind.org diff --git a/man/gen-cam-menu.pre b/man/gen-cam-menu.pre deleted file mode 100644 index 8e6b684..0000000 --- a/man/gen-cam-menu.pre +++ /dev/null @@ -1,24 +0,0 @@ -.Dd $DATE$ -.Dt GEN-CAM-Menu 1 -.Os -. -.Sh NAME -.Nm gen-cam-menu -.Nd utility for updating Enlightenment user menus for feh-cam -.Sh SYNOPSIS -.Nm -. -.Sh VERSION -This manual documents gen-cam-menu, shipped with feh version $VERSION$ -. -.Sh DESCRIPTION -.Nm -is a shell script which creates Enlightenment user menu entries for the -bookmarks stored by -.Xr feh-cam 1 -in -.Pa ~/.enlightenment/ . -. -.Sh SEE ALSO -.Xr feh 1 , -.Xr feh-cam 1 diff --git a/scripts/checkkeys.pl b/scripts/checkkeys.pl new file mode 100755 index 0000000..ecf01e0 --- /dev/null +++ b/scripts/checkkeys.pl @@ -0,0 +1,91 @@ +#!/usr/bin/env perl +## Copyright © 2011 by Birte Kristina Friesel <derf@finalrewind.org> +## License: WTFPL: +## 0. You just DO WHAT THE FUCK YOU WANT TO. +use strict; +use warnings; +use 5.010; + +use autodie; + +my $fh; +my $keys; + +my $re_struct = qr{ + struct \s __fehkey \s (?<action> [^;]+ ) ; +}x; + +my $re_set = qr{ + feh_set_kb \( \& keys \. (?<action> [^ ,]+ ) \s* , + \s* (?<mod1> \d+ ) , + \s* (?<key1> [^ ,]+ ) \s* , + \s* (?<mod2> \d+ ) , + \s* (?<key2> [^ ,]+ ) \s* , + \s* (?<mod3> \d+ ) , + \s* (?<key3> [^ ,]+ ) \s* \) ; +}x; + +my $re_parse_action = qr{ + if \s \( \! strcmp \( action , \s " (?<action> [^"]+ ) " \) \) +}x; + +my $re_parse_conf = qr{ + cur_kb \s = \s \& keys \. (?<str> [^;]+ ) ; +}x; + +my $re_man = qr{ + ^ \. It \s (?<keys> .+ ) \s Bq \s (?<action> .+ ) $ +}x; + +my $re_skip = qr{ + ^ ( action | orient ) _ +}x; + +open($fh, '<', 'src/options.h'); +while (my $line = <$fh>) { + if ($line =~ $re_struct) { + $keys->{ $+{action} }->{struct} = 1; + } +} +close($fh); + +open($fh, '<', 'src/keyevents.c'); +while (my $line = <$fh>) { + if ($line =~ $re_set) { + $keys->{ $+{action} }->{default} + = [@+{'mod1', 'key1', 'mod2', 'key2', 'mod3', 'key3'}]; + } + elsif ($line =~ $re_parse_action) { + $keys->{ $+{action} }->{parse} = 1; + } +} +close($fh); + +open($fh, '<', 'man/feh.pre'); +while (my $line = <$fh>) { + if ($line =~ $re_man) { + $keys->{ $+{action} }->{man} = 1; + } +} +close($fh); + +for my $action (sort keys %{$keys}) { + my $k = $keys->{$action}; + + if ($action =~ $re_skip) { + next; + } + + if (not defined $k->{struct}) { + say "$action missing in struct"; + } + if (not defined $k->{default}) { + say "$action missing in defaults"; + } + if (not defined $k->{parse}) { + say "$action missing in parser"; + } + if (not defined $k->{man}) { + say "$action missing in manual"; + } +} diff --git a/scripts/checkopts.pl b/scripts/checkopts.pl index ec82343..e5779ca 100755 --- a/scripts/checkopts.pl +++ b/scripts/checkopts.pl @@ -1,5 +1,5 @@ #!/usr/bin/env perl -## Copyright © 2010 by Daniel Friesel <derf@derf.homelinux.org> +## Copyright © 2010 by Birte Kristina Friesel <derf@finalrewind.org> ## License: WTFPL <http://sam.zoy.org/wtfpl> use strict; use warnings; diff --git a/scripts/lskeys.pl b/scripts/lskeys.pl new file mode 100755 index 0000000..530e38d --- /dev/null +++ b/scripts/lskeys.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use 5.010; + +use autodie; + +my @keys; + +open(my $fh, '<', 'src/keyevents.c'); +while (my $line = <$fh>) { + chomp($line); + if ($line =~ qr{ + if \s \( \! strcmp \( action , \s " (?<key> [^"]+ ) " \)\) }x) { + push(@keys, $+{key}); + } +} +close($fh); + +say join("\n", sort @keys); diff --git a/scripts/update-todo.sh b/scripts/update-todo.sh new file mode 100755 index 0000000..962ad70 --- /dev/null +++ b/scripts/update-todo.sh @@ -0,0 +1,4 @@ +#!/bin/sh -e + +ghi --repo=derf/feh --verbose list > TODO +git commit -m 'Update TODO (via github)' TODO diff --git a/share/applications/Makefile b/share/applications/Makefile new file mode 100644 index 0000000..d57e33d --- /dev/null +++ b/share/applications/Makefile @@ -0,0 +1,24 @@ +include ../../config.mk + +SOURCES = ${shell echo *.pre} +TARGETS = ${SOURCES:.pre=.desktop} + +all: ${TARGETS} + +.pre.desktop: + sed \ + -e 's/\$$VERSION\$$/${VERSION}/g' \ + -e 's/\$$DATE\$$/'"$$(date '+%B %d, %Y')"/g \ + -e 's/\$$MAN_CURL\$$/${MAN_CURL}/' \ + -e 's/\$$MAN_DEBUG\$$/${MAN_DEBUG}/' \ + -e 's/\$$MAN_EXIF\$$/${MAN_EXIF}/' \ + -e 's/\$$MAN_XINERAMA\$$/${MAN_XINERAMA}/' \ + -e 's:\$$IMAGEDIR\$$:${image_dir}:' \ + < ${@:.desktop=.pre} > $@ + +clean: + rm -f *.desktop + +.SUFFIXES: .pre .desktop + +.PHONY: clean diff --git a/share/applications/feh.pre b/share/applications/feh.pre new file mode 100644 index 0000000..8007630 --- /dev/null +++ b/share/applications/feh.pre @@ -0,0 +1,13 @@ +[Desktop Entry] +Name=Feh +Name[en_US]=feh +GenericName=Image viewer +GenericName[en_US]=Image viewer +Comment=Image viewer and cataloguer +Exec=feh --start-at %u +Terminal=false +Type=Application +Icon=feh +Categories=Graphics;2DGraphics;Viewer; +MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/pjpeg;image/png;image/tiff;image/webp;image/x-bmp;image/x-pcx;image/x-png;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-tga;image/x-xbitmap;image/heic; +NoDisplay=true diff --git a/data/fonts/black.style b/share/fonts/black.style index d2a86a9..21827d3 100644 --- a/data/fonts/black.style +++ b/share/fonts/black.style @@ -1,4 +1,3 @@ #Style #NAME Menu -255 255 255 64 2 2 255 255 255 255 0 0 diff --git a/data/fonts/menu.style b/share/fonts/menu.style index 8557360..e448873 100644 --- a/data/fonts/menu.style +++ b/share/fonts/menu.style @@ -1,4 +1,3 @@ #Style #NAME Menu -0 0 0 64 2 2 0 0 0 0 0 0 diff --git a/data/fonts/yudit.ttf b/share/fonts/yudit.ttf Binary files differindex 8099e63..8099e63 100644 --- a/data/fonts/yudit.ttf +++ b/share/fonts/yudit.ttf diff --git a/share/images/feh.png b/share/images/feh.png Binary files differnew file mode 100644 index 0000000..85d4bbd --- /dev/null +++ b/share/images/feh.png diff --git a/share/images/feh.svg b/share/images/feh.svg new file mode 100644 index 0000000..66f3f1c --- /dev/null +++ b/share/images/feh.svg @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="100" + height="100" + version="1.1" +> + <defs> + <linearGradient + id="linear_gradient_for_letters" + x1="60.296875" + y1="1026.6521" + x2="141.11719" + y2="1026.6521" + gradientUnits="userSpaceOnUse" + > + <stop + offset="0" + stop-color="#cd022f" + stop-opacity="1" + /> + <stop + offset="1" + stop-color="#cd022f" + stop-opacity="0" + /> + </linearGradient> + <filter + id="filter_for_shadow" + width="1.5" + height="1.5" + x="-0.25" + y="-0.25" + > + <feGaussianBlur + in="SourceAlpha" + stdDeviation="1" + result="blur" + /> + <feColorMatrix + result="bluralpha" + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.9 0" + /> + <feOffset + in="bluralpha" + dx="2" + dy="2" + result="offsetBlur" + /> + <feMerge> + <feMergeNode + in="offsetBlur" + /> + <feMergeNode + in="SourceGraphic" + /> + </feMerge> + </filter> + </defs> + <g + transform="translate(-50.707033,-976.6521)" + opacity="1" + fill-opacity="1" + fill-rule="nonzero" + stroke="none" + > + <!-- shadows --> + <g + fill="black" + filter="url(#filter_for_shadow)" + > + <!-- shadow for letter "F" --> + <path + d="m 60.296875,1012.072 20.292969,0 0,5.6836 -12.773438,0 0,5.4297 12.011719,0 0,5.6836 -12.011719,0 0,12.3633 -7.519531,0 0,-29.1602" + /> + <!-- shadow for letter "E" --> + <path + d="m 87.640625,1012.072 20.292965,0 0,5.6836 -12.77343,0 0,5.4297 12.01172,0 0,5.6836 -12.01172,0 0,6.6797 13.20312,0 0,5.6836 -20.722655,0 0,-29.1602" + /> + <!-- shadow for letter "H" --> + <path + d="m 114.98438,1012.072 7.51953,0 0,11.1133 11.09375,0 0,-11.1133 7.51953,0 0,29.1602 -7.51953,0 0,-12.3633 -11.09375,0 0,12.3633 -7.51953,0 0,-29.1602" + /> + </g> + <!-- letters --> + <g + fill="url(#linear_gradient_for_letters)" + > + <!-- letter "F" --> + <path + d="m 60.296875,1012.072 20.292969,0 0,5.6836 -12.773438,0 0,5.4297 12.011719,0 0,5.6836 -12.011719,0 0,12.3633 -7.519531,0 0,-29.1602" + /> + <!-- letter "E" --> + <path + d="m 87.640625,1012.072 20.292965,0 0,5.6836 -12.77343,0 0,5.4297 12.01172,0 0,5.6836 -12.01172,0 0,6.6797 13.20312,0 0,5.6836 -20.722655,0 0,-29.1602" + /> + <!-- letter "H" --> + <path + d="m 114.98438,1012.072 7.51953,0 0,11.1133 11.09375,0 0,-11.1133 7.51953,0 0,29.1602 -7.51953,0 0,-12.3633 -11.09375,0 0,12.3633 -7.51953,0 0,-29.1602" + /> + </g> + </g> +</svg> diff --git a/share/images/menubg_default.png b/share/images/menubg_default.png Binary files differnew file mode 100644 index 0000000..7f3b6a9 --- /dev/null +++ b/share/images/menubg_default.png diff --git a/src/Makefile b/src/Makefile index 84ee7ab..2968671 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,41 @@ include ../config.mk -TARGETS = ${shell echo *.c} +TARGETS = \ + events.c \ + feh_png.c \ + filelist.c \ + gib_hash.c \ + gib_imlib.c \ + gib_list.c \ + gib_style.c \ + imlib.c \ + index.c \ + keyevents.c \ + list.c \ + main.c \ + md5.c \ + menu.c \ + multiwindow.c \ + options.c \ + signals.c \ + slideshow.c \ + thumbnail.c \ + timers.c \ + utils.c \ + wallpaper.c \ + winwidget.c + +ifeq (${exif},1) + TARGETS += \ + exif.c \ + exif_canon.c \ + exif_nikon.c +endif + +ifneq (${verscmp},1) + TARGETS += strverscmp.c +endif + OBJECTS = ${TARGETS:.c=.o} I_SRCS = ${shell echo *.raw} @@ -17,9 +52,9 @@ include deps.mk fehrc.inc: fehrc.raw help.inc: help.raw - +# CFLAGS might contain include paths needed to resolve includes in headers deps.mk: ${TARGETS} ${I_DSTS} - ${CC} -MM ${TARGETS} > $@ + ${CC} ${CFLAGS} -MM ${TARGETS} > $@ clean: rm -f feh *.o *.inc diff --git a/src/collage.c b/src/collage.c deleted file mode 100644 index 782d5f8..0000000 --- a/src/collage.c +++ /dev/null @@ -1,210 +0,0 @@ -/* collage.c - -Copyright (C) 1999-2003 Tom Gilbert. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies of the Software and its documentation and acknowledgment shall be -given in the documentation and software packages that this Software was -used. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "feh.h" -#include "winwidget.h" -#include "filelist.h" -#include "options.h" - -void init_collage_mode(void) -{ - Imlib_Image im_main; - Imlib_Image im_temp; - int ww, hh, www, hhh, xxx, yyy; - int w = 800, h = 600; - int bg_w = 0, bg_h = 0; - winwidget winwid = NULL; - Imlib_Image bg_im = NULL, im_thumb = NULL; - feh_file *file = NULL; - unsigned char trans_bg = 0; - gib_list *l, *last = NULL; - char *s; - - mode = "collage"; - - /* Use bg image dimensions for default size */ - if (opt.bg && opt.bg_file) { - if (!strcmp(opt.bg_file, "trans")) - trans_bg = 1; - else { - - D(("Time to apply a background to blend onto\n")); - if (feh_load_image_char(&bg_im, opt.bg_file) != 0) { - bg_w = gib_imlib_image_get_width(bg_im); - bg_h = gib_imlib_image_get_height(bg_im); - } - } - } - - if (!opt.limit_w || !opt.limit_h) { - if (bg_im) { - if (opt.verbose) - fprintf(stdout, - PACKAGE - " - No size restriction specified for collage.\n" - " You did specify a background however, so the\n" - " collage size has defaulted to the size of the image\n"); - opt.limit_w = bg_w; - opt.limit_h = bg_h; - } else { - if (opt.verbose) - fprintf(stdout, - PACKAGE - " - No size restriction specified for collage.\n" - " - For collage mode, you need to specify width and height.\n" - " Using defaults (width 800, height 600)\n"); - opt.limit_w = 800; - opt.limit_h = 600; - } - } - - w = opt.limit_w; - h = opt.limit_h; - D(("Limiting width to %d and height to %d\n", w, h)); - - im_main = imlib_create_image(w, h); - - if (!im_main) - eprintf("Imlib error creating image"); - - if (bg_im) - gib_imlib_blend_image_onto_image(im_main, bg_im, - gib_imlib_image_has_alpha(bg_im), 0, 0, - bg_w, bg_h, 0, 0, w, h, 1, 0, 0); - else if (trans_bg) { - gib_imlib_image_fill_rectangle(im_main, 0, 0, w, h, 0, 0, 0, 0); - gib_imlib_image_set_has_alpha(im_main, 1); - } else { - /* Colour the background */ - gib_imlib_image_fill_rectangle(im_main, 0, 0, w, h, 0, 0, 0, 255); - } - - /* Create the title string */ - - if (!opt.title) - s = estrdup(PACKAGE " [collage mode]"); - else - s = estrdup(feh_printf(opt.title, NULL)); - - if (opt.display) { - winwid = winwidget_create_from_image(im_main, s, WIN_TYPE_SINGLE); - winwidget_show(winwid); - } - - for (l = filelist; l; l = l->next) { - file = FEH_FILE(l->data); - if (last) { - filelist = feh_file_remove_from_list(filelist, last); - last = NULL; - } - D(("About to load image %s\n", file->filename)); - if (feh_load_image(&im_temp, file) != 0) { - D(("Successfully loaded %s\n", file->filename)); - if (opt.verbose) - feh_display_status('.'); - www = opt.thumb_w; - hhh = opt.thumb_h; - ww = gib_imlib_image_get_width(im_temp); - hh = gib_imlib_image_get_height(im_temp); - - if (opt.aspect) { - double ratio = 0.0; - - /* Keep the aspect ratio for the thumbnail */ - ratio = ((double) ww / hh) / ((double) www / hhh); - - if (ratio > 1.0) - hhh = opt.thumb_h / ratio; - else if (ratio != 1.0) - www = opt.thumb_w * ratio; - } - - if ((!opt.stretch) && ((www > ww) || (hhh > hh))) { - /* Don't make the image larger unless stretch is specified */ - www = ww; - hhh = hh; - } - - /* pick random coords for thumbnail */ - xxx = ((w - www) * ((double) rand() / RAND_MAX)); - yyy = ((h - hhh) * ((double) rand() / RAND_MAX)); - D(("image going on at x=%d, y=%d\n", xxx, yyy)); - - im_thumb = gib_imlib_create_cropped_scaled_image(im_temp, - 0, 0, ww, hh, www, hhh, 1); - gib_imlib_free_image_and_decache(im_temp); - - if (opt.alpha) { - DATA8 atab[256]; - - D(("Applying alpha options\n")); - gib_imlib_image_set_has_alpha(im_thumb, 1); - memset(atab, opt.alpha_level, sizeof(atab)); - gib_imlib_apply_color_modifier_to_rectangle(im_thumb, - 0, 0, www, hhh, NULL, NULL, NULL, atab); - } - gib_imlib_blend_image_onto_image(im_main, im_thumb, - gib_imlib_image_has_alpha(im_thumb), 0, 0, www, hhh, xxx, - yyy,www, hhh, 1, gib_imlib_image_has_alpha(im_thumb), 0); - gib_imlib_free_image_and_decache(im_thumb); - } else { - last = l; - if (opt.verbose) - feh_display_status('x'); - } - if (opt.display) { - winwidget_render_image(winwid, 0, 0); - if (!feh_main_iteration(0)) - exit(0); - } - } - if (opt.verbose) - fprintf(stdout, "\n"); - - if (opt.output && opt.output_file) { - char output_buf[1024]; - if (opt.output_dir) - snprintf(output_buf, 1024, "%s/%s", opt.output_dir, opt.output_file); - else - strncpy(output_buf, opt.output_file, 1024); - gib_imlib_save_image(im_main, output_buf); - if (opt.verbose) { - int tw, th; - - tw = gib_imlib_image_get_width(im_main); - th = gib_imlib_image_get_height(im_main); - fprintf(stdout, PACKAGE " - File saved as %s\n", output_buf); - fprintf(stdout, - " - Image is %dx%d pixels and contains %d thumbnails\n", - tw, th, (tw / opt.thumb_w) * (th / opt.thumb_h)); - } - } - - if (!opt.display) - gib_imlib_free_image_and_decache(im_main); - free(s); - - return; -} diff --git a/src/debug.h b/src/debug.h index 93cb6bf..0ff1447 100644 --- a/src/debug.h +++ b/src/debug.h @@ -1,6 +1,7 @@ /* debug.h Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 diff --git a/src/deps.mk b/src/deps.mk deleted file mode 100644 index 36b264f..0000000 --- a/src/deps.mk +++ /dev/null @@ -1,41 +0,0 @@ -collage.o: collage.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - winwidget.h filelist.h options.h -events.o: events.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h winwidget.h timers.h options.h events.h thumbnail.h -feh_png.o: feh_png.c feh_png.h feh.h structs.h menu.h utils.h getopt.h \ - debug.h -filelist.o: filelist.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h options.h -getopt.o: getopt.c -getopt1.o: getopt1.c getopt.h -imlib.o: imlib.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h winwidget.h options.h -index.o: index.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h winwidget.h options.h -keyevents.o: keyevents.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - thumbnail.h filelist.h winwidget.h options.h -list.o: list.c feh.h structs.h menu.h utils.h getopt.h debug.h filelist.h \ - options.h -main.o: main.c feh.h structs.h menu.h utils.h getopt.h debug.h filelist.h \ - winwidget.h timers.h options.h events.h support.h -md5.o: md5.c md5.h -menu.o: menu.c feh.h structs.h menu.h utils.h getopt.h debug.h support.h \ - thumbnail.h filelist.h winwidget.h options.h -multiwindow.o: multiwindow.c feh.h structs.h menu.h utils.h getopt.h \ - debug.h winwidget.h timers.h filelist.h options.h -options.o: options.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h options.h help.inc fehrc.inc -signals.o: signals.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - winwidget.h -slideshow.o: slideshow.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h timers.h winwidget.h options.h signals.h -support.o: support.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h options.h support.h -thumbnail.o: thumbnail.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h winwidget.h options.h thumbnail.h md5.h feh_png.h -timers.o: timers.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - options.h timers.h -utils.o: utils.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - options.h -winwidget.o: winwidget.c feh.h structs.h menu.h utils.h getopt.h debug.h \ - filelist.h winwidget.h options.h diff --git a/src/events.c b/src/events.c index 36379b1..bafc517 100644 --- a/src/events.c +++ b/src/events.c @@ -1,6 +1,7 @@ /* events.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 @@ -31,15 +32,147 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "events.h" #include "thumbnail.h" +#define FEH_JITTER_OFFSET 2 +#define FEH_JITTER_TIME 1 + +extern struct __fehkey keys[EVENT_LIST_END]; +fehkey *feh_str_to_kb(char *action); + feh_event_handler *ev_handler[LASTEvent]; static void feh_event_handle_ButtonPress(XEvent * ev); static void feh_event_handle_ButtonRelease(XEvent * ev); -static void feh_event_handle_ConfigureNotify(XEvent * ev); static void feh_event_handle_LeaveNotify(XEvent * ev); static void feh_event_handle_MotionNotify(XEvent * ev); static void feh_event_handle_ClientMessage(XEvent * ev); +static void feh_set_bb(unsigned int bb_index, int modifier, char button) +{ + keys[bb_index].state = modifier; + keys[bb_index].button = button; +} + +static void feh_set_parse_bb_partial(fehkey *button, char *binding) +{ + char *cur = binding; + int mod = 0; + + if (!*binding) { + button->button = 0; + return; + } + + while (cur[1] == '-') { + switch (cur[0]) { + case 'C': + mod |= ControlMask; + break; + case 'S': + mod |= ShiftMask; + break; + case '1': + mod |= Mod1Mask; + break; + case '4': + mod |= Mod4Mask; + break; + default: + weprintf("buttons: invalid modifier %c in \"%s\"", cur[0], binding); + break; + } + cur += 2; + } + + button->button = atoi(cur); + button->state = mod; + + if (button->button == 0) { + /* + * Mod3 is unused on today's keyboards. If Mod3 is unset and button==0, + * we are dealing with an uninitialized or unset binding. If Mod3 is set + * and button==0, it refers to mouse movement. + */ + button->state |= Mod3Mask; + } +} + +/* + * Called after init_keyevents in keyevents.c + * -> no need to memset + */ +void init_buttonbindings(void) +{ + char *home = NULL; + char *confhome = NULL; + char *confpath = NULL; + char line[128]; + char action[32], button[8]; + struct __fehkey *cur_bb = NULL; + FILE *conf = NULL; + int read = 0; + + feh_set_bb(EVENT_pan, 0, 1); + feh_set_bb(EVENT_zoom, 0, 2); + feh_set_bb(EVENT_toggle_menu, 0, 3); + feh_set_bb(EVENT_prev_img, 0, 4); + feh_set_bb(EVENT_next_img, 0, 5); + feh_set_bb(EVENT_blur, 4, 1); + feh_set_bb(EVENT_rotate, 4, 2); + + home = getenv("HOME"); + confhome = getenv("XDG_CONFIG_HOME"); + + if (confhome) + confpath = estrjoin("/", confhome, "feh/buttons", NULL); + else if (home) + confpath = estrjoin("/", home, ".config/feh/buttons", NULL); + else + return; + + conf = fopen(confpath, "r"); + + free(confpath); + + if (!conf && ((conf = fopen("/etc/feh/buttons", "r")) == NULL)) + return; + + while (fgets(line, sizeof(line), conf)) { + *action = '\0'; + *button = '\0'; + cur_bb = NULL; + + read = sscanf(line, "%31s %7s\n", (char *) &action, (char *) &button); + + if ((read == EOF) || (read == 0) || (line[0] == '#')) + continue; + + cur_bb = feh_str_to_kb(action); + if (cur_bb == NULL) { + if (!strcmp(action, "reload")) + cur_bb = &keys[EVENT_reload_image]; + else if (!strcmp(action, "menu")) + cur_bb = &keys[EVENT_toggle_menu]; + else if (!strcmp(action, "prev")) + cur_bb = &keys[EVENT_prev_img]; + else if (!strcmp(action, "next")) + cur_bb = &keys[EVENT_next_img]; + } + if (cur_bb) + feh_set_parse_bb_partial(cur_bb, button); + else + weprintf("buttons: Invalid action: %s", action); + } + fclose(conf); +} + +static short feh_is_bb(unsigned int key_index, unsigned int button, unsigned int mod) +{ + if ((keys[key_index].state == mod) && (keys[key_index].button == button)) + return 1; + return 0; +} + + void feh_event_init(void) { int i; @@ -61,6 +194,7 @@ void feh_event_init(void) static void feh_event_handle_ButtonPress(XEvent * ev) { winwidget winwid = NULL; + unsigned int state, button; /* get the heck out if it's a mouse-click on the cover, we'll hide the menus on release */ @@ -69,74 +203,126 @@ static void feh_event_handle_ButtonPress(XEvent * ev) } winwid = winwidget_get_from_window(ev->xbutton.window); - if (winwid && winwid->caption_entry) { + if (winwid == NULL || winwid->caption_entry) { return; } - if (!opt.no_menus && EV_IS_MENU_BUTTON(ev)) { + state = ev->xbutton.state & (ControlMask | ShiftMask | Mod1Mask | Mod4Mask); + button = ev->xbutton.button; + + if (!opt.no_menus && feh_is_bb(EVENT_toggle_menu, button, state)) { D(("Menu Button Press event\n")); - if (winwid != NULL) { - winwidget_show_menu(winwid); - } - } else if ((ev->xbutton.button == opt.rotate_button) - && ((opt.no_rotate_ctrl_mask) - || (ev->xbutton.state & ControlMask))) { - if (winwid != NULL) { - opt.mode = MODE_ROTATE; - winwid->mode = MODE_ROTATE; - D(("rotate starting at %d, %d\n", ev->xbutton.x, ev->xbutton.y)); - } - } else if ((ev->xbutton.button == opt.blur_button) - && ((opt.no_blur_ctrl_mask) - || (ev->xbutton.state & ControlMask))) { - if (winwid != NULL) { - opt.mode = MODE_BLUR; - winwid->mode = MODE_BLUR; - D(("blur starting at %d, %d\n", ev->xbutton.x, ev->xbutton.y)); - } - } else if (ev->xbutton.button == opt.pan_button) { - D(("Pan Button Press event\n")); - if (winwid != NULL) { - D(("Next button, but could be pan mode\n")); - opt.mode = MODE_NEXT; - winwid->mode = MODE_NEXT; - D(("click offset is %d,%d\n", ev->xbutton.x, ev->xbutton.y)); - winwid->click_offset_x = ev->xbutton.x - winwid->im_x; - winwid->click_offset_y = ev->xbutton.y - winwid->im_y; - } - } else if (ev->xbutton.button == opt.zoom_button) { + winwidget_show_menu(winwid); + + } else if (feh_is_bb(EVENT_rotate, button, state) + && (winwid->type != WIN_TYPE_THUMBNAIL)) { + opt.mode = MODE_ROTATE; + winwid->mode = MODE_ROTATE; + D(("rotate starting at %d, %d\n", ev->xbutton.x, ev->xbutton.y)); + + } else if (feh_is_bb(EVENT_blur, button, state) + && (winwid->type != WIN_TYPE_THUMBNAIL)) { + opt.mode = MODE_BLUR; + winwid->mode = MODE_BLUR; + D(("blur starting at %d, %d\n", ev->xbutton.x, ev->xbutton.y)); + + } else if (feh_is_bb(EVENT_pan, button, state)) { + D(("Next button, but could be pan mode\n")); + opt.mode = MODE_NEXT; + winwid->mode = MODE_NEXT; + D(("click offset is %d,%d\n", ev->xbutton.x, ev->xbutton.y)); + winwid->click_offset_x = ev->xbutton.x - winwid->im_x; + winwid->click_offset_y = ev->xbutton.y - winwid->im_y; + winwid->click_start_time = time(NULL); + + } else if (feh_is_bb(EVENT_zoom, button, state)) { D(("Zoom Button Press event\n")); - if (winwid != NULL) { - D(("Zoom mode baby!\n")); - opt.mode = MODE_ZOOM; - winwid->mode = MODE_ZOOM; - D(("click offset is %d,%d\n", ev->xbutton.x, ev->xbutton.y)); - winwid->click_offset_x = ev->xbutton.x; - winwid->click_offset_y = ev->xbutton.y; - winwid->old_zoom = winwid->zoom; - - /* required to adjust the image position in zoom mode */ - winwid->im_click_offset_x = (winwid->click_offset_x - - winwid->im_x) / winwid->old_zoom; - winwid->im_click_offset_y = (winwid->click_offset_y - - winwid->im_y) / winwid->old_zoom; - } - } else if (ev->xbutton.button == opt.reload_button) { + opt.mode = MODE_ZOOM; + winwid->mode = MODE_ZOOM; + D(("click offset is %d,%d\n", ev->xbutton.x, ev->xbutton.y)); + winwid->click_offset_x = ev->xbutton.x; + winwid->click_offset_y = ev->xbutton.y; + winwid->old_zoom = winwid->zoom; + + /* required to adjust the image position in zoom mode */ + winwid->im_click_offset_x = (winwid->click_offset_x + - winwid->im_x) / winwid->old_zoom; + winwid->im_click_offset_y = (winwid->click_offset_y + - winwid->im_y) / winwid->old_zoom; + + } else if (feh_is_bb(EVENT_zoom_in, button, state)) { + D(("Zoom_In Button Press event\n")); + D(("click offset is %d,%d\n", ev->xbutton.x, ev->xbutton.y)); + winwid->click_offset_x = ev->xbutton.x; + winwid->click_offset_y = ev->xbutton.y; + winwid->old_zoom = winwid->zoom; + + /* required to adjust the image position in zoom mode */ + winwid->im_click_offset_x = (winwid->click_offset_x + - winwid->im_x) / winwid->old_zoom; + winwid->im_click_offset_y = (winwid->click_offset_y + - winwid->im_y) / winwid->old_zoom; + + /* copied from zoom_in, keyevents.c */ + winwid->zoom = winwid->zoom * opt.zoom_rate; + + if (winwid->zoom > ZOOM_MAX) + winwid->zoom = ZOOM_MAX; + + /* copied from below (ZOOM, feh_event_handle_MotionNotify) */ + winwid->im_x = winwid->click_offset_x + - (winwid->im_click_offset_x * winwid->zoom); + winwid->im_y = winwid->click_offset_y + - (winwid->im_click_offset_y * winwid->zoom); + + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 0); + + } else if (feh_is_bb(EVENT_zoom_out, button, state)) { + D(("Zoom_Out Button Press event\n")); + D(("click offset is %d,%d\n", ev->xbutton.x, ev->xbutton.y)); + winwid->click_offset_x = ev->xbutton.x; + winwid->click_offset_y = ev->xbutton.y; + winwid->old_zoom = winwid->zoom; + + /* required to adjust the image position in zoom mode */ + winwid->im_click_offset_x = (winwid->click_offset_x + - winwid->im_x) / winwid->old_zoom; + winwid->im_click_offset_y = (winwid->click_offset_y + - winwid->im_y) / winwid->old_zoom; + + /* copied from zoom_out, keyevents.c */ + winwid->zoom = winwid->zoom / opt.zoom_rate; + + if (winwid->zoom < ZOOM_MIN) + winwid->zoom = ZOOM_MIN; + + /* copied from below (ZOOM, feh_event_handle_MotionNotify) */ + winwid->im_x = winwid->click_offset_x + - (winwid->im_click_offset_x * winwid->zoom); + winwid->im_y = winwid->click_offset_y + - (winwid->im_click_offset_y * winwid->zoom); + + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 0); + + } else if (feh_is_bb(EVENT_reload_image, button, state)) { D(("Reload Button Press event\n")); - if (winwid != NULL) - feh_reload_image(winwid, 0, 0); - } else if (ev->xbutton.button == opt.prev_button) { + feh_reload_image(winwid, 0, 1); + + } else if (feh_is_bb(EVENT_prev_img, button, state)) { D(("Prev Button Press event\n")); - if ((winwid != NULL) - && (winwid->type == WIN_TYPE_SLIDESHOW)) - slideshow_change_image(winwid, SLIDE_PREV); - } else if (ev->xbutton.button == opt.next_button) { + if (winwid->type == WIN_TYPE_SLIDESHOW) + slideshow_change_image(winwid, SLIDE_PREV, 1); + + } else if (feh_is_bb(EVENT_next_img, button, state)) { D(("Next Button Press event\n")); - if ((winwid != NULL) - && (winwid->type == WIN_TYPE_SLIDESHOW)) - slideshow_change_image(winwid, SLIDE_NEXT); + if (winwid->type == WIN_TYPE_SLIDESHOW) + slideshow_change_image(winwid, SLIDE_NEXT, 1); + } else { D(("Received other ButtonPress event\n")); + feh_event_handle_generic(winwid, state, NoSymbol, button); } return; } @@ -144,6 +330,8 @@ static void feh_event_handle_ButtonPress(XEvent * ev) static void feh_event_handle_ButtonRelease(XEvent * ev) { winwidget winwid = NULL; + unsigned int state = ev->xbutton.state & (ControlMask | ShiftMask | Mod1Mask | Mod4Mask); + unsigned int button = ev->xbutton.button; if (menu_root) { /* if menus are open, close them, and execute action if needed */ @@ -164,39 +352,28 @@ static void feh_event_handle_ButtonRelease(XEvent * ev) } winwid = winwidget_get_from_window(ev->xbutton.window); - if (winwid && winwid->caption_entry) { + if (winwid == NULL || winwid->caption_entry) { return; } - if ((ev->xbutton.button == opt.menu_button) - && (((!opt.menu_ctrl_mask) - && ((!(ev->xbutton.state & ControlMask)) - || ((ev->xbutton.state & ControlMask) - && (opt.menu_ctrl_mask))))) - && (opt.no_menus)) - winwidget_destroy_all(); - else if (ev->xbutton.button == opt.pan_button) { + if (feh_is_bb(EVENT_pan, button, state)) { if (opt.mode == MODE_PAN) { - if (winwid != NULL) { - D(("Disabling pan mode\n")); - opt.mode = MODE_NORMAL; - winwid->mode = MODE_NORMAL; - winwidget_sanitise_offsets(winwid); - winwidget_render_image(winwid, 0, 1); - } + D(("Disabling pan mode\n")); + opt.mode = MODE_NORMAL; + winwid->mode = MODE_NORMAL; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 0); } else if (opt.mode == MODE_NEXT) { opt.mode = MODE_NORMAL; - if (winwid != NULL) - winwid->mode = MODE_NORMAL; - if ((winwid != NULL) - && (winwid->type == WIN_TYPE_SLIDESHOW)) { - slideshow_change_image(winwid, SLIDE_NEXT); - } else if ((winwid != NULL) - && (winwid->type == WIN_TYPE_THUMBNAIL)) { + winwid->mode = MODE_NORMAL; + if (winwid->type == WIN_TYPE_SLIDESHOW) { + if (opt.tap_zones && ev->xbutton.x < winwid->w / 2) + slideshow_change_image(winwid, SLIDE_PREV, 1); + else + slideshow_change_image(winwid, SLIDE_NEXT, 1); + } else if (winwid->type == WIN_TYPE_THUMBNAIL) { feh_file *thumbfile; - winwidget thumbwin = NULL; int x, y; - char *s; x = ev->xbutton.x; y = ev->xbutton.y; @@ -206,63 +383,45 @@ static void feh_event_handle_ButtonRelease(XEvent * ev) y /= winwid->zoom; thumbfile = feh_thumbnail_get_file_from_coords(x, y); if (thumbfile) { - if (!opt.thumb_title) - s = thumbfile->name; - else - s = feh_printf(opt.thumb_title, thumbfile); - thumbwin = winwidget_get_first_window_of_type(WIN_TYPE_THUMBNAIL_VIEWER); - if (!thumbwin) { - thumbwin = winwidget_create_from_file( - gib_list_add_front(NULL, thumbfile), - s, WIN_TYPE_THUMBNAIL_VIEWER); - if (thumbwin) - winwidget_show(thumbwin); - } else if (FEH_FILE(thumbwin->file->data) != thumbfile) { - free(thumbwin->file); - thumbwin->file = gib_list_add_front(NULL, thumbfile); - winwidget_rename(thumbwin, s); - feh_reload_image(thumbwin, 1, 0); + if (opt.actions[0]) { + feh_action_run(thumbfile, opt.actions[0], winwid); + if (!opt.hold_actions[0]) + feh_thumbnail_mark_removed(thumbfile, 0); + } else { + feh_thumbnail_show_fullsize(thumbfile); } } } } else { - if (winwid != NULL) { - opt.mode = MODE_NORMAL; - winwid->mode = MODE_NORMAL; - } - } - } else if ((ev->xbutton.button == opt.rotate_button) - || (ev->xbutton.button == opt.zoom_button)) { - D(("Mode-based Button Release event\n")); - if (winwid != NULL) { - D(("Disabling mode\n")); opt.mode = MODE_NORMAL; winwid->mode = MODE_NORMAL; + } - if ((ev->xbutton.button == opt.zoom_button) - && (ev->xbutton.x == winwid->click_offset_x) - && (ev->xbutton.y == winwid->click_offset_y)) { - winwid->zoom = 1.0; - winwidget_center_image(winwid); - } else - winwidget_sanitise_offsets(winwid); + } else if (feh_is_bb(EVENT_rotate, button, state) + || feh_is_bb(EVENT_zoom, button, state)) { + D(("Disabling mode\n")); + opt.mode = MODE_NORMAL; + winwid->mode = MODE_NORMAL; + + if ((feh_is_bb(EVENT_zoom, button, state)) + && (ev->xbutton.x == winwid->click_offset_x) + && (ev->xbutton.y == winwid->click_offset_y)) { + winwid->zoom = 1.0; + winwidget_center_image(winwid); + } else + winwidget_sanitise_offsets(winwid); - winwidget_render_image(winwid, 0, 1); - } - } else if ((ev->xbutton.button == opt.blur_button) - && ((opt.no_blur_ctrl_mask) - || (ev->xbutton.state & ControlMask))) { - D(("Blur Button Release event\n")); - if (winwid != NULL) { - D(("Disabling Blur mode\n")); - opt.mode = MODE_NORMAL; - winwid->mode = MODE_NORMAL; - } + winwidget_render_image(winwid, 0, 0); + + } else if (feh_is_bb(EVENT_blur, button, state)) { + D(("Disabling Blur mode\n")); + opt.mode = MODE_NORMAL; + winwid->mode = MODE_NORMAL; } return; } -static void feh_event_handle_ConfigureNotify(XEvent * ev) +void feh_event_handle_ConfigureNotify(XEvent * ev) { while (XCheckTypedWindowEvent(disp, ev->xconfigure.window, ConfigureNotify, ev)); if (!menu_root) { @@ -280,7 +439,7 @@ static void feh_event_handle_ConfigureNotify(XEvent * ev) opt.geom_w = w->w; opt.geom_h = w->h; } - winwidget_render_image(w, 0, 1); + winwidget_render_image(w, 0, 0); } } } @@ -362,6 +521,8 @@ static void feh_event_handle_MotionNotify(XEvent * ev) dy = scr_height - (m->y + m->h); dx = dx < 0 ? dx : 0; dy = dy < 0 ? dy : 0; + dx = m->x + dx < 0 ? -m->x : dx; + dy = m->y + dy < 0 ? -m->y : dy; if (dx || dy) feh_menu_slide_all_menus_relative(dx, dy); } @@ -374,6 +535,8 @@ static void feh_event_handle_MotionNotify(XEvent * ev) dy = scr->height - (m->next->y + m->next->h); dx = dx < 0 ? dx : 0; dy = dy < 0 ? dy : 0; + dx = m->x + dx < 0 ? -m->x : dx; + dy = m->y + dy < 0 ? -m->y : dy; if (dx || dy) feh_menu_slide_all_menus_relative(dx, dy); } @@ -387,15 +550,15 @@ static void feh_event_handle_MotionNotify(XEvent * ev) winwid->zoom = winwid->old_zoom + ( ((double) ev->xmotion.x - (double) winwid->click_offset_x) / 128.0); - /*/ ((double) (winwid->click_offset_x + 1)));*/ else winwid->zoom = winwid->old_zoom - ( ((double) winwid->click_offset_x - (double) ev->xmotion.x) / 128.0); - /*/ ((double) (winwid->click_offset_x + 1)));*/ - if (winwid->zoom < 0.01) - winwid->zoom = 0.01; + if (winwid->zoom < ZOOM_MIN) + winwid->zoom = ZOOM_MIN; + else if (winwid->zoom > ZOOM_MAX) + winwid->zoom = ZOOM_MAX; /* center around click_offset */ winwid->im_x = winwid->click_offset_x @@ -403,7 +566,7 @@ static void feh_event_handle_MotionNotify(XEvent * ev) winwid->im_y = winwid->click_offset_y - (winwid->im_click_offset_y * winwid->zoom); - winwidget_render_image(winwid, 0, 0); + winwidget_render_image(winwid, 0, 1); } } else if ((opt.mode == MODE_PAN) || (opt.mode == MODE_NEXT)) { int orig_x, orig_y; @@ -412,8 +575,14 @@ static void feh_event_handle_MotionNotify(XEvent * ev) winwid = winwidget_get_from_window(ev->xmotion.window); if (winwid) { if (opt.mode == MODE_NEXT) { - opt.mode = MODE_PAN; - winwid->mode = MODE_PAN; + if ((abs(winwid->click_offset_x - (ev->xmotion.x - winwid->im_x)) > FEH_JITTER_OFFSET) + || (abs(winwid->click_offset_y - (ev->xmotion.y - winwid->im_y)) > FEH_JITTER_OFFSET) + || (time(NULL) - winwid->click_start_time > FEH_JITTER_TIME)) { + opt.mode = MODE_PAN; + winwid->mode = MODE_PAN; + } + else + return; } D(("Panning\n")); orig_x = winwid->im_x; @@ -424,36 +593,38 @@ static void feh_event_handle_MotionNotify(XEvent * ev) winwidget_sanitise_offsets(winwid); - D(("im_x %d, im_w %d, off %d, mx %d\n", winwid->im_x, - winwid->im_w, winwid->click_offset_x, ev->xmotion.x)); + D(("im_x %d, im_w %d, off %d, mx %d, my %d\n", winwid->im_x, + winwid->im_w, winwid->click_offset_x, ev->xmotion.x, + ev->xmotion.y)); /* XWarpPointer generates a MotionNotify event which we will * parse. Since that event would undo the effect of the pointer * warp, we need to change the click_offset to compensate this. */ - if ((winwid->w - ev->xmotion.x <= 1) - && (winwid->click_offset_x >= winwid->w - 4)) + if ((winwid->w - ev->xmotion.x <= 1) && (winwid->im_x < 0)) { XWarpPointer(disp, None, winwid->win, 0, 0, 0, 0, 3, ev->xmotion.y); winwid->click_offset_x -= winwid->w - 4; } - else if ((ev->xmotion.x <= 1) && (winwid->click_offset_x - <= (winwid->im_w * winwid->zoom) - winwid->w - 3)) + // TODO needlessly warps for certain zoom levels + else if ((ev->xmotion.x <= 1) && (winwid->im_x > + (winwid->w - winwid->im_w * winwid->zoom))) { XWarpPointer(disp, None, winwid->win, 0, 0, 0, 0, winwid->w - 4, ev->xmotion.y); winwid->click_offset_x += winwid->w - 4; } else if ((winwid->h - ev->xmotion.y <= 1) - && (winwid->click_offset_y >= winwid->h - 4)) + && (winwid->im_y < 0)) { XWarpPointer(disp, None, winwid->win, 0, 0, 0, 0, ev->xmotion.x, 3); winwid->click_offset_y -= winwid->h - 4; } - else if ((ev->xmotion.y <= 1) && (winwid->click_offset_y - <= (winwid->im_h * winwid->zoom) - winwid->h - 3)) + // TODO needlessly warps for certain zoomlevels + else if ((ev->xmotion.y <= 1) && (winwid->im_y > + (winwid->h - winwid->im_h * winwid->zoom))) { XWarpPointer(disp, None, winwid->win, 0, 0, 0, 0, ev->xmotion.x, winwid->h - 4); @@ -462,7 +633,7 @@ static void feh_event_handle_MotionNotify(XEvent * ev) if ((winwid->im_x != orig_x) || (winwid->im_y != orig_y)) - winwidget_render_image(winwid, 0, 0); + winwidget_render_image(winwid, 0, 1); } } else if (opt.mode == MODE_ROTATE) { while (XCheckTypedWindowEvent(disp, ev->xmotion.window, MotionNotify, ev)); @@ -473,16 +644,18 @@ static void feh_event_handle_MotionNotify(XEvent * ev) Imlib_Image temp; temp = gib_imlib_create_rotated_image(winwid->im, 0.0); - winwid->im_w = gib_imlib_image_get_width(temp); - winwid->im_h = gib_imlib_image_get_height(temp); - gib_imlib_free_image_and_decache(temp); - if (!winwid->full_screen && !opt.geom_flags) - winwidget_resize(winwid, winwid->im_w, winwid->im_h); - winwid->has_rotated = 1; + if (temp != NULL) { + winwid->im_w = gib_imlib_image_get_width(temp); + winwid->im_h = gib_imlib_image_get_height(temp); + gib_imlib_free_image_and_decache(temp); + if (!winwid->full_screen && !opt.geom_flags) + winwidget_resize(winwid, winwid->im_w, winwid->im_h, 0); + winwid->has_rotated = 1; + } } winwid->im_angle = (ev->xmotion.x - winwid->w / 2) / ((double) winwid->w / 2) * 3.1415926535; D(("angle: %f\n", winwid->im_angle)); - winwidget_render_image(winwid, 0, 0); + winwidget_render_image(winwid, 0, 1); } } else if (opt.mode == MODE_BLUR) { while (XCheckTypedWindowEvent(disp, ev->xmotion.window, MotionNotify, ev)); @@ -494,71 +667,34 @@ static void feh_event_handle_MotionNotify(XEvent * ev) D(("Blurring\n")); temp = gib_imlib_clone_image(winwid->im); - blur_radius = (((double) ev->xmotion.x / winwid->w) * 20) - 10; - D(("angle: %d\n", blur_radius)); - if (blur_radius > 0) - gib_imlib_image_sharpen(temp, blur_radius); - else - gib_imlib_image_blur(temp, 0 - blur_radius); - ptr = winwid->im; - winwid->im = temp; - winwidget_render_image(winwid, 0, 0); - gib_imlib_free_image_and_decache(winwid->im); - winwid->im = ptr; + if (temp != NULL) { + blur_radius = (((double) ev->xmotion.x / winwid->w) * 20) - 10; + D(("angle: %d\n", blur_radius)); + if (blur_radius > 0) + gib_imlib_image_sharpen(temp, blur_radius); + else + gib_imlib_image_blur(temp, 0 - blur_radius); + ptr = winwid->im; + winwid->im = temp; + winwidget_render_image(winwid, 0, 1); + gib_imlib_free_image_and_decache(winwid->im); + winwid->im = ptr; + } } } else { while (XCheckTypedWindowEvent(disp, ev->xmotion.window, MotionNotify, ev)); winwid = winwidget_get_from_window(ev->xmotion.window); if (winwid != NULL) { - if (winwid->type == WIN_TYPE_ABOUT) { - Imlib_Image orig_im; - int x, y; - - x = ev->xmotion.x - winwid->im_x; - y = ev->xmotion.y - winwid->im_y; - orig_im = winwid->im; - winwid->im = gib_imlib_clone_image(orig_im); - imlib_context_set_image(winwid->im); - imlib_apply_filter("bump_map_point(x=[],y=[],map=" - PREFIX "/share/feh/images/about.png);", &x, &y); - winwidget_render_image(winwid, 0, 1); - gib_imlib_free_image_and_decache(winwid->im); - winwid->im = orig_im; - } else if (winwid->type == WIN_TYPE_THUMBNAIL) { - static feh_thumbnail *last_thumb = NULL; + if (winwid->type == WIN_TYPE_THUMBNAIL) { feh_thumbnail *thumbnail; int x, y; x = (ev->xbutton.x - winwid->im_x) / winwid->zoom; y = (ev->xbutton.y - winwid->im_y) / winwid->zoom; thumbnail = feh_thumbnail_get_thumbnail_from_coords(x, y); - if (thumbnail != last_thumb) { - if (thumbnail) { - Imlib_Image origwin; - - origwin = winwid->im; - winwid->im = gib_imlib_clone_image(origwin); - gib_imlib_image_fill_rectangle(winwid->im, - thumbnail->x, thumbnail->y, thumbnail->w, - thumbnail->h, 50, 50, 255, 100); - gib_imlib_image_draw_rectangle(winwid->im, - thumbnail->x, thumbnail->y, thumbnail->w, - thumbnail->h, 255, 255, 255, 255); - gib_imlib_image_draw_rectangle(winwid->im, - thumbnail->x + 1, thumbnail->y + 1, - thumbnail->w - 2, thumbnail->h - 2, - 0, 0, 0, 255); - gib_imlib_image_draw_rectangle(winwid->im, - thumbnail->x + 2, thumbnail->y + 2, - thumbnail->w - 4, thumbnail->h - 4, - 255, 255, 255, 255); - winwidget_render_image(winwid, 0, 1); - gib_imlib_free_image_and_decache(winwid->im); - winwid->im = origwin; - } else - winwidget_render_image(winwid, 0, 1); - } - last_thumb = thumbnail; + feh_thumbnail_select(winwid, thumbnail); + } else { + feh_event_handle_generic(winwid, ev->xmotion.state | Mod3Mask, NoSymbol, 0); } } } diff --git a/src/events.h b/src/events.h index 22abaf7..f334c58 100644 --- a/src/events.h +++ b/src/events.h @@ -32,6 +32,6 @@ extern feh_event_handler *ev_handler[]; void feh_event_init(void); -#define EV_IS_MENU_BUTTON(ev) ((((ev)->xbutton.button == opt.menu_button) || (opt.menu_button == 0)) && (((!opt.menu_ctrl_mask) && (!((ev)->xbutton.state & ControlMask))) || (((ev)->xbutton.state & ControlMask) && (opt.menu_ctrl_mask)))) +void feh_event_handle_ConfigureNotify(XEvent * ev); #endif diff --git a/src/exif.c b/src/exif.c new file mode 100644 index 0000000..6b0719d --- /dev/null +++ b/src/exif.c @@ -0,0 +1,439 @@ +/* exif.c + +Copyright (C) 2012 Dennis Real. +Copyright (C) 2021 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 +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifdef HAVE_LIBEXIF + +#include <stdio.h> +#include <string.h> +#include <libexif/exif-data.h> +#include <limits.h> + +#include "feh.h" +#include "options.h" +#include "debug.h" +#include "exif.h" +#include "exif_canon.h" +#include "exif_nikon.h" +#include "exif_cfg.h" + + +/* remove all spaces on the right end of a string */ +void exif_trim_spaces(char *str) +{ + char *end; + + for (end = str; *str != '\0'; str++) { + if (*str != ' ') { + end = str + 1; + } + } + *end = '\0'; +} + + + +/* show given exif tag content with tag name */ +void exif_get_tag(ExifData * d, ExifIfd ifd, ExifTag tag, char *buffer, + unsigned int maxsize) +{ + char s[EXIF_MAX_DATA]; + ExifEntry *entry = NULL; + + if ((d != NULL) && (buffer != NULL) && (maxsize > 0)) { + entry = exif_content_get_entry(d->ifd[ifd], tag); + if (entry != NULL) { + /* Get the contents of the tag in human-readable form */ + exif_entry_get_value(entry, s, EXIF_MAX_DATA); + + /* Don't bother printing it if it's entirely blank */ + exif_trim_spaces(s); + if (*s != '\0') { + D(("%s: %s\n", + exif_tag_get_name_in_ifd(tag, ifd), s)); + snprintf(buffer, (size_t) maxsize, + "%s: %s\n", + exif_tag_get_name_in_ifd(tag, + ifd), s); + } + } + } +} + + + +/* show given exif tag content without tag name */ +void exif_get_tag_content(ExifData * d, ExifIfd ifd, ExifTag tag, + char *buffer, unsigned int maxsize) +{ + char s[EXIF_MAX_DATA]; + ExifEntry *entry = NULL; + + if ((d != NULL) && (buffer != NULL) && (maxsize > 0)) { + entry = exif_content_get_entry(d->ifd[ifd], tag); + if (entry != NULL) { + /* Get the contents of the tag in human-readable form */ + exif_entry_get_value(entry, s, EXIF_MAX_DATA); + + /* Don't bother printing it if it's entirely blank */ + exif_trim_spaces(s); + if (*s != '\0') { + D(("%s - %s\n", + exif_tag_get_name_in_ifd(tag, ifd), s)); + snprintf(buffer, (size_t) maxsize, "%s", + s); + } + } + } + +} + + + +/* Show the given MakerNote tag if it exists */ +void exif_get_mnote_tag(ExifData * d, unsigned int tag, char *buffer, + unsigned int maxsize) +{ + ExifMnoteData *mn = NULL; + int i, num; + char buf[1024]; + + if ((d != NULL) && (buffer != NULL) && (maxsize > 0)) { + mn = exif_data_get_mnote_data(d); + } else { + return; + } + + if (mn != NULL) { + num = exif_mnote_data_count(mn); + + /* Loop through all MakerNote tags, searching for the desired one */ + for (i = 0; i < num; ++i) { + D(("%d/%d %d 0x%2x %s; %s\n", i, num, + exif_mnote_data_get_id(mn, i), + exif_mnote_data_get_id(mn, i), + exif_mnote_data_get_name(mn, i), + exif_mnote_data_get_title(mn, i))); + + if (exif_mnote_data_get_id(mn, i) == tag) { + if (exif_mnote_data_get_value + (mn, i, buf, sizeof(buf))) { + /* Don't bother printing it if it's entirely blank */ + exif_trim_spaces(buf); + if (*buf != '\0') { + D(("%s\n", buf)); + snprintf(buffer, + (size_t) maxsize, + "%s: %s\n", + exif_mnote_data_get_title + (mn, i), buf); + } + } + } + } + } +} + +void exif_get_make_model_lens(ExifData * ed, char *buffer, unsigned int maxsize) +{ + char make[EXIF_STD_BUF_LEN]; + char model[EXIF_STD_BUF_LEN]; + char lens[EXIF_STD_BUF_LEN]; + unsigned int offset = 0; + + make[0] = model[0] = lens[0] = '\0'; + + exif_get_tag_content(ed, EXIF_IFD_0, EXIF_TAG_MAKE, make, sizeof(make)); + exif_get_tag_content(ed, EXIF_IFD_0, EXIF_TAG_MODEL, model, sizeof(model)); + exif_get_tag_content(ed, EXIF_IFD_EXIF, 0xa434, lens, sizeof(lens)); + + if (make[0] && strncmp(make, model, strlen(make)) != 0) { + offset += snprintf(buffer, maxsize, "%s ", make); + } + if (model[0]) { + offset += snprintf(buffer + offset, maxsize - offset, "%s", model); + } + if (lens[0]) { + offset += snprintf(buffer + offset, maxsize - offset, " + %s", lens); + } + snprintf(buffer + offset, maxsize - offset, "\n"); +} + +void exif_get_exposure(ExifData * ed, char *buffer, unsigned int maxsize) +{ + char fnumber[EXIF_STD_BUF_LEN]; + char exposure[EXIF_STD_BUF_LEN]; + char iso[EXIF_STD_BUF_LEN]; + char focus[EXIF_STD_BUF_LEN]; + char focus35[EXIF_STD_BUF_LEN]; + unsigned int offset = 0; + + fnumber[0] = exposure[0] = iso[0] = '\0'; + focus[0] = focus35[0] = '\0'; + + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_FNUMBER, fnumber, sizeof(fnumber)); + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME, exposure, sizeof(exposure)); + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_ISO_SPEED_RATINGS, iso, sizeof(iso)); + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, focus, sizeof(focus)); + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM, focus35, sizeof(focus35)); + + if (fnumber[0] || exposure[0]) { + offset += snprintf(buffer, maxsize, "%s %s ", fnumber, exposure); + } + if (iso[0]) { + offset += snprintf(buffer + offset, maxsize - offset, "ISO%s ", iso); + } + if (focus[0] && focus35[0]) { + snprintf(buffer + offset, maxsize - offset, "%s (%s mm)\n", focus, focus35); + } else if (focus[0]) { + snprintf(buffer + offset, maxsize - offset, "%s\n", focus); + } +} + +void exif_get_flash(ExifData * ed, char *buffer, unsigned int maxsize) +{ + char flash[EXIF_STD_BUF_LEN]; + + flash[0] = '\0'; + + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_FLASH, flash, sizeof(flash)); + + if (flash[0]) { + snprintf(buffer, maxsize, "%s\n", flash); + } +} + +void exif_get_mode(ExifData * ed, char *buffer, unsigned int maxsize) +{ + char mode[EXIF_STD_BUF_LEN]; + char program[EXIF_STD_BUF_LEN]; + + mode[0] = program[0] = '\0'; + + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_MODE, mode, sizeof(mode)); + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_PROGRAM, program, sizeof(program)); + + if (mode[0] || program[0]) { + snprintf(buffer, maxsize, "%s (%s)\n", mode, program); + } +} + +void exif_get_datetime(ExifData * ed, char *buffer, unsigned int maxsize) +{ + char datetime[EXIF_STD_BUF_LEN]; + + datetime[0] = '\0'; + + exif_get_tag_content(ed, EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_ORIGINAL, datetime, sizeof(datetime)); + + if (datetime[0]) { + snprintf(buffer, maxsize, "%s\n", datetime); + } +} + +void exif_get_description(ExifData * ed, char *buffer, unsigned int maxsize) +{ + char description[EXIF_STD_BUF_LEN]; + + description[0] = '\0'; + + exif_get_tag_content(ed, EXIF_IFD_0, EXIF_TAG_IMAGE_DESCRIPTION, description, sizeof(description)); + + if (description[0]) { + snprintf(buffer, maxsize, "\"%s\"\n", description); + } +} + + +/* get gps coordinates if available */ +void exif_get_gps_coords(ExifData * ed, char *buffer, unsigned int maxsize) +{ + char buf[EXIF_STD_BUF_LEN]; + + buf[0] = '\0'; + exif_get_tag_content(ed, EXIF_IFD_GPS, EXIF_TAG_GPS_LATITUDE_REF, + buf, sizeof(buf)); + if (buf[0] != '\0') { + snprintf(buffer + strlen(buffer), maxsize - strlen(buffer), + "GPS: %s ", buf); + } else { + return; + } + + buf[0] = '\0'; + exif_get_tag_content(ed, EXIF_IFD_GPS, EXIF_TAG_GPS_LATITUDE, buf, + sizeof(buf)); + if (buf[0] != '\0') { + snprintf(buffer + strlen(buffer), maxsize - strlen(buffer), + "%s ", buf); + } else { + return; + } + + buf[0] = '\0'; + exif_get_tag_content(ed, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE_REF, + buf, sizeof(buf)); + if (buf[0] != '\0') { + snprintf(buffer + strlen(buffer), maxsize - strlen(buffer), + ", %s ", buf); + } else { + return; + } + + buf[0] = '\0'; + exif_get_tag_content(ed, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE, buf, + sizeof(buf)); + if (buf[0] != '\0') { + snprintf(buffer + strlen(buffer), maxsize - strlen(buffer), + "%s ", buf); + } else { + return; + } + + buf[0] = '\0'; + exif_get_tag_content(ed, EXIF_IFD_GPS, EXIF_TAG_GPS_MAP_DATUM, buf, + sizeof(buf)); + if (buf[0] != '\0') { + snprintf(buffer + strlen(buffer), maxsize - strlen(buffer), + "(%s)\n", buf); + } else { + return; + } + +} + + + +/* return data structure with exif data if available */ +ExifData *exif_get_data(char *path) +{ + ExifData *ed = NULL; + + /* Load an ExifData object from an EXIF file */ + ed = exif_data_new_from_file(path); + if (ed == NULL) { + D(("File not readable or no Exif data present in %s\n", + path)); + } + + return (ed); +} + + + + +/* get all exif data in readable form */ +void exif_get_info(ExifData * ed, char *buffer, unsigned int maxsize) +{ + ExifEntry *entry = NULL; + char buf[EXIF_STD_BUF_LEN]; + unsigned short int i = 0; + + if ((buffer == NULL) || (maxsize == 0)) { + return; + } else if (ed == NULL) { + snprintf(buffer, (size_t) maxsize, "%s\n", + "No Exif data in file."); + return; + } + + exif_get_description(ed, buffer + strlen(buffer), + maxsize - strlen(buffer)); + exif_get_make_model_lens(ed, buffer + strlen(buffer), + maxsize - strlen(buffer)); + exif_get_exposure(ed, buffer + strlen(buffer), + maxsize - strlen(buffer)); + exif_get_mode(ed, buffer + strlen(buffer), + maxsize - strlen(buffer)); + exif_get_flash(ed, buffer + strlen(buffer), + maxsize - strlen(buffer)); + exif_get_datetime(ed, buffer + strlen(buffer), + maxsize - strlen(buffer)); + + /* show vendor specific makernote tags */ + entry = + exif_content_get_entry(ed->ifd[EXIF_IFD_0], + EXIF_TAG_MAKE); + if (entry != NULL) { + + if (exif_entry_get_value(entry, buf, sizeof(buf))) { + exif_trim_spaces(buf); + + if ((strcmp(buf, "NIKON CORPORATION") == 0) + || (strcmp(buf, "Nikon") == 0) + || (strcmp(buf, "NIKON") == 0) + ) { + /* show nikon makernote exif tags. list must be defined in exif_cfg.h */ + i = 0; + while ((i < USHRT_MAX) + && + (Exif_makernote_nikon_tag_list + [i] != + EXIF_NIKON_MAKERNOTE_END)) + { + exn_get_mnote_nikon_tags + (ed, + Exif_makernote_nikon_tag_list + [i], + buffer + + strlen(buffer), + maxsize - + strlen(buffer)); + i++; + } + + } else if ((strcmp(buf, "Canon") == 0)) { + /* show canon makernote exif tags. list must be defined in exif_cfg.h */ + i = 0; + while ((i < USHRT_MAX) + && + (Exif_makernote_canon_tag_list + [i] != + EXIF_CANON_MAKERNOTE_END)) + { + exc_get_mnote_canon_tags + (ed, + Exif_makernote_canon_tag_list + [i], + buffer + + strlen(buffer), + maxsize - + strlen(buffer)); + i++; + } + + } else { + } + } + + } + + /* show gps coordinates */ + exif_get_gps_coords(ed, buffer + strlen(buffer), + maxsize - strlen(buffer)); + +} + +#endif diff --git a/src/exif.h b/src/exif.h new file mode 100644 index 0000000..41769c0 --- /dev/null +++ b/src/exif.h @@ -0,0 +1,47 @@ +/* exif.h + +Copyright (C) 2012 Dennis Real. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef EXIF_H +#define EXIF_H + +#include <libexif/exif-data.h> + +#define EXIF_MAX_DATA 1024 +#define EXIF_STD_BUF_LEN 128 + +extern void exif_trim_spaces(char *str); +extern void exif_get_tag(ExifData * d, ExifIfd ifd, ExifTag tag, + char *buffer, unsigned int maxsize); +extern void exif_get_tag_content(ExifData * d, ExifIfd ifd, ExifTag tag, + char *buffer, unsigned int maxsize); +extern void exif_get_mnote_tag(ExifData * d, unsigned int tag, + char *buffer, unsigned int maxsize); +extern void exif_get_gps_coords(ExifData * ed, char *buffer, + unsigned int maxsize); +extern ExifData *exif_get_data(char *path); +extern void exif_get_info(ExifData * ed, char *buffer, + unsigned int maxsize); + +#endif diff --git a/src/exif_canon.c b/src/exif_canon.c new file mode 100644 index 0000000..ee72164 --- /dev/null +++ b/src/exif_canon.c @@ -0,0 +1,61 @@ +/* exif_canon.c + +Copyright (C) 2012 Dennis Real. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifdef HAVE_LIBEXIF + +#include <stdio.h> +#include <libexif/exif-data.h> + +#include "feh.h" +#include "debug.h" +#include "exif.h" +#include "exif_canon.h" + + + +/* get interesting canon maker note tags in readable form */ +void exc_get_mnote_canon_tags(ExifData * ed, unsigned int tag, + char *buffer, unsigned int maxsize) +{ + /* char buf[EXIF_STD_BUF_LEN]; + + buf[0] = '\0'; + exif_get_tag(ed, EXIF_IFD_EXIF, EXIF_TAG_FLASH, buf, sizeof(buf)); + exif_trim_spaces(buf); */ + + switch (tag) { + default: + { + /* normal makernote tags without special treatment */ + exif_get_mnote_tag(ed, tag, buffer, maxsize); + } + break; + } + + + return; +} + +#endif diff --git a/src/exif_canon.h b/src/exif_canon.h new file mode 100644 index 0000000..58ecc0e --- /dev/null +++ b/src/exif_canon.h @@ -0,0 +1,34 @@ +/* exif_canon.h + +Copyright (C) 2012 Dennis Real. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef EXIF_CANON_H +#define EXIF_CANON_H + +#include <libexif/exif-data.h> + +extern void exc_get_mnote_canon_tags(ExifData * ed, unsigned int tag, + char *buffer, unsigned int maxsize); + +#endif diff --git a/src/exif_cfg.h b/src/exif_cfg.h new file mode 100644 index 0000000..0a13fa3 --- /dev/null +++ b/src/exif_cfg.h @@ -0,0 +1,71 @@ +/* exif_cfg.h + +Copyright (C) 2012 Dennis Real. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef EXIF_CFG_H +#define EXIF_CFG_H + +#include <libexif/exif-data.h> + +/* Nikon */ + +#define EXIF_NIKON_MAKERNOTE_END 0 /* end marker: if 0 used as a tag we must find something else */ + +/* show these nikon makernote tags */ +const unsigned int Exif_makernote_nikon_tag_list[] = { + + 6, + 8, /* Flash Setting */ + 9, /* Flash Mode */ + 135, /* Flash used */ + 18, /* Flash Exposure Comp */ + 168, /* Flash info: control mode */ + + 2, /* ISO. Has some more info than EXIF_TAG_ISO_SPEED_RATINGS but also fails on Lo.1 */ + 5, /* White Balance */ + 132, /* Lens */ + 171, /* Digital Vari-Program */ + 34, /* Active D-Lighting */ + + 35, /* PictureControlData */ + 183, /* AFInfo2 */ + + EXIF_NIKON_MAKERNOTE_END /* end marker */ +}; + + + +/* Canon */ +#define EXIF_CANON_MAKERNOTE_END 0xFFFF /* end marker: if this is used as a tag we must find something else */ + +/* show these canon makernote tags */ +const unsigned int Exif_makernote_canon_tag_list[] = { + 8, /* Image Number */ + 9, /* Owner Name */ + + EXIF_CANON_MAKERNOTE_END /* end marker */ +}; + + +#endif diff --git a/src/exif_nikon.c b/src/exif_nikon.c new file mode 100644 index 0000000..a81f290 --- /dev/null +++ b/src/exif_nikon.c @@ -0,0 +1,542 @@ +/* exif_nikon.c + +Copyright (C) 2012 Dennis Real. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifdef HAVE_LIBEXIF + +#include <stdio.h> +#include <libexif/exif-data.h> + +#include "feh.h" +#include "debug.h" +#include "exif.h" +#include "exif_nikon.h" + + +/* Flash control mode */ +/* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#FlashControlMode */ +#define EXN_FLASH_CONTROL_MODES_MAX 9 +char *EXN_NikonFlashControlModeValues[EXN_FLASH_CONTROL_MODES_MAX] = + { "Off", + "iTTL-BL", "iTTL", "Auto Aperture", + "Automatic", "GN (distance priority)", + "Manual", "Repeating Flash", + "N/A" /* "N/A" is not a nikon setting */ +}; + +#define EXN_FLASH_CONTROL_MODE_MASK 0x7F + + +/* AFInfo2 */ +/* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#AFInfo2 */ +#define EXN_CONTRAST_DETECT_AF_MAX 2 +char *EXN_NikonContrastDetectAF[EXN_CONTRAST_DETECT_AF_MAX] = + { "Off", "On" }; + +/* AFArea Mode for ContrastDetectAF Off */ +#define EXN_AF_AREA_MODE_P_MAX 13 +char *EXN_NikonAFAreaModePhase[EXN_AF_AREA_MODE_P_MAX] = { + "Single Area", "Dynamic Area", "Dynamic Area (closest subject)", + "Group Dynamic ", "Dynamic Area (9 points) ", + "Dynamic Area (21 points)", + "Dynamic Area (51 points) ", + "Dynamic Area (51 points, 3D-tracking)", + "Auto-area", "Dynamic Area (3D-tracking)", "Single Area (wide)", + "Dynamic Area (wide)", "Dynamic Area (wide, 3D-tracking)" +}; + +/* AFArea Mode for ContrastDetectAF On */ +#define EXN_AF_AREA_MODE_C_MAX 5 +char *EXN_NikonAFAreaModeContr[EXN_AF_AREA_MODE_C_MAX] = { + "Contrast-detect", + "Contrast-detect (normal area)", + "Contrast-detect (wide area)", + "Contrast-detect (face priority)", + "Contrast-detect (subject tracking)" +}; + +#define EXN_PHASE_DETECT_AF_MAX 4 +char *EXN_NikonPhaseDetectAF[EXN_PHASE_DETECT_AF_MAX] = + { "Off", "On (51-point)", + "On (11-point)", "On (39-point)" +}; + +/* PrimaryAFPoint and AFPointsUsed only valid with PhaseDetectAF == On */ + +#define EXN_PRIM_AF_PT_51_MAX 52 +char *EXN_Prim_AF_Pt_51[EXN_PRIM_AF_PT_51_MAX] = + { "(none)", "C6 (Center)", "B6", "A5", + "D6", "E5", "C7", "B7", "A6", "D7", "E6", "C5", "B5", "A4", "D5", + "E4", "C8", "B8", + "A7", "D8", "E7", "C9", "B9", "A8", "D9", "E8", "C10", "B10", "A9", + "D10", "E9", + "C11", "B11", "D11", "C4", "B4", "A3", "D4", "E3", "C3", "B3", + "A2", "D3", "E2", + "C2", "B2", "A1", "D2", "E1", "C1", "B1", "D1" +}; + +#define EXN_PRIM_AF_PT_11_MAX 12 +char *EXN_Prim_AF_Pt_11[EXN_PRIM_AF_PT_11_MAX] = + { "(none)", "Center", "Top", "Bottom", + "Mid-left", "Upper-left", "Lower-left", "Far Left", "Mid-right", + "Upper-right", + "Lower-right", "Far Right" +}; + +#define EXN_PRIM_AF_PT_39_MAX 40 +char *EXN_Prim_AF_Pt_39[EXN_PRIM_AF_PT_39_MAX] = + { "(none)", "C6 (Center)", "B6", "A2", + "D6", "E2", "C7", "B7", "A3", "D7", "E3", "C5", "B5", "A1", "D5", + "E1", "C8", "B8", + "D8", "C9", "B9", "D9", "C10", "B10", "D10", "C11", "B11", "D11", + "C4", "B4", "D4", + "C3", "B3", "D3", "C2", "B2", "D2", "C1", "B1", "D1" +}; + + +#define EXN_PIC_CTRL_ADJ_MAX 3 +char *EXN_Pic_Ctrl_Adj[EXN_PIC_CTRL_ADJ_MAX] = { "Default Settings", + "Quick Adjust", + "Full Control" +}; + + + +static void exn_get_prim_af_pt(unsigned int phasedetectaf, + unsigned int primafpt, + char *buffer, unsigned int maxsize); +static void exn_get_flash_output(unsigned int flashoutput, char *buffer, + unsigned int maxsize); +static void exn_get_mnote_nikon_18(ExifData * ed, char *buffer, + unsigned int maxsize); +static void exn_get_mnote_nikon_34(ExifData * ed, char *buffer, + unsigned int maxsize); +static void exn_get_mnote_nikon_35(ExifData * ed, char *buffer, + unsigned int maxsize); +static void exn_get_mnote_nikon_168(ExifData * ed, char *buffer, + unsigned int maxsize); +static void exn_get_mnote_nikon_183(ExifData * ed, char *buffer, + unsigned int maxsize); + + + +/* get primary AF point */ +static void exn_get_prim_af_pt(unsigned int phasedetectaf, + unsigned int primafpt, + char *buffer, unsigned int maxsize) +{ + + switch (phasedetectaf) { + case 0: + { + /* phasedetect not used. should not happen */ + snprintf(buffer, maxsize, "FAIL"); + return; + } + break; + case 1: + { + /* 51 pt */ + if (primafpt < EXN_PRIM_AF_PT_51_MAX) { + snprintf(buffer, maxsize, "%s", + EXN_Prim_AF_Pt_51[primafpt]); + } + return; + } + break; + case 2: + { + /* 11 pt */ + if (primafpt < EXN_PRIM_AF_PT_11_MAX) { + snprintf(buffer, maxsize, "%s", + EXN_Prim_AF_Pt_11[primafpt]); + } + return; + } + break; + case 3: + { + /* 39 pt */ + if (primafpt < EXN_PRIM_AF_PT_39_MAX) { + snprintf(buffer, maxsize, "%s", + EXN_Prim_AF_Pt_39[primafpt]); + } + return; + } + break; + default: + { + snprintf(buffer, maxsize, "?"); + return; + } + break; + + } + +} + + + +/* get flash output power (for FlashInfo010x) */ +static void exn_get_flash_output(unsigned int flashoutput, char *buffer, + unsigned int maxsize) +{ + + if (flashoutput == 0) { + /* full power */ + snprintf(buffer, maxsize, "Full"); + } else { + if ((flashoutput % 6) == 0) { + /* value is a power of 2 */ + snprintf(buffer, maxsize, "1/%d", + 1 << (flashoutput / 6)); + } else { + /* something uneven...ugly. maybe introduce pow() function from libm later */ + snprintf(buffer, maxsize, "1/2^(%f)", + ((float) flashoutput) / 6.0); + } + } +} + + + +/* get ActiveD-Lighting (18) info */ +static void exn_get_mnote_nikon_18(ExifData * ed, char *buffer, + unsigned int maxsize) +{ + + char buf[EXIF_STD_BUF_LEN]; + float data = 0; + + buf[0] = '\0'; + exif_get_mnote_tag(ed, 18, buf, sizeof(buf)); + + sscanf(buf, "Flash Exposure Compensation: %f", &data); /* libexif buggy here. fix conversion */ + + snprintf(buffer, maxsize, "FlashExposureCompensation: %+.1f EV\n", + ((float) ((signed char) round(data * 6.0))) / 6.0); +} + + + +/* get ActiveD-Lighting (34) info */ +static void exn_get_mnote_nikon_34(ExifData * ed, char *buffer, + unsigned int maxsize) +{ + char buf[EXIF_STD_BUF_LEN]; + unsigned int data = 0; + char *answer; + + buf[0] = '\0'; + exif_get_mnote_tag(ed, 34, buf, sizeof(buf)); + sscanf(buf, "(null): %u", &data); /* not directly supported by libexif yet */ + + switch (data) { + case 0: + { + answer = "Off"; + } + break; + case 1: + { + answer = "Low"; + } + break; + case 3: + { + answer = "Normal"; + } + break; + case 5: + { + answer = "High"; + } + break; + case 7: + { + answer = "Extra High"; + } + break; + case 65535: + { + answer = "Auto"; + } + break; + default: + { + answer = "N/A"; /* this is not a nikon value */ + } + + } + + snprintf(buffer, maxsize, "Active D-Lightning: %s\n", answer); + +} + + + +/* get nikon PictureControlData (35) info */ +static void exn_get_mnote_nikon_35(ExifData * ed, char *buffer, + unsigned int maxsize) +{ + char buf[EXIF_STD_BUF_LEN]; + char picturecontrolname[EXIF_STD_BUF_LEN]; + char picturecontrolbase[EXIF_STD_BUF_LEN]; + unsigned int version = 0; + unsigned int length = 0; + unsigned int piccontroladj = 0; + unsigned int piccontrolquickadj = 0; + unsigned int sharpness = 0; + unsigned int contrast = 0; + unsigned int brightness = 0; + unsigned int saturation = 0; + unsigned int hueadjustment = 0; + unsigned int i, j; + + /* libexif does not support PictureControlData 35 yet. so we have to parse the debug data :-( */ + buf[0] = '\0'; + exif_get_mnote_tag(ed, 35, buf, sizeof(buf)); + + sscanf(buf, + "(null): %u bytes unknown data: 303130%02X%40s%40s%*8s%02X%02X%02X%02X%02X%02X%02X", + &length, &version, &picturecontrolname[0], + &picturecontrolbase[0], &piccontroladj, &piccontrolquickadj, + &sharpness, &contrast, &brightness, &saturation, + &hueadjustment); + + /* printf("--%s %d-%d-\n", buf, version, piccontroladj); */ + + for (i = 0; i < 40; i++) { + sscanf(&picturecontrolname[2 * i], "%2X", &j); + picturecontrolname[i] = j; + sscanf(&picturecontrolbase[2 * i], "%2X", &j); + picturecontrolbase[i] = j; + + } + exif_trim_spaces(picturecontrolname); + exif_trim_spaces(picturecontrolbase); + + if (((length == 58) && (version == '0')) + && (piccontroladj < EXN_PIC_CTRL_ADJ_MAX) + ) { + snprintf(buffer, maxsize, + "PictCtrlData: Name: %s; Base: %s; CtrlAdj: %s; Quick: %d; Shrp: %d; Contr: %d; Brght: %d; Sat: %d; Hue: %d\n", + picturecontrolname, picturecontrolbase, + EXN_Pic_Ctrl_Adj[piccontroladj], + piccontrolquickadj, sharpness, contrast, + brightness, saturation, hueadjustment); + } + +} + + + + +/* get nikon Flash info: control mode (168) info */ +static void exn_get_mnote_nikon_168(ExifData * ed, char *buffer, + unsigned int maxsize) +{ + char buf[EXIF_STD_BUF_LEN]; + unsigned int version = 0; + unsigned int length = 0; + unsigned int exn_fcm = (EXN_FLASH_CONTROL_MODES_MAX - 1); /* default to N/A */ + unsigned int flashoutput = 0; + unsigned int externalflashflags = 0; + unsigned int flashcompensation = 0; + + /* libexif does not support flash info 168 yet. so we have to parse the debug data :-( */ + buf[0] = '\0'; + exif_get_mnote_tag(ed, 168, buf, sizeof(buf)); + sscanf(buf, + "(null): %u bytes unknown data: 303130%02X%*8s%02X%02X%02X%02X", + &length, &version, &externalflashflags, &exn_fcm, + &flashoutput, &flashcompensation); + exn_fcm = exn_fcm & EXN_FLASH_CONTROL_MODE_MASK; + + /* printf("%s - %d %d %d %d\n", buf, externalflashflags, exn_fcm, flashoutput, (signed char)flashcompensation); */ + + if ((exn_fcm < EXN_FLASH_CONTROL_MODES_MAX) + && (((length == 22) && (version == '3')) /* Nikon FlashInfo0103 */ + ||((length == 22) && (version == '4')) /* Nikon FlashInfo0104 */ + ||((length == 21) && (version == '2')) /* Nikon FlashInfo0102 */ + ||((length == 19) && (version == '0')) /* Nikon FlashInfo0100 */ + ) + ) { + + buf[0] = '\0'; + exn_get_flash_output(flashoutput, buf, EXIF_STD_BUF_LEN); + snprintf(buffer, maxsize, + "NikonFlashControlMode: %s (Power: %s)\n", + EXN_NikonFlashControlModeValues[exn_fcm], buf); + + /* External Flash Flags. Not as useful as expected. Not used (yet). */ + /* if ( (externalflashflags & (1<<2)) ) -> Bounce Flash */ + /* if ( (externalflashflags & (1<<4)) ) -> Wide Flash Adapter */ + /* if ( (externalflashflags & (1<<5)) ) -> Dome Diffusor */ + + } + +} + + + +/* get nikon AFInfo2 (183) info */ +static void exn_get_mnote_nikon_183(ExifData * ed, char *buffer, + unsigned int maxsize) +{ + char buf[EXIF_STD_BUF_LEN]; + unsigned int contrastdetectaf = 0; + unsigned int afareamode = 0; + unsigned int phasedetectaf = 0; + unsigned int primaryafpoint = 0; + unsigned int version = 0; + unsigned int length = 0; + + /* AFInfo2 */ + /* libexif does not support AFInfo2 183 yet. so we have to parse the debug data :-( */ + buf[0] = '\0'; + exif_get_mnote_tag(ed, 183, buf, sizeof(buf)); + sscanf(buf, + "(null): %u bytes unknown data: 303130%02X%02X%02X%02X%02X", + &length, &version, &contrastdetectaf, &afareamode, + &phasedetectaf, &primaryafpoint); + + + if (((length == 30) && (version == '0')) + && (contrastdetectaf < EXN_CONTRAST_DETECT_AF_MAX) + && (phasedetectaf < EXN_PHASE_DETECT_AF_MAX) + ) { + if ((contrastdetectaf != 0) + && (afareamode < EXN_AF_AREA_MODE_C_MAX)) { + /* Contrast AF (live view) */ + snprintf(buffer, maxsize, + "ContrastDetectAF: %s; AFAreaMode: %s\n", + EXN_NikonContrastDetectAF + [contrastdetectaf], + EXN_NikonAFAreaModeContr[afareamode]); + + } else if ((phasedetectaf != 0) + && (afareamode < EXN_AF_AREA_MODE_P_MAX)) { + /* Phase AF */ + buf[0] = '\0'; + exn_get_prim_af_pt(phasedetectaf, primaryafpoint, + buf, EXIF_STD_BUF_LEN); + + snprintf(buffer, maxsize, + "PhaseDetectAF: %s; AreaMode: %s; PrimaryAFPoint: %s\n", + EXN_NikonPhaseDetectAF[phasedetectaf], + EXN_NikonAFAreaModePhase[afareamode], + buf); + } + + } +} + + + +/* get interesting nikon maker note tags in readable form */ +void exn_get_mnote_nikon_tags(ExifData * ed, unsigned int tag, + char *buffer, unsigned int maxsize) +{ + char buf[EXIF_STD_BUF_LEN]; + + buf[0] = '\0'; + exif_get_tag(ed, EXIF_IFD_EXIF, EXIF_TAG_FLASH, buf, sizeof(buf)); + exif_trim_spaces(buf); + + switch (tag) { + /* show only if flash was used */ + case 8: /* Flash Setting */ + case 9: /* Flash Mode */ + case 135: /* Flash used */ + { + if (! + (strcmp("Flash: Flash did not fire\n", buf) == + 0)) { + /* show extended flash info only if flash was fired */ + exif_get_mnote_tag(ed, tag, buffer, + maxsize); + } + } + break; + + case 18: /* FlashExposureComp */ + { + if (! + (strcmp("Flash: Flash did not fire\n", buf) == + 0)) { + /* show only if flash was fired */ + exn_get_mnote_nikon_18(ed, buffer, + maxsize); + } + } + break; + + case 34: + { + /* ActiveD-Lighting */ + exn_get_mnote_nikon_34(ed, buffer, maxsize); + } + break; + + case 35: + { + /* PictureControlData */ + exn_get_mnote_nikon_35(ed, buffer, maxsize); + } + break; + + case 168: + { + /* Flash info: control mode */ + if (! + (strcmp("Flash: Flash did not fire\n", buf) == + 0)) { + /* show extended flash info only if flash was fired */ + exn_get_mnote_nikon_168(ed, buffer, + maxsize); + } + } + break; + + case 183: + { + /* AFInfo 2 */ + exn_get_mnote_nikon_183(ed, buffer, maxsize); + } + break; + + default: + { + /* normal makernote tags without special treatment */ + exif_get_mnote_tag(ed, tag, buffer, maxsize); + } + break; + } + + + return; +} + +#endif diff --git a/src/exif_nikon.h b/src/exif_nikon.h new file mode 100644 index 0000000..49d14b6 --- /dev/null +++ b/src/exif_nikon.h @@ -0,0 +1,34 @@ +/* exif_nikon.h + +Copyright (C) 2012 Dennis Real. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef EXIF_NIKON_H +#define EXIF_NIKON_H + +#include <libexif/exif-data.h> + +extern void exn_get_mnote_nikon_tags(ExifData * ed, unsigned int tag, + char *buffer, unsigned int maxsize); + +#endif @@ -1,6 +1,7 @@ /* feh.h Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -26,6 +27,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef FEH_H #define FEH_H +/* + * strverscmp(3) is a GNU extension. In most supporting C libraries it + * requires _GNU_SOURCE to be defined. + */ +#ifdef HAVE_STRVERSCMP +#define _GNU_SOURCE +#endif + #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xatom.h> @@ -54,15 +63,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include <signal.h> #include <sys/wait.h> #include <math.h> +#include <getopt.h> #include <Imlib2.h> -#include <giblib/giblib.h> +#include "gib_hash.h" +#include "gib_imlib.h" +#include "gib_list.h" +#include "gib_style.h" #include "structs.h" #include "menu.h" #include "utils.h" -#include "getopt.h" #include "debug.h" @@ -92,11 +104,31 @@ enum bgmode_type { BG_MODE_NONE = 0, BG_MODE_TILE, BG_MODE_CENTER, BG_MODE_SCALE, BG_MODE_FILL, BG_MODE_MAX }; +enum zoom_mode { ZOOM_MODE_FILL = 1, ZOOM_MODE_MAX }; + +enum text_bg { TEXT_BG_CLEAR = 0, TEXT_BG_TINTED }; + enum slide_change { SLIDE_NEXT, SLIDE_PREV, SLIDE_RAND, SLIDE_FIRST, SLIDE_LAST, SLIDE_JUMP_FWD, - SLIDE_JUMP_BACK + SLIDE_JUMP_BACK, + SLIDE_JUMP_NEXT_DIR, + SLIDE_JUMP_PREV_DIR }; +enum feh_load_error { + LOAD_ERROR_IMLIB = 0, + LOAD_ERROR_IMAGEMAGICK, + LOAD_ERROR_CURL, + LOAD_ERROR_DCRAW, + LOAD_ERROR_MAGICBYTES +}; + +#define INPLACE_EDIT_FLIP -1 +#define INPLACE_EDIT_MIRROR -2 + +#define ZOOM_MIN 0.002 +#define ZOOM_MAX 2000 + typedef void (*sighandler_t) (int); int feh_main_iteration(int block); @@ -108,43 +140,63 @@ void init_xinerama(void); #endif /* HAVE_LIBXINERAMA */ void init_multiwindow_mode(void); void init_thumbnail_mode(void); -void init_collage_mode(void); void init_index_mode(void); void init_slideshow_mode(void); void init_list_mode(void); void init_loadables_mode(void); void init_unloadables_mode(void); +#ifdef HAVE_LIBMAGIC +void uninit_magic(void); +void init_magic(void); +#endif void feh_clean_exit(void); +int feh_should_ignore_image(Imlib_Image * im); int feh_load_image(Imlib_Image * im, feh_file * file); void show_mini_usage(void); -void slideshow_change_image(winwidget winwid, int change); +void slideshow_change_image(winwidget winwid, int change, int render); void slideshow_pause_toggle(winwidget w); -char *slideshow_create_name(feh_file * file); +void init_keyevents(void); +void init_buttonbindings(void); +void setup_stdin(void); +void restore_stdin(void); void feh_event_handle_keypress(XEvent * ev); -void feh_action_run(feh_file * file, char *action); -char *feh_printf(char *str, feh_file * file); +void feh_event_handle_stdin(void); +void feh_event_handle_generic(winwidget winwid, unsigned int state, KeySym keysym, unsigned int button); +fehkey *feh_str_to_kb(char * action); +void feh_action_run(feh_file * file, char *action, winwidget winwid); +char *format_size(double size); +char *feh_printf(char *str, feh_file * file, winwidget winwid); +void im_weprintf(winwidget w, char *fmt, ...); void feh_draw_zoom(winwidget w); void feh_draw_checks(winwidget win); void cb_slide_timer(void *data); void cb_reload_timer(void *data); -char *feh_http_load_image(char *url); int feh_load_image_char(Imlib_Image * im, char *filename); void feh_draw_filename(winwidget w); +#ifdef HAVE_LIBEXIF +void feh_draw_exif(winwidget w); +#endif void feh_draw_actions(winwidget w); void feh_draw_caption(winwidget w); void feh_draw_info(winwidget w); +void feh_draw_errstr(winwidget w); void feh_display_status(char stat); void real_loadables_mode(int loadable); void feh_reload_image(winwidget w, int resize, int force_new); void feh_filelist_image_remove(winwidget winwid, char do_delete); -char *feh_strip_hostname(char *url); -struct hostent *feh_gethostbyname(const char *name); +void feh_print_load_error(char *file, winwidget w, Imlib_Load_Error err, enum feh_load_error feh_err); void slideshow_save_image(winwidget win); -void feh_edit_inplace_orient(winwidget w, int orientation); -void feh_edit_inplace_lossless_rotate(winwidget w, int orientation); +void feh_edit_inplace(winwidget w, int orientation); +void feh_edit_inplace_lossless(winwidget w, int orientation); gib_list *feh_wrap_string(char *text, int wrap_width, Imlib_Font fn, gib_style * style); -char *build_caption_filename(feh_file * file); +char *build_caption_filename(feh_file * file, short create_dir); gib_list *feh_list_jump(gib_list * root, gib_list * l, int direction, int num); +#ifdef HAVE_INOTIFY +void feh_event_handle_inotify(void); +#endif +#ifndef HAVE_STRVERSCMP +int strverscmp(const char *l0, const char *r0); +#endif /* Imlib stuff */ extern Display *disp; @@ -170,4 +222,9 @@ extern feh_menu *menu_main; extern feh_menu *menu_close; extern char *mode; /* label for the current mode */ +/* to terminate long-running children with SIGALRM */ +extern int childpid; + +extern unsigned char control_via_stdin; + #endif diff --git a/src/feh_png.c b/src/feh_png.c index e2842f8..8f5b94d 100644 --- a/src/feh_png.c +++ b/src/feh_png.c @@ -23,20 +23,18 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "feh_png.h" - #include <png.h> #include <stdio.h> #include <stdarg.h> +#include "feh_png.h" + #define FEH_PNG_COMPRESSION 3 -#define FEH_PNG_NUM_COMMENTS 2 /* only Thumb::URI and Thumb::MTime for now */ +#define FEH_PNG_NUM_COMMENTS 4 gib_hash *feh_png_read_comments(char *file) { - gib_hash *hash = NULL; - FILE *fp; int i, sig_bytes, comments = 0; @@ -45,31 +43,31 @@ gib_hash *feh_png_read_comments(char *file) png_textp text_ptr; if (!(fp = fopen(file, "rb"))) - return hash; + return NULL; if (!(sig_bytes = feh_png_file_is_png(fp))) { fclose(fp); - return hash; + return NULL; } /* initialize data structures */ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { fclose(fp); - return hash; + return NULL; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL); fclose(fp); - return hash; + return NULL; } - if (setjmp(png_ptr->jmpbuf)) { + if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); fclose(fp); - return hash; + return NULL; } /* initialize reading */ @@ -78,6 +76,8 @@ gib_hash *feh_png_read_comments(char *file) png_read_info(png_ptr, info_ptr); + gib_hash *hash = NULL; + #ifdef PNG_TEXT_SUPPORTED png_get_text(png_ptr, info_ptr, &text_ptr, &comments); if (comments > 0) { @@ -94,7 +94,7 @@ gib_hash *feh_png_read_comments(char *file) } /* grab image data from image and write info file with comments ... */ -int feh_png_write_png(Imlib_Image image, char *file, ...) +int feh_png_write_png_fd(Imlib_Image image, int fd, ...) { FILE *fp; int i, w, h; @@ -111,20 +111,23 @@ int feh_png_write_png(Imlib_Image image, char *file, ...) char *pair_key, *pair_text; #endif /* PNG_TEXT_SUPPORTED */ - if (!(fp = fopen(file, "wb"))) + if (!(fp = fdopen(fd, "wb"))) return 0; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_ptr) + if (!png_ptr) { + fclose(fp); return 0; + } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp) NULL); + fclose(fp); return 0; } - if (setjmp(png_ptr->jmpbuf)) { + if (setjmp(png_jmpbuf(png_ptr))) { fclose(fp); png_destroy_write_struct(&png_ptr, &info_ptr); png_destroy_info_struct(png_ptr, &info_ptr); @@ -152,7 +155,7 @@ int feh_png_write_png(Imlib_Image image, char *file, ...) png_set_sBIT(png_ptr, info_ptr, &sig_bit); #ifdef PNG_TEXT_SUPPORTED - va_start(args, file); + va_start(args, fd); for (i = 0; i < FEH_PNG_NUM_COMMENTS; i++) { if ((pair_key = va_arg(args, char *)) && (pair_text = va_arg(args, char *))) { @@ -194,7 +197,10 @@ int feh_png_file_is_png(FILE * fp) { unsigned char buf[8]; - fread(buf, 1, 8, fp); + if (fread(buf, 1, 8, fp) != 8) { + return 0; + } + if (png_sig_cmp(buf, 0, 8)) { return 0; } diff --git a/src/feh_png.h b/src/feh_png.h index 6502978..035d36a 100644 --- a/src/feh_png.h +++ b/src/feh_png.h @@ -26,13 +26,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef FEH_PNG_H #define FEH_PNG_H -#include "feh.h" - #include <stdio.h> #include <stdarg.h> +#include "feh.h" + gib_hash *feh_png_read_comments(char *file); -int feh_png_write_png(Imlib_Image image, char *file, ...); +int feh_png_write_png_fd(Imlib_Image image, int fd, ...); int feh_png_file_is_png(FILE * fp); diff --git a/src/filelist.c b/src/filelist.c index 5c0777d..3d9bcef 100644 --- a/src/filelist.c +++ b/src/filelist.c @@ -1,6 +1,7 @@ /* filelist.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,12 +26,17 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "feh.h" #include "filelist.h" +#include "signals.h" #include "options.h" +#ifdef HAVE_LIBCURL +#include <curl/curl.h> +#endif + gib_list *filelist = NULL; +gib_list *original_file_items = NULL; /* original file items from argv */ int filelist_len = 0; gib_list *current_file = NULL; -extern int errno; static gib_list *rm_filelist = NULL; @@ -47,7 +53,12 @@ feh_file *feh_file_new(char *filename) newfile->name = estrdup(s + 1); else newfile->name = estrdup(filename); + newfile->size = -1; + newfile->mtime = 0; newfile->info = NULL; +#ifdef HAVE_LIBEXIF + newfile->ed = NULL; +#endif return(newfile); } @@ -63,6 +74,10 @@ void feh_file_free(feh_file * file) free(file->caption); if (file->info) feh_file_info_free(file->info); +#ifdef HAVE_LIBEXIF + if (file->ed) + exif_data_unref(file->ed); +#endif free(file); return; } @@ -76,7 +91,6 @@ feh_file_info *feh_file_info_new(void) info->width = 0; info->height = 0; - info->size = 0; info->pixels = 0; info->has_alpha = 0; info->format = NULL; @@ -111,13 +125,81 @@ gib_list *feh_file_remove_from_list(gib_list * list, gib_list * l) return(gib_list_remove(list, l)); } +int file_selector_all(const struct dirent *unused __attribute__((unused))) +{ + return 1; +} + +static void feh_print_stat_error(char *path) +{ + if (opt.quiet) + return; + + switch (errno) { + case ENOENT: + case ENOTDIR: + weprintf("%s does not exist - skipping", path); + break; + case ELOOP: + weprintf("%s - too many levels of symbolic links - skipping", path); + break; + case EACCES: + weprintf("you don't have permission to open %s - skipping", path); + break; + case EOVERFLOW: + weprintf("Cannot open %s - EOVERFLOW.\n" + "Recompile with stat64=1 to fix this", path); + break; + default: + weprintf("couldn't open %s", path); + break; + } +} + +static void add_stdin_to_filelist(void) +{ + char buf[1024]; + size_t readsize; + char *sfn = estrjoin("_", "/tmp/feh_stdin", "XXXXXX", NULL); + int fd = mkstemp(sfn); + FILE *outfile; + + if (fd == -1) { + free(sfn); + weprintf("cannot read from stdin: mktemp:"); + return; + } + + outfile = fdopen(fd, "w"); + + if (outfile == NULL) { + free(sfn); + weprintf("cannot read from stdin: fdopen:"); + return; + } + + while ((readsize = fread(buf, sizeof(char), sizeof(buf), stdin)) > 0) { + if (fwrite(buf, sizeof(char), readsize, outfile) < readsize) { + free(sfn); + fclose(outfile); + return; + } + } + fclose(outfile); + + filelist = gib_list_add_front(filelist, feh_file_new(sfn)); + add_file_to_rm_filelist(sfn); + free(sfn); +} + + /* Recursive */ void add_file_to_filelist_recursively(char *origpath, unsigned char level) { struct stat st; char *path; - if (!origpath) + if (!origpath || *origpath == '\0') return; path = estrdup(origpath); @@ -131,15 +213,17 @@ void add_file_to_filelist_recursively(char *origpath, unsigned char level) if (path[len - 1] == '/') path[len - 1] = '\0'; - if ((!strncmp(path, "http://", 7)) - || (!strncmp(path, "https://", 8)) - || (!strncmp(path, "ftp://", 6))) { - /* Its a url */ + if (path_is_url(path)) { D(("Adding url %s to filelist\n", path)); filelist = gib_list_add_front(filelist, feh_file_new(path)); /* We'll download it later... */ free(path); return; + } else if ((len == 1) && (path[0] == '-')) { + D(("Adding temporary file for stdin (-) to filelist\n")); + add_stdin_to_filelist(); + free(path); + return; } else if (opt.filelistfile) { char *newpath = feh_absolute_path(path); @@ -150,33 +234,15 @@ void add_file_to_filelist_recursively(char *origpath, unsigned char level) errno = 0; if (stat(path, &st)) { - /* Display useful error message */ - switch (errno) { - case ENOENT: - case ENOTDIR: - if (!opt.quiet) - weprintf("%s does not exist - skipping", path); - break; - case ELOOP: - if (!opt.quiet) - weprintf("%s - too many levels of symbolic links - skipping", path); - break; - case EACCES: - if (!opt.quiet) - weprintf("you don't have permission to open %s - skipping", path); - break; - default: - if (!opt.quiet) - weprintf("couldn't open %s", path); - break; - } + feh_print_stat_error(path); free(path); return; } if ((S_ISDIR(st.st_mode)) && (level != FILELIST_LAST)) { - struct dirent *de; + struct dirent **de; DIR *dir; + int cnt, n; D(("It is a directory\n")); @@ -186,24 +252,35 @@ void add_file_to_filelist_recursively(char *origpath, unsigned char level) free(path); return; } - de = readdir(dir); - while (de != NULL) { - if (strcmp(de->d_name, ".") - && strcmp(de->d_name, "..")) { - char *newfile; - - newfile = estrjoin("", path, "/", de->d_name, NULL); - - /* This ensures we go down one level even if not fully recursive - - this way "feh some_dir" expands to some_dir's contents */ - if (opt.recursive) - add_file_to_filelist_recursively(newfile, FILELIST_CONTINUE); - else - add_file_to_filelist_recursively(newfile, FILELIST_LAST); - - free(newfile); + n = scandir(path, &de, file_selector_all, alphasort); + if (n < 0) { + switch (errno) { + case ENOMEM: + weprintf("Insufficient memory to scan directory %s:", path); + break; + default: + weprintf("Failed to scan directory %s:", path); } - de = readdir(dir); + } else { + for (cnt = 0; cnt < n; cnt++) { + if (strcmp(de[cnt]->d_name, ".") + && strcmp(de[cnt]->d_name, "..")) { + char *newfile; + + newfile = estrjoin("", path, "/", de[cnt]->d_name, NULL); + + /* This ensures we go down one level even if not fully recursive + - this way "feh some_dir" expands to some_dir's contents */ + if (opt.recursive) + add_file_to_filelist_recursively(newfile, FILELIST_CONTINUE); + else + add_file_to_filelist_recursively(newfile, FILELIST_LAST); + + free(newfile); + } + free(de[cnt]); + } + free(de); } closedir(dir); } else if (S_ISREG(st.st_mode)) { @@ -229,32 +306,49 @@ void delete_rm_files(void) return; } -gib_list *feh_file_info_preload(gib_list * list) +gib_list *feh_file_info_preload(gib_list * list, int load_images) { gib_list *l; feh_file *file = NULL; gib_list *remove_list = NULL; - if (opt.verbose) - fprintf(stdout, PACKAGE " - preloading...\n"); - for (l = list; l; l = l->next) { file = FEH_FILE(l->data); D(("file %p, file->next %p, file->name %s\n", l, l->next, file->name)); - if (feh_file_info_load(file, NULL)) { - D(("Failed to load file %p\n", file)); - remove_list = gib_list_add_front(remove_list, l); - if (opt.verbose) - feh_display_status('x'); - } else if (opt.verbose) - feh_display_status('.'); + if (load_images) { + if (feh_file_info_load(file, NULL)) { + D(("Failed to load file %p\n", file)); + remove_list = gib_list_add_front(remove_list, l); + if (opt.verbose) + feh_display_status('x'); + } else if (((unsigned int)file->info->width < opt.min_width) + || ((unsigned int)file->info->width > opt.max_width) + || ((unsigned int)file->info->height < opt.min_height) + || ((unsigned int)file->info->height > opt.max_height)) { + remove_list = gib_list_add_front(remove_list, l); + if (opt.verbose) + feh_display_status('s'); + } else if (opt.verbose) + feh_display_status('.'); + } else { + if (feh_file_stat(file)) { + D(("Failed to stat file %p\n", file)); + remove_list = gib_list_add_front(remove_list, l); + } + } + if (sig_exit) { + feh_display_status(0); + exit(sig_exit); + } } if (opt.verbose) - fprintf(stdout, "\n"); + feh_display_status(0); if (remove_list) { - for (l = remove_list; l; l = l->next) + for (l = remove_list; l; l = l->next) { + feh_file_free(FEH_FILE(((gib_list *) l->data)->data)); filelist = list = gib_list_remove(list, (gib_list *) l->data); + } gib_list_free(remove_list); } @@ -262,48 +356,39 @@ gib_list *feh_file_info_preload(gib_list * list) return(list); } -int feh_file_info_load(feh_file * file, Imlib_Image im) +int feh_file_stat(feh_file * file) { struct stat st; + + errno = 0; + if (stat(file->filename, &st)) { + feh_print_stat_error(file->filename); + return(1); + } + + file->mtime = st.st_mtime; + + file->size = st.st_size; + + return(0); +} + +int feh_file_info_load(feh_file * file, Imlib_Image im) +{ int need_free = 1; Imlib_Image im1; + if (feh_file_stat(file)) + return(1); + D(("im is %p\n", im)); if (im) need_free = 0; - errno = 0; - if (stat(file->filename, &st)) { - /* Display useful error message */ - switch (errno) { - case ENOENT: - case ENOTDIR: - if (!opt.quiet) - weprintf("%s does not exist - skipping", file->filename); - break; - case ELOOP: - if (!opt.quiet) - weprintf("%s - too many levels of symbolic links - skipping", file->filename); - break; - case EACCES: - if (!opt.quiet) - weprintf("you don't have permission to open %s - skipping", file->filename); - break; - default: - if (!opt.quiet) - weprintf("couldn't open %s ", file->filename); - break; - } - return(1); - } - if (im) im1 = im; - else if (!feh_load_image(&im1, file)) - return(1); - - if (!im1) + else if (!feh_load_image(&im1, file) || !im1) return(1); file->info = feh_file_info_new(); @@ -317,21 +402,59 @@ int feh_file_info_load(feh_file * file, Imlib_Image im) file->info->format = estrdup(gib_imlib_image_format(im1)); - file->info->size = st.st_size; - - if (need_free && im1) + if (need_free) gib_imlib_free_image_and_decache(im1); return(0); } +void feh_file_dirname(char *dst, feh_file * f, int maxlen) +{ + int n = strlen(f->filename) - strlen(f->name); + + /* Give up on long dirnames */ + if (n <= 0 || n >= maxlen) { + dst[0] = '\0'; + return; + } + + memcpy(dst, f->filename, n); + dst[n] = '\0'; +} + +static inline int strcmp_or_strverscmp(const char *s1, const char *s2) +{ + if (!opt.version_sort) + return(strcmp(s1, s2)); + else + return(strverscmp(s1, s2)); +} + int feh_cmp_filename(void *file1, void *file2) { - return(strcmp(FEH_FILE(file1)->filename, FEH_FILE(file2)->filename)); + return(strcmp_or_strverscmp(FEH_FILE(file1)->filename, FEH_FILE(file2)->filename)); } int feh_cmp_name(void *file1, void *file2) { - return(strcmp(FEH_FILE(file1)->name, FEH_FILE(file2)->name)); + return(strcmp_or_strverscmp(FEH_FILE(file1)->name, FEH_FILE(file2)->name)); +} + +int feh_cmp_dirname(void *file1, void *file2) +{ + char dir1[PATH_MAX], dir2[PATH_MAX]; + int cmp; + feh_file_dirname(dir1, FEH_FILE(file1), PATH_MAX); + feh_file_dirname(dir2, FEH_FILE(file2), PATH_MAX); + if ((cmp = strcmp_or_strverscmp(dir1, dir2)) != 0) + return(cmp); + return(feh_cmp_name(file1, file2)); +} + +/* Return -1 if file1 is _newer_ than file2 */ +int feh_cmp_mtime(void *file1, void *file2) +{ + /* gib_list_sort is not stable, so explicitly return 0 as -1 */ + return(FEH_FILE(file1)->mtime >= FEH_FILE(file2)->mtime ? -1 : 1); } int feh_cmp_width(void *file1, void *file2) @@ -351,7 +474,7 @@ int feh_cmp_pixels(void *file1, void *file2) int feh_cmp_size(void *file1, void *file2) { - return((FEH_FILE(file1)->info->size - FEH_FILE(file2)->info->size)); + return((FEH_FILE(file1)->size - FEH_FILE(file2)->size)); } int feh_cmp_format(void *file1, void *file2) @@ -361,10 +484,25 @@ int feh_cmp_format(void *file1, void *file2) void feh_prepare_filelist(void) { - if (opt.list || opt.customlist || (opt.sort > SORT_FILENAME) - || opt.preload) { + /* + * list and customlist mode as well as the somewhat more fancy sort modes + * need access to file infos. Preloading them is also useful for + * list/customlist as --min-dimension/--max-dimension may filter images + * which should not be processed. + * Finally, if --min-dimension/--max-dimension (-> opt.filter_by_dimensions) + * is set and we're in thumbnail mode, we need to filter images first so + * we can create a properly sized thumbnail list. + */ + if (opt.list || opt.preload || opt.customlist || (opt.sort >= SORT_WIDTH) + || (opt.filter_by_dimensions && (opt.index || opt.thumbs || opt.bgmode))) { /* For these sort options, we have to preload images */ - filelist = feh_file_info_preload(filelist); + filelist = feh_file_info_preload(filelist, TRUE); + if (!gib_list_length(filelist)) + show_mini_usage(); + } else if (opt.sort >= SORT_SIZE) { + /* For these sort options, we need stat(2) information on the files, + * but there is no need to load the images. */ + filelist = feh_file_info_preload(filelist, FALSE); if (!gib_list_length(filelist)) show_mini_usage(); } @@ -386,6 +524,12 @@ void feh_prepare_filelist(void) case SORT_FILENAME: filelist = gib_list_sort(filelist, feh_cmp_filename); break; + case SORT_DIRNAME: + filelist = gib_list_sort(filelist, feh_cmp_dirname); + break; + case SORT_MTIME: + filelist = gib_list_sort(filelist, feh_cmp_mtime); + break; case SORT_WIDTH: filelist = gib_list_sort(filelist, feh_cmp_width); break; @@ -419,7 +563,7 @@ int feh_write_filelist(gib_list * list, char *filename) FILE *fp; gib_list *l; - if (!list || !filename) + if (!list || !filename || !strcmp(filename, "/dev/stdin")) return(0); errno = 0; @@ -441,26 +585,39 @@ gib_list *feh_read_filelist(char *filename) FILE *fp; gib_list *list = NULL; char s[1024], s1[1024]; - Imlib_Image im1; + Imlib_Load_Error err = IMLIB_LOAD_ERROR_NONE; + Imlib_Image tmp_im; + struct stat st; + signed short tmp_conversion_timeout; if (!filename) return(NULL); - /* try and load the given filelist as an image, cowardly refuse to - * overwrite an image with a filelist. (requested by user who did feh -df * - * when he meant feh -dF *, as it overwrote the first image with the - * filelist). + /* + * feh_load_image will fail horribly if filename is not seekable */ - if (feh_load_image_char(&im1, filename)) { - weprintf( - "The file you specified as a filelist to read - %s - appears to be an image. Ignoring it (this is a common mistake).\n", - filename); - opt.filelistfile = NULL; - return(NULL); + tmp_conversion_timeout = opt.conversion_timeout; + opt.conversion_timeout = -1; + if (!stat(filename, &st) && S_ISREG(st.st_mode)) { + tmp_im = imlib_load_image_with_error_return(filename, &err); + if (err == IMLIB_LOAD_ERROR_NONE) { + gib_imlib_free_image_and_decache(tmp_im); + weprintf("Filelist file %s is an image, refusing to use it.\n" + "Did you mix up -f and -F?", filename); + opt.filelistfile = NULL; + return NULL; + } } + opt.conversion_timeout = tmp_conversion_timeout; errno = 0; - if ((fp = fopen(filename, "r")) == NULL) { + + if (!strcmp(filename, "/dev/stdin")) + fp = stdin; + else + fp = fopen(filename, "r"); + + if (fp == NULL) { /* return quietly, as it's okay to specify a filelist file that doesn't exist. In that case we create it on exit. */ return(NULL); @@ -476,7 +633,8 @@ gib_list *feh_read_filelist(char *filename) /* Add it to the new list */ list = gib_list_add_front(list, feh_file_new(s1)); } - fclose(fp); + if (strcmp(filename, "/dev/stdin")) + fclose(fp); return(list); } @@ -490,17 +648,19 @@ char *feh_absolute_path(char *path) if (!path) return(NULL); - if (path[0] == '/') + if (path[0] == '/' || path_is_url(path)) return(estrdup(path)); /* This path is not relative. We're gonna convert it, so that a filelist file can be saved anywhere and feh will still find the images */ D(("Need to convert %s to an absolute form\n", path)); - /* I SHOULD be able to just use a simple realpath() here, but dumb * + /* I SHOULD be able to just use a simple realpath() here, but dumb * old Solaris's realpath doesn't return an absolute path if the path you give it is relative. Linux and BSD get this right... */ - getcwd(cwd, sizeof(cwd)); - snprintf(temp, sizeof(temp), "%s/%s", cwd, path); + if (getcwd(cwd, sizeof(cwd)) == NULL) + eprintf("Cannot determine working directory:"); + if ((size_t) snprintf(temp, sizeof(temp), "%s/%s", cwd, path) >= sizeof(temp)) + eprintf("Absolute path for working directory was truncated"); if (realpath(temp, fullpath) != NULL) { ret = estrdup(fullpath); } else { @@ -510,16 +670,49 @@ char *feh_absolute_path(char *path) return(ret); } -void feh_save_filelist() +void feh_save_filelist(void) { char *tmpname; + char *base_dir = ""; - tmpname = feh_unique_filename("", "filelist"); + if (opt.output_dir) { + base_dir = estrjoin("", opt.output_dir, "/", NULL); + } - if (!opt.quiet) - printf("saving filelist to filename '%s'\n", tmpname); + tmpname = feh_unique_filename(base_dir, "filelist"); + + if (opt.output_dir) { + free(base_dir); + } + + if (opt.verbose) + fprintf(stderr, "saving filelist to filename '%s'\n", tmpname); feh_write_filelist(filelist, tmpname); free(tmpname); return; } + +#ifdef HAVE_LIBCURL + +char *feh_http_unescape(char *url) +{ + CURL *curl = curl_easy_init(); + if (!curl) { + return NULL; + } + char *tmp_url = curl_easy_unescape(curl, url, 0, NULL); + char *new_url = estrdup(tmp_url); + curl_free(tmp_url); + curl_easy_cleanup(curl); + return new_url; +} + +#else + +char *feh_http_unescape(char *url) +{ + return NULL; +} + +#endif diff --git a/src/filelist.h b/src/filelist.h index 842e3af..4fc3930 100644 --- a/src/filelist.h +++ b/src/filelist.h @@ -1,6 +1,7 @@ /* filelist.h 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 @@ -26,19 +27,27 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef FILELIST_H #define FILELIST_H +#ifdef HAVE_LIBEXIF +#include <libexif/exif-data.h> +#endif + struct __feh_file { char *filename; char *caption; char *name; /* info stuff */ + time_t mtime; + int size; feh_file_info *info; /* only set when needed */ +#ifdef HAVE_LIBEXIF + ExifData *ed; +#endif }; struct __feh_file_info { int width; int height; - int size; int pixels; unsigned char has_alpha; char *format; @@ -47,12 +56,29 @@ struct __feh_file_info { #define FEH_FILE(l) ((feh_file *) l) +/* + * PATH_MAX may not be defined on all systems. Since we only use it in for a + * getcwd call in feh_absolute_path, it isn't really worth the effort to malloc + * ever-increasing buffers until it fits. Instead, we just set it to 4096 and + * have --filelist and --bg-* hiccup if the path is larger. + */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + enum filelist_recurse { FILELIST_FIRST, FILELIST_CONTINUE, FILELIST_LAST }; -enum sort_type { SORT_NONE, SORT_NAME, SORT_FILENAME, SORT_WIDTH, +enum sort_type { + SORT_NONE, + SORT_NAME, + SORT_FILENAME, + SORT_DIRNAME, + SORT_SIZE, // everything after SORT_SIZE requires stat(2) information on the filelist + SORT_MTIME, + SORT_WIDTH, // everything after SORT_WIDTH requires preloading the images in the filelist SORT_HEIGHT, SORT_PIXELS, - SORT_SIZE, SORT_FORMAT + SORT_FORMAT }; feh_file *feh_file_new(char *filename); @@ -60,20 +86,26 @@ void feh_file_free(feh_file * file); feh_file_info *feh_file_info_new(void); void feh_file_info_free(feh_file_info * info); gib_list *feh_file_rm_and_free(gib_list * list, gib_list * file); +int file_selector_all(const struct dirent *unused); void add_file_to_filelist_recursively(char *origpath, unsigned char level); void add_file_to_rm_filelist(char *file); void delete_rm_files(void); -gib_list *feh_file_info_preload(gib_list * list); +gib_list *feh_file_info_preload(gib_list * list, int load_images); +int feh_file_stat(feh_file * file); int feh_file_info_load(feh_file * file, Imlib_Image im); +void feh_file_dirname(char *dst, feh_file * f, int maxlen); void feh_prepare_filelist(void); int feh_write_filelist(gib_list * list, char *filename); gib_list *feh_read_filelist(char *filename); char *feh_absolute_path(char *path); gib_list *feh_file_remove_from_list(gib_list * list, gib_list * l); -void feh_save_filelist(); +void feh_save_filelist(void); +char *feh_http_unescape(char * url); int feh_cmp_name(void *file1, void *file2); +int feh_cmp_dirname(void *file1, void *file2); int feh_cmp_filename(void *file1, void *file2); +int feh_cmp_mtime(void *file1, void *file2); int feh_cmp_width(void *file1, void *file2); int feh_cmp_height(void *file1, void *file2); int feh_cmp_pixels(void *file1, void *file2); @@ -81,6 +113,7 @@ int feh_cmp_size(void *file1, void *file2); int feh_cmp_format(void *file1, void *file2); extern gib_list *filelist; +extern gib_list *original_file_items; extern int filelist_len; extern gib_list *current_file; diff --git a/src/getopt.c b/src/getopt.c deleted file mode 100644 index d212b3a..0000000 --- a/src/getopt.c +++ /dev/null @@ -1,949 +0,0 @@ -/* Getopt for GNU. - NOTE: getopt is now part of the C library, so if you don't know what - "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu - before changing it! - - Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97 - Free Software Foundation, Inc. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. - - The GNU C Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the GNU C Library; see the file COPYING.LIB. If not, - write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. */ - -/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>. - Ditto for AIX 3.2 and <stdlib.h>. */ -#ifndef _NO_PROTO -#define _NO_PROTO -#endif - -#if !defined (__STDC__) || !__STDC__ -/* This is a separate conditional since some stdc systems - reject `defined (const)'. */ -#ifndef const -#define const -#endif -#endif - -#include <stdio.h> - -/* Comment out all this code if we are using the GNU C Library, and are not - actually compiling the library itself. This code is part of the GNU C - Library, but also included in many other GNU distributions. Compiling - and linking in this code is a waste when using the GNU C library - (especially if it is a shared library). Rather than having every GNU - program understand `configure --with-gnu-libc' and omit the object files, - it is simpler to just do this in the source for each such file. */ - -#define GETOPT_INTERFACE_VERSION 2 -#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2 -#include <gnu-versions.h> -#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION -#define ELIDE_CODE -#endif -#endif - -#ifndef ELIDE_CODE - -/* This needs to come after some library #include - to get __GNU_LIBRARY__ defined. */ -#ifdef __GNU_LIBRARY__ -/* Don't include stdlib.h for non-GNU C libraries because some of them - contain conflicting prototypes for getopt. */ -#include <stdlib.h> -#include <unistd.h> -#endif /* GNU C library. */ - -#ifdef VMS -#include <unixlib.h> -#if HAVE_STRING_H - 0 -#include <string.h> -#endif -#endif - -#ifndef _ -/* This is for other GNU distributions with internationalized messages. - When compiling libc, the _ macro is predefined. */ -#ifdef HAVE_LIBINTL_H -# include <libintl.h> -# define _(msgid) gettext (msgid) -#else -# define _(msgid) (msgid) -#endif -#endif - -/* This version of `getopt' appears to the caller like standard Unix `getopt' - but it behaves differently for the user, since it allows the user - to intersperse the options with the other arguments. - - As `getopt' works, it permutes the elements of ARGV so that, - when it is done, all the options precede everything else. Thus - all application programs are extended to handle flexible argument order. - - Setting the environment variable POSIXLY_CORRECT disables permutation. - Then the behavior is completely standard. - - GNU application programs can use a third alternative mode in which - they can distinguish the relative order of options and other arguments. */ - -#include "getopt.h" - -/* For communication from `getopt' to the caller. - When `getopt' finds an option that takes an argument, - the argument value is returned here. - Also, when `ordering' is RETURN_IN_ORDER, - each non-option ARGV-element is returned here. */ - -char *optarg = NULL; - -/* Index in ARGV of the next element to be scanned. - This is used for communication to and from the caller - and for communication between successive calls to `getopt'. - - On entry to `getopt', zero means this is the first call; initialize. - - When `getopt' returns -1, this is the index of the first of the - non-option elements that the caller should itself scan. - - Otherwise, `optind' communicates from one call to the next - how much of ARGV has been scanned so far. */ - -/* 1003.2 says this must be 1 before any call. */ -int optind = 1; - -/* Formerly, initialization of getopt depended on optind==0, which - causes problems with re-calling getopt as programs generally don't - know that. */ - -int __getopt_initialized = 0; - -/* The next char to be scanned in the option-element - in which the last option character we returned was found. - This allows us to pick up the scan where we left off. - - If this is zero, or a null string, it means resume the scan - by advancing to the next ARGV-element. */ - -static char *nextchar; - -/* Callers store zero here to inhibit the error message - for unrecognized options. */ - -int opterr = 1; - -/* Set to an option character which was unrecognized. - This must be initialized on some systems to avoid linking in the - system's own getopt implementation. */ - -int optopt = '?'; - -/* Describe how to deal with options that follow non-option ARGV-elements. - - If the caller did not specify anything, - the default is REQUIRE_ORDER if the environment variable - POSIXLY_CORRECT is defined, PERMUTE otherwise. - - REQUIRE_ORDER means don't recognize them as options; - stop option processing when the first non-option is seen. - This is what Unix does. - This mode of operation is selected by either setting the environment - variable POSIXLY_CORRECT, or using `+' as the first character - of the list of option characters. - - PERMUTE is the default. We permute the contents of ARGV as we scan, - so that eventually all the non-options are at the end. This allows options - to be given in any order, even with programs that were not written to - expect this. - - RETURN_IN_ORDER is an option available to programs that were written - to expect options and other ARGV-elements in any order and that care about - the ordering of the two. We describe each non-option ARGV-element - as if it were the argument of an option with character code 1. - Using `-' as the first character of the list of option characters - selects this mode of operation. - - The special argument `--' forces an end of option-scanning regardless - of the value of `ordering'. In the case of RETURN_IN_ORDER, only - `--' can cause `getopt' to return -1 with `optind' != ARGC. */ - -static enum { - REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER -} ordering; - -/* Value of POSIXLY_CORRECT environment variable. */ -static char *posixly_correct; - -#ifdef __GNU_LIBRARY__ -/* We want to avoid inclusion of string.h with non-GNU libraries - because there are many ways it can cause trouble. - On some systems, it contains special magic macros that don't work - in GCC. */ -#include <string.h> -#define my_index strchr -#else - -/* Avoid depending on library functions or files - whose names are inconsistent. */ - -char *getenv(); - -static char *my_index(str, chr) -const char *str; -int chr; -{ - while (*str) { - if (*str == chr) - return (char *) str; - str++; - } - return 0; -} - -/* If using GCC, we can safely declare strlen this way. - If not using GCC, it is ok not to declare it. */ -#ifdef __GNUC__ -/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. - That was relevant to code that was here before. */ -#if !defined (__STDC__) || !__STDC__ -/* gcc with -traditional declares the built-in strlen to return int, - and has done so at least since version 2.4.5. -- rms. */ -extern int strlen(const char *); -#endif /* not __STDC__ */ -#endif /* __GNUC__ */ - -#endif /* not __GNU_LIBRARY__ */ - -/* Handle permutation of arguments. */ - -/* Describe the part of ARGV that contains non-options that have - been skipped. `first_nonopt' is the index in ARGV of the first of them; - `last_nonopt' is the index after the last of them. */ - -static int first_nonopt; -static int last_nonopt; - -#ifdef _LIBC -/* Bash 2.0 gives us an environment variable containing flags - indicating ARGV elements that should not be considered arguments. */ - -/* Defined in getopt_init.c */ -extern char *__getopt_nonoption_flags; - -static int nonoption_flags_max_len; -static int nonoption_flags_len; - -static int original_argc; -static char *const *original_argv; - -/* Make sure the environment variable bash 2.0 puts in the environment - is valid for the getopt call we must make sure that the ARGV passed - to getopt is that one passed to the process. */ -static void - __attribute__ ((unused)) store_args_and_env(int argc, char *const *argv) -{ - /* XXX This is no good solution. We should rather copy the args so that - we can compare them later. But we must not use malloc(3). */ - original_argc = argc; - original_argv = argv; -} - -# ifdef text_set_element -text_set_element(__libc_subinit, store_args_and_env); -# endif /* text_set_element */ - -# define SWAP_FLAGS(ch1, ch2) \ - if (nonoption_flags_len > 0) \ - { \ - char __tmp = __getopt_nonoption_flags[ch1]; \ - __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ - __getopt_nonoption_flags[ch2] = __tmp; \ - } -#else /* !_LIBC */ -# define SWAP_FLAGS(ch1, ch2) -#endif /* _LIBC */ - -/* Exchange two adjacent subsequences of ARGV. - One subsequence is elements [first_nonopt,last_nonopt) - which contains all the non-options that have been skipped so far. - The other is elements [last_nonopt,optind), which contains all - the options processed since those non-options were skipped. - - `first_nonopt' and `last_nonopt' are relocated so that they describe - the new indices of the non-options in ARGV after they are moved. */ - -#if defined (__STDC__) && __STDC__ -static void exchange(char **); -#endif - -static void exchange(argv) -char **argv; -{ - int bottom = first_nonopt; - int middle = last_nonopt; - int top = optind; - char *tem; - - /* Exchange the shorter segment with the far end of the longer segment. - That puts the shorter segment into the right place. It leaves the - longer segment in the right place overall, but it consists of two parts - that need to be swapped next. */ - -#ifdef _LIBC - /* First make sure the handling of the `__getopt_nonoption_flags' string - can work normally. Our top argument must be in the range of the - string. */ - if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { - /* We must extend the array. The user plays games with us and presents - new arguments. */ - char *new_str = malloc(top + 1); - - if (new_str == NULL) - nonoption_flags_len = nonoption_flags_max_len = 0; - else { - memset(__mempcpy - (new_str, __getopt_nonoption_flags, - nonoption_flags_max_len), '\0', top + 1 - nonoption_flags_max_len); - nonoption_flags_max_len = top + 1; - __getopt_nonoption_flags = new_str; - } - } -#endif - - while (top > middle && middle > bottom) { - if (top - middle > middle - bottom) { - /* Bottom segment is the short one. */ - int len = middle - bottom; - register int i; - - /* Swap it with the top part of the top segment. */ - for (i = 0; i < len; i++) { - tem = argv[bottom + i]; - argv[bottom + i] = argv[top - (middle - bottom) + i]; - argv[top - (middle - bottom) + i] = tem; - SWAP_FLAGS(bottom + i, top - (middle - bottom) + i); - } - /* Exclude the moved bottom segment from further swapping. */ - top -= len; - } else { - /* Top segment is the short one. */ - int len = top - middle; - register int i; - - /* Swap it with the bottom part of the bottom segment. */ - for (i = 0; i < len; i++) { - tem = argv[bottom + i]; - argv[bottom + i] = argv[middle + i]; - argv[middle + i] = tem; - SWAP_FLAGS(bottom + i, middle + i); - } - /* Exclude the moved top segment from further swapping. */ - bottom += len; - } - } - - /* Update records for the slots the non-options now occupy. */ - - first_nonopt += (optind - last_nonopt); - last_nonopt = optind; -} - -/* Initialize the internal data when the first call is made. */ - -#if defined (__STDC__) && __STDC__ -static const char *_getopt_initialize(int, char *const *, const char *); -#endif -static const char *_getopt_initialize(argc, argv, optstring) -int argc; -char *const *argv; -const char *optstring; -{ - /* Start processing options with ARGV-element 1 (since ARGV-element 0 is - the program name); the sequence of previously skipped non-option - ARGV-elements is empty. */ - - first_nonopt = last_nonopt = optind; - - nextchar = NULL; - - posixly_correct = getenv("POSIXLY_CORRECT"); - - /* Determine how to handle the ordering of options and nonoptions. */ - - if (optstring[0] == '-') { - ordering = RETURN_IN_ORDER; - ++optstring; - } else if (optstring[0] == '+') { - ordering = REQUIRE_ORDER; - ++optstring; - } else if (posixly_correct != NULL) - ordering = REQUIRE_ORDER; - else - ordering = PERMUTE; - -#ifdef _LIBC - if (posixly_correct == NULL && argc == original_argc && argv == original_argv) { - if (nonoption_flags_max_len == 0) { - if (__getopt_nonoption_flags == NULL || __getopt_nonoption_flags[0] == '\0') - nonoption_flags_max_len = -1; - else { - const char *orig_str = __getopt_nonoption_flags; - int len = nonoption_flags_max_len = strlen(orig_str); - - if (nonoption_flags_max_len < argc) - nonoption_flags_max_len = argc; - __getopt_nonoption_flags = (char *) - malloc(nonoption_flags_max_len); - if (__getopt_nonoption_flags == NULL) - nonoption_flags_max_len = -1; - else - memset(__mempcpy - (__getopt_nonoption_flags, - orig_str, len), '\0', nonoption_flags_max_len - len); - } - } - nonoption_flags_len = nonoption_flags_max_len; - } else - nonoption_flags_len = 0; -#endif - - return optstring; -} - -/* Scan elements of ARGV (whose length is ARGC) for option characters - given in OPTSTRING. - - If an element of ARGV starts with '-', and is not exactly "-" or "--", - then it is an option element. The characters of this element - (aside from the initial '-') are option characters. If `getopt' - is called repeatedly, it returns successively each of the option characters - from each of the option elements. - - If `getopt' finds another option character, it returns that character, - updating `optind' and `nextchar' so that the next call to `getopt' can - resume the scan with the following option character or ARGV-element. - - If there are no more option characters, `getopt' returns -1. - Then `optind' is the index in ARGV of the first ARGV-element - that is not an option. (The ARGV-elements have been permuted - so that those that are not options now come last.) - - OPTSTRING is a string containing the legitimate option characters. - If an option character is seen that is not listed in OPTSTRING, - return '?' after printing an error message. If you set `opterr' to - zero, the error message is suppressed but we still return '?'. - - If a char in OPTSTRING is followed by a colon, that means it wants an arg, - so the following text in the same ARGV-element, or the text of the following - ARGV-element, is returned in `optarg'. Two colons mean an option that - wants an optional arg; if there is text in the current ARGV-element, - it is returned in `optarg', otherwise `optarg' is set to zero. - - If OPTSTRING starts with `-' or `+', it requests different methods of - handling the non-option ARGV-elements. - See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. - - Long-named options begin with `--' instead of `-'. - Their names may be abbreviated as long as the abbreviation is unique - or is an exact match for some defined option. If they have an - argument, it follows the option name in the same ARGV-element, separated - from the option name by a `=', or else the in next ARGV-element. - When `getopt' finds a long-named option, it returns 0 if that option's - `flag' field is nonzero, the value of the option's `val' field - if the `flag' field is zero. - - The elements of ARGV aren't really const, because we permute them. - But we pretend they're const in the prototype to be compatible - with other systems. - - LONGOPTS is a vector of `struct option' terminated by an - element containing a name which is zero. - - LONGIND returns the index in LONGOPT of the long-named option found. - It is only valid when a long-named option has been found by the most - recent call. - - If LONG_ONLY is nonzero, '-' as well as '--' can introduce - long-named options. */ - -int _getopt_internal(argc, argv, optstring, longopts, longind, long_only) -int argc; -char *const *argv; -const char *optstring; -const struct option *longopts; -int *longind; -int long_only; -{ - optarg = NULL; - - if (optind == 0 || !__getopt_initialized) { - if (optind == 0) - optind = 1; /* Don't scan ARGV[0], the - program name. */ - optstring = _getopt_initialize(argc, argv, optstring); - __getopt_initialized = 1; - } - - /* Test whether ARGV[optind] points to a non-option argument. Either it - does not have option syntax, or there is an environment flag from the - shell indicating it is not an option. The later information is only - used when the used in the GNU libc. */ -#ifdef _LIBC -#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ - || (optind < nonoption_flags_len \ - && __getopt_nonoption_flags[optind] == '1')) -#else -#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') -#endif - - if (nextchar == NULL || *nextchar == '\0') { - /* Advance to the next ARGV-element. */ - - /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been - moved back by the user (who may also have changed the arguments). */ - if (last_nonopt > optind) - last_nonopt = optind; - if (first_nonopt > optind) - first_nonopt = optind; - - if (ordering == PERMUTE) { - /* If we have just processed some options following some - non-options, exchange them so that the options come first. */ - - if (first_nonopt != last_nonopt && last_nonopt != optind) - exchange((char **) argv); - else if (last_nonopt != optind) - first_nonopt = optind; - - /* Skip any additional non-options and extend the range of - non-options previously skipped. */ - - while (optind < argc && NONOPTION_P) - optind++; - last_nonopt = optind; - } - - /* The special ARGV-element `--' means premature end of options. Skip - it like a null option, then exchange with previous non-options as if - it were an option, then skip everything else like a non-option. */ - - if (optind != argc && !strcmp(argv[optind], "--")) { - optind++; - - if (first_nonopt != last_nonopt && last_nonopt != optind) - exchange((char **) argv); - else if (first_nonopt == last_nonopt) - first_nonopt = optind; - last_nonopt = argc; - - optind = argc; - } - - /* If we have done all the ARGV-elements, stop the scan and back over - any non-options that we skipped and permuted. */ - - if (optind == argc) { - /* Set the next-arg-index to point at the non-options that we - previously skipped, so the caller will digest them. */ - if (first_nonopt != last_nonopt) - optind = first_nonopt; - return -1; - } - - /* If we have come to a non-option and did not permute it, either stop - the scan or describe it to the caller and pass it by. */ - - if (NONOPTION_P) { - if (ordering == REQUIRE_ORDER) - return -1; - optarg = argv[optind++]; - return 1; - } - - /* We have found another option-ARGV-element. Skip the initial - punctuation. */ - - nextchar = (argv[optind] + 1 + (longopts != NULL && argv[optind][1] == '-')); - } - - /* Decode the current option-ARGV-element. */ - - /* Check whether the ARGV-element is a long option. - - If long_only and the ARGV-element has the form "-f", where f is a valid - short option, don't consider it an abbreviated form of a long option - that starts with f. Otherwise there would be no way to give the -f - short option. - - On the other hand, if there's a long option "fubar" and the - ARGV-element is "-fu", do consider that an abbreviation of the long - option, just like "--fu", and not "-f" with arg "u". - - This distinction seems to be the most useful approach. */ - - if (longopts != NULL && (argv[optind][1] == '-' || (long_only && (argv[optind][2] - || !my_index(optstring, argv[optind] - [1]))))) { - char *nameend; - const struct option *p; - const struct option *pfound = NULL; - int exact = 0; - int ambig = 0; - int indfound = -1; - int option_index; - - for (nameend = nextchar; *nameend && *nameend != '='; nameend++) - /* Do nothing. */ ; - - /* Test all long options for either exact match or abbreviated matches. - */ - for (p = longopts, option_index = 0; p->name; p++, option_index++) - if (!strncmp(p->name, nextchar, nameend - nextchar)) { - if ((unsigned int) (nameend - nextchar) == (unsigned int) strlen(p->name)) { - /* Exact match found. */ - pfound = p; - indfound = option_index; - exact = 1; - break; - } else if (pfound == NULL) { - /* First nonexact match found. */ - pfound = p; - indfound = option_index; - } else - /* Second or later nonexact match found. */ - ambig = 1; - } - - if (ambig && !exact) { - if (opterr) - fprintf(stderr, _("%s: option `%s' is ambiguous\n"), argv[0], argv[optind]); - nextchar += strlen(nextchar); - optind++; - optopt = 0; - return '?'; - } - - if (pfound != NULL) { - option_index = indfound; - optind++; - if (*nameend) { - /* Don't test has_arg with >, because some C compilers don't - allow it to be used on enums. */ - if (pfound->has_arg) - optarg = nameend + 1; - else { - if (opterr) { - if (argv[optind - 1][1] == '-') - /* --option */ - fprintf(stderr, - _ - ("%s: option `--%s' doesn't allow an argument\n"), - argv[0], pfound->name); - else - /* +option or -option */ - fprintf(stderr, - _ - ("%s: option `%c%s' doesn't allow an argument\n"), - argv[0], argv[optind - 1][0], pfound->name); - } - - nextchar += strlen(nextchar); - - optopt = pfound->val; - return '?'; - } - } else if (pfound->has_arg == 1) { - if (optind < argc) - optarg = argv[optind++]; - else { - if (opterr) - fprintf(stderr, - _ - ("%s: option `%s' requires an argument\n"), - argv[0], argv[optind - 1]); - nextchar += strlen(nextchar); - optopt = pfound->val; - return optstring[0] == ':' ? ':' : '?'; - } - } - nextchar += strlen(nextchar); - if (longind != NULL) - *longind = option_index; - if (pfound->flag) { - *(pfound->flag) = pfound->val; - return 0; - } - return pfound->val; - } - - /* Can't find it as a long option. If this is not getopt_long_only, or - the option starts with '--' or is not a valid short option, then - it's an error. Otherwise interpret it as a short option. */ - if (!long_only || argv[optind][1] == '-' || my_index(optstring, *nextchar) == NULL) { - if (opterr) { - if (argv[optind][1] == '-') - /* --option */ - fprintf(stderr, _("%s: unrecognized option `--%s'\n"), argv[0], nextchar); - else - /* +option or -option */ - fprintf(stderr, - _ - ("%s: unrecognized option `%c%s'\n"), - argv[0], argv[optind][0], nextchar); - } - nextchar = (char *) ""; - optind++; - optopt = 0; - return '?'; - } - } - - /* Look at and handle the next short option-character. */ - - { - char c = *nextchar++; - char *temp = my_index(optstring, c); - - /* Increment `optind' when we start to process its last character. */ - if (*nextchar == '\0') - ++optind; - - if (temp == NULL || c == ':') { - if (opterr) { - if (posixly_correct) - /* 1003.2 specifies the format of this message. */ - fprintf(stderr, _("%s: illegal option -- %c\n"), argv[0], c); - else - fprintf(stderr, _("%s: invalid option -- %c\n"), argv[0], c); - } - optopt = c; - return '?'; - } - /* Convenience. Treat POSIX -W foo same as long option --foo */ - if (temp[0] == 'W' && temp[1] == ';') { - char *nameend; - const struct option *p; - const struct option *pfound = NULL; - int exact = 0; - int ambig = 0; - int indfound = 0; - int option_index; - - /* This is an option that requires an argument. */ - if (*nextchar != '\0') { - optarg = nextchar; - /* If we end this ARGV-element by taking the rest as an arg, we - must advance to the next element now. */ - optind++; - } else if (optind == argc) { - if (opterr) { - /* 1003.2 specifies the format of this message. */ - fprintf(stderr, _("%s: option requires an argument -- %c\n"), argv[0], c); - } - optopt = c; - if (optstring[0] == ':') - c = ':'; - else - c = '?'; - return c; - } else - /* We already incremented `optind' once; increment it again when - taking next ARGV-elt as argument. */ - optarg = argv[optind++]; - - /* optarg is now the argument, see if it's in the table of longopts. - */ - - for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) - /* Do nothing. */ ; - - /* Test all long options for either exact match or abbreviated - matches. */ - for (p = longopts, option_index = 0; p->name; p++, option_index++) - if (!strncmp(p->name, nextchar, nameend - nextchar)) { - if ((unsigned int) (nameend - nextchar) == strlen(p->name)) { - /* Exact match found. */ - pfound = p; - indfound = option_index; - exact = 1; - break; - } else if (pfound == NULL) { - /* First nonexact match found. */ - pfound = p; - indfound = option_index; - } else - /* Second or later nonexact match found. */ - ambig = 1; - } - if (ambig && !exact) { - if (opterr) - fprintf(stderr, _("%s: option `-W %s' is ambiguous\n"), argv[0], argv[optind]); - nextchar += strlen(nextchar); - optind++; - return '?'; - } - if (pfound != NULL) { - option_index = indfound; - if (*nameend) { - /* Don't test has_arg with >, because some C compilers don't - allow it to be used on enums. */ - if (pfound->has_arg) - optarg = nameend + 1; - else { - if (opterr) - fprintf(stderr, _("\ -%s: option `-W %s' doesn't allow an argument\n"), argv[0], pfound->name); - - nextchar += strlen(nextchar); - return '?'; - } - } else if (pfound->has_arg == 1) { - if (optind < argc) - optarg = argv[optind++]; - else { - if (opterr) - fprintf(stderr, - _ - ("%s: option `%s' requires an argument\n"), - argv[0], argv[optind - 1]); - nextchar += strlen(nextchar); - return optstring[0] == ':' ? ':' : '?'; - } - } - nextchar += strlen(nextchar); - if (longind != NULL) - *longind = option_index; - if (pfound->flag) { - *(pfound->flag) = pfound->val; - return 0; - } - return pfound->val; - } - nextchar = NULL; - return 'W'; /* Let the application handle it. - */ - } - if (temp[1] == ':') { - if (temp[2] == ':') { - /* This is an option that accepts an argument optionally. */ - if (*nextchar != '\0') { - optarg = nextchar; - optind++; - } else - optarg = NULL; - nextchar = NULL; - } else { - /* This is an option that requires an argument. */ - if (*nextchar != '\0') { - optarg = nextchar; - /* If we end this ARGV-element by taking the rest as an arg, - we must advance to the next element now. */ - optind++; - } else if (optind == argc) { - if (opterr) { - /* 1003.2 specifies the format of this message. */ - fprintf(stderr, - _("%s: option requires an argument -- %c\n"), argv[0], c); - } - optopt = c; - if (optstring[0] == ':') - c = ':'; - else - c = '?'; - } else - /* We already incremented `optind' once; increment it again - when taking next ARGV-elt as argument. */ - optarg = argv[optind++]; - nextchar = NULL; - } - } - return c; - } -} - -int getopt(argc, argv, optstring) -int argc; -char *const *argv; -const char *optstring; -{ - return _getopt_internal(argc, argv, optstring, (const struct option *) 0, (int *) 0, 0); -} - -#endif /* Not ELIDE_CODE. */ - -#ifdef TEST - -/* Compile with -DTEST to make an executable for use in testing - the above definition of `getopt'. */ - -int main(argc, argv) -int argc; -char **argv; -{ - int c; - int digit_optind = 0; - - while (1) { - int this_option_optind = optind ? optind : 1; - - c = getopt(argc, argv, "abc:d:0123456789"); - if (c == -1) - break; - - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (digit_optind != 0 && digit_optind != this_option_optind) - printf("digits occur in two different argv-elements.\n"); - digit_optind = this_option_optind; - printf("option %c\n", c); - break; - - case 'a': - printf("option a\n"); - break; - - case 'b': - printf("option b\n"); - break; - - case 'c': - printf("option c with value `%s'\n", optarg); - break; - - case '?': - break; - - default: - printf("?? getopt returned character code 0%o ??\n", c); - } - } - - if (optind < argc) { - printf("non-option ARGV-elements: "); - while (optind < argc) - printf("%s ", argv[optind++]); - printf("\n"); - } - - exit(0); -} - -#endif /* TEST */ diff --git a/src/getopt.h b/src/getopt.h deleted file mode 100644 index e8fe13f..0000000 --- a/src/getopt.h +++ /dev/null @@ -1,130 +0,0 @@ -/* Declarations for getopt. - Copyright (C) 1989,90,91,92,93,94,96,97 Free Software Foundation, Inc. - This file is part of the GNU C Library. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. - - The GNU C Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the GNU C Library; see the file COPYING.LIB. If not, - write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. */ - -#ifndef _GETOPT_H -#define _GETOPT_H 1 - -#ifdef __cplusplus -extern "C" { -#endif - -/* For communication from `getopt' to the caller. - When `getopt' finds an option that takes an argument, - the argument value is returned here. - Also, when `ordering' is RETURN_IN_ORDER, - each non-option ARGV-element is returned here. */ - - extern char *optarg; - -/* Index in ARGV of the next element to be scanned. - This is used for communication to and from the caller - and for communication between successive calls to `getopt'. - - On entry to `getopt', zero means this is the first call; initialize. - - When `getopt' returns -1, this is the index of the first of the - non-option elements that the caller should itself scan. - - Otherwise, `optind' communicates from one call to the next - how much of ARGV has been scanned so far. */ - - extern int optind; - -/* Callers store zero here to inhibit the error message `getopt' prints - for unrecognized options. */ - - extern int opterr; - -/* Set to an option character which was unrecognized. */ - - extern int optopt; - -/* Describe the long-named options requested by the application. - The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector - of `struct option' terminated by an element containing a name which is - zero. - - The field `has_arg' is: - no_argument (or 0) if the option does not take an argument, - required_argument (or 1) if the option requires an argument, - optional_argument (or 2) if the option takes an optional argument. - - If the field `flag' is not NULL, it points to a variable that is set - to the value given in the field `val' when the option is found, but - left unchanged if the option is not found. - - To have a long-named option do something other than set an `int' to - a compiled-in constant, such as set a value from `optarg', set the - option's `flag' field to zero and its `val' field to a nonzero - value (the equivalent single-letter option character, if there is - one). For long options that have a zero `flag' field, `getopt' - returns the contents of the `val' field. */ - - struct option { -#if defined (__STDC__) && __STDC__ - const char *name; -#else - char *name; -#endif - /* has_arg can't be an enum because some compilers complain about type - mismatches in all the code that assumes it is an int. */ - int has_arg; - int *flag; - int val; - }; - -/* Names for the values of the `has_arg' field of `struct option'. */ - -#define no_argument 0 -#define required_argument 1 -#define optional_argument 2 - -#if defined (__STDC__) && __STDC__ -#ifdef __GNU_LIBRARY__ -/* Many other libraries have conflicting prototypes for getopt, with - differences in the consts, in stdlib.h. To avoid compilation - errors, only prototype getopt for the GNU C library. */ - extern int getopt(int argc, char *const *argv, const char *shortopts); -#else /* not __GNU_LIBRARY__ */ -#ifndef __cplusplus -/* C++ is more pedantic, and demands a full prototype, not this. - Hope that stdlib.h has a prototype for `getopt'. */ - extern int getopt(); -#endif /* __cplusplus */ -#endif /* __GNU_LIBRARY__ */ - extern int getopt_long(int argc, char *const *argv, - const char *shortopts, const struct option *longopts, int *longind); - extern int getopt_long_only(int argc, char *const *argv, - const char *shortopts, const struct option *longopts, int *longind); - -/* Internal only. Users should not call this directly. */ - extern int _getopt_internal(int argc, char *const *argv, - const char *shortopts, const struct option *longopts, int *longind, int long_only); -#else /* not __STDC__ */ - extern int getopt(); - extern int getopt_long(); - extern int getopt_long_only(); - - extern int _getopt_internal(); -#endif /* __STDC__ */ - -#ifdef __cplusplus -} -#endif -#endif /* getopt.h */ diff --git a/src/getopt1.c b/src/getopt1.c deleted file mode 100644 index 5a5c483..0000000 --- a/src/getopt1.c +++ /dev/null @@ -1,173 +0,0 @@ -/* getopt_long and getopt_long_only entry points for GNU getopt. - Copyright (C) 1987,88,89,90,91,92,93,94,96,97 Free Software Foundation, Inc. - This file is part of the GNU C Library. - - The GNU C Library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. - - The GNU C Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the GNU C Library; see the file COPYING.LIB. If not, - write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. */ - -#include "getopt.h" - -#if !defined (__STDC__) || !__STDC__ -/* This is a separate conditional since some stdc systems - reject `defined (const)'. */ -#ifndef const -#define const -#endif -#endif - -#include <stdio.h> - -/* Comment out all this code if we are using the GNU C Library, and are not - actually compiling the library itself. This code is part of the GNU C - Library, but also included in many other GNU distributions. Compiling - and linking in this code is a waste when using the GNU C library - (especially if it is a shared library). Rather than having every GNU - program understand `configure --with-gnu-libc' and omit the object files, - it is simpler to just do this in the source for each such file. */ - -#define GETOPT_INTERFACE_VERSION 2 -#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2 -#include <gnu-versions.h> -#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION -#define ELIDE_CODE -#endif -#endif - -#ifndef ELIDE_CODE - -/* This needs to come after some library #include - to get __GNU_LIBRARY__ defined. */ -#ifdef __GNU_LIBRARY__ -#include <stdlib.h> -#endif - -#ifndef NULL -#define NULL 0 -#endif - -int getopt_long(argc, argv, options, long_options, opt_index) -int argc; -char *const *argv; -const char *options; -const struct option *long_options; -int *opt_index; -{ - return _getopt_internal(argc, argv, options, long_options, opt_index, 0); -} - -/* Like getopt_long, but '-' as well as '--' can indicate a long option. - If an option that starts with '-' (not '--') doesn't match a long option, - but does match a short option, it is parsed as a short option - instead. */ - -int getopt_long_only(argc, argv, options, long_options, opt_index) -int argc; -char *const *argv; -const char *options; -const struct option *long_options; -int *opt_index; -{ - return _getopt_internal(argc, argv, options, long_options, opt_index, 1); -} - -#endif /* Not ELIDE_CODE. */ - -#ifdef TEST - -#include <stdio.h> - -int main(argc, argv) -int argc; -char **argv; -{ - int c; - int digit_optind = 0; - - while (1) { - int this_option_optind = optind ? optind : 1; - int option_index = 0; - static struct option long_options[] = { - {"add", 1, 0, 0}, - {"append", 0, 0, 0}, - {"delete", 1, 0, 0}, - {"verbose", 0, 0, 0}, - {"create", 0, 0, 0}, - {"file", 1, 0, 0}, - {0, 0, 0, 0} - }; - - c = getopt_long(argc, argv, "abc:d:0123456789", long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 0: - printf("option %s", long_options[option_index].name); - if (optarg) - printf(" with arg %s", optarg); - printf("\n"); - break; - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (digit_optind != 0 && digit_optind != this_option_optind) - printf("digits occur in two different argv-elements.\n"); - digit_optind = this_option_optind; - printf("option %c\n", c); - break; - - case 'a': - printf("option a\n"); - break; - - case 'b': - printf("option b\n"); - break; - - case 'c': - printf("option c with value `%s'\n", optarg); - break; - - case 'd': - printf("option d with value `%s'\n", optarg); - break; - - case '?': - break; - - default: - printf("?? getopt returned character code 0%o ??\n", c); - } - } - - if (optind < argc) { - printf("non-option ARGV-elements: "); - while (optind < argc) - printf("%s ", argv[optind++]); - printf("\n"); - } - - exit(0); -} - -#endif /* TEST */ diff --git a/src/gib_hash.c b/src/gib_hash.c new file mode 100644 index 0000000..9497d04 --- /dev/null +++ b/src/gib_hash.c @@ -0,0 +1,149 @@ +/* gib_hash.c + +Copyright (C) 1999,2000 Paul Duncan. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +#include <strings.h> + +#include "gib_hash.h" +#include "utils.h" +#include "debug.h" + +gib_hash_node *gib_hash_node_new(char *key, void *data) +{ + gib_hash_node *node = emalloc(sizeof(gib_hash_node)); + node->key = strdup(key); + GIB_LIST(node)->data = data; + GIB_LIST(node)->next = NULL; + GIB_LIST(node)->prev = NULL; + return node; + +} + +void gib_hash_node_free(gib_hash_node *node) +{ + free(node->key); + free(node); + return; +} + +void gib_hash_node_free_and_data(gib_hash_node *node) +{ + free(node->list.data); + gib_hash_node_free(node); + return; +} + +gib_hash *gib_hash_new(void) +{ + gib_hash *hash = emalloc(sizeof(gib_hash)); + hash->base = gib_hash_node_new("__gib_hash_new",NULL); + return hash; +} + +void gib_hash_free(gib_hash *hash) +{ + /* free hash keys as it's not taken care of by gib_list_free */ + gib_list *i; + for (i = GIB_LIST(hash->base); i; i = i->next) + free(GIB_HASH_NODE(i)->key); + + gib_list_free(GIB_LIST(hash->base)); + free(hash); + return; +} + +void gib_hash_free_and_data(gib_hash *hash) +{ + /* free hash keys as it's not taken care of by gib_list_free */ + gib_list *i; + for (i = GIB_LIST(hash->base); i; i = i->next) + free(GIB_HASH_NODE(i)->key); + + gib_list_free_and_data(GIB_LIST(hash->base)); + free(hash); + return; +} + +static unsigned char gib_hash_find_callback(gib_list *list, void *data) +{ + gib_hash_node *node = GIB_HASH_NODE(list); + char *key = (char*) data; + + /* strncasecmp causes similar keys like key1 and key11 clobber each other */ + return !strcasecmp(node->key, key); +} + +void gib_hash_set(gib_hash *hash, char *key, void *data) +{ + gib_list *l; + gib_hash_node *n; + + n = GIB_HASH_NODE(gib_list_find(GIB_LIST(hash->base), + gib_hash_find_callback, + key)); + if (n) { + GIB_LIST(n)->data = data; + } else { + /* not using gib_list_add_end here as that would nest the + data one level, doing manual insert instead */ + n = gib_hash_node_new(key,data); + l = gib_list_last(GIB_LIST(hash->base)); + + GIB_LIST(n)->next = NULL; + GIB_LIST(n)->prev = l; + + if (l) + l->next = GIB_LIST(n); + } +} + +void *gib_hash_get(gib_hash *hash, char *key) +{ + gib_list *n = gib_list_find(GIB_LIST(hash->base),gib_hash_find_callback,key); + return n?n->data:NULL; +} + +/* unused + +void gib_hash_remove(gib_hash *hash, char *key) +{ + gib_list *n = gib_list_find(GIB_LIST(hash->base), gib_hash_find_callback, key); + if (n) { + gib_list_unlink(GIB_LIST(hash->base), n); + gib_hash_node_free(GIB_HASH_NODE(n->data)); + } + + return; +} + +void gib_hash_foreach(gib_hash *hash, void (*foreach_cb)(gib_hash_node *node, void *data), void *data) +{ + gib_hash_node *i, *next; + for (i=hash->base; i; i=next) { + next = GIB_HASH_NODE(GIB_LIST(i)->next); + foreach_cb(i,data); + } + return; +} + +*/ diff --git a/src/gib_hash.h b/src/gib_hash.h new file mode 100644 index 0000000..125e280 --- /dev/null +++ b/src/gib_hash.h @@ -0,0 +1,75 @@ + +/* gib_hash.h + +Copyright (C) 1999,2000 Paul Duncan. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef GIB_HASH_H +#define GIB_HASH_H + +#include "gib_list.h" + +#define GIB_HASH(a) ((gib_hash*)a) +#define GIB_HASH_NODE(a) ((gib_hash_node*)a) + +typedef struct __gib_hash gib_hash; +typedef struct __gib_hash_node gib_hash_node; + +struct __gib_hash +{ + gib_hash_node *base; +}; + +struct __gib_hash_node +{ + gib_list list; + char *key; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +gib_hash_node *gib_hash_node_new(char *key, void *data); +void gib_hash_node_free(gib_hash_node *node); +void gib_hash_node_free_and_data(gib_hash_node *node); + +gib_hash *gib_hash_new(void); +void gib_hash_free(gib_hash *hash); +void gib_hash_free_and_data(gib_hash *hash); + +void gib_hash_set(gib_hash *hash, char *key, void *data); +void *gib_hash_get(gib_hash *hash, char *key); + +/* unused +void gib_hash_remove(gib_hash *hash, char *key); + +void gib_hash_foreach(gib_hash *hash, void (*foreach_cb)(gib_hash_node *node, void *data), void *data); +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* GIB_HASH_H */ diff --git a/src/gib_imlib.c b/src/gib_imlib.c new file mode 100644 index 0000000..39d0081 --- /dev/null +++ b/src/gib_imlib.c @@ -0,0 +1,732 @@ +/* gib_imlib.c + +Copyright (C) 1999,2000 Tom Gilbert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "gib_imlib.h" +#include "utils.h" +#include "debug.h" + +/* +int +gib_imlib_load_image(Imlib_Image * im, char *filename) +{ + Imlib_Load_Error err; + + imlib_context_set_progress_function(NULL); + if (!filename) + return (0); + *im = imlib_load_image_with_error_return(filename, &err); + if ((err) || (!im)) + { + switch (err) + { + case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST: + weprintf("%s - File does not exist", filename); + break; + case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY: + weprintf("%s - Directory specified for image filename", filename); + break; + case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ: + weprintf("%s - No read access to directory", filename); + break; + case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT: + weprintf("%s - No Imlib2 loader for that file format", filename); + break; + case IMLIB_LOAD_ERROR_PATH_TOO_LONG: + weprintf("%s - Path specified is too long", filename); + break; + case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT: + weprintf("%s - Path component does not exist", filename); + break; + case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY: + weprintf("%s - Path component is not a directory", filename); + break; + case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE: + weprintf("%s - Path points outside address space", filename); + break; + case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS: + weprintf("%s - Too many levels of symbolic links", filename); + break; + case IMLIB_LOAD_ERROR_OUT_OF_MEMORY: + eprintf("While loading %s - Out of memory", filename); + break; + case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS: + eprintf("While loading %s - Out of file descriptors", filename); + break; + case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE: + weprintf("%s - Cannot write to directory", filename); + break; + case IMLIB_LOAD_ERROR_OUT_OF_DISK_SPACE: + weprintf("%s - Cannot write - out of disk space", filename); + break; + case IMLIB_LOAD_ERROR_UNKNOWN: + default: + weprintf + ("While loading %s - Unknown error. Attempting to continue", + filename); + break; + } + return (0); + } + return (1); +} +*/ + +int +gib_imlib_image_get_width(Imlib_Image im) +{ + imlib_context_set_image(im); + return imlib_image_get_width(); +} + +int +gib_imlib_image_get_height(Imlib_Image im) +{ + imlib_context_set_image(im); + return imlib_image_get_height(); +} + +int +gib_imlib_image_has_alpha(Imlib_Image im) +{ + imlib_context_set_image(im); + return imlib_image_has_alpha(); +} + +void +gib_imlib_free_image_and_decache(Imlib_Image im) +{ + imlib_context_set_image(im); + imlib_free_image_and_decache(); +} + +void +gib_imlib_free_image(Imlib_Image im) +{ + imlib_context_set_image(im); + imlib_free_image(); +} + +const char * +gib_imlib_image_get_filename(Imlib_Image im) +{ + if (im) + { + imlib_context_set_image(im); + return imlib_image_get_filename(); + } + else + return NULL; +} + +void +gib_imlib_render_image_on_drawable(Drawable d, Imlib_Image im, int x, int y, + char dither, char blend, char alias) +{ + imlib_context_set_image(im); + imlib_context_set_drawable(d); + imlib_context_set_anti_alias(alias); + imlib_context_set_dither(dither); + imlib_context_set_blend(blend); + imlib_context_set_angle(0); + imlib_render_image_on_drawable(x, y); +} + +/* +void +gib_imlib_render_image_on_drawable_with_rotation(Drawable d, Imlib_Image im, + int x, int y, double angle, + char dither, char blend, + char alias) +{ + Imlib_Image new_im; + + imlib_context_set_image(im); + imlib_context_set_anti_alias(alias); + imlib_context_set_dither(dither); + imlib_context_set_blend(blend); + imlib_context_set_angle(angle); + imlib_context_set_drawable(d); + new_im = imlib_create_rotated_image(angle); + imlib_context_set_image(new_im); + imlib_render_image_on_drawable(x, y); + imlib_free_image(); +} +*/ + +void +gib_imlib_render_image_on_drawable_at_size(Drawable d, Imlib_Image im, int x, + int y, int w, int h, char dither, + char blend, char alias) +{ + imlib_context_set_image(im); + imlib_context_set_drawable(d); + imlib_context_set_anti_alias(alias); + imlib_context_set_dither(dither); + imlib_context_set_blend(blend); + imlib_context_set_angle(0); + imlib_render_image_on_drawable_at_size(x, y, w, h); +} + + +/* +void +gib_imlib_render_image_on_drawable_at_size_with_rotation(Drawable d, + Imlib_Image im, + int x, int y, int w, + int h, double angle, + char dither, + char blend, + char alias) +{ + Imlib_Image new_im; + + imlib_context_set_image(im); + imlib_context_set_drawable(d); + imlib_context_set_anti_alias(alias); + imlib_context_set_dither(dither); + imlib_context_set_blend(blend); + imlib_context_set_angle(angle); + new_im = imlib_create_rotated_image(angle); + imlib_context_set_image(new_im); + imlib_render_image_on_drawable_at_size(x, y, w, h); + imlib_free_image_and_decache(); +} +*/ + + +void +gib_imlib_render_image_part_on_drawable_at_size(Drawable d, Imlib_Image im, + int sx, int sy, int sw, + int sh, int dx, int dy, + int dw, int dh, char dither, + char blend, char alias) +{ + imlib_context_set_image(im); + imlib_context_set_drawable(d); + imlib_context_set_anti_alias(alias); + imlib_context_set_dither(dither); + imlib_context_set_blend(blend); + imlib_context_set_angle(0); + imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, + dh); +} + + +void +gib_imlib_render_image_part_on_drawable_at_size_with_rotation(Drawable d, + Imlib_Image im, + int sx, int sy, + int sw, int sh, + int dx, int dy, + int dw, int dh, + double angle, + char dither, + char blend, + char alias) +{ + Imlib_Image new_im; + + imlib_context_set_image(im); + imlib_context_set_drawable(d); + imlib_context_set_anti_alias(alias); + imlib_context_set_dither(dither); + imlib_context_set_angle(angle); + imlib_context_set_blend(blend); + new_im = imlib_create_rotated_image(angle); + imlib_context_set_image(new_im); + imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, + dh); + imlib_free_image_and_decache(); +} + + +void +gib_imlib_image_fill_rectangle(Imlib_Image im, int x, int y, int w, int h, + int r, int g, int b, int a) +{ + imlib_context_set_image(im); + imlib_context_set_color(r, g, b, a); + imlib_image_fill_rectangle(x, y, w, h); +} + +void +gib_imlib_image_fill_polygon(Imlib_Image im, ImlibPolygon poly, int r, int g, + int b, int a, unsigned char alias, int cx, + int cy, int cw, int ch) +{ + imlib_context_set_image(im); + imlib_context_set_color(r, g, b, a); + imlib_context_set_anti_alias(alias); + imlib_context_set_cliprect(cx, cy, cw, ch); + imlib_image_fill_polygon(poly); + imlib_context_set_cliprect(0, 0, 0, 0); +} + +void +gib_imlib_image_draw_polygon(Imlib_Image im, ImlibPolygon poly, int r, int g, + int b, int a, unsigned char closed, + unsigned char alias, int cx, int cy, int cw, + int ch) +{ + imlib_context_set_image(im); + imlib_context_set_color(r, g, b, a); + imlib_context_set_anti_alias(alias); + imlib_context_set_cliprect(cx, cy, cw, ch); + imlib_image_draw_polygon(poly, closed); + imlib_context_set_cliprect(0, 0, 0, 0); +} + + +void +gib_imlib_image_draw_rectangle(Imlib_Image im, int x, int y, int w, int h, + int r, int g, int b, int a) +{ + imlib_context_set_image(im); + imlib_context_set_color(r, g, b, a); + imlib_image_draw_rectangle(x, y, w, h); +} + + +void +gib_imlib_text_draw(Imlib_Image im, Imlib_Font fn, gib_style * s, int x, + int y, char *text, Imlib_Text_Direction dir, int r, int g, + int b, int a) +{ + imlib_context_set_image(im); + imlib_context_set_font(fn); + imlib_context_set_direction(dir); + if (s) + { + int min_x = 0, min_y = 0; + gib_style_bit *bb; + gib_list *l; + + /* here we shift the draw to accommodate bits with negative offsets, + * which would be drawn at negative coords otherwise */ + l = s->bits; + while (l) + { + bb = (gib_style_bit *) l->data; + if (bb) + { + if (bb->x_offset < min_x) + min_x = bb->x_offset; + if (bb->y_offset < min_y) + min_y = bb->y_offset; + } + l = l->next; + } + x -= min_x; + y -= min_y; + + /* Now draw the bits */ + l = s->bits; + while (l) + { + bb = (gib_style_bit *) l->data; + if (bb) + { + if ((bb->r + bb->g + bb->b + bb->a) == 0) + imlib_context_set_color(r, g, b, a); + else + imlib_context_set_color(bb->r, bb->g, bb->b, bb->a); + imlib_text_draw(x + bb->x_offset, y + bb->y_offset, text); + } + l = l->next; + } + } + else + { + imlib_context_set_color(r, g, b, a); + imlib_text_draw(x, y, text); + } +} + +char ** +gib_imlib_list_fonts(int *num) +{ + return imlib_list_fonts(num); +} + + +void +gib_imlib_get_text_size(Imlib_Font fn, char *text, gib_style * s, int *w, + int *h, Imlib_Text_Direction dir) +{ + + imlib_context_set_font(fn); + imlib_context_set_direction(dir); + imlib_get_text_size(text, w, h); + if (s) + { + gib_style_bit *b; + int max_x_off = 0, min_x_off = 0, max_y_off = 0, min_y_off = 0; + gib_list *l; + + l = s->bits; + while (l) + { + b = (gib_style_bit *) l->data; + if (b) + { + if (b->x_offset > max_x_off) + max_x_off = b->x_offset; + else if (b->x_offset < min_x_off) + min_x_off = b->x_offset; + if (b->y_offset > max_y_off) + max_y_off = b->y_offset; + else if (b->y_offset < min_y_off) + min_y_off = b->y_offset; + } + l = l->next; + } + if (h) + { + *h += max_y_off; + *h -= min_y_off; + } + if (w) + { + *w += max_x_off; + *w -= min_x_off; + } + } +} + +Imlib_Image gib_imlib_clone_image(Imlib_Image im) +{ + imlib_context_set_image(im); + return imlib_clone_image(); +} + +char * +gib_imlib_image_format(Imlib_Image im) +{ + imlib_context_set_image(im); + return imlib_image_format(); +} + + +void +gib_imlib_blend_image_onto_image(Imlib_Image dest_image, + Imlib_Image source_image, char merge_alpha, + int sx, int sy, int sw, int sh, int dx, + int dy, int dw, int dh, char dither, + char blend, char alias) +{ + imlib_context_set_image(dest_image); + imlib_context_set_anti_alias(alias); + imlib_context_set_dither(dither); + imlib_context_set_blend(blend); + imlib_context_set_angle(0); + imlib_blend_image_onto_image(source_image, merge_alpha, sx, sy, sw, sh, dx, + dy, dw, dh); +} + + +/* +void +gib_imlib_blend_image_onto_image_with_rotation(Imlib_Image dest_image, + Imlib_Image source_image, + char merge_alpha, int sx, + int sy, int sw, int sh, int dx, + int dy, int dw, int dh, + double angle, char dither, + char blend, char alias) +{ + imlib_context_set_image(dest_image); + imlib_context_set_anti_alias(alias); + imlib_context_set_dither(dither); + imlib_context_set_blend(blend); + imlib_context_set_angle(angle); + imlib_blend_image_onto_image_at_angle(source_image, merge_alpha, sx, sy, + sw, sh, dx, dy, (int) angle, + (int) angle); + return; + dw = 0; + dh = 0; +} +*/ + +Imlib_Image gib_imlib_create_cropped_scaled_image(Imlib_Image im, int sx, + int sy, int sw, int sh, + int dw, int dh, char alias) +{ + imlib_context_set_image(im); + imlib_context_set_anti_alias(alias); + return imlib_create_cropped_scaled_image(sx, sy, sw, sh, dw, dh); +} + +void +gib_imlib_apply_color_modifier_to_rectangle(Imlib_Image im, int x, int y, + int w, int h, DATA8 * rtab, + DATA8 * gtab, DATA8 * btab, + DATA8 * atab) +{ + Imlib_Color_Modifier cm; + + imlib_context_set_image(im); + cm = imlib_create_color_modifier(); + imlib_context_set_color_modifier(cm); + imlib_set_color_modifier_tables(rtab, gtab, btab, atab); + imlib_apply_color_modifier_to_rectangle(x, y, w, h); + imlib_free_color_modifier(); +} + +void +gib_imlib_image_set_has_alpha(Imlib_Image im, int alpha) +{ + imlib_context_set_image(im); + imlib_image_set_has_alpha(alpha); +} + +void +gib_imlib_save_image(Imlib_Image im, char *file) +{ + char *tmp; + + imlib_context_set_image(im); + tmp = strrchr(file, '.'); + if (tmp) + { + char *p, *pp; + p = strdup(tmp + 1); + pp = p; + while(*pp) { + *pp = tolower(*pp); + pp++; + } + imlib_image_set_format(p); + free(p); + } + imlib_save_image(file); +} + +void +gib_imlib_save_image_with_error_return(Imlib_Image im, char *file, + Imlib_Load_Error * error_return) +{ + char *tmp; + + imlib_context_set_image(im); + tmp = strrchr(file, '.'); + if (tmp) { + char *p, *pp; + p = estrdup(tmp + 1); + pp = p; + while(*pp) { + *pp = tolower(*pp); + pp++; + } + imlib_image_set_format(p); + free(p); + } + imlib_save_image_with_error_return(file, error_return); +} + +void +gib_imlib_free_font(Imlib_Font fn) +{ + imlib_context_set_font(fn); + imlib_free_font(); +} + +void +gib_imlib_image_draw_line(Imlib_Image im, int x1, int y1, int x2, int y2, + char make_updates, int r, int g, int b, int a) +{ + imlib_context_set_image(im); + imlib_context_set_color(r, g, b, a); + imlib_image_draw_line(x1, y1, x2, y2, make_updates); +} + +Imlib_Image gib_imlib_create_rotated_image(Imlib_Image im, double angle) +{ + imlib_context_set_image(im); + return (imlib_create_rotated_image(angle)); +} + +void +gib_imlib_image_tile(Imlib_Image im) +{ + imlib_context_set_image(im); + imlib_image_tile(); +} + +void +gib_imlib_image_blur(Imlib_Image im, int radius) +{ + imlib_context_set_image(im); + imlib_image_blur(radius); +} + +void +gib_imlib_image_sharpen(Imlib_Image im, int radius) +{ + imlib_context_set_image(im); + imlib_image_sharpen(radius); +} + +void +gib_imlib_line_clip_and_draw(Imlib_Image dest, int x0, int y0, int x1, int y1, + int cx, int cy, int cw, int ch, int r, int g, + int b, int a) +{ + imlib_context_set_cliprect(cx, cy, cw, ch); + gib_imlib_image_draw_line(dest, x0, y0, x1, y1, 0, r, g, b, a); + imlib_context_set_cliprect(0, 0, 0, 0); +} + +Imlib_Image +gib_imlib_create_image_from_drawable(Drawable d, Pixmap mask, int x, int y, + int width, int height, + char need_to_grab_x) +{ + imlib_context_set_drawable(d); + return imlib_create_image_from_drawable(mask, x, y, width, height, + need_to_grab_x); +} + +void gib_imlib_parse_color(char *col, int *r, int *g, int *b, int *a) + { + gib_list *ll; + unsigned long cc, rr, gg, bb, aa; + int len; + + if (col[0] == '#') + { + /* #RRGGBBAA style */ + /* skip the '#' */ + col++; + len = strlen(col); + if (len == 8) + { + cc = (unsigned long) strtoul(col, NULL, 16); + rr = (cc & 0xff000000) >> 24; + gg = (cc & 0x00ff0000) >> 16; + bb = (cc & 0x0000ff00) >> 8; + aa = (cc & 0x000000ff); + } + else if (len == 6) + { + cc = (unsigned long) strtoul(col, NULL, 16); + rr = (cc & 0xff0000) >> 16; + gg = (cc & 0x00ff00) >> 8; + bb = (cc & 0x0000ff); + aa = 255; + } + else + { + weprintf("unable to parse color %s\n", col); + return; + } + } + else + { + /* r,g,b,a style */ + ll = gib_string_split(col, ","); + if (!ll) + { + weprintf("unable to parse color %s\n", col); + return; + } + len = gib_list_length(ll); + if (len == 3) + { + rr = atoi(ll->data); + gg = atoi(ll->next->data); + bb = atoi(ll->next->next->data); + aa = 255; + } + else if (len == 4) + { + rr = atoi(ll->data); + gg = atoi(ll->next->data); + bb = atoi(ll->next->next->data); + aa = atoi(ll->next->next->next->data); + } + else + { + weprintf("unable to parse color %s\n", col); + return; + } + } + *r = rr; + *g = gg; + *b = bb; + *a = aa; + } + +void +gib_imlib_parse_fontpath(char *path) +{ + gib_list *l, *ll; + + if (!path) + return; + + l = gib_string_split(path, ":"); + if (!l) + return; + ll = l; + while (ll) + { + imlib_add_path_to_font_path((char *) ll->data); + ll = ll->next; + } + gib_list_free_and_data(l); +} + +Imlib_Font +gib_imlib_load_font(char *name) +{ + Imlib_Font fn; + + if ((fn = imlib_load_font(name))) + return fn; + weprintf("couldn't load font %s, attempting to fall back to fixed.", name); + if ((fn = imlib_load_font("fixed"))) + return fn; + weprintf("failed to even load fixed! Attempting to find any font."); + return imlib_load_font("*"); +} + +void gib_imlib_image_orientate(Imlib_Image im, int orientation) +{ + imlib_context_set_image(im); + imlib_image_orientate(orientation); +} + +void gib_imlib_image_flip_horizontal(Imlib_Image im) +{ + imlib_context_set_image(im); + imlib_image_flip_horizontal(); +} + +void gib_imlib_image_flip_vertical(Imlib_Image im) +{ + imlib_context_set_image(im); + imlib_image_flip_vertical(); +} diff --git a/src/gib_imlib.h b/src/gib_imlib.h new file mode 100644 index 0000000..6a35e7e --- /dev/null +++ b/src/gib_imlib.h @@ -0,0 +1,194 @@ +/* gib_imlib.h + +Copyright (C) 1999,2000 Tom Gilbert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef GIB_IMLIB_H +#define GIB_IMLIB_H + +#include <stdio.h> +#include <stdlib.h> +#include <X11/Xlib.h> +#include <Imlib2.h> +#include <stdarg.h> +#include <ctype.h> +#include "gib_style.h" + +#define GIBCLIP(x, y, w, h, xx, yy, ww, hh) \ +{ \ + if ((yy) > y) { h -= (yy) - y; y = (yy); } \ + if ((yy) + hh < y + h) { h -= y + h - ((yy) + (hh)); } \ + if ((xx) > x) { w -= (xx) - x; x = (xx); } \ + if ((xx) + (ww) < x + w) { w -= x + w - ((xx) + (ww)); } \ +} + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* +int gib_imlib_load_image(Imlib_Image * im, char *filename); +*/ +int gib_imlib_image_get_width(Imlib_Image im); +int gib_imlib_image_get_height(Imlib_Image im); +int gib_imlib_image_has_alpha(Imlib_Image im); +const char *gib_imlib_image_get_filename(Imlib_Image im); +void gib_imlib_free_image_and_decache(Imlib_Image im); +void gib_imlib_render_image_on_drawable(Drawable d, Imlib_Image im, int x, + int y, char dither, char blend, + + char alias); +/* +void gib_imlib_render_image_on_drawable_with_rotation(Drawable d, + Imlib_Image im, int x, + int y, double angle, + + char dither, char blend, + char alias); +*/ +void gib_imlib_render_image_part_on_drawable_at_size(Drawable d, + Imlib_Image im, int sx, + int sy, int sw, int sh, + int dx, int dy, int dw, + int dh, char dither, + + char blend, char alias); + +void gib_imlib_render_image_part_on_drawable_at_size_with_rotation(Drawable d, + Imlib_Image + + im, int sx, + int sy, + int sw, + int sh, + int dx, + int dy, + int dw, + int dh, + double + angle, + char + dither, + char blend, + char + alias); + +void gib_imlib_image_fill_rectangle(Imlib_Image im, int x, int y, int w, + int h, int r, int g, int b, int a); +void gib_imlib_text_draw(Imlib_Image im, Imlib_Font fn, gib_style * s, int x, + int y, char *text, Imlib_Text_Direction dir, int r, + int g, int b, int a); +void gib_imlib_get_text_size(Imlib_Font fn, char *text, gib_style * s, int *w, + int *h, Imlib_Text_Direction dir); +Imlib_Image gib_imlib_clone_image(Imlib_Image im); +char *gib_imlib_image_format(Imlib_Image im); +char **gib_imlib_list_fonts(int *num); +void gib_imlib_render_image_on_drawable_at_size(Drawable d, Imlib_Image im, + int x, int y, int w, int h, + char dither, char blend, + + char alias); +/* +void gib_imlib_render_image_on_drawable_at_size_with_rotation(Drawable d, + Imlib_Image im, + int x, int y, + + int w, int h, + double angle, + char dither, + char blend, + char alias); +*/ + +void gib_imlib_blend_image_onto_image(Imlib_Image dest_image, + Imlib_Image source_image, + char merge_alpha, int sx, int sy, + int sw, int sh, int dx, int dy, int dw, + int dh, char dither, char blend, + + char alias); + +/* +void gib_imlib_blend_image_onto_image_with_rotation(Imlib_Image dest_image, + Imlib_Image source_image, + char merge_alpha, int sx, + int sy, int sw, int sh, + int dx, int dy, int dw, + int dh, double angle, + + char dither, char blend, + char alias); +*/ +Imlib_Image gib_imlib_create_cropped_scaled_image(Imlib_Image im, int sx, + int sy, int sw, int sh, + int dw, int dh, char alias); +void gib_imlib_apply_color_modifier_to_rectangle(Imlib_Image im, int x, int y, + int w, int h, DATA8 * rtab, + DATA8 * gtab, DATA8 * btab, + DATA8 * atab); +void gib_imlib_image_set_has_alpha(Imlib_Image im, int alpha); +void gib_imlib_save_image(Imlib_Image im, char *file); +void gib_imlib_save_image_with_error_return(Imlib_Image im, char *file, + Imlib_Load_Error * error_return); +void gib_imlib_free_font(Imlib_Font fn); +void gib_imlib_free_image(Imlib_Image im); +void gib_imlib_image_draw_line(Imlib_Image im, int x1, int y1, int x2, int y2, + char make_updates, int r, int g, int b, int a); +void gib_imlib_image_set_has_alpha(Imlib_Image im, int alpha); +void gib_imlib_free_font(Imlib_Font fn); +Imlib_Image gib_imlib_create_rotated_image(Imlib_Image im, double angle); +void gib_imlib_image_tile(Imlib_Image im); +void gib_imlib_image_blur(Imlib_Image im, int radius); +void gib_imlib_image_sharpen(Imlib_Image im, int radius); +void gib_imlib_image_draw_rectangle(Imlib_Image im, int x, int y, int w, + int h, int r, int g, int b, int a); +void gib_imlib_line_clip_and_draw(Imlib_Image dest, int x0, int y0, int x1, + int y1, int cx, int cy, int cw, int ch, + int r, int g, int b, int a); +void gib_imlib_image_fill_polygon(Imlib_Image im, ImlibPolygon poly, int r, + int g, int b, int a, unsigned char alias, + int cx, int cy, int cw, int ch); +void gib_imlib_image_draw_polygon(Imlib_Image im, ImlibPolygon poly, int r, + int g, int b, int a, unsigned char closed, + unsigned char alias, int cx, int cy, int cw, + + int ch); +Imlib_Image gib_imlib_create_image_from_drawable(Drawable d, Pixmap mask, + int x, int y, int width, + + int height, + char need_to_grab_x); +void gib_imlib_parse_color(char *col, int *r, int *g, int *b, int *a); +void gib_imlib_parse_fontpath(char *path); +Imlib_Font gib_imlib_load_font(char *name); +void gib_imlib_image_orientate(Imlib_Image im, int orientation); +void gib_imlib_image_flip_horizontal(Imlib_Image im); +void gib_imlib_image_flip_vertical(Imlib_Image im); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/gib_list.c b/src/gib_list.c new file mode 100644 index 0000000..e7710bc --- /dev/null +++ b/src/gib_list.c @@ -0,0 +1,579 @@ +/* gib_list.c + +Copyright (C) 1999,2000 Tom Gilbert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include <time.h> +#include "gib_list.h" +#include "utils.h" +#include "debug.h" + +gib_list * +gib_list_new(void) +{ + gib_list *l; + + l = (gib_list *) emalloc(sizeof(gib_list)); + l->data = NULL; + l->next = NULL; + l->prev = NULL; + return (l); +} + +void +gib_list_free(gib_list * l) +{ + gib_list *ll; + + if (!l) + return; + + while (l) + { + ll = l; + l = l->next; + free(ll); + } + + return; +} + +void +gib_list_free_and_data(gib_list * l) +{ + gib_list *ll; + + if (!l) + return; + + while (l) + { + ll = l; + l = l->next; + free(ll->data); + free(ll); + } + return; +} + +#if 0 +gib_list * +gib_list_dup(gib_list * list) +{ + gib_list *ret = NULL; + + if (list) + { + gib_list *last; + + ret = gib_list_new(); + ret->data = list->data; + last = ret; + list = list->next; + while (list) + { + last->next = gib_list_new(); + last->next->prev = last; + last = last->next; + last->data = list->data; + list = list->next; + } + } + return (ret); +} + +gib_list * +gib_list_dup_special(gib_list * list, + void (*cpy_func) (void **dest, void *data)) +{ + gib_list *ret = NULL; + + if (list) + { + gib_list *last; + + ret = gib_list_new(); + cpy_func(&(ret->data), list->data); + last = ret; + list = list->next; + while (list) + { + last->next = gib_list_new(); + last->next->prev = last; + last = last->next; + cpy_func(&(last->data), list->data); + list = list->next; + } + } + return (ret); +} +#endif + +gib_list * +gib_list_add_front(gib_list * root, void *data) +{ + gib_list *l; + + l = gib_list_new(); + l->next = root; + l->data = data; + if (root) + root->prev = l; + return (l); +} + +gib_list * +gib_list_add_end(gib_list * root, void *data) +{ + gib_list *l, *last; + + last = gib_list_last(root); + l = gib_list_new(); + l->prev = last; + l->data = data; + if (last) + { + last->next = l; + return (root); + } + else + { + return (l); + } +} + +#if 0 +gib_list * +gib_list_add_at_pos(gib_list * root, int pos, void *data) +{ + gib_list *l, *top; + + if (pos == gib_list_length(root)) + root = gib_list_add_end(root, data); + else if (pos == 0) + root = gib_list_add_front(root, data); + else + { + top = gib_list_nth(root, pos); + + if (!top) + return (root); + + l = gib_list_new(); + l->next = top; + l->prev = top->prev; + l->data = data; + if (top->prev) + top->prev->next = l; + + top->prev = l; + } + return (root); +} + +gib_list * +gib_list_move_up_by_one(gib_list * root, gib_list * l) +{ + if (l || l->prev) + root = gib_list_move_down_by_one(root, l->prev); + return (root); +} + +gib_list * +gib_list_move_down_by_one(gib_list * root, gib_list * l) +{ + gib_list *temp; + + if (!l || !l->next) + return (root); + + /* store item we link next to */ + temp = l->next; + /* remove from list */ + root = gib_list_unlink(root, l); + /* add back one before */ + l->next = temp->next; + l->prev = temp; + if (temp->next) + temp->next->prev = l; + temp->next = l; + + return (root); +} +#endif + + +unsigned char +gib_list_has_more_than_one_item(gib_list * root) +{ + if (root->next) + return (1); + else + return (0); +} + +#if 0 +gib_list * +gib_list_pop_to_end(gib_list * root, gib_list * l) +{ + root = gib_list_unlink(root, l); + root = gib_list_add_end(root, l->data); + free(l); + + return (root); +} +#endif + +gib_list * +gib_list_cat(gib_list * root, gib_list * l) +{ + gib_list *last; + + if (!l) + return (root); + if (!root) + return (l); + last = gib_list_last(root); + last->next = l; + l->prev = last; + return (root); +} + +int +gib_list_length(gib_list * l) +{ + int length; + + length = 0; + while (l) + { + length++; + l = l->next; + } + return (length); +} + +gib_list * +gib_list_last(gib_list * l) +{ + if (l) + { + while (l->next) + l = l->next; + } + return (l); +} + +gib_list * +gib_list_first(gib_list * l) +{ + if (l) + { + while (l->prev) + l = l->prev; + } + return (l); +} + +gib_list * +gib_list_jump(gib_list * root, gib_list * l, int direction, int num) +{ + int i; + gib_list *ret = NULL; + + if (!root) + return (NULL); + if (!l) + return (root); + + ret = l; + + for (i = 0; i < num; i++) + { + if (direction == FORWARD) + { + if (ret->next) + ret = ret->next; + else + ret = root; + } + else + { + if (ret->prev) + ret = ret->prev; + else + ret = gib_list_last(ret); + } + } + return (ret); +} + +gib_list * +gib_list_reverse(gib_list * l) +{ + gib_list *last; + + last = NULL; + while (l) + { + last = l; + l = last->next; + last->next = last->prev; + last->prev = l; + } + return (last); +} + +gib_list * +gib_list_randomize(gib_list * list) +{ + int len, r, i; + gib_list **farray, *f, *t; + + if (!list) + return (NULL); + len = gib_list_length(list); + if (len <= 1) + return (list); + farray = (gib_list **) emalloc(sizeof(gib_list *) * len); + for (f = list, i = 0; f; f = f->next, i++) + { + farray[i] = f; + } + for (i = 0; i < len - 1; i++) + { + r = i + random() / (RAND_MAX / (len - i) + 1 ); + t = farray[r]; + farray[r] = farray[i]; + farray[i] = t; + } + list = farray[0]; + list->prev = NULL; + list->next = farray[1]; + for (i = 1, f = farray[1]; i < len - 1; i++, f = f->next) + { + f->prev = farray[i - 1]; + f->next = farray[i + 1]; + } + f->prev = farray[len - 2]; + f->next = NULL; + free(farray); + return (list); +} + +int +gib_list_num(gib_list * root, gib_list * l) +{ + int i = 0; + + while (root) + { + if (root == l) + return (i); + i++; + root = root->next; + } + return (-1); +} + +gib_list * +gib_list_unlink(gib_list * root, gib_list * l) +{ + if (!l) + return (root); + + if ((!root) || ((l == root) && (!l->next))) + return (NULL); + + if (l->prev) + l->prev->next = l->next; + if (l->next) + l->next->prev = l->prev; + if (root == l) + root = root->next; + return (root); +} + + +gib_list * +gib_list_remove(gib_list * root, gib_list * l) +{ + root = gib_list_unlink(root, l); + free(l); + return (root); +} + +gib_list * +gib_list_sort(gib_list * list, gib_compare_fn cmp) +{ + gib_list *l1, *l2; + + if (!list) + return (NULL); + if (!list->next) + return (list); + + l1 = list; + l2 = list->next; + + while ((l2 = l2->next) != NULL) + { + if ((l2 = l2->next) == NULL) + break; + l1 = l1->next; + } + l2 = l1->next; + l1->next = NULL; + + return (gib_list_sort_merge + (gib_list_sort(list, cmp), gib_list_sort(l2, cmp), cmp)); +} + +gib_list * +gib_list_sort_merge(gib_list * l1, gib_list * l2, gib_compare_fn cmp) +{ + gib_list list, *l, *lprev; + + l = &list; + lprev = NULL; + + while (l1 && l2) + { + if (cmp(l1->data, l2->data) < 0) + { + l->next = l1; + l = l->next; + l->prev = lprev; + lprev = l; + l1 = l1->next; + } + else + { + l->next = l2; + l = l->next; + l->prev = lprev; + lprev = l; + l2 = l2->next; + } + } + l->next = l1 ? l1 : l2; + l->next->prev = l; + + return (list.next); +} + +#if 0 +gib_list * +gib_list_nth(gib_list * root, unsigned int num) +{ + unsigned int i; + gib_list *l; + + if (num > (unsigned int) gib_list_length(root)) + return (gib_list_last(root)); + l = root; + for (i = 0; l; ++i) + { + if (i == num) + return (l); + l = l->next; + } + return (root); +} +#endif + +gib_list * +gib_list_foreach(gib_list *root, void (*fe_func)(gib_list *node, void *data), void *data) +{ + gib_list *i, *next = NULL; + for (i=root; i; i=next) { + next=i->next; + fe_func(i, data); + } + return root; +} + +gib_list * +gib_list_find(gib_list *root, unsigned char (*find_func)(gib_list *node, void *data), void *data) +{ + gib_list *i = NULL; + for (i=root; i; i=i->next) + if (find_func(i,data)) + return i; + + return NULL; +} + +static unsigned char gib_list_find_by_data_callback(gib_list *list, void *data) +{ + return (list->data==data); +} + +gib_list * +gib_list_find_by_data(gib_list *root, void *data) +{ + return gib_list_find(root, gib_list_find_by_data_callback, data); +} + +gib_list * +gib_string_split(const char *string, const char *delimiter) +{ + gib_list *string_list = NULL; + char *s; + unsigned int n = 1; + + if (!string || !delimiter) + return NULL; + + s = strstr(string, delimiter); + if (s) + { + unsigned int delimiter_len = strlen(delimiter); + + do + { + unsigned int len; + char *new_string; + + len = s - string; + new_string = emalloc(sizeof(char) * (len + 1)); + + strncpy(new_string, string, len); + new_string[len] = 0; + string_list = gib_list_add_front(string_list, new_string); + n++; + string = s + delimiter_len; + s = strstr(string, delimiter); + } + while (s); + } + if (*string) + { + n++; + string_list = gib_list_add_front(string_list, strdup((char *)string)); + } + + string_list = gib_list_reverse(string_list); + + return string_list; +} diff --git a/src/gib_list.h b/src/gib_list.h new file mode 100644 index 0000000..94c4c39 --- /dev/null +++ b/src/gib_list.h @@ -0,0 +1,105 @@ +/* gib_list.h + +Copyright (C) 1999,2000 Tom Gilbert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef GIB_LIST_H +#define GIB_LIST_H + +#include <stdarg.h> + +#define GIB_LIST(a) ((gib_list*)a) + +enum __direction +{ FORWARD, BACK }; + +typedef struct __gib_list gib_list; + +struct __gib_list +{ + void *data; + + gib_list *next; + gib_list *prev; +}; + +typedef int (gib_compare_fn) (void *data1, void *data2); + +#ifdef __cplusplus +extern "C" +{ +#endif + +gib_list *gib_list_new(void); +void gib_list_free(gib_list * l); +gib_list *gib_list_add_front(gib_list * root, void *data); +gib_list *gib_list_add_end(gib_list * root, void *data); +/* +gib_list *gib_list_add_at_pos(gib_list * root, int pos, void *data); +gib_list *gib_list_pop_to_end(gib_list * root, gib_list * l); +*/ +gib_list *gib_list_unlink(gib_list * root, gib_list * l); +gib_list *gib_list_cat(gib_list * root, gib_list * l); +int gib_list_length(gib_list * l); +gib_list *gib_list_last(gib_list * l); +gib_list *gib_list_first(gib_list * l); +gib_list *gib_list_jump(gib_list * root, gib_list * l, int direction, + + int num); +gib_list *gib_list_reverse(gib_list * l); +gib_list *gib_list_randomize(gib_list * list); +int gib_list_num(gib_list * root, gib_list * l); +gib_list *gib_list_remove(gib_list * root, gib_list * l); +gib_list *gib_list_sort(gib_list * list, gib_compare_fn cmp); +gib_list *gib_list_sort_merge(gib_list * l1, gib_list * l2, + + gib_compare_fn cmp); +/* +gib_list *gib_list_nth(gib_list * root, unsigned int num); +*/ +unsigned char gib_list_has_more_than_one_item(gib_list * root); +void gib_list_free_and_data(gib_list * l); +/* +gib_list *gib_list_dup(gib_list * list); +gib_list *gib_list_dup_special(gib_list * list, + void (*cpy_func) (void **dest, void *data)); +gib_list *gib_list_move_down_by_one(gib_list * root, gib_list * l); +gib_list *gib_list_move_up_by_one(gib_list * root, gib_list * l); +*/ + +gib_list *gib_list_foreach(gib_list *root, void (*fe_func)(gib_list *node, void *data), void *data); +gib_list *gib_list_find(gib_list *root, unsigned char (*find_func)(gib_list *node, void *data), void *data); +gib_list *gib_list_find_by_data(gib_list *root, void *data); + +/* don't really belong here, will do for now */ +gib_list *gib_string_split(const char *string, const char *delimiter); +/* +char *gib_strjoin(const char *separator, ...); +*/ + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/gib_style.c b/src/gib_style.c new file mode 100644 index 0000000..9280217 --- /dev/null +++ b/src/gib_style.c @@ -0,0 +1,114 @@ +/* gib_style.c + +Copyright (C) 1999,2000 Tom Gilbert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include "gib_style.h" +#include "utils.h" +#include "debug.h" + +gib_style * +gib_style_new(char *name) +{ + gib_style *s = NULL; + + s = emalloc(sizeof(gib_style)); + + memset(s, 0, sizeof(gib_style)); + if (name) + s->name = strdup(name); + + return (s); +} + +void +gib_style_free(gib_style * s) +{ + if (s) + { + if (s->name) + free(s->name); + if (s->bits) + { + gib_list *l; + + l = s->bits; + while (l) + { + gib_style_bit_free((gib_style_bit *) l->data); + l = l->next; + } + gib_list_free(s->bits); + } + free(s); + } + return; +} + +gib_style_bit * +gib_style_bit_new(int x_offset, int y_offset, int r, int g, int b, int a) +{ + gib_style_bit *sb; + + sb = emalloc(sizeof(gib_style_bit)); + memset(sb, 0, sizeof(gib_style_bit)); + + sb->x_offset = x_offset; + sb->y_offset = y_offset; + sb->r = r; + sb->g = g; + sb->b = b; + sb->a = a; + + return (sb); +} + +void +gib_style_bit_free(gib_style_bit * s) +{ + if (s) + free(s); + return; +} + +#if 0 +gib_style * +gib_style_dup(gib_style * s) +{ + gib_style *ret; + + ret = gib_style_new(s->name); + ret->bits = gib_list_dup_special(s->bits, gib_dup_style_bit); + + return (ret); +} + +void +gib_dup_style_bit(void **dest, void *data) +{ + *dest = malloc(sizeof(gib_style_bit)); + memcpy(*dest, data, sizeof(gib_style_bit)); + + return; +} +#endif diff --git a/src/gib_style.h b/src/gib_style.h new file mode 100644 index 0000000..d54e2d4 --- /dev/null +++ b/src/gib_style.h @@ -0,0 +1,69 @@ +/* gib_style.h + +Copyright (C) 1999,2000 Tom Gilbert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + + +#ifndef GIB_STYLE_H +#define GIB_STYLE_H + +#include "gib_list.h" + +#define GIB_STYLE(O) ((gib_style *)O) +#define GIB_STYLE_BIT(O) ((gib_style_bit *)O) + +typedef struct __gib_style_bit gib_style_bit; +typedef struct __gib_style gib_style; + +struct __gib_style_bit +{ + int x_offset, y_offset; + int r,g,b,a; +}; + +struct __gib_style +{ + gib_list *bits; + char *name; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + +gib_style_bit *gib_style_bit_new(int x_offset, int y_offset, int r, int g, int b, int a); +gib_style *gib_style_new(char *name); +void gib_style_bit_free(gib_style_bit *s); +void gib_style_free(gib_style *s); +/* +gib_style *gib_style_dup(gib_style *s); +void gib_dup_style_bit(void **dest, void *data); +*/ + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/help.raw b/src/help.raw index e0ea432..0e99c68 100644 --- a/src/help.raw +++ b/src/help.raw @@ -2,7 +2,7 @@ Usage : " PACKAGE " [options] <files or directories ...> - This is just a short option summary. Please consult the manual for details. + This is just a short option summary. Please see \"man " PACKAGE "\" for details. OPTIONS -h, --help Show help and exit @@ -10,34 +10,42 @@ OPTIONS -V, --verbose Show progress bars and other extra information -q, --quiet Hide non-fatal errors. May be used with --verbose -T, --theme THEME Load options with name THEME - -_, --rcfile FILE Use FILE to parse themes and options from -r, --recursive Recursively expand any directories in FILE to - the content of those directories. (Take it easy) + the content of those directories + --no-recursive Do not recursively expand directories + (this is the default) -z, --randomize Randomize the filelist --no-jump-on-resort Don't jump to the first image when the filelist is resorted - -g, --geometry STRING Limit the window size to STRING, like \"640x480\" + -g, --geometry WxH[+X+Y] Limit the window size to DIMENSION[+OFFSET] -f, --filelist FILE Load/save images from/to the FILE filelist - -|, --start-at POSITION Start at POSITION in the filelist - -p, --preload Remove unlaodable files from the internal filelist + -|, --start-at FILENAME Start at FILENAME in the filelist + -p, --preload Remove unloadable files from the internal filelist before attempting to display anything - -., --scale-down Automatically scale down images to fit the screen - size + -., --scale-down Automatically scale down images to fit screen size -F, --fullscreen Make the window full screen - -Z, --auto-zoom Zoom picture to screen size in full screen mode + -Z, --auto-zoom Zoom picture to screen size in fullscreen/geom mode --zoom PERCENT Zooms images by a PERCENT, when in full screen mode or when window geometry is fixed. If combined with --auto-zoom, zooming will be limited to the - the size. + the size. Also support \"max\" and \"fill\" + --zoom-step PERCENT Zoom images in and out by PERCENT (default: 25) + when using the zoom keys / buttons + --keep-zoom-vp Keep viewport zoom and settings while changing images -w, --multiwindow Open all files at once, one window per image -x, --borderless Create borderless windows -d, --draw-filename Show the filename in the image window + --draw-tinted Show overlay texts on semi-transparent background + --draw-exif Show some Exif information (if compiled with exif=1) + --edit Make flip/rotation keys flip/rotate the underlying file + --auto-rotate Rotate images according to Exif info (if compiled with exif=1) -^, --title TITLE Set window title (see FORMAT SPECIFIERS) -D, --slideshow-delay NUM Set delay between automatically changing slides - --cycle-once Exit after one loop through the slideshow + --on-last-slide quit Exit after one loop through the slide show (old --cycle-once) + --on-last-slide hold Stop at both ends of the filelist -R, --reload NUM Reload images after NUM seconds - -Q, --builtin Use builtin http client instead of wget -k, --keep-http Keep local copies when viewing HTTP/FTP files + --insecure Disable peer/host verification when using HTTPS. -K, --caption-path PATH Path to caption directory, enables caption display -j, --output-dir With -k: Output directory for saved files -l, --list list mode: ls-style output with image information @@ -45,15 +53,18 @@ OPTIONS -U, --loadable List all loadable files. No image display -u, --unloadable List all unloadable files. No image display -S, --sort SORT_TYPE Sort files by: - name, filename, width, height, pixels, size or format + name, filename, mtime, width, height, pixels, size, + or format -n, --reverse Reverse sort order - -A, --action ACTION Specify action to perform when pressing <return>. + --version-sort Natural sort of (version) numbers within text + -A, --action [;]ACTION Specify action to perform when pressing <return>. Executed by /bin/sh, may contain FORMAT SPECIFIERS + reloads image with \";\", switches to next otherwise --action[1-9] Extra actions triggered by pressing keys <1>to <9> - --action-hold-slide Do not change to next image after running an action -G, --draw-actions Show the defined actions in the image window + --tap-zones Enable tap zones for previous/next file in slide show mode + --force-aliasing Disable antialiasing -m, --montage Enable montage mode - -c, --collage Montage mode with randomly distributed thumbnails -i, --index Create an index print of all images --info CMD Run CMD and show its output in the image window -t, --thumbnails Show images as clickable thumbnails @@ -62,9 +73,7 @@ OPTIONS -J, --thumb-redraw N Redraw thumbnail window every N images -~, --thumb-title STRING Title for windows opened from thumbnail mode -I, --fullindex Index mode with additional image information - --index-name BOOL Show/Don't show filename in index/thumbnail mode - --index-size BOOL Show/Don't show filesize in index/thumbnail mode - --index-dim BOOL Show/Don't show dimensions in index/thumbnail mode + --index-info FORMAT Show FORMAT below images in index/thumbnail mode --bg-center FILE Set FILE as centered desktop background --bg-fill FILE Like --bg-scale, but preserves aspect ratio by zooming the image until it fits. May cut off @@ -76,41 +85,25 @@ OPTIONS fill the whole background, but the images' aspect ratio may not be preserved --bg-tile FILE Set FILE as tiled desktop background + --no-fehbg Do not write a ~/.fehbg file -C, --fontpath PATH Specify an extra directory to look in for fonts, can be used multiple times to add multiple paths. -M, --menu-font FONT Use FONT for the font in menus. - --menu-style FILE Use FILE as the style descriptor for menu text. - -), --menu-bg BG Use BG for the background image in menus. -B, --image-bg STYLE Set background for transparent images and the like. - Accepted values: white, black, default + Accepted values: default, checks, or a XColor (eg. #428bdd) + --xinerama-index I Assumee that I is the active xinerama screen -N, --no-menus Don't load or show any menus. - -0, --reload-button B Use button B to reload the image (defaults to 0) - -1, --pan-button B Use button B pan the image (hold button down, move - the mouse to move the image around. Advances to the - next image when the mouse is not moved (defaults to - 1, usually the left button). - -2, --zoom-button B Use button B to zoom the current image in any - mode (defaults to 2, usually the middle button). - -3, --menu-button B Click button B to activate the menu in any - mode. Set to 0 for any button. This option - is disabled if the -N or --no-menus option is set - (defaults to 3, usually the right button). - --menu-ctrl-mask Require CTRL+Button for menu activation - -4, --prev-button B Use button B to switch to the previous image - (defaults to 4, which usually is <mousewheel up>). - -5, --next-button B Use button B to switch to the next image - (defaults to 5, which usually is <mousewheel down>). - -8, --rotate-button B Use CTRL+Button B to rotate the current image in - any mode (default=2). - --no-rotate-ctrl-mask Don't require CTRL+Button for rotation in - any mode -- just use the button (default=off). - -9, --blur-button B Use CTRL+Button B to blur the current image in - any mode (default=1). - --no-blur-ctrl-mask Don't require CTRL+Button for blurring in - any mode -- just use the button (default=off). --no-xinerama Disable Xinerama support --no-screen-clip Do not limit window size to screen size -Y, --hide-pointer Hide the pointer + --conversion-timeout INT Load unknown files with dcraw or ImageMagick, + timeout after INT seconds (0: no timeout) + --min-dimension WxH Only show images with width >= W and height >= H + --max-dimension WxH Only show images with width <= W and height <= H + --scroll-step COUNT scroll COUNT pixels when movement key is pressed + --cache-size NUM imlib cache size in mebibytes (0 .. 2048) + --auto-reload automatically reload shown image if file was changed + --window-id ID Draw to an existing X11 window by its ID MONTAGE MODE OPTIONS -X, --ignore-aspect Set thumbnail to specified width/height without @@ -135,60 +128,84 @@ INDEX MODE OPTIONS font is specified, a title will not be printed FORMAT SPECIFIERS + %a information about slideshow state (playing/paused) %f image path/filename + %F image path/filename (shell-escaped) + %g window dimensions (\"width,height\") in pixels + %h image height + %l total number of files in the filelist + %L path to temporary copy of filelist + %m current mode (slideshow, multiwindow...) %n image name - %s image size (bytes) + %N image name (shell-escaped) + %o offset of top-left image corner to window (\"x,y\") in pixels %p image pixel size - %w image width - %h image height + %P image pixel size in kilo-/megapixels + %r image rotation. half right turn == 3.1415 (pi) + %s image size in bytes + %S image size with appropriate unit (kB/MB) %t image format - %P " PACKAGE " - %v " PACKAGE " version - %m current mode (slideshow, multiwindow...) - %l total number of files in the filelist %u current file number + %w image width + %v " PACKAGE " version + %V process ID + %z current image zoom, rounded to two decimal places + %Z current image zoom, high precision %% % - \\n newline + \\n newline -KEYS - p, <BACKSPACE>, <LEFT> Go to previous slide - n, <SPACE>, <RIGHT> Go to next slide - r Reload image - v Toggle fullscreen - m Show menu - c Enable caption entry mode - w Resize window to current image dimensions - h Pause/Continue the slideshow - z Jump to a random position in the current filelist +DEFAULT KEYS a Toggle action display (--draw-actions) + A Toggle anti-aliasing + c Enable caption entry mode d Toggle filename display (--draw-filename) + e Toggle exif tag display (if compiled with exif=1) + f Toggle fullscreen + g Toggle fixed geometry mode + h pause/continue slideshow + i Toggle --info display + k Toggle zoom/viewport freeze when switching images + L Save current filelist to unique filename + m Show/hide menu + n, <SPACE>, <RIGHT> Go to next image + o Toggle pointer visibility + p, <BACKSPACE>, <LEFT> Go to previous image + q, <ESCAPE> Quit + r Reload image + R Render/anti-alias image s Save current image to unique filename - f Save current filelist to unique filename - <, > In place editing, rotate 90 degrees right/left + w Resize window to current image dimensions + x Close current window + z Jump to a random position in the current filelist + Z Toggle auto-zoom + [, ] Jump to previous/next directory + <, > Rotate 90 degrees right/left + _ Vertical flip + | Horizontal flip + 0, <ENTER> Run action specified by --action option + 1-9 Run action 1-9 specified by --action[1-9] options <HOME> Go to first slide <END> Go to last slide - <ESCAPE> Quit the slideshow - + Increase reload delay - - Decrease reload delay + <PAGEUP> Go forward 5% of the filelist + <PAGEDOWN> Go backward 5% of the filelist + + Increase reload delay by 1 second + - Decrease reload delay by 1 second <DELETE> Remove the currently viewed file from the filelist <CTRL+DELETE> Like <DELETE>, but also removes the file from the - filesystem. Caution: Does _not_ ask for confirmation - x Close current window - q Quit " PACKAGE " + filesystem. Caution: Does not ask for confirmation <KEYPAD LEFT> Move the image to the left <KEYPAD RIGHT> Move the image to the right <KEYPAD UP> Move the image up <KEYPAD DOWN> Move the image down <KEYPAD BEGIN> Antialias the image - <KEYPAD +> Zoom in - <KEYPAD -> Zoom out + <KEYPAD +>, <UP> Zoom in + <KEYPAD ->, <DOWN> Zoom out <KEYPAD *> Zoom to 100% <KEYPAD /> Zoom to fit the window - <ENTER>, 0 Run action specified by --action option - 1-9 Run action 1-9 specified by --action[1-9] options This program is free software, see the file COPYING for licensing info. -Copyright Tom Gilbert (and various contributors) 1999-2003 +Copyright Tom Gilbert (and various contributors) 1999-2003. +Copyright Birte Kristina Friesel (and various contributors) 2010-2020. -Homepage: https://derf.homelinux.org/p/feh -Email bugs to <derf@finalrewind.org> +Homepage: http://feh.finalrewind.org +Report bugs to <derf+feh@finalrewind.org> or #feh on irc.oftc.net. 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, diff --git a/src/index.c b/src/index.c index 15dcea1..b0b6923 100644 --- a/src/index.c +++ b/src/index.c @@ -1,6 +1,7 @@ /* index.c Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -27,15 +28,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "filelist.h" #include "winwidget.h" #include "options.h" +#include "index.h" -static char *create_index_dimension_string(int w, int h); -static char *create_index_size_string(char *file); -static char *create_index_title_string(int num, int w, int h); /* TODO Break this up a bit ;) */ /* TODO s/bit/lot */ void init_index_mode(void) { + Imlib_Load_Error err; Imlib_Image im_main; Imlib_Image im_temp; int w = 800, h = 600, ww = 0, hh = 0, www, hhh, xxx, yyy; @@ -50,17 +50,16 @@ void init_index_mode(void) Imlib_Font title_fn = NULL; int text_area_w = 0; int tw = 0, th = 0; - int fw_name, fw_size, fw_dim, fw, fh; + int fw, fh; int vertical = 0; int max_column_w = 0; int thumbnailcount = 0; gib_list *l = NULL, *last = NULL; feh_file *file = NULL; - int lines; + int lineno; unsigned char trans_bg = 0; int index_image_width, index_image_height; - int x_offset_name = 0, x_offset_dim = 0, x_offset_size = 0; - char *s; + gib_list *line, *lines; if (opt.montage) { mode = "montage"; @@ -75,11 +74,11 @@ void init_index_mode(void) fn = gib_imlib_load_font(DEFAULT_FONT); if (opt.title_font) { - int fh, fw; title_fn = gib_imlib_load_font(opt.title_font); - if (!fn) + if (!title_fn) title_fn = gib_imlib_load_font(DEFAULT_FONT_TITLE); + gib_imlib_get_text_size(title_fn, "W", NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); title_area_h = fh + 4; } else @@ -90,8 +89,9 @@ void init_index_mode(void) /* Work out how tall the font is */ gib_imlib_get_text_size(fn, "W", NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT); + get_index_string_dim(NULL, fn, &fw, &fh); /* For now, allow room for the right number of lines with small gaps */ - text_area_h = ((th + 2) * (opt.index_show_name + opt.index_show_size + opt.index_show_dim)) + 5; + text_area_h = fh + 5; /* This includes the text area for index data */ tot_thumb_h = opt.thumb_h + text_area_h; @@ -111,174 +111,52 @@ void init_index_mode(void) if (!opt.limit_w && !opt.limit_h) { if (bg_im) { - if (opt.verbose) - fprintf(stdout, - PACKAGE - " - No size restriction specified for index.\n" - " You did specify a background however, so the\n" - " index size has defaulted to the size of the image\n"); opt.limit_w = bg_w; opt.limit_h = bg_h; - } else { - if (opt.verbose) - fprintf(stdout, - PACKAGE - " - No size restriction specified for index.\n" - " Using defaults (width limited to 800)\n"); + } else opt.limit_w = 800; - } } /* Here we need to whiz through the files, and look at the filenames and info in the selected font, work out how much space we need, and calculate the size of the image we will require */ - if (opt.limit_w && opt.limit_h) { - int rec_h = 0; - + if (opt.limit_w) { w = opt.limit_w; - h = opt.limit_h; - - /* Work out if this is big enough, and give a warning if not */ - /* Pretend we are limiting width by that specified, loop through, and - see it we fit in the height specified. If not, continue the loop, - and recommend the final value instead. Carry on and make the index - anyway. */ - - for (l = filelist; l; l = l->next) { - file = FEH_FILE(l->data); - text_area_w = opt.thumb_w; - if (opt.index_show_name) { - gib_imlib_get_text_size(fn, file->name, NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - if (opt.index_show_dim) { - gib_imlib_get_text_size(fn, - create_index_dimension_string - (1000, 1000), NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - if (opt.index_show_size) { - gib_imlib_get_text_size(fn, - create_index_size_string - (file->filename), NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - if (text_area_w > opt.thumb_w) - text_area_w += 5; - - if ((x > w - text_area_w)) { - x = 0; - y += tot_thumb_h; - } - - x += text_area_w; - } - rec_h = y + tot_thumb_h; - - if (h < rec_h) { - weprintf( - "The image size you specified (%d by %d) is not large\n" - "enough to hold all the thumnails you specified (%d). To fit all\n" - "the thumnails, either decrease their size, choose a smaller font,\n" - "or use a larger image (may I recommend %d by %d?)", - opt.limit_w, opt.limit_h, filelist_len, opt.limit_w, rec_h); + index_calculate_height(fn, w, &h, &tot_thumb_h); + + if (opt.limit_h) { + if (h > opt.limit_h) + weprintf( + "The image size you specified (%dx%d) is not large\n" + "enough to hold all %d thumbnails. To fit all the thumbnails,\n" + "either decrease their size, choose a smaller font,\n" + "or use a larger image (like %dx%d)", + opt.limit_w, opt.limit_h, filelist_len, w, h); + h = opt.limit_h; } } else if (opt.limit_h) { vertical = 1; h = opt.limit_h; - /* calc w */ - for (l = filelist; l; l = l->next) { - file = FEH_FILE(l->data); - text_area_w = opt.thumb_w; - /* Calc width of text */ - if (opt.index_show_name) { - gib_imlib_get_text_size(fn, file->name, NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - if (opt.index_show_dim) { - gib_imlib_get_text_size(fn, - create_index_dimension_string - (1000, 1000), NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - if (opt.index_show_size) { - gib_imlib_get_text_size(fn, - create_index_size_string - (file->filename), NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - if (text_area_w > opt.thumb_w) - text_area_w += 5; - - if (text_area_w > max_column_w) - max_column_w = text_area_w; - if ((y > h - tot_thumb_h)) { - y = 0; - x += max_column_w; - max_column_w = 0; - } - - y += tot_thumb_h; - } - w = x + text_area_w; - max_column_w = 0; - } else if (opt.limit_w) { - w = opt.limit_w; - /* calc h */ - - for (l = filelist; l; l = l->next) { - file = FEH_FILE(l->data); - text_area_w = opt.thumb_w; - if (opt.index_show_name) { - gib_imlib_get_text_size(fn, file->name, NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - if (opt.index_show_dim) { - gib_imlib_get_text_size(fn, - create_index_dimension_string - (1000, 1000), NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - if (opt.index_show_size) { - gib_imlib_get_text_size(fn, - create_index_size_string - (file->filename), NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > text_area_w) - text_area_w = fw; - } - - if (text_area_w > opt.thumb_w) - text_area_w += 5; - - if ((x > w - text_area_w)) { - x = 0; - y += tot_thumb_h; - } - - x += text_area_w; - } - h = y + tot_thumb_h; + index_calculate_width(fn, &w, h, &tot_thumb_h); } - x = y = 0; - index_image_width = w; index_image_height = h + title_area_h; im_main = imlib_create_image(index_image_width, index_image_height); - if (!im_main) - eprintf("Imlib error creating index image, are you low on RAM?"); + if (!im_main) { + if (index_image_height >= 32768 || index_image_width >= 32768) { + eprintf("Failed to create %dx%d pixels (%d MB) index image.\n" + "This is probably due to Imlib2 issues when dealing with images larger than 32k x 32k pixels.", + index_image_width, index_image_height, index_image_width * index_image_height * 4 / (1024*1024)); + } else { + eprintf("Failed to create %dx%d pixels (%d MB) index image. Do you have enough RAM?", + index_image_width, index_image_height, index_image_width * index_image_height * 4 / (1024*1024)); + } + } if (bg_im) gib_imlib_blend_image_onto_image(im_main, bg_im, @@ -292,15 +170,9 @@ void init_index_mode(void) gib_imlib_image_fill_rectangle(im_main, 0, 0, w, h + title_area_h, 0, 0, 0, 255); } - /* Create the window title at this point */ - - if (!opt.title) - s = estrdup(PACKAGE " [index mode]"); - else - s = estrdup(feh_printf(opt.title, NULL)); - if (opt.display) { - winwid = winwidget_create_from_image(im_main, s, WIN_TYPE_SINGLE); + winwid = winwidget_create_from_image(im_main, WIN_TYPE_SINGLE); + winwidget_rename(winwid, PACKAGE " [index mode]"); winwidget_show(winwid); } @@ -354,33 +226,14 @@ void init_index_mode(void) text_area_w = opt.thumb_w; /* Now draw on the info text */ - if (opt.index_show_name) { - gib_imlib_get_text_size(fn, file->name, NULL, &fw_name, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw_name > text_area_w) - text_area_w = fw_name; - } - if (opt.index_show_dim) { - gib_imlib_get_text_size(fn, - create_index_dimension_string - (ww, hh), NULL, &fw_dim, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw_dim > text_area_w) - text_area_w = fw_dim; - } - if (opt.index_show_size) { - gib_imlib_get_text_size(fn, - create_index_size_string - (file->filename), NULL, &fw_size, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw_size > text_area_w) - text_area_w = fw_size; + if (opt.index_info) { + get_index_string_dim(file, fn, &fw, &fh); + if (fw > text_area_w) + text_area_w = fw; } if (text_area_w > opt.thumb_w) text_area_w += 5; - /* offsets for centering text */ - x_offset_name = (text_area_w - fw_name) / 2; - x_offset_dim = (text_area_w - fw_dim) / 2; - x_offset_size = (text_area_w - fw_size) / 2; - if (vertical) { if (text_area_w > max_column_w) max_column_w = text_area_w; @@ -416,29 +269,23 @@ void init_index_mode(void) gib_imlib_free_image_and_decache(im_thumb); - lines = 0; - if (opt.index_show_name) - gib_imlib_text_draw(im_main, fn, NULL, - x + x_offset_name, - y + opt.thumb_h + - (lines++ * (th + 2)) + - 2, file->name, IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); - if (opt.index_show_dim) - gib_imlib_text_draw(im_main, fn, NULL, - x + x_offset_dim, - y + opt.thumb_h + - (lines++ * (th + 2)) + - 2, - create_index_dimension_string - (ww, hh), IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); - if (opt.index_show_size) - gib_imlib_text_draw(im_main, fn, NULL, - x + x_offset_size, - y + opt.thumb_h + - (lines++ * (th + 2)) + - 2, - create_index_size_string - (file->filename), IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + lineno = 0; + if (opt.index_info) { + line = lines = feh_wrap_string(create_index_string(file), + opt.thumb_w * 3, fn, NULL); + + while (line) { + gib_imlib_get_text_size(fn, (char *) line->data, + NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); + gib_imlib_text_draw(im_main, fn, NULL, + x + ((text_area_w - fw) >> 1), + y + opt.thumb_h + (lineno++ * (th + 2)) + 2, + (char *) line->data, + IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + line = line->next; + } + gib_list_free_and_data(lines); + } if (vertical) y += tot_thumb_h; @@ -457,7 +304,7 @@ void init_index_mode(void) } } if (opt.verbose) - fprintf(stdout, "\n"); + putc('\n', stderr); if (opt.title_font) { int fw, fh, fx, fy; @@ -468,6 +315,9 @@ void init_index_mode(void) fx = (index_image_width - fw) >> 1; fy = index_image_height - fh - 2; gib_imlib_text_draw(im_main, title_fn, NULL, fx, fy, s, IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + + if (opt.display) + winwidget_render_image(winwid, 0, 0); } if (opt.output && opt.output_file) { @@ -475,17 +325,22 @@ void init_index_mode(void) if (opt.output_dir) snprintf(output_buf, 1024, "%s/%s", opt.output_dir, opt.output_file); - else - strncpy(output_buf, opt.output_file, 1024); + else { + strncpy(output_buf, opt.output_file, 1023); + output_buf[1023] = '\0'; + } - gib_imlib_save_image(im_main, output_buf); - if (opt.verbose) { + gib_imlib_save_image_with_error_return(im_main, output_buf, &err); + if (err) { + feh_print_load_error(output_buf, im_main, err, LOAD_ERROR_IMLIB); + } + else if (opt.verbose) { int tw, th; tw = gib_imlib_image_get_width(im_main); th = gib_imlib_image_get_height(im_main); - fprintf(stdout, PACKAGE " - File saved as %s\n", output_buf); - fprintf(stdout, + fprintf(stderr, PACKAGE " - File saved as %s\n", output_buf); + fprintf(stderr, " - Image is %dx%d pixels and contains %d thumbnails\n", tw, th, thumbnailcount); } } @@ -493,37 +348,133 @@ void init_index_mode(void) if (!opt.display) gib_imlib_free_image_and_decache(im_main); - free(s); return; } -static char *create_index_size_string(char *file) +void index_calculate_height(Imlib_Font fn, int w, int *h, int *tot_thumb_h) { - static char str[50]; - int size = 0; - double kbs = 0.0; - struct stat st; - - if (stat(file, &st)) - kbs = 0.0; - else { - size = st.st_size; - kbs = (double) size / 1000; + gib_list *l; + feh_file *file = NULL; + int x = 0, y = 0; + int fw = 0, fh = 0; + int text_area_w = 0, text_area_h = 0; + + for (l = filelist; l; l = l->next) { + file = FEH_FILE(l->data); + text_area_w = opt.thumb_w; + if (opt.index_info) { + get_index_string_dim(file, fn, &fw, &fh); + if (fw > text_area_w) + text_area_w = fw; + if (fh > text_area_h) { + text_area_h = fh + 5; + *tot_thumb_h = opt.thumb_h + text_area_h; + } + } + + if (text_area_w > opt.thumb_w) + text_area_w += 5; + + if ((x > w - text_area_w)) { + x = 0; + y += *tot_thumb_h; + } + + x += text_area_w; } + *h = y + *tot_thumb_h; +} - snprintf(str, sizeof(str), "%.2fKb", kbs); - return(str); +void index_calculate_width(Imlib_Font fn, int *w, int h, int *tot_thumb_h) +{ + gib_list *l; + feh_file *file = NULL; + int x = 0, y = 0; + int fw = 0, fh = 0; + int text_area_w = 0, text_area_h = 0; + int max_column_w = 0; + + for (l = filelist; l; l = l->next) { + file = FEH_FILE(l->data); + text_area_w = opt.thumb_w; + /* Calc width of text */ + if (opt.index_info) { + get_index_string_dim(file, fn, &fw, &fh); + if (fw > text_area_w) + text_area_w = fw; + if (fh > text_area_h) { + text_area_h = fh + 5; + *tot_thumb_h = opt.thumb_h + text_area_h; + } + } + if (text_area_w > opt.thumb_w) + text_area_w += 5; + + if (text_area_w > max_column_w) + max_column_w = text_area_w; + + if ((y > h - *tot_thumb_h)) { + y = 0; + x += max_column_w; + max_column_w = 0; + } + + y += *tot_thumb_h; + } + *w = x + text_area_w; } -static char *create_index_dimension_string(int w, int h) +void get_index_string_dim(feh_file *file, Imlib_Font fn, int *fw, int *fh) { - static char str[50]; + int line_w, line_h; + char fake_file = 0; + gib_list *line, *lines; + int max_w = 0, total_h = 0; + + if (!opt.index_info) { + *fw = 0; + *fh = 0; + return; + } - snprintf(str, sizeof(str), "%dx%d", w, h); - return(str); + /* called with file = NULL in the setup phase. + * We need a fake file, otherwise feh_printf will remove format specifiers, + * leading e.g. to a 0x0 report for index_dim = "%n". + */ + if (file == NULL) { + fake_file = 1; + file = feh_file_new("foo"); + file->info = feh_file_info_new(); + } + + line = lines = feh_wrap_string(create_index_string(file), opt.thumb_w * 3, fn, NULL); + + while (line) { + gib_imlib_get_text_size(fn, (char *) line->data, + NULL, &line_w, &line_h, IMLIB_TEXT_TO_RIGHT); + + if (line_w > max_w) + max_w = line_w; + total_h += line_h + 2; + + line = line->next; + } + + gib_list_free_and_data(lines); + if (fake_file) + feh_file_free(file); + + *fw = max_w; + *fh = total_h; + return; +} + +char *create_index_string(feh_file * file) +{ + return feh_printf(opt.index_info, file, NULL); } -static char *create_index_title_string(int num, int w, int h) +char *create_index_title_string(int num, int w, int h) { static char str[50]; diff --git a/src/index.h b/src/index.h new file mode 100644 index 0000000..b805cc0 --- /dev/null +++ b/src/index.h @@ -0,0 +1,35 @@ +/* index.h + +Copyright (C) 2018 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 +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its documentation and acknowledgment shall be +given in the documentation and software packages that this Software was +used. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef INDEX_H +#define INDEX_H + +char *create_index_string(feh_file *file); +char *create_index_title_string(int num, int w, int h); +void get_index_string_dim(feh_file *file, Imlib_Font fn, int *w, int *h); +void index_calculate_height(Imlib_Font fn, int w, int *h, int *tot_thumb_w); +void index_calculate_width(Imlib_Font fn, int *w, int h, int *tot_thumb_h); + +#endif diff --git a/src/keyevents.c b/src/keyevents.c index 7940973..2f9b1d6 100644 --- a/src/keyevents.c +++ b/src/keyevents.c @@ -1,6 +1,7 @@ /* keyevents.c Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -28,37 +29,349 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "filelist.h" #include "winwidget.h" #include "options.h" +#include <termios.h> + +struct __fehkey keys[EVENT_LIST_END]; +struct termios old_term_settings; +unsigned char control_via_stdin = 0; + +void setup_stdin(void) { + struct termios ctrl; + + control_via_stdin = 1; + + if (tcgetattr(STDIN_FILENO, &old_term_settings) == -1) + eprintf("tcgetattr failed"); + if (tcgetattr(STDIN_FILENO, &ctrl) == -1) + eprintf("tcgetattr failed"); + + ctrl.c_iflag &= ~(PARMRK | ISTRIP + | INLCR | IGNCR | IXON); + ctrl.c_lflag &= ~(ECHO | ICANON | IEXTEN); + ctrl.c_cflag &= ~(CSIZE | PARENB); + ctrl.c_cflag |= CS8; + + if (tcsetattr(STDIN_FILENO, TCSANOW, &ctrl) == -1) + eprintf("tcsetattr failed"); +} + +void restore_stdin(void) { + if (tcsetattr(STDIN_FILENO, TCSANOW, &old_term_settings) == -1) + eprintf("tcsetattr failed"); +} + +static void feh_set_kb(char *name, unsigned int s0, unsigned int y0, + unsigned int s1, unsigned int y1, unsigned int s2, unsigned int y2) { + static int key_index = 0; + fehkey *key = &keys[key_index]; + key->keystates[0] = s0; + key->keystates[1] = s1; + key->keystates[2] = s2; + key->keysyms[0] = y0; + key->keysyms[1] = y1; + key->keysyms[2] = y2; + key->state = 0; + key->button = 0; + key->name = name; + key_index++; +} + +static inline int ignore_space(int keysym) { + /* + * Passing values which do not fit inside a signed 8bit char to isprint, + * isspace and the likes is undefined behaviour... which glibc (for some + * values) implements as a segmentation fault. So let's not do that. + */ + return ((keysym <= 127) && (keysym >= -128) && isprint(keysym) && !isspace(keysym)); +} + +static void feh_set_parse_kb_partial(fehkey *key, int index, char *ks) { + char *cur = ks; + int mod = 0; + + if (!*ks) { + key->keysyms[index] = 0; + return; + } + + while (cur[1] == '-') { + switch (cur[0]) { + case 'C': + mod |= ControlMask; + break; + case 'S': + mod |= ShiftMask; + break; + case '1': + mod |= Mod1Mask; + break; + case '4': + mod |= Mod4Mask; + break; + default: + weprintf("keys: invalid modifier %c in \"%s\"", cur[0], ks); + break; + } + cur += 2; + } + + key->keysyms[index] = XStringToKeysym(cur); + if (ignore_space(key->keysyms[index])) + mod &= ~ShiftMask; + key->keystates[index] = mod; + + if (key->keysyms[index] == NoSymbol) + weprintf("keys: Invalid keysym: %s", cur); +} + +void init_keyevents(void) { + char *home = NULL; + char *confhome = NULL; + char *confpath = NULL; + char line[128]; + char action[32], k1[32], k2[32], k3[32]; + fehkey *cur_kb = NULL; + FILE *conf = NULL; + int read = 0; + + /* + * The feh_set_kb statements must have the same order as the key_action + * enum. + */ + + feh_set_kb("menu_close" , 0, XK_Escape , 0, 0 , 0, 0); + feh_set_kb("menu_parent", 0, XK_Left , 0, 0 , 0, 0); + feh_set_kb("menu_down", 0, XK_Down , 0, 0 , 0, 0); + feh_set_kb("menu_up", 0, XK_Up , 0, 0 , 0, 0); + feh_set_kb("menu_child", 0, XK_Right , 0, 0 , 0, 0); + feh_set_kb("menu_select", 0, XK_Return , 0, XK_space , 0, 0); + feh_set_kb("scroll_left",0, XK_KP_Left , 4, XK_Left , 0, 0); + feh_set_kb("scroll_right", 0,XK_KP_Right , 4, XK_Right , 0, 0); + feh_set_kb("scroll_down",0, XK_KP_Down , 4, XK_Down , 0, 0); + feh_set_kb("scroll_up", 0, XK_KP_Up , 4, XK_Up , 0, 0); + feh_set_kb("scroll_left_page" , 8, XK_Left , 0, 0 , 0, 0); + feh_set_kb("scroll_right_page", 8, XK_Right, 0, 0 , 0, 0); + feh_set_kb("scroll_down_page" , 8, XK_Down , 0, 0 , 0, 0); + feh_set_kb("scroll_up_page" , 8, XK_Up , 0, 0 , 0, 0); + feh_set_kb("prev_img" , 0, XK_Left , 0, XK_p , 0, XK_BackSpace); + feh_set_kb("next_img" , 0, XK_Right , 0, XK_n , 0, XK_space); + feh_set_kb("jump_back" , 0, XK_Page_Up , 0, XK_KP_Page_Up, 0, 0); + feh_set_kb("jump_fwd" , 0, XK_Page_Down , 0, XK_KP_Page_Down,0,0); + feh_set_kb("prev_dir" , 0, XK_bracketleft, 0, 0 , 0, 0); + feh_set_kb("next_dir" , 0, XK_bracketright, 0, 0 , 0, 0); + feh_set_kb("jump_random" ,0, XK_z , 0, 0 , 0, 0); + feh_set_kb("quit" , 0, XK_Escape , 0, XK_q , 0, 0); + feh_set_kb("close" , 0, XK_x , 0, 0 , 0, 0); + feh_set_kb("remove" , 0, XK_Delete , 0, 0 , 0, 0); + feh_set_kb("delete" , 4, XK_Delete , 0, 0 , 0, 0); + feh_set_kb("jump_first" , 0, XK_Home , 0, XK_KP_Home , 0, 0); + feh_set_kb("jump_last" , 0, XK_End , 0, XK_KP_End , 0, 0); + feh_set_kb("action_0" , 0, XK_Return , 0, XK_0 , 0, XK_KP_0); + feh_set_kb("action_1" , 0, XK_1 , 0, XK_KP_1 , 0, 0); + feh_set_kb("action_2" , 0, XK_2 , 0, XK_KP_2 , 0, 0); + feh_set_kb("action_3" , 0, XK_3 , 0, XK_KP_3 , 0, 0); + feh_set_kb("action_4" , 0, XK_4 , 0, XK_KP_4 , 0, 0); + feh_set_kb("action_5" , 0, XK_5 , 0, XK_KP_5 , 0, 0); + feh_set_kb("action_6" , 0, XK_6 , 0, XK_KP_6 , 0, 0); + feh_set_kb("action_7" , 0, XK_7 , 0, XK_KP_7 , 0, 0); + feh_set_kb("action_8" , 0, XK_8 , 0, XK_KP_8 , 0, 0); + feh_set_kb("action_9" , 0, XK_9 , 0, XK_KP_9 , 0, 0); + feh_set_kb("zoom_in" , 0, XK_Up , 0, XK_KP_Add , 0, 0); + feh_set_kb("zoom_out" , 0, XK_Down , 0, XK_KP_Subtract,0, 0); + feh_set_kb("zoom_default" , 0, XK_KP_Multiply, 0, XK_asterisk,0, 0); + feh_set_kb("zoom_fit" , 0, XK_KP_Divide , 0, XK_slash , 0, 0); + feh_set_kb("zoom_fill" , 0, XK_exclam , 0, 0 , 0, 0); + feh_set_kb("size_to_image" , 0, XK_w , 0, 0 , 0, 0); + feh_set_kb("render" , 0, XK_KP_Begin , 0, XK_R , 0, 0); + feh_set_kb("toggle_actions" , 0, XK_a, 0, 0, 0, 0); + feh_set_kb("toggle_aliasing" , 0, XK_A, 0, 0, 0, 0); + feh_set_kb("toggle_auto_zoom" , 0, XK_Z, 0, 0, 0, 0); +#ifdef HAVE_LIBEXIF + feh_set_kb("toggle_exif" , 0, XK_e, 0, 0, 0, 0); +#endif + feh_set_kb("toggle_filenames" , 0, XK_d, 0, 0, 0, 0); + feh_set_kb("toggle_info" , 0, XK_i, 0, 0, 0, 0); + feh_set_kb("toggle_pointer" , 0, XK_o, 0, 0, 0, 0); + feh_set_kb("toggle_caption" , 0, XK_c, 0, 0, 0, 0); + feh_set_kb("toggle_pause" , 0, XK_h, 0, 0, 0, 0); + feh_set_kb("toggle_menu" , 0, XK_m, 0, 0, 0, 0); + feh_set_kb("toggle_fullscreen" , 0, XK_f, 0, 0, 0, 0); + feh_set_kb("reload_image" , 0, XK_r, 0, 0, 0, 0); + feh_set_kb("save_image" , 0, XK_s, 0, 0, 0, 0); + feh_set_kb("save_filelist" , 0, XK_L, 0, 0, 0, 0); + feh_set_kb("orient_1" , 0, XK_greater, 0, 0, 0, 0); + feh_set_kb("orient_3" , 0, XK_less, 0, 0, 0, 0); + feh_set_kb("flip" , 0, XK_underscore, 0, 0, 0, 0); + feh_set_kb("mirror" , 0, XK_bar, 0, 0, 0, 0); + feh_set_kb("reload_minus" , 0, XK_minus, 0, 0, 0, 0); + feh_set_kb("reload_plus" , 0, XK_plus, 0, 0, 0, 0); + feh_set_kb("toggle_keep_vp" , 0, XK_k, 0, 0, 0, 0); + feh_set_kb("toggle_fixed_geometry" , 0, XK_g, 0, 0, 0, 0); + feh_set_kb("pan" , 0, 0, 0, 0, 0, 0); + feh_set_kb("zoom" , 0, 0, 0, 0, 0, 0); + feh_set_kb("blur" , 0, 0, 0, 0, 0, 0); + feh_set_kb("rotate" , 0, 0, 0, 0, 0, 0); + + home = getenv("HOME"); + confhome = getenv("XDG_CONFIG_HOME"); + + if (confhome) + confpath = estrjoin("/", confhome, "feh/keys", NULL); + else if (home) + confpath = estrjoin("/", home, ".config/feh/keys", NULL); + else + return; + + conf = fopen(confpath, "r"); + + free(confpath); + + if (!conf && ((conf = fopen("/etc/feh/keys", "r")) == NULL)) + return; + + while (fgets(line, sizeof(line), conf)) { + *action = '\0'; + *k1 = '\0'; + *k2 = '\0'; + *k3 = '\0'; + cur_kb = NULL; + + read = sscanf(line, "%31s %31s %31s %31s\n", + (char *) &action, (char *) &k1, (char* ) &k2, (char *) &k3); + + if ((read == EOF) || (read == 0) || (line[0] == '#')) + continue; + + cur_kb = feh_str_to_kb(action); + + if (cur_kb) { + feh_set_parse_kb_partial(cur_kb, 0, k1); + feh_set_parse_kb_partial(cur_kb, 1, k2); + feh_set_parse_kb_partial(cur_kb, 2, k3); + } else { + weprintf("keys: Invalid action: %s", action); + } + } + fclose(conf); +} + +static short feh_is_kp(unsigned int key_index, unsigned int state, + unsigned int sym, unsigned int button) { + int i; + + if (sym != NoSymbol) { + for (i = 0; i < 3; i++) { + if ( + (keys[key_index].keysyms[i] == sym) && + (keys[key_index].keystates[i] == state)) + return 1; + else if (keys[key_index].keysyms[i] == 0) + return 0; + } + return 0; + } + if ((keys[key_index].state == state) + && (keys[key_index].button == button)) { + return 1; + } + return 0; +} void feh_event_invoke_action(winwidget winwid, unsigned char action) { + struct stat st; if (opt.actions[action]) { if (opt.slideshow) { - feh_action_run(FEH_FILE(winwid->file->data), opt.actions[action]); - winwidget_update_caption(winwid); + feh_action_run(FEH_FILE(winwid->file->data), opt.actions[action], winwid); - if (! opt.hold_actions[action]) - slideshow_change_image(winwid, SLIDE_NEXT); + if (opt.hold_actions[action]) + feh_reload_image(winwid, 1, 1); + else if (stat(FEH_FILE(winwid->file->data)->filename, &st) == -1) + feh_filelist_image_remove(winwid, 0); + else + slideshow_change_image(winwid, SLIDE_NEXT, 1); } else if ((winwid->type == WIN_TYPE_SINGLE) || (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER)) { - feh_action_run(FEH_FILE(winwid->file->data), opt.actions[action]); - winwidget_destroy(winwid); + feh_action_run(FEH_FILE(winwid->file->data), opt.actions[action], winwid); + + if (opt.hold_actions[action]) + feh_reload_image(winwid, 1, 1); + else + winwidget_destroy(winwid); } else if (winwid->type == WIN_TYPE_THUMBNAIL) { - printf("actions from the main thumb window aren't currentl supported!\n"); - printf("For now, open the image to perform the action on it.\n"); + feh_file *thumbfile; + thumbfile = feh_thumbnail_get_selected_file(); + + if (thumbfile) { + feh_action_run(thumbfile, opt.actions[action], winwid); + + if (!opt.hold_actions[action]) + feh_thumbnail_mark_removed(thumbfile, 0); + } } } return; } +void feh_event_handle_stdin(void) +{ + char stdin_buf[2]; + static char is_esc = 0; + KeySym keysym = NoSymbol; + if (read(STDIN_FILENO, &stdin_buf, 1) <= 0) { + control_via_stdin = 0; + if (isatty(STDIN_FILENO) && getpgrp() == (tcgetpgrp(STDIN_FILENO))) { + weprintf("reading a command from stdin failed - disabling control via stdin"); + restore_stdin(); + } + return; + } + stdin_buf[1] = '\0'; + + // escape? + if (stdin_buf[0] == 0x1b) { + is_esc = 1; + return; + } + if ((is_esc == 1) && (stdin_buf[0] == '[')) { + is_esc = 2; + return; + } + + if (stdin_buf[0] == ' ') + keysym = XK_space; + else if (stdin_buf[0] == '\n') + keysym = XK_Return; + else if ((stdin_buf[0] == '\b') || (stdin_buf[0] == 127)) + keysym = XK_BackSpace; + else if (is_esc == 2) { + if (stdin_buf[0] == 'A') + keysym = XK_Up; + else if (stdin_buf[0] == 'B') + keysym = XK_Down; + else if (stdin_buf[0] == 'C') + keysym = XK_Right; + else if (stdin_buf[0] == 'D') + keysym = XK_Left; + is_esc = 0; + } + else + keysym = XStringToKeysym(stdin_buf); + + if (window_num && keysym) + feh_event_handle_generic(windows[0], is_esc * Mod1Mask, keysym, 0); + + is_esc = 0; +} + void feh_event_handle_keypress(XEvent * ev) { - int len; + int state; char kbuf[20]; KeySym keysym; XKeyEvent *kev; winwidget winwid = NULL; - int curr_screen = 0; feh_menu_item *selected_item; feh_menu *selected_menu; @@ -70,45 +383,53 @@ void feh_event_handle_keypress(XEvent * ev) } kev = (XKeyEvent *) ev; - len = XLookupString(&ev->xkey, (char *) kbuf, sizeof(kbuf), &keysym, NULL); + XLookupString(&ev->xkey, (char *) kbuf, sizeof(kbuf), &keysym, NULL); + state = kev->state & (ControlMask | ShiftMask | Mod1Mask | Mod4Mask); + + if (ignore_space(keysym)) + state &= ~ShiftMask; /* menus are showing, so this is a menu control keypress */ if (ev->xbutton.window == menu_cover) { selected_item = feh_menu_find_selected_r(menu_root, &selected_menu); - switch (keysym) { - case XK_Escape: + if (feh_is_kp(EVENT_menu_close, state, keysym, 0)) feh_menu_hide(menu_root, True); - break; - case XK_Left: + else if (feh_is_kp(EVENT_menu_parent, state, keysym, 0)) feh_menu_select_parent(selected_menu); - break; - case XK_Down: + else if (feh_is_kp(EVENT_menu_down, state, keysym, 0)) feh_menu_select_next(selected_menu, selected_item); - break; - case XK_Up: + else if (feh_is_kp(EVENT_menu_up, state, keysym, 0)) feh_menu_select_prev(selected_menu, selected_item); - break; - case XK_Right: + else if (feh_is_kp(EVENT_menu_child, state, keysym, 0)) feh_menu_select_submenu(selected_menu); - break; - case XK_Return: - case XK_space: + else if (feh_is_kp(EVENT_menu_select, state, keysym, 0)) feh_menu_item_activate(selected_menu, selected_item); - break; - default: - break; - } - return; } if (winwid == NULL) return; - if (winwid->caption_entry) { + feh_event_handle_generic(winwid, state, keysym, 0); +} + +fehkey *feh_str_to_kb(char *action) +{ + for (unsigned int i = 0; i < EVENT_LIST_END; i++) { + if (!strcmp(action, keys[i].name)) { + return &keys[i]; + } + } + return NULL; +} + +void feh_event_handle_generic(winwidget winwid, unsigned int state, KeySym keysym, unsigned int button) { + int curr_screen = 0; + + if (winwid->caption_entry && (keysym != NoSymbol)) { switch (keysym) { case XK_Return: - if (kev->state & ControlMask) { + if (state & ControlMask) { /* insert actual newline */ ESTRAPPEND(FEH_FILE(winwid->file->data)->caption, "\n"); winwidget_render_image_cached(winwid); @@ -116,14 +437,15 @@ void feh_event_handle_keypress(XEvent * ev) /* finish caption entry, write to captions file */ FILE *fp; char *caption_filename; - caption_filename = build_caption_filename(FEH_FILE(winwid->file->data)); + caption_filename = + build_caption_filename(FEH_FILE(winwid->file->data), 1); winwid->caption_entry = 0; winwidget_render_image_cached(winwid); XFreePixmap(disp, winwid->bg_pmap_cache); winwid->bg_pmap_cache = 0; fp = fopen(caption_filename, "w"); if (!fp) { - weprintf("couldn't write to captions file %s:", caption_filename); + eprintf("couldn't write to captions file %s:", caption_filename); return; } fprintf(fp, "%s", FEH_FILE(winwid->file->data)->caption); @@ -156,258 +478,280 @@ void feh_event_handle_keypress(XEvent * ev) return; } - switch (keysym) { - case XK_Left: - if (kev->state & ControlMask) { - winwid->im_x += 10; - winwidget_render_image(winwid, 0, 0); - } - else if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_PREV); - break; - case XK_Right: - if (kev->state & ControlMask) { - winwid->im_x -= 10; - winwidget_render_image(winwid, 0, 0); - } - else if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_NEXT); - break; - case XK_Page_Up: - case XK_KP_Page_Up: + if (feh_is_kp(EVENT_next_img, state, keysym, button)) { if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_JUMP_BACK); - break; - case XK_Escape: - winwidget_destroy_all(); - break; - case XK_Page_Down: - case XK_KP_Page_Down: + slideshow_change_image(winwid, SLIDE_NEXT, 1); + else if (winwid->type == WIN_TYPE_THUMBNAIL) + feh_thumbnail_select_next(winwid, 1); + } + else if (feh_is_kp(EVENT_prev_img, state, keysym, button)) { if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_JUMP_FWD); - break; - case XK_Delete: - /* Holding ctrl gets you a filesystem deletion and removal from the * - filelist. Just DEL gets you filelist removal only. */ - if (kev->state & ControlMask) { - if (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER) - feh_thumbnail_mark_removed(FEH_FILE(winwid->file->data), 1); - feh_filelist_image_remove(winwid, 1); - } else { - if (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER) - feh_thumbnail_mark_removed(FEH_FILE(winwid->file->data), 0); - feh_filelist_image_remove(winwid, 0); - } - break; - case XK_Home: - case XK_KP_Home: + slideshow_change_image(winwid, SLIDE_PREV, 1); + else if (winwid->type == WIN_TYPE_THUMBNAIL) + feh_thumbnail_select_prev(winwid, 1); + } + else if (feh_is_kp(EVENT_scroll_right, state, keysym, button)) { + winwid->im_x -= opt.scroll_step;; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 1); + } + else if (feh_is_kp(EVENT_scroll_left, state, keysym, button)) { + winwid->im_x += opt.scroll_step; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 1); + } + else if (feh_is_kp(EVENT_scroll_down, state, keysym, button)) { + winwid->im_y -= opt.scroll_step; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 1); + } + else if (feh_is_kp(EVENT_scroll_up, state, keysym, button)) { + winwid->im_y += opt.scroll_step; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 1); + } + else if (feh_is_kp(EVENT_scroll_right_page, state, keysym, button)) { + winwid->im_x -= winwid->w; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 0); + } + else if (feh_is_kp(EVENT_scroll_left_page, state, keysym, button)) { + winwid->im_x += winwid->w; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 0); + } + else if (feh_is_kp(EVENT_scroll_down_page, state, keysym, button)) { + winwid->im_y -= winwid->h; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 0); + } + else if (feh_is_kp(EVENT_scroll_up_page, state, keysym, button)) { + winwid->im_y += winwid->h; + winwidget_sanitise_offsets(winwid); + winwidget_render_image(winwid, 0, 0); + } + else if (feh_is_kp(EVENT_jump_back, state, keysym, button)) { + if (opt.slideshow) + slideshow_change_image(winwid, SLIDE_JUMP_BACK, 1); + else if (winwid->type == WIN_TYPE_THUMBNAIL) + feh_thumbnail_select_prev(winwid, 10); + } + else if (feh_is_kp(EVENT_jump_fwd, state, keysym, button)) { if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_FIRST); - break; - case XK_End: - case XK_KP_End: + slideshow_change_image(winwid, SLIDE_JUMP_FWD, 1); + else if (winwid->type == WIN_TYPE_THUMBNAIL) + feh_thumbnail_select_next(winwid, 10); + } + else if (feh_is_kp(EVENT_next_dir, state, keysym, button)) { if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_LAST); - break; - case XK_Return: - case XK_KP_Enter: - case XK_0: - case XK_KP_0: + slideshow_change_image(winwid, SLIDE_JUMP_NEXT_DIR, 1); + } + else if (feh_is_kp(EVENT_prev_dir, state, keysym, button)) { + if (opt.slideshow) + slideshow_change_image(winwid, SLIDE_JUMP_PREV_DIR, 1); + } + else if (feh_is_kp(EVENT_quit, state, keysym, button)) { + winwidget_destroy_all(); + } + else if (feh_is_kp(EVENT_delete, state, keysym, button)) { + if (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER) + feh_thumbnail_mark_removed(FEH_FILE(winwid->file->data), 1); + feh_filelist_image_remove(winwid, 1); + } + else if (feh_is_kp(EVENT_remove, state, keysym, button)) { + if (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER) + feh_thumbnail_mark_removed(FEH_FILE(winwid->file->data), 0); + feh_filelist_image_remove(winwid, 0); + } + else if (feh_is_kp(EVENT_jump_first, state, keysym, button)) { + if (opt.slideshow) + slideshow_change_image(winwid, SLIDE_FIRST, 1); + } + else if (feh_is_kp(EVENT_jump_last, state, keysym, button)) { + if (opt.slideshow) + slideshow_change_image(winwid, SLIDE_LAST, 1); + } + else if (feh_is_kp(EVENT_action_0, state, keysym, button)) { feh_event_invoke_action(winwid, 0); - break; - case XK_1: - case XK_KP_1: + } + else if (feh_is_kp(EVENT_action_1, state, keysym, button)) { feh_event_invoke_action(winwid, 1); - break; - case XK_2: - case XK_KP_2: + } + else if (feh_is_kp(EVENT_action_2, state, keysym, button)) { feh_event_invoke_action(winwid, 2); - break; - case XK_3: - case XK_KP_3: + } + else if (feh_is_kp(EVENT_action_3, state, keysym, button)) { feh_event_invoke_action(winwid, 3); - break; - case XK_4: - case XK_KP_4: + } + else if (feh_is_kp(EVENT_action_4, state, keysym, button)) { feh_event_invoke_action(winwid, 4); - break; - case XK_5: - case XK_KP_5: + } + else if (feh_is_kp(EVENT_action_5, state, keysym, button)) { feh_event_invoke_action(winwid, 5); - break; - case XK_6: - case XK_KP_6: + } + else if (feh_is_kp(EVENT_action_6, state, keysym, button)) { feh_event_invoke_action(winwid, 6); - break; - case XK_7: - case XK_KP_7: + } + else if (feh_is_kp(EVENT_action_7, state, keysym, button)) { feh_event_invoke_action(winwid, 7); - break; - case XK_8: - case XK_KP_8: + } + else if (feh_is_kp(EVENT_action_8, state, keysym, button)) { feh_event_invoke_action(winwid, 8); - break; - case XK_9: - case XK_KP_9: + } + else if (feh_is_kp(EVENT_action_9, state, keysym, button)) { feh_event_invoke_action(winwid, 9); - break; - case XK_KP_Left: - winwid->im_x += 10; - winwidget_render_image(winwid, 0, 0); - break; - case XK_KP_Right: - winwid->im_x -= 10; + } + else if (feh_is_kp(EVENT_zoom_in, state, keysym, button)) { + winwid->old_zoom = winwid->zoom; + winwid->zoom = winwid->zoom * opt.zoom_rate; + + if (winwid->zoom > ZOOM_MAX) + winwid->zoom = ZOOM_MAX; + + winwid->im_x = (winwid->w / 2) - (((winwid->w / 2) - winwid->im_x) / + winwid->old_zoom * winwid->zoom); + winwid->im_y = (winwid->h / 2) - (((winwid->h / 2) - winwid->im_y) / + winwid->old_zoom * winwid->zoom); + winwidget_sanitise_offsets(winwid); winwidget_render_image(winwid, 0, 0); - break; - case XK_KP_Up: - winwid->im_y += 10; + } + else if (feh_is_kp(EVENT_zoom_out, state, keysym, button)) { + winwid->old_zoom = winwid->zoom; + winwid->zoom = winwid->zoom / opt.zoom_rate; + + if (winwid->zoom < ZOOM_MIN) + winwid->zoom = ZOOM_MIN; + + winwid->im_x = (winwid->w / 2) - (((winwid->w / 2) - winwid->im_x) / + winwid->old_zoom * winwid->zoom); + winwid->im_y = (winwid->h / 2) - (((winwid->h / 2) - winwid->im_y) / + winwid->old_zoom * winwid->zoom); + winwidget_sanitise_offsets(winwid); winwidget_render_image(winwid, 0, 0); - break; - case XK_KP_Down: - winwid->im_y -= 10; + } + else if (feh_is_kp(EVENT_zoom_default, state, keysym, button)) { + winwid->zoom = 1.0; + winwidget_center_image(winwid); winwidget_render_image(winwid, 0, 0); - break; - case XK_KP_Add: - case XK_Up: - if (kev->state & ControlMask) { - winwid->im_y += 10; - winwidget_render_image(winwid, 0, 0); - } - else { - /* erroneously recognized as '+' in the *kbuf switch. Work around this. */ - len = 0; - winwid->old_zoom = winwid->zoom; - winwid->zoom = winwid->zoom * 1.25; - winwid->im_x = (winwid->w / 2) - (((winwid->w / 2) - winwid->im_x) / - winwid->old_zoom * winwid->zoom); - winwid->im_y = (winwid->h / 2) - (((winwid->h / 2) - winwid->im_y) / - winwid->old_zoom * winwid->zoom); - winwidget_sanitise_offsets(winwid); - winwidget_render_image(winwid, 0, 1); - } - break; - case XK_KP_Subtract: - case XK_Down: - if (kev->state & ControlMask) { - winwid->im_y -= 10; - winwidget_render_image(winwid, 0, 0); - } - else { - len = 0; - winwid->old_zoom = winwid->zoom; - winwid->zoom = winwid->zoom * 0.75; - winwid->im_x = (winwid->w / 2) - (((winwid->w / 2) - winwid->im_x) / - winwid->old_zoom * winwid->zoom); - winwid->im_y = (winwid->h / 2) - (((winwid->h / 2) - winwid->im_y) / - winwid->old_zoom * winwid->zoom); - winwidget_sanitise_offsets(winwid); - winwidget_render_image(winwid, 0, 1); - } - break; - case XK_KP_Multiply: - len = 0; - winwid->zoom = 1; + } + else if (feh_is_kp(EVENT_zoom_fit, state, keysym, button)) { + feh_calc_needed_zoom(&winwid->zoom, winwid->im_w, winwid->im_h, winwid->w, winwid->h); winwidget_center_image(winwid); winwidget_render_image(winwid, 0, 0); - break; - case XK_KP_Divide: - len = 0; + } + else if (feh_is_kp(EVENT_zoom_fill, state, keysym, button)) { + int save_zoom = opt.zoom_mode; + opt.zoom_mode = ZOOM_MODE_FILL; feh_calc_needed_zoom(&winwid->zoom, winwid->im_w, winwid->im_h, winwid->w, winwid->h); winwidget_center_image(winwid); - winwidget_render_image(winwid, 0, 1); - break; - case XK_KP_Begin: - winwidget_render_image(winwid, 0, 1); - break; - default: - break; + winwidget_render_image(winwid, 0, 0); + opt.zoom_mode = save_zoom; } - - if (len <= 0 || len > (int) sizeof(kbuf)) - return; - kbuf[len] = '\0'; - - switch (*kbuf) { - case 'a': + else if (feh_is_kp(EVENT_render, state, keysym, button)) { + if (winwid->type == WIN_TYPE_THUMBNAIL) + feh_thumbnail_show_selected(); + else + winwidget_render_image(winwid, 0, 0); + } + else if (feh_is_kp(EVENT_toggle_actions, state, keysym, button)) { opt.draw_actions = !opt.draw_actions; - winwidget_rerender_all(0, 1); - break; - case 'd': + winwidget_rerender_all(0); + } + else if (feh_is_kp(EVENT_toggle_aliasing, state, keysym, button)) { + opt.force_aliasing = !opt.force_aliasing; + winwid->force_aliasing = !winwid->force_aliasing; + winwidget_render_image(winwid, 0, 0); + } + else if (feh_is_kp(EVENT_toggle_auto_zoom, state, keysym, button)) { + opt.zoom_mode = (opt.zoom_mode == 0 ? ZOOM_MODE_MAX : 0); + winwidget_rerender_all(1); + } + else if (feh_is_kp(EVENT_toggle_filenames, state, keysym, button)) { opt.draw_filename = !opt.draw_filename; - winwidget_rerender_all(0, 1); - break; - case 'n': - case ' ': - if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_NEXT); - break; - case 'o': + winwidget_rerender_all(0); + } +#ifdef HAVE_LIBEXIF + else if (feh_is_kp(EVENT_toggle_exif, state, keysym, button)) { + opt.draw_exif = !opt.draw_exif; + winwidget_rerender_all(0); + } +#endif + else if (feh_is_kp(EVENT_toggle_info, state, keysym, button)) { + opt.draw_info = !opt.draw_info; + winwidget_rerender_all(0); + } + else if (feh_is_kp(EVENT_toggle_pointer, state, keysym, button)) { winwidget_set_pointer(winwid, opt.hide_pointer); opt.hide_pointer = !opt.hide_pointer; - break; - case 'p': - case '\b': - if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_PREV); - break; - case 'z': - if (opt.slideshow) - slideshow_change_image(winwid, SLIDE_RAND); - break; - case 'q': - winwidget_destroy_all(); - break; - case 'c': - if (opt.caption_path) + } + else if (feh_is_kp(EVENT_jump_random, state, keysym, button)) { + if (winwid->type == WIN_TYPE_THUMBNAIL) + feh_thumbnail_select_next(winwid, random() % (filelist_len - 1)); + else + slideshow_change_image(winwid, SLIDE_RAND, 1); + } + else if (feh_is_kp(EVENT_toggle_caption, state, keysym, button)) { + if (opt.caption_path && path_is_url(FEH_FILE(winwid->file->data)->filename)) { + im_weprintf(winwid, "Caption entry is not supported on URLs"); + } + else if (opt.caption_path) { + /* + * editing captions in slideshow mode does not make any sense + * at all; this is just in case someone accidentally does it... + */ + if (opt.slideshow_delay) + opt.paused = 1; winwid->caption_entry = 1; - winwidget_render_image(winwid, 0, 1); - break; - case 'r': + } + winwidget_render_image(winwid, 0, 0); + } + else if (feh_is_kp(EVENT_reload_image, state, keysym, button)) { feh_reload_image(winwid, 0, 0); - break; - case 'h': + } + else if (feh_is_kp(EVENT_toggle_pause, state, keysym, button)) { slideshow_pause_toggle(winwid); - break; - case 's': + /* We need to re-render the image to update the info string immediately. */ + winwidget_render_image(winwid, 0, 0); + } + else if (feh_is_kp(EVENT_save_image, state, keysym, button)) { slideshow_save_image(winwid); - break; - case 'f': + } + else if (feh_is_kp(EVENT_save_filelist, state, keysym, button)) { if ((winwid->type == WIN_TYPE_THUMBNAIL) || (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER)) weprintf("Filelist saving is not supported in thumbnail mode"); else feh_save_filelist(); - break; - case 'w': + } + else if (feh_is_kp(EVENT_size_to_image, state, keysym, button)) { winwidget_size_to_image(winwid); - break; - case 'm': + } + else if (!opt.no_menus && feh_is_kp(EVENT_toggle_menu, state, keysym, button)) { winwidget_show_menu(winwid); - break; - case 'x': + } + else if (feh_is_kp(EVENT_close, state, keysym, button)) { winwidget_destroy(winwid); - break; - case '>': - feh_edit_inplace_orient(winwid, 1); - break; - case '<': - feh_edit_inplace_orient(winwid, 3); - break; - case 'v': + } + else if (feh_is_kp(EVENT_orient_1, state, keysym, button)) { + feh_edit_inplace(winwid, 1); + } + else if (feh_is_kp(EVENT_orient_3, state, keysym, button)) { + feh_edit_inplace(winwid, 3); + } + else if (feh_is_kp(EVENT_flip, state, keysym, button)) { + feh_edit_inplace(winwid, INPLACE_EDIT_FLIP); + } + else if (feh_is_kp(EVENT_mirror, state, keysym, button)) { + feh_edit_inplace(winwid, INPLACE_EDIT_MIRROR); + } + else if (feh_is_kp(EVENT_toggle_fullscreen, state, keysym, button)) { #ifdef HAVE_LIBXINERAMA if (opt.xinerama && xinerama_screens) { int i, rect[4]; winwidget_get_geometry(winwid, rect); - /* printf("window: (%d, %d)\n", rect[0], rect[1]); - printf("found %d screens.\n", num_xinerama_screens); */ for (i = 0; i < num_xinerama_screens; i++) { xinerama_screen = 0; - /* printf("%d: [%d, %d, %d, %d] (%d, %d)\n", - i, - xinerama_screens[i].x_org, xinerama_screens[i].y_org, - xinerama_screens[i].width, xinerama_screens[i].height, - rect[0], rect[1]); */ if (XY_IN_RECT(rect[0], rect[1], xinerama_screens[i].x_org, xinerama_screens[i].y_org, @@ -417,15 +761,14 @@ void feh_event_handle_keypress(XEvent * ev) break; } } - if (getenv("XINERAMA_SCREEN")) - curr_screen = xinerama_screen = - atoi(getenv("XINERAMA_SCREEN")); + if (opt.xinerama_index >= 0) + curr_screen = xinerama_screen = opt.xinerama_index; } #endif /* HAVE_LIBXINERAMA */ winwid->full_screen = !winwid->full_screen; winwidget_destroy_xwin(winwid); winwidget_create_window(winwid, winwid->im_w, winwid->im_h); - winwidget_render_image(winwid, 1, 1); + winwidget_render_image(winwid, 1, 0); winwidget_show(winwid); #ifdef HAVE_LIBXINERAMA /* if we have xinerama and we're using it, then full screen the window @@ -436,20 +779,31 @@ void feh_event_handle_keypress(XEvent * ev) xinerama_screens[curr_screen].x_org, xinerama_screens[curr_screen].y_org); } #endif /* HAVE_LIBXINERAMA */ - case '+': + } + else if (feh_is_kp(EVENT_reload_plus, state, keysym, button)){ if (opt.reload < SLIDESHOW_RELOAD_MAX) opt.reload++; else if (opt.verbose) - weprintf("Cannot set RELOAD higher than %d seconds.", opt.reload); - break; - case '-': + weprintf("Cannot set RELOAD higher than %f seconds.", opt.reload); + } + else if (feh_is_kp(EVENT_reload_minus, state, keysym, button)) { if (opt.reload > 1) opt.reload--; else if (opt.verbose) weprintf("Cannot set RELOAD lower than 1 second."); - break; - default: - break; + } + else if (feh_is_kp(EVENT_toggle_keep_vp, state, keysym, button)) { + opt.keep_zoom_vp = !opt.keep_zoom_vp; + } + else if (feh_is_kp(EVENT_toggle_fixed_geometry, state, keysym, button)) { + if (opt.geom_flags & ((WidthValue | HeightValue))) { + opt.geom_flags &= ~(WidthValue | HeightValue); + } else { + opt.geom_flags |= (WidthValue | HeightValue); + opt.geom_w = winwid->w; + opt.geom_h = winwid->h; + } + winwidget_render_image(winwid, 1, 0); } return; } @@ -1,6 +1,7 @@ /* list.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 @@ -36,20 +37,24 @@ void init_list_mode(void) mode = "list"; if (!opt.customlist) - printf("NUM\tFORMAT\tWIDTH\tHEIGHT\tPIXELS\tSIZE(bytes)\tALPHA\tFILENAME\n"); + fputs("NUM\tFORMAT\tWIDTH\tHEIGHT\tPIXELS\tSIZE\tALPHA\tFILENAME\n", + stdout); for (l = filelist; l; l = l->next) { file = FEH_FILE(l->data); if (opt.customlist) - printf("%s\n", feh_printf(opt.customlist, file)); - else - printf("%d\t%s\t%d\t%d\t%d\t%d\t\t%c\t%s\n", ++j, + printf("%s\n", feh_printf(opt.customlist, file, NULL)); + else { + printf("%d\t%s\t%d\t%d\t%s", ++j, file->info->format, file->info->width, - file->info->height, file->info->pixels, - file->info->size, + file->info->height, + format_size(file->info->pixels)); + printf("\t%s\t%c\t%s\n", + format_size(file->size), file->info->has_alpha ? 'X' : '-', file->filename); + } - feh_action_run(file, opt.actions[0]); + feh_action_run(file, opt.actions[0], NULL); } exit(0); } @@ -72,6 +77,7 @@ void real_loadables_mode(int loadable) { feh_file *file; gib_list *l; + char ret = 0; opt.quiet = 1; @@ -83,17 +89,35 @@ void real_loadables_mode(int loadable) if (feh_load_image(&im, file)) { /* loaded ok */ if (loadable) { - fprintf(stdout, "%s\n", file->filename); - feh_action_run(file, opt.actions[0]); + if (opt.verbose) + feh_display_status('.'); + puts(file->filename); + fflush(stdout); + feh_action_run(file, opt.actions[0], NULL); + } + else { + if (opt.verbose) + feh_display_status('s'); + ret = 1; } gib_imlib_free_image_and_decache(im); } else { /* Oh dear. */ if (!loadable) { - fprintf(stdout, "%s\n", file->filename); - feh_action_run(file, opt.actions[0]); + if (opt.verbose) + feh_display_status('.'); + puts(file->filename); + fflush(stdout); + feh_action_run(file, opt.actions[0], NULL); + } + else { + if (opt.verbose) + feh_display_status('s'); + ret = 1; } } } - exit(0); + if (opt.verbose) + feh_display_status(0); + exit(ret); } @@ -1,6 +1,7 @@ /* main.c Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2023 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 @@ -29,7 +30,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "timers.h" #include "options.h" #include "events.h" -#include "support.h" +#include "signals.h" +#include "wallpaper.h" +#include <termios.h> + +#ifdef HAVE_INOTIFY +#include <sys/inotify.h> +#endif char **cmdargv = NULL; int cmdargc = 0; @@ -39,19 +46,38 @@ int main(int argc, char **argv) { atexit(feh_clean_exit); + srandom(getpid() * time(NULL) % ((unsigned int) -1)); + + setup_signal_handlers(); + +#ifdef HAVE_LIBMAGIC + init_magic(); +#endif + init_parse_options(argc, argv); init_imlib_fonts(); - if (opt.display) + if (opt.display) { init_x_and_imlib(); + init_keyevents(); + init_buttonbindings(); +#ifdef HAVE_INOTIFY + if (opt.auto_reload) { + opt.inotify_fd = inotify_init(); + if (opt.inotify_fd < 0) { + opt.auto_reload = 0; + weprintf("inotify_init failed:"); + weprintf("Disabling inotify-based auto-reload"); + } + } +#endif + } feh_event_init(); if (opt.index) init_index_mode(); - else if (opt.collage) - init_collage_mode(); else if (opt.multiwindow) init_multiwindow_mode(); else if (opt.list || opt.customlist) @@ -63,19 +89,44 @@ int main(int argc, char **argv) else if (opt.thumbs) init_thumbnail_mode(); else if (opt.bgmode) { - feh_wm_set_bg_file(opt.output_file, opt.bgmode); + feh_wm_set_bg_filelist(opt.bgmode); exit(0); } - else { + else if (opt.display){ /* Slideshow mode is the default. Because it's spiffy */ opt.slideshow = 1; init_slideshow_mode(); } + else { + eprintf("Invalid option combination"); + } /* main event loop */ while (feh_main_iteration(1)); - return(0); + return(sig_exit); +} + +static void feh_process_signal(void) +{ + winwidget winwid = winwidget_get_first_window_of_type(WIN_TYPE_SLIDESHOW); + int i; + int signo = sig_received; + sig_received = 0; + + if (winwid) { + if (filelist_len > 1) { + if (signo == SIGUSR1) + slideshow_change_image(winwid, SLIDE_NEXT, 1); + else if (signo == SIGUSR2) + slideshow_change_image(winwid, SLIDE_PREV, 1); + } else { + feh_reload_image(winwid, 0, 0); + } + } else if (opt.multiwindow) { + for (i = window_num - 1; i >= 0; i--) + feh_reload_image(windows[i], 0, 0); + } } /* Return 0 to stop iterating, 1 if ok to continue. */ @@ -92,15 +143,32 @@ int feh_main_iteration(int block) double t1 = 0.0, t2 = 0.0; fehtimer ft; - if (window_num == 0) + if (window_num == 0 || sig_exit != 0) return(0); + if (sig_received) { + feh_process_signal(); + } + if (first) { /* Only need to set these up the first time */ xfd = ConnectionNumber(disp); fdsize = xfd + 1; pt = feh_get_time(); first = 0; + /* + * Only accept commands from stdin if + * - stdin is a terminal (otherwise it's probably used as an image / filelist) + * - we aren't running in multiwindow mode (cause it's not clear which + * window commands should be applied to in that case) + * - we're in the same process group as stdin, AKA we're not running + * in the background. Background processes are stopped with SIGTTOU + * if they try to write to stdout or change terminal attributes. They + * also don't get input from stdin anyway. + */ + if (isatty(STDIN_FILENO) && !opt.multiwindow && getpgrp() == (tcgetpgrp(STDIN_FILENO))) { + setup_stdin(); + } } /* Timers */ @@ -112,8 +180,12 @@ int feh_main_iteration(int block) if (ev_handler[ev.type]) (*(ev_handler[ev.type])) (&ev); - if (window_num == 0) + if (window_num == 0 || sig_exit != 0) return(0); + + if (sig_received) { + feh_process_signal(); + } } XFlush(disp); @@ -121,6 +193,16 @@ int feh_main_iteration(int block) FD_ZERO(&fdset); FD_SET(xfd, &fdset); + if (control_via_stdin) { + FD_SET(STDIN_FILENO, &fdset); + } +#ifdef HAVE_INOTIFY + if (opt.auto_reload) { + FD_SET(opt.inotify_fd, &fdset); + if (opt.inotify_fd >= fdsize) + fdsize = opt.inotify_fd + 1; + } +#endif /* Timers */ ft = first_timer; @@ -158,12 +240,23 @@ int feh_main_iteration(int block) && ((errno == ENOMEM) || (errno == EINVAL) || (errno == EBADF))) eprintf("Connection to X display lost"); - if ((ft) && (count == 0)) { + if (count == 0) { /* This means the timer is due to be executed. If count was > 0, that would mean an X event had woken us, we're not interested in that */ feh_handle_timer(); } + /* + * Beware: If stdin is not connected, we may end up with xfd == 0. + * However, STDIN_FILENO == 0 holds as well in most cases. So we must + * check control_via_stdin to avoid mistaking an X11 event for stdin. + */ + else if ((count > 0) && control_via_stdin && (FD_ISSET(STDIN_FILENO, &fdset))) + feh_event_handle_stdin(); +#ifdef HAVE_INOTIFY + else if ((count > 0) && (FD_ISSET(opt.inotify_fd, &fdset))) + feh_event_handle_inotify(); +#endif } } else { /* Don't block if there are events in the queue. That's a bit rude ;-) */ @@ -175,11 +268,21 @@ int feh_main_iteration(int block) && ((errno == ENOMEM) || (errno == EINVAL) || (errno == EBADF))) eprintf("Connection to X display lost"); + else if ((count > 0) && control_via_stdin && (FD_ISSET(STDIN_FILENO, &fdset))) + feh_event_handle_stdin(); +#ifdef HAVE_INOTIFY + else if ((count > 0) && (FD_ISSET(opt.inotify_fd, &fdset))) + feh_event_handle_inotify(); +#endif } } - if (window_num == 0) + if (window_num == 0 || sig_exit != 0) return(0); - + + if (sig_received) { + feh_process_signal(); + } + return(1); } @@ -187,6 +290,32 @@ void feh_clean_exit(void) { delete_rm_files(); + free(opt.menu_font); + +#ifdef HAVE_INOTIFY + if (opt.auto_reload) + if (close(opt.inotify_fd)) + eprintf("inotify close failed"); +#endif + + if(disp) + XCloseDisplay(disp); + +#ifdef HAVE_LIBMAGIC + uninit_magic(); +#endif + + /* + * Only restore the old terminal settings if + * - we changed them in the first place + * - stdin still is a terminal (it might have been closed) + * - stdin still belongs to us (we might have been detached from the + * controlling terminal, in that case we probably shouldn't be messing + * around with it) <https://github.com/derf/feh/issues/324> + */ + if (control_via_stdin && isatty(STDIN_FILENO) && getpgrp() == (tcgetpgrp(STDIN_FILENO))) + restore_stdin(); + if (opt.filelistfile) feh_write_filelist(filelist, opt.filelistfile); @@ -1,6 +1,7 @@ /* menu.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 @@ -24,8 +25,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "feh.h" -#include "support.h" #include "thumbnail.h" +#include "wallpaper.h" #include "winwidget.h" #include "filelist.h" #include "options.h" @@ -34,7 +35,6 @@ Window menu_cover = 0; feh_menu *menu_root = NULL; feh_menu *menu_main = NULL; feh_menu *menu_single_win = NULL; -feh_menu *menu_about_win = NULL; feh_menu *menu_thumbnail_viewer = NULL; feh_menu *menu_thumbnail_win = NULL; feh_menu *menu_bg = NULL; @@ -45,18 +45,45 @@ static feh_menu *feh_menu_func_gen_info(feh_menu * m); static void feh_menu_func_free_info(feh_menu * m); static void feh_menu_func_free_options(feh_menu * m); static feh_menu *feh_menu_func_gen_options(feh_menu * m); -void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, void *data); +void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, unsigned short data); void feh_menu_cb_opt_fullscreen(feh_menu * m, feh_menu_item * i); enum { - CB_ABOUT = 1, CB_CLOSE, CB_EXIT, CB_RELOAD, CB_REMOVE, CB_DELETE, CB_RESET, - CB_REMOVE_THUMB, CB_DELETE_THUMB, CB_BG_TILED, CB_BG_SCALED, - CB_BG_CENTERED, CB_BG_FILLED, CB_BG_TILED_NOFILE, - CB_BG_SCALED_NOFILE, CB_BG_CENTERED_NOFILE, CB_BG_FILLED_NOFILE, - CB_SORT_FILENAME, CB_SORT_IMAGENAME, CB_SORT_FILESIZE, CB_SORT_RANDOMIZE, - CB_SAVE_IMAGE, CB_SAVE_FILELIST, CB_FIT, CB_OPT_DRAW_FILENAME, - CB_OPT_DRAW_ACTIONS, CB_OPT_KEEP_HTTP, CB_OPT_FREEZE_WINDOW, - CB_OPT_FULLSCREEN, CB_EDIT_ROTATE, CB_OPT_AUTO_ZOOM + CB_CLOSE = 1, + CB_EXIT, + CB_RELOAD, + CB_REMOVE, + CB_DELETE, + CB_RESET, + CB_REMOVE_THUMB, + CB_DELETE_THUMB, + CB_BG_TILED, + CB_BG_SCALED, + CB_BG_CENTERED, + CB_BG_FILLED, + CB_BG_TILED_NOFILE, + CB_BG_SCALED_NOFILE, + CB_BG_CENTERED_NOFILE, + CB_BG_FILLED_NOFILE, + CB_SORT_FILENAME, + CB_SORT_IMAGENAME, + CB_SORT_DIRNAME, + CB_SORT_MTIME, + CB_SORT_FILESIZE, + CB_SORT_RANDOMIZE, + CB_SAVE_IMAGE, + CB_SAVE_FILELIST, + CB_FIT, + CB_OPT_DRAW_FILENAME, + CB_OPT_DRAW_ACTIONS, + CB_OPT_KEEP_HTTP, + CB_OPT_FREEZE_WINDOW, + CB_OPT_FULLSCREEN, + CB_EDIT_ROTATE, + CB_EDIT_MIRROR, + CB_EDIT_FLIP, + CB_OPT_AUTO_ZOOM, + CB_OPT_KEEP_ZOOM_VP }; feh_menu *feh_menu_new(void) @@ -99,7 +126,7 @@ feh_menu *feh_menu_new(void) m->updates = NULL; m->needs_redraw = 1; m->func_free = NULL; - m->data = NULL; + m->data = 0; m->calc = 0; m->bg = NULL; @@ -109,7 +136,7 @@ feh_menu *feh_menu_new(void) menus = l; if (!bg) { - feh_load_image_char(&bg, opt.menu_bg); + feh_load_image_char(&bg, PREFIX "/share/feh/images/menubg_default.png"); if (bg) { border.left = border.right = border.top = border.bottom = 4; @@ -141,8 +168,6 @@ void feh_menu_free(feh_menu * m) ii = i; i = i->next; - if (ii->func_free) - (ii->func_free) (ii->data); if (ii->text) free(ii->text); if (ii->submenu) @@ -241,7 +266,6 @@ void feh_menu_select_prev(feh_menu * selected_menu, feh_menu_item * selected_ite while (1) { i = i->prev; if (!i) { - i = selected_menu->items; for (ii = selected_menu->items; ii->next; ii = ii->next); i = ii; } @@ -439,14 +463,12 @@ void feh_menu_show_at_submenu(feh_menu * m, feh_menu * parent_m, feh_menu_item * void feh_menu_move(feh_menu * m, int x, int y) { - int dx, dy; - if (!m) return; - dx = x - m->x; - dy = y - m->y; + if (m->visible) XMoveWindow(disp, m->win, x, y); + m->x = x; m->y = y; return; @@ -515,26 +537,25 @@ void feh_menu_show(feh_menu * m) } feh_menu_item *feh_menu_add_toggle_entry(feh_menu * m, char *text, - Imlib_Image icon, char *submenu, int action, - void *data, void (*func_free) (void *data), int setting) + char *submenu, int action, + unsigned short data, void (*func_free) (void *data), int setting) { feh_menu_item *mi; - mi = feh_menu_add_entry(m, text, icon, submenu, action, data, func_free); + mi = feh_menu_add_entry(m, text, submenu, action, data, func_free); mi->is_toggle = TRUE; MENU_ITEM_TOGGLE_SET(mi, setting); return(mi); } -feh_menu_item *feh_menu_add_entry(feh_menu * m, char *text, Imlib_Image icon, - char *submenu, int action, void *data, void (*func_free) (void *data)) +feh_menu_item *feh_menu_add_entry(feh_menu * m, char *text, char *submenu, + int action, unsigned short data, void (*func_free) (void *data)) { feh_menu_item *mi, *ptr; mi = (feh_menu_item *) emalloc(sizeof(feh_menu_item)); mi->state = MENU_ITEM_STATE_NORMAL; - mi->icon = icon; mi->is_toggle = FALSE; if (text) mi->text = estrdup(text); @@ -566,12 +587,12 @@ feh_menu_item *feh_menu_add_entry(feh_menu * m, char *text, Imlib_Image icon, return(mi); } -void feh_menu_entry_get_size(feh_menu * m, feh_menu_item * i, int *w, int *h) +void feh_menu_entry_get_size(feh_menu_item * i, int *w, int *h) { int tw, th; if (i->text) { - gib_imlib_get_text_size(opt.menu_fn, i->text, opt.menu_style_l, &tw, &th, IMLIB_TEXT_TO_RIGHT); + gib_imlib_get_text_size(opt.menu_fn, i->text, NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT); *w = tw + FEH_MENUITEM_PAD_LEFT + FEH_MENUITEM_PAD_RIGHT; *h = th + FEH_MENUITEM_PAD_TOP + FEH_MENUITEM_PAD_BOTTOM; } else { @@ -580,15 +601,14 @@ void feh_menu_entry_get_size(feh_menu * m, feh_menu_item * i, int *w, int *h) } return; - m = NULL; } void feh_menu_calc_size(feh_menu * m) { int prev_w, prev_h; feh_menu_item *i; - int j = 0, count = 0, max_w = 0, max_h = 0, icon_w = 0, next_w = 0; - int toggle_w = 0; + int j = 0, count = 0, max_w = 0, max_h = 0, next_w = 0; + int toggle_w = 1; prev_w = m->w; prev_h = m->h; @@ -597,7 +617,7 @@ void feh_menu_calc_size(feh_menu * m) for (i = m->items; i; i = i->next) { int w, h; - feh_menu_entry_get_size(m, i, &w, &h); + feh_menu_entry_get_size(i, &w, &h); if (w > max_w) max_w = w; if (h > max_h) @@ -615,35 +635,12 @@ void feh_menu_calc_size(feh_menu * m) count++; } - for (i = m->items; i; i = i->next) { - if (i->icon) { - Imlib_Image im; - - im = i->icon; - if (im) { - int iw, ih, ow, oh; - - iw = gib_imlib_image_get_width(im); - ih = gib_imlib_image_get_height(im); - if (ih <= max_h) { - ow = iw; - oh = ih; - } else { - ow = (iw * max_h) / ih; - oh = max_h; - } - if (ow > icon_w) - icon_w = ow; - } - } - } m->h = FEH_MENU_PAD_TOP; for (i = m->items; i; i = i->next) { i->x = FEH_MENU_PAD_LEFT; i->y = m->h; - i->w = max_w + icon_w + toggle_w + next_w; - i->icon_x = FEH_MENUITEM_PAD_LEFT; - i->toggle_x = i->icon_x + icon_w; + i->w = max_w + toggle_w + next_w; + i->toggle_x = 1; i->text_x = i->toggle_x + toggle_w; i->sub_x = i->text_x + max_w; if (i->text) @@ -654,7 +651,7 @@ void feh_menu_calc_size(feh_menu * m) j++; } m->h += FEH_MENU_PAD_BOTTOM; - m->w = next_w + toggle_w + icon_w + max_w + FEH_MENU_PAD_LEFT + FEH_MENU_PAD_RIGHT; + m->w = next_w + toggle_w + max_w + FEH_MENU_PAD_LEFT + FEH_MENU_PAD_RIGHT; if ((prev_w != m->w) || (prev_h != m->h)) { if (m->pmap) @@ -704,42 +701,9 @@ void feh_menu_draw_item(feh_menu_item * i, Imlib_Image im, int ox, int oy) } /* draw text */ - gib_imlib_text_draw(im, opt.menu_fn, opt.menu_style_l, + gib_imlib_text_draw(im, opt.menu_fn, NULL, i->x - ox + i->text_x, i->y - oy + FEH_MENUITEM_PAD_TOP, i->text, IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255); - if (i->icon) { - Imlib_Image im2; - - D(("icon item\n")); - - im2 = i->icon; - if (im2) { - int iw, ih, ow, oh; - - iw = gib_imlib_image_get_width(im2); - ih = gib_imlib_image_get_height(im2); - if (ih <= (i->h - FEH_MENUITEM_PAD_TOP - FEH_MENUITEM_PAD_BOTTOM)) { - ow = iw; - oh = ih; - } else { - ow = (iw * (i->h - FEH_MENUITEM_PAD_TOP - FEH_MENUITEM_PAD_BOTTOM)) / ih; - oh = i->h - FEH_MENUITEM_PAD_TOP - FEH_MENUITEM_PAD_BOTTOM; - } - gib_imlib_blend_image_onto_image(im, im2, - 0, 0, 0, - iw, ih, - i->x + - i->icon_x - - ox, - i->y + - FEH_MENUITEM_PAD_TOP - + - (((i->h - - FEH_MENUITEM_PAD_TOP - FEH_MENUITEM_PAD_BOTTOM) - - oh) / 2) - oy, ow, oh, 1, 1, 1); - gib_imlib_free_image(im2); - } - } if (i->submenu) { D(("submenu item\n")); feh_menu_draw_submenu_at(i->x + i->sub_x, @@ -861,27 +825,16 @@ void feh_menu_draw_toggle_at(int x, int y, int w, int h, Imlib_Image dst, int ox void feh_menu_draw_submenu_at(int x, int y, Imlib_Image dst, int ox, int oy) { - ImlibPolygon poly; - - x -= ox; + // Draw filled triangle + x -= ox; y -= oy; imlib_context_set_image(dst); - poly = imlib_polygon_new(); - imlib_polygon_add_point(poly, x + 2, y + 5); - imlib_polygon_add_point(poly, x + 5, y + 7); - imlib_polygon_add_point(poly, x + 2, y + 11); - imlib_context_set_color(0, 0, 0, 60); - imlib_image_fill_polygon(poly); - imlib_polygon_free(poly); - - poly = imlib_polygon_new(); - imlib_polygon_add_point(poly, x, y + 3); - imlib_polygon_add_point(poly, x + 3, y + 6); - imlib_polygon_add_point(poly, x, y + 9); imlib_context_set_color(0, 0, 0, 255); - imlib_image_fill_polygon(poly); - imlib_polygon_free(poly); + + for (int i= 0; i <= 3; i++) { + imlib_image_draw_line(x+i, y+3+i, x+i, y+9-i, 0); + } return; } @@ -896,7 +849,7 @@ void feh_menu_item_draw_at(int x, int y, int w, int h, Imlib_Image dst, int ox, { imlib_context_set_image(dst); if (selected) - gib_imlib_image_fill_rectangle(dst, x - ox, y - oy, w, h, 255, 255, 255, 178); + gib_imlib_image_fill_rectangle(dst, x - ox, y - oy, w, h, 127, 127, 127, 178); return; } @@ -944,39 +897,42 @@ void feh_menu_init_main(void) menu_main = feh_menu_new(); menu_main->name = estrdup("MAIN"); - feh_menu_add_entry(menu_main, "File", NULL, "FILE", 0, NULL, NULL); + feh_menu_add_entry(menu_main, "File", "FILE", 0, 0, NULL); if (opt.slideshow || opt.multiwindow) { - feh_menu_add_entry(menu_main, "Sort List", NULL, "SORT", 0, NULL, NULL); - mi = feh_menu_add_entry(menu_main, "Image Info", NULL, "INFO", 0, NULL, NULL); + feh_menu_add_entry(menu_main, "Sort List", "SORT", 0, 0, NULL); + mi = feh_menu_add_entry(menu_main, "Image Info", "INFO", 0, 0, NULL); mi->func_gen_sub = feh_menu_func_gen_info; - feh_menu_add_entry(menu_main, NULL, NULL, NULL, 0, NULL, NULL); + feh_menu_add_entry(menu_main, NULL, NULL, 0, 0, NULL); } - mi = feh_menu_add_entry(menu_main, "Options", NULL, "OPTIONS", 0, NULL, NULL); + mi = feh_menu_add_entry(menu_main, "Options", "OPTIONS", 0, 0, NULL); mi->func_gen_sub = feh_menu_func_gen_options; - if (!opt.full_screen) - feh_menu_add_entry(menu_main, "About " PACKAGE, NULL, NULL, CB_ABOUT, NULL, NULL); if (opt.multiwindow) - feh_menu_add_entry(menu_main, "Close", NULL, NULL, CB_CLOSE, NULL, NULL); - feh_menu_add_entry(menu_main, "Exit", NULL, NULL, CB_EXIT, NULL, NULL); + feh_menu_add_entry(menu_main, "Close", NULL, CB_CLOSE, 0, NULL); + feh_menu_add_entry(menu_main, "Exit", NULL, CB_EXIT, 0, NULL); m = feh_menu_new(); m->name = estrdup("FILE"); - feh_menu_add_entry(m, "Reset", NULL, NULL, CB_RESET, NULL, NULL); - feh_menu_add_entry(m, "Resize Window", NULL, NULL, CB_FIT, NULL, NULL); - feh_menu_add_entry(m, "Reload", NULL, NULL, CB_RELOAD, NULL, NULL); - feh_menu_add_entry(m, "Save Image", NULL, NULL, CB_SAVE_IMAGE, NULL, NULL); - feh_menu_add_entry(m, "Save List", NULL, NULL, CB_SAVE_FILELIST, NULL, NULL); - feh_menu_add_entry(m, "Edit in Place", NULL, "EDIT", 0, NULL, NULL); - feh_menu_add_entry(m, "Background", NULL, "BACKGROUND", 0, NULL, NULL); - feh_menu_add_entry(m, NULL, NULL, NULL, 0, NULL, NULL); - feh_menu_add_entry(m, "Hide", NULL, NULL, CB_REMOVE, NULL, NULL); - feh_menu_add_entry(m, "Delete", NULL, "CONFIRM", 0, NULL, NULL); + feh_menu_add_entry(m, "Reset", NULL, CB_RESET, 0, NULL); + feh_menu_add_entry(m, "Resize Window", NULL, CB_FIT, 0, NULL); + feh_menu_add_entry(m, "Reload", NULL, CB_RELOAD, 0, NULL); + feh_menu_add_entry(m, "Save Image", NULL, CB_SAVE_IMAGE, 0, NULL); + feh_menu_add_entry(m, "Save List", NULL, CB_SAVE_FILELIST, 0, NULL); + if (opt.edit) { + feh_menu_add_entry(m, "Edit in Place", "EDIT", 0, 0, NULL); + } + else { + feh_menu_add_entry(m, "Change View", "EDIT", 0, 0, NULL); + } + feh_menu_add_entry(m, "Background", "BACKGROUND", 0, 0, NULL); + feh_menu_add_entry(m, NULL, NULL, 0, 0, NULL); + feh_menu_add_entry(m, "Hide", NULL, CB_REMOVE, 0, NULL); + feh_menu_add_entry(m, "Delete", "CONFIRM", 0, 0, NULL); return; } -void feh_menu_init_common() +void feh_menu_init_common(void) { int num_desks, i; char buf[30]; @@ -986,57 +942,53 @@ void feh_menu_init_common() opt.menu_fn = gib_imlib_load_font(opt.menu_font); if (!opt.menu_fn) eprintf - ("couldn't load menu font %s, did you make install?\nAre you specifying a nonexistant font?\nDid you tell feh where to find it with --fontpath?", + ("couldn't load menu font %s, did you make install?\nAre you specifying a nonexistent font?\nDid you tell feh where to find it with --fontpath?", opt.menu_font); } - if (!opt.menu_style_l) { - opt.menu_style_l = gib_style_new_from_ascii(opt.menu_style); - if (!opt.menu_style_l) { - weprintf - ("couldn't load style file for menu fonts, (%s).\nDid you make install? Menus will look boring without the style file.", - opt.menu_style); - } - } m = feh_menu_new(); m->name = estrdup("SORT"); - feh_menu_add_entry(m, "By File Name", NULL, NULL, CB_SORT_FILENAME, NULL, NULL); - feh_menu_add_entry(m, "By Image Name", NULL, NULL, CB_SORT_IMAGENAME, NULL, NULL); - if (opt.preload || (opt.sort > SORT_FILENAME)) - feh_menu_add_entry(m, "By File Size", NULL, NULL, CB_SORT_FILESIZE, NULL, NULL); - feh_menu_add_entry(m, "Randomize", NULL, NULL, CB_SORT_RANDOMIZE, NULL, NULL); + feh_menu_add_entry(m, "By File Name", NULL, CB_SORT_FILENAME, 0, NULL); + feh_menu_add_entry(m, "By Image Name", NULL, CB_SORT_IMAGENAME, 0, NULL); + feh_menu_add_entry(m, "By Directory Name", NULL, CB_SORT_DIRNAME, 0, NULL); + feh_menu_add_entry(m, "By Modification Date", NULL, CB_SORT_MTIME, 0, NULL); + if (opt.preload || (opt.sort > SORT_MTIME)) + feh_menu_add_entry(m, "By File Size", NULL, CB_SORT_FILESIZE, 0, NULL); + feh_menu_add_entry(m, "Randomize", NULL, CB_SORT_RANDOMIZE, 0, NULL); m = feh_menu_new(); m->name = estrdup("CONFIRM"); - feh_menu_add_entry(m, "Confirm", NULL, NULL, CB_DELETE, NULL, NULL); + feh_menu_add_entry(m, "Confirm", NULL, CB_DELETE, 0, NULL); m = feh_menu_new(); m->name = estrdup("EDIT"); - feh_menu_add_entry(m, "Rotate 90 CW", NULL, NULL, CB_EDIT_ROTATE, (void *) 1, NULL); - feh_menu_add_entry(m, "Rotate 180", NULL, NULL, CB_EDIT_ROTATE, (void *) 2, NULL); - feh_menu_add_entry(m, "Rotate 90 CCW", NULL, NULL, CB_EDIT_ROTATE, (void *) 3, NULL); + feh_menu_add_entry(m, "Rotate 90 CW", NULL, CB_EDIT_ROTATE, 1, NULL); + feh_menu_add_entry(m, "Rotate 180", NULL, CB_EDIT_ROTATE, 2, NULL); + feh_menu_add_entry(m, "Rotate 90 CCW", NULL, CB_EDIT_ROTATE, 3, NULL); + feh_menu_add_entry(m, "Mirror", NULL, CB_EDIT_MIRROR, 0, NULL); + feh_menu_add_entry(m, "Flip", NULL, CB_EDIT_FLIP, 0, NULL); menu_bg = feh_menu_new(); menu_bg->name = estrdup("BACKGROUND"); num_desks = feh_wm_get_num_desks(); if (num_desks > 1) { - feh_menu_add_entry(menu_bg, "Set Tiled", NULL, "TILED", 0, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Scaled", NULL, "SCALED", 0, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Centered", NULL, "CENTERED", 0, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Filled", NULL, "FILLED", 0, NULL, NULL); + feh_menu_add_entry(menu_bg, "Set Tiled", "TILED", 0, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Scaled", "SCALED", 0, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Centered", "CENTERED", 0, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Filled", "FILLED", 0, 0, NULL); m = feh_menu_new(); m->name = estrdup("TILED"); for (i = 0; i < num_desks; i++) { snprintf(buf, sizeof(buf), "Desktop %d", i + 1); if (opt.slideshow || opt.multiwindow) - feh_menu_add_entry(m, buf, NULL, NULL, CB_BG_TILED, - (void *) i, NULL); + feh_menu_add_entry(m, buf, NULL, CB_BG_TILED, + i, NULL); else - feh_menu_add_entry(m, buf, NULL, NULL, CB_BG_TILED_NOFILE, - (void *) i, NULL); + feh_menu_add_entry(m, buf, NULL, CB_BG_TILED_NOFILE, + i, NULL); } m = feh_menu_new(); @@ -1045,11 +997,11 @@ void feh_menu_init_common() snprintf(buf, sizeof(buf), "Desktop %d", i + 1); if (opt.slideshow || opt.multiwindow) - feh_menu_add_entry(m, buf, NULL, NULL, CB_BG_SCALED, - (void *) i, NULL); + feh_menu_add_entry(m, buf, NULL, CB_BG_SCALED, + i, NULL); else - feh_menu_add_entry(m, buf, NULL, NULL, CB_BG_SCALED_NOFILE, - (void *) i, NULL); + feh_menu_add_entry(m, buf, NULL, CB_BG_SCALED_NOFILE, + i, NULL); } m = feh_menu_new(); @@ -1057,11 +1009,11 @@ void feh_menu_init_common() for (i = 0; i < num_desks; i++) { snprintf(buf, sizeof(buf), "Desktop %d", i + 1); if (opt.slideshow || opt.multiwindow) - feh_menu_add_entry(m, buf, NULL, NULL, - CB_BG_CENTERED, (void *) i, NULL); + feh_menu_add_entry(m, buf, NULL, + CB_BG_CENTERED, i, NULL); else - feh_menu_add_entry(m, buf, NULL, NULL, - CB_BG_CENTERED_NOFILE, (void *) i, NULL); + feh_menu_add_entry(m, buf, NULL, + CB_BG_CENTERED_NOFILE, i, NULL); } m = feh_menu_new(); @@ -1069,33 +1021,33 @@ void feh_menu_init_common() for (i = 0; i < num_desks; i++) { snprintf(buf, sizeof(buf), "Desktop %d", i + 1); if (opt.slideshow || opt.multiwindow) - feh_menu_add_entry(m, buf, NULL, NULL, + feh_menu_add_entry(m, buf, NULL, CB_BG_FILLED, - (void *) i, NULL); + i, NULL); else - feh_menu_add_entry(m, buf, NULL, NULL, + feh_menu_add_entry(m, buf, NULL, CB_BG_FILLED_NOFILE, - (void *) i, NULL); + i, NULL); } } else { if (opt.slideshow || opt.multiwindow) { - feh_menu_add_entry(menu_bg, "Set Tiled", NULL, - NULL, CB_BG_TILED, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Scaled", NULL, - NULL, CB_BG_SCALED, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Centered", NULL, - NULL, CB_BG_CENTERED, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Filled", NULL, - NULL, CB_BG_FILLED, NULL, NULL); + feh_menu_add_entry(menu_bg, "Set Tiled", + NULL, CB_BG_TILED, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Scaled", + NULL, CB_BG_SCALED, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Centered", + NULL, CB_BG_CENTERED, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Filled", + NULL, CB_BG_FILLED, 0, NULL); } else { - feh_menu_add_entry(menu_bg, "Set Tiled", NULL, - NULL, CB_BG_TILED_NOFILE, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Scaled", NULL, - NULL, CB_BG_SCALED_NOFILE, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Centered", NULL, - NULL, CB_BG_CENTERED_NOFILE, NULL, NULL); - feh_menu_add_entry(menu_bg, "Set Filled", NULL, - NULL, CB_BG_FILLED_NOFILE, NULL, NULL); + feh_menu_add_entry(menu_bg, "Set Tiled", + NULL, CB_BG_TILED_NOFILE, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Scaled", + NULL, CB_BG_SCALED_NOFILE, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Centered", + NULL, CB_BG_CENTERED_NOFILE, 0, NULL); + feh_menu_add_entry(menu_bg, "Set Filled", + NULL, CB_BG_FILLED_NOFILE, 0, NULL); } } common_menus = 1; @@ -1103,17 +1055,6 @@ void feh_menu_init_common() return; } -void feh_menu_init_about_win(void) -{ - menu_about_win = feh_menu_new(); - menu_about_win->name = estrdup("ABOUTWIN"); - - feh_menu_add_entry(menu_about_win, "Close", NULL, NULL, CB_CLOSE, NULL, NULL); - feh_menu_add_entry(menu_about_win, "Exit", NULL, NULL, CB_EXIT, NULL, NULL); - - return; -} - void feh_menu_init_single_win(void) { feh_menu *m; @@ -1125,31 +1066,29 @@ void feh_menu_init_single_win(void) menu_single_win = feh_menu_new(); menu_single_win->name = estrdup("SINGLEWIN"); - feh_menu_add_entry(menu_single_win, "File", NULL, "SINGLEWIN_FILE", 0, NULL, NULL); + feh_menu_add_entry(menu_single_win, "File", "SINGLEWIN_FILE", 0, 0, NULL); m = feh_menu_new(); m->name = estrdup("SINGLEWIN_FILE"); - feh_menu_add_entry(m, "Reset", NULL, NULL, CB_RESET, NULL, NULL); - feh_menu_add_entry(m, "Resize Window", NULL, NULL, CB_FIT, NULL, NULL); - feh_menu_add_entry(m, "Reload", NULL, NULL, CB_RELOAD, NULL, NULL); - feh_menu_add_entry(m, "Save Image", NULL, NULL, CB_SAVE_IMAGE, NULL, NULL); - feh_menu_add_entry(m, "Save List", NULL, NULL, CB_SAVE_FILELIST, NULL, NULL); - feh_menu_add_entry(m, "Edit in Place", NULL, "EDIT", 0, NULL, NULL); - feh_menu_add_entry(m, "Background", NULL, "BACKGROUND", 0, NULL, NULL); + feh_menu_add_entry(m, "Reset", NULL, CB_RESET, 0, NULL); + feh_menu_add_entry(m, "Resize Window", NULL, CB_FIT, 0, NULL); + feh_menu_add_entry(m, "Reload", NULL, CB_RELOAD, 0, NULL); + feh_menu_add_entry(m, "Save Image", NULL, CB_SAVE_IMAGE, 0, NULL); + feh_menu_add_entry(m, "Save List", NULL, CB_SAVE_FILELIST, 0, NULL); + feh_menu_add_entry(m, "Edit in Place", "EDIT", 0, 0, NULL); + feh_menu_add_entry(m, "Background", "BACKGROUND", 0, 0, NULL); if (opt.multiwindow || opt.slideshow) { - feh_menu_add_entry(m, NULL, NULL, NULL, 0, NULL, NULL); - feh_menu_add_entry(m, "Hide", NULL, NULL, CB_REMOVE, NULL, NULL); - feh_menu_add_entry(m, "Delete", NULL, "CONFIRM", 0, NULL, NULL); + feh_menu_add_entry(m, NULL, NULL, 0, 0, NULL); + feh_menu_add_entry(m, "Hide", NULL, CB_REMOVE, 0, NULL); + feh_menu_add_entry(m, "Delete", "CONFIRM", 0, 0, NULL); } - mi = feh_menu_add_entry(menu_single_win, "Image Info", NULL, "INFO", 0, NULL, NULL); + mi = feh_menu_add_entry(menu_single_win, "Image Info", "INFO", 0, 0, NULL); mi->func_gen_sub = feh_menu_func_gen_info; - feh_menu_add_entry(menu_single_win, NULL, NULL, NULL, 0, NULL, NULL); - mi = feh_menu_add_entry(menu_single_win, "Options", NULL, "OPTIONS", 0, NULL, NULL); + feh_menu_add_entry(menu_single_win, NULL, NULL, 0, 0, NULL); + mi = feh_menu_add_entry(menu_single_win, "Options", "OPTIONS", 0, 0, NULL); mi->func_gen_sub = feh_menu_func_gen_options; - feh_menu_add_entry(menu_single_win, "About " PACKAGE, NULL, NULL, - CB_ABOUT, NULL, NULL); - feh_menu_add_entry(menu_single_win, "Close", NULL, NULL, CB_CLOSE, NULL, NULL); - feh_menu_add_entry(menu_single_win, "Exit", NULL, NULL, CB_EXIT, NULL, NULL); + feh_menu_add_entry(menu_single_win, "Close", NULL, CB_CLOSE, 0, NULL); + feh_menu_add_entry(menu_single_win, "Exit", NULL, CB_EXIT, 0, NULL); return; } @@ -1165,21 +1104,19 @@ void feh_menu_init_thumbnail_win(void) menu_thumbnail_win = feh_menu_new(); menu_thumbnail_win->name = estrdup("THUMBWIN"); - feh_menu_add_entry(menu_thumbnail_win, "File", NULL, "THUMBWIN_FILE", 0, NULL, NULL); + feh_menu_add_entry(menu_thumbnail_win, "File", "THUMBWIN_FILE", 0, 0, NULL); m = feh_menu_new(); m->name = estrdup("THUMBWIN_FILE"); - feh_menu_add_entry(m, "Reset", NULL, NULL, CB_RESET, NULL, NULL); - feh_menu_add_entry(m, "Resize Window", NULL, NULL, CB_FIT, NULL, NULL); - feh_menu_add_entry(m, "Save Image", NULL, NULL, CB_SAVE_IMAGE, NULL, NULL); - feh_menu_add_entry(m, "Save List", NULL, NULL, CB_SAVE_FILELIST, NULL, NULL); - feh_menu_add_entry(m, "Background", NULL, "BACKGROUND", 0, NULL, NULL); - feh_menu_add_entry(menu_thumbnail_win, NULL, NULL, NULL, 0, NULL, NULL); - mi = feh_menu_add_entry(menu_thumbnail_win, "Options", NULL, "OPTIONS", 0, NULL, NULL); + feh_menu_add_entry(m, "Reset", NULL, CB_RESET, 0, NULL); + feh_menu_add_entry(m, "Resize Window", NULL, CB_FIT, 0, NULL); + feh_menu_add_entry(m, "Save Image", NULL, CB_SAVE_IMAGE, 0, NULL); + feh_menu_add_entry(m, "Save List", NULL, CB_SAVE_FILELIST, 0, NULL); + feh_menu_add_entry(m, "Background", "BACKGROUND", 0, 0, NULL); + feh_menu_add_entry(menu_thumbnail_win, NULL, NULL, 0, 0, NULL); + mi = feh_menu_add_entry(menu_thumbnail_win, "Options", "OPTIONS", 0, 0, NULL); mi->func_gen_sub = feh_menu_func_gen_options; - feh_menu_add_entry(menu_thumbnail_win, "About " PACKAGE, NULL, NULL, - CB_ABOUT, NULL, NULL); - feh_menu_add_entry(menu_thumbnail_win, "Close", NULL, NULL, CB_CLOSE, NULL, NULL); - feh_menu_add_entry(menu_thumbnail_win, "Exit", NULL, NULL, CB_EXIT, NULL, NULL); + feh_menu_add_entry(menu_thumbnail_win, "Close", NULL, CB_CLOSE, 0, NULL); + feh_menu_add_entry(menu_thumbnail_win, "Exit", NULL, CB_EXIT, 0, NULL); return; } @@ -1194,34 +1131,32 @@ void feh_menu_init_thumbnail_viewer(void) menu_thumbnail_viewer = feh_menu_new(); menu_thumbnail_viewer->name = estrdup("THUMBVIEW"); - feh_menu_add_entry(menu_thumbnail_viewer, "File", NULL, "THUMBVIEW_FILE", - 0, NULL, NULL); + feh_menu_add_entry(menu_thumbnail_viewer, "File", "THUMBVIEW_FILE", + 0, 0, NULL); m = feh_menu_new(); m->name = estrdup("THUMBVIEW_FILE"); - feh_menu_add_entry(m, "Reset", NULL, NULL, CB_RESET, NULL, NULL); - feh_menu_add_entry(m, "Resize Window", NULL, NULL, CB_FIT, NULL, NULL); - feh_menu_add_entry(m, "Reload", NULL, NULL, CB_RELOAD, NULL, NULL); - feh_menu_add_entry(m, "Save Image", NULL, NULL, CB_SAVE_IMAGE, NULL, NULL); - feh_menu_add_entry(m, "Save List", NULL, NULL, CB_SAVE_FILELIST, NULL, NULL); - feh_menu_add_entry(m, "Edit in Place", NULL, "EDIT", 0, NULL, NULL); - feh_menu_add_entry(m, "Background", NULL, "BACKGROUND", 0, NULL, NULL); - feh_menu_add_entry(m, NULL, NULL, NULL, 0, NULL, NULL); - feh_menu_add_entry(m, "Hide", NULL, NULL, CB_REMOVE_THUMB, NULL, NULL); - feh_menu_add_entry(m, "Delete", NULL, "THUMBVIEW_CONFIRM", 0, NULL, NULL); - mi = feh_menu_add_entry(menu_thumbnail_viewer, "Image Info", NULL, - "INFO", 0, NULL, NULL); + feh_menu_add_entry(m, "Reset", NULL, CB_RESET, 0, NULL); + feh_menu_add_entry(m, "Resize Window", NULL, CB_FIT, 0, NULL); + feh_menu_add_entry(m, "Reload", NULL, CB_RELOAD, 0, NULL); + feh_menu_add_entry(m, "Save Image", NULL, CB_SAVE_IMAGE, 0, NULL); + feh_menu_add_entry(m, "Save List", NULL, CB_SAVE_FILELIST, 0, NULL); + feh_menu_add_entry(m, "Edit in Place", "EDIT", 0, 0, NULL); + feh_menu_add_entry(m, "Background", "BACKGROUND", 0, 0, NULL); + feh_menu_add_entry(m, NULL, NULL, 0, 0, NULL); + feh_menu_add_entry(m, "Hide", NULL, CB_REMOVE_THUMB, 0, NULL); + feh_menu_add_entry(m, "Delete", "THUMBVIEW_CONFIRM", 0, 0, NULL); + mi = feh_menu_add_entry(menu_thumbnail_viewer, "Image Info", + "INFO", 0, 0, NULL); mi->func_gen_sub = feh_menu_func_gen_info; - feh_menu_add_entry(menu_thumbnail_viewer, NULL, NULL, NULL, 0, NULL, NULL); - mi = feh_menu_add_entry(menu_thumbnail_viewer, "Options", NULL, - "OPTIONS", 0, NULL, NULL); + feh_menu_add_entry(menu_thumbnail_viewer, NULL, NULL, 0, 0, NULL); + mi = feh_menu_add_entry(menu_thumbnail_viewer, "Options", + "OPTIONS", 0, 0, NULL); mi->func_gen_sub = feh_menu_func_gen_options; - feh_menu_add_entry(menu_thumbnail_viewer, "About " PACKAGE, NULL, NULL, - CB_ABOUT, NULL, NULL); - feh_menu_add_entry(menu_thumbnail_viewer, "Close", NULL, NULL, CB_CLOSE, NULL, NULL); - feh_menu_add_entry(menu_thumbnail_viewer, "Exit", NULL, NULL, CB_EXIT, NULL, NULL); + feh_menu_add_entry(menu_thumbnail_viewer, "Close", NULL, CB_CLOSE, 0, NULL); + feh_menu_add_entry(menu_thumbnail_viewer, "Exit", NULL, CB_EXIT, 0, NULL); m = feh_menu_new(); m->name = estrdup("THUMBVIEW_CONFIRM"); - feh_menu_add_entry(m, "Confirm", NULL, NULL, CB_DELETE_THUMB, NULL, NULL); + feh_menu_add_entry(m, "Confirm", NULL, CB_DELETE_THUMB, 0, NULL); return; } @@ -1240,15 +1175,8 @@ void feh_menu_cb_opt_fullscreen(feh_menu * m, feh_menu_item * i) int i, rect[4]; winwidget_get_geometry(m->fehwin, rect); - /* printf("window: (%d, %d)\n", rect[0], rect[1]); - printf("found %d screens.\n", num_xinerama_screens); */ for (i = 0; i < num_xinerama_screens; i++) { xinerama_screen = 0; - /* printf("%d: [%d, %d, %d, %d] (%d, %d)\n", - i, - xinerama_screens[i].x_org, xinerama_screens[i].y_org, - xinerama_screens[i].width, xinerama_screens[i].height, - rect[0], rect[1]); */ if (XY_IN_RECT(rect[0], rect[1], xinerama_screens[i].x_org, xinerama_screens[i].y_org, @@ -1258,16 +1186,15 @@ void feh_menu_cb_opt_fullscreen(feh_menu * m, feh_menu_item * i) } } - if (getenv("XINERAMA_SCREEN")) - curr_screen = xinerama_screen = - atoi(getenv("XINERAMA_SCREEN")); + if (opt.xinerama_index >= 0) + curr_screen = xinerama_screen = opt.xinerama_index; } #endif /* HAVE_LIBXINERAMA */ winwidget_destroy_xwin(m->fehwin); winwidget_create_window(m->fehwin, m->fehwin->im_w, m->fehwin->im_h); - winwidget_render_image(m->fehwin, 1, 1); + winwidget_render_image(m->fehwin, 1, 0); winwidget_show(m->fehwin); #ifdef HAVE_LIBXINERAMA @@ -1280,54 +1207,38 @@ void feh_menu_cb_opt_fullscreen(feh_menu * m, feh_menu_item * i) #endif /* HAVE_LIBXINERAMA */ } -void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, void *data) +void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, unsigned short data) { char *path; - Imlib_Image im; - winwidget winwid; switch (action) { case CB_BG_TILED: - path = feh_absolute_path(FEH_FILE(m->fehwin->file->data)->filename); - feh_wm_set_bg(path, m->fehwin->im, 0, 0, 0, (int) data, 1); - free(path); + path = FEH_FILE(m->fehwin->file->data)->filename; + feh_wm_set_bg(path, m->fehwin->im, 0, 0, 0, data, 0); break; case CB_BG_SCALED: - path = feh_absolute_path(FEH_FILE(m->fehwin->file->data)->filename); - feh_wm_set_bg(path, m->fehwin->im, 0, 1, 0, (int) data, 1); - free(path); + path = FEH_FILE(m->fehwin->file->data)->filename; + feh_wm_set_bg(path, m->fehwin->im, 0, 1, 0, data, 0); break; case CB_BG_CENTERED: - path = feh_absolute_path(FEH_FILE(m->fehwin->file->data)->filename); - feh_wm_set_bg(path, m->fehwin->im, 1, 0, 0, (int) data, 1); - free(path); + path = FEH_FILE(m->fehwin->file->data)->filename; + feh_wm_set_bg(path, m->fehwin->im, 1, 0, 0, data, 0); break; case CB_BG_FILLED: - path = feh_absolute_path(FEH_FILE(m->fehwin->file->data)->filename); - feh_wm_set_bg(path, m->fehwin->im, 0, 0, 1, (int) data, 1); - free(path); + path = FEH_FILE(m->fehwin->file->data)->filename; + feh_wm_set_bg(path, m->fehwin->im, 0, 0, 1, data, 0); break; case CB_BG_TILED_NOFILE: - feh_wm_set_bg(NULL, m->fehwin->im, 0, 0, 0, (int) data, 1); + feh_wm_set_bg(NULL, m->fehwin->im, 0, 0, 0, data, 0); break; case CB_BG_SCALED_NOFILE: - feh_wm_set_bg(NULL, m->fehwin->im, 0, 1, 0, (int) data, 1); + feh_wm_set_bg(NULL, m->fehwin->im, 0, 1, 0, data, 0); break; case CB_BG_CENTERED_NOFILE: - feh_wm_set_bg(NULL, m->fehwin->im, 1, 0, 0, (int) data, 1); + feh_wm_set_bg(NULL, m->fehwin->im, 1, 0, 0, data, 0); break; case CB_BG_FILLED_NOFILE: - feh_wm_set_bg(NULL, m->fehwin->im, 0, 0, 1, (int) data, 1); - break; - case CB_ABOUT: - if (feh_load_image_char(&im, PREFIX "/share/feh/images/about.png") - != 0) { - winwid = winwidget_create_from_image(im, "About " PACKAGE, - WIN_TYPE_ABOUT); - winwid->file = gib_list_add_front(NULL, - feh_file_new(PREFIX "/share/feh/images/about.png")); - winwidget_show(winwid); - } + feh_wm_set_bg(NULL, m->fehwin->im, 0, 0, 1, data, 0); break; case CB_CLOSE: winwidget_destroy(m->fehwin); @@ -1339,13 +1250,13 @@ void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, void *data) if (m->fehwin->has_rotated) { m->fehwin->im_w = gib_imlib_image_get_width(m->fehwin->im); m->fehwin->im_h = gib_imlib_image_get_height(m->fehwin->im); - winwidget_resize(m->fehwin, m->fehwin->im_w, m->fehwin->im_h); + winwidget_resize(m->fehwin, m->fehwin->im_w, m->fehwin->im_h, 0); } winwidget_reset_image(m->fehwin); - winwidget_render_image(m->fehwin, 1, 1); + winwidget_render_image(m->fehwin, 1, 0); break; case CB_RELOAD: - feh_reload_image(m->fehwin, 0, 0); + feh_reload_image(m->fehwin, 0, 1); break; case CB_REMOVE: feh_filelist_image_remove(m->fehwin, 0); @@ -1364,32 +1275,50 @@ void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, void *data) case CB_SORT_FILENAME: filelist = gib_list_sort(filelist, feh_cmp_filename); if (opt.jump_on_resort) { - slideshow_change_image(m->fehwin, SLIDE_FIRST); + slideshow_change_image(m->fehwin, SLIDE_FIRST, 1); } break; case CB_SORT_IMAGENAME: filelist = gib_list_sort(filelist, feh_cmp_name); if (opt.jump_on_resort) { - slideshow_change_image(m->fehwin, SLIDE_FIRST); + slideshow_change_image(m->fehwin, SLIDE_FIRST, 1); + } + break; + case CB_SORT_DIRNAME: + filelist = gib_list_sort(filelist, feh_cmp_dirname); + if (opt.jump_on_resort) { + slideshow_change_image(m->fehwin, SLIDE_FIRST, 1); + } + break; + case CB_SORT_MTIME: + filelist = gib_list_sort(filelist, feh_cmp_mtime); + if (opt.jump_on_resort) { + slideshow_change_image(m->fehwin, SLIDE_FIRST, 1); } break; case CB_SORT_FILESIZE: filelist = gib_list_sort(filelist, feh_cmp_size); if (opt.jump_on_resort) { - slideshow_change_image(m->fehwin, SLIDE_FIRST); + slideshow_change_image(m->fehwin, SLIDE_FIRST, 1); } break; case CB_SORT_RANDOMIZE: filelist = gib_list_randomize(filelist); if (opt.jump_on_resort) { - slideshow_change_image(m->fehwin, SLIDE_FIRST); + slideshow_change_image(m->fehwin, SLIDE_FIRST, 1); } break; case CB_FIT: winwidget_size_to_image(m->fehwin); break; case CB_EDIT_ROTATE: - feh_edit_inplace_orient(m->fehwin, (int) data); + feh_edit_inplace(m->fehwin, data); + break; + case CB_EDIT_MIRROR: + feh_edit_inplace(m->fehwin, INPLACE_EDIT_MIRROR); + break; + case CB_EDIT_FLIP: + feh_edit_inplace(m->fehwin, INPLACE_EDIT_FLIP); break; case CB_SAVE_IMAGE: slideshow_save_image(m->fehwin); @@ -1403,7 +1332,7 @@ void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, void *data) opt.draw_filename = TRUE; else opt.draw_filename = FALSE; - winwidget_rerender_all(0, 1); + winwidget_rerender_all(0); break; case CB_OPT_DRAW_ACTIONS: MENU_ITEM_TOGGLE(i); @@ -1411,7 +1340,7 @@ void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, void *data) opt.draw_actions = TRUE; else opt.draw_actions = FALSE; - winwidget_rerender_all(0, 1); + winwidget_rerender_all(0); break; case CB_OPT_KEEP_HTTP: MENU_ITEM_TOGGLE(i); @@ -1435,8 +1364,18 @@ void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, void *data) break; case CB_OPT_AUTO_ZOOM: MENU_ITEM_TOGGLE(i); - opt.auto_zoom = MENU_ITEM_IS_ON(i) ? 1 : 0; - winwidget_rerender_all(1, 1); + if (MENU_ITEM_IS_ON(i)) + opt.zoom_mode = ZOOM_MODE_MAX; + else + opt.zoom_mode = 0; + winwidget_rerender_all(1); + break; + case CB_OPT_KEEP_ZOOM_VP: + MENU_ITEM_TOGGLE(i); + if (MENU_ITEM_IS_ON(i)) + opt.keep_zoom_vp = 1; + else + opt.keep_zoom_vp = 0; break; } return; @@ -1458,16 +1397,16 @@ static feh_menu *feh_menu_func_gen_info(feh_menu * m) mm = feh_menu_new(); mm->name = estrdup("INFO"); snprintf(buffer, sizeof(buffer), "Filename: %s", file->name); - feh_menu_add_entry(mm, buffer, NULL, NULL, 0, NULL, NULL); + feh_menu_add_entry(mm, buffer, NULL, 0, 0, NULL); if (!file->info) feh_file_info_load(file, im); if (file->info) { - snprintf(buffer, sizeof(buffer), "Size: %dKb", file->info->size / 1024); - feh_menu_add_entry(mm, buffer, NULL, NULL, 0, NULL, NULL); + snprintf(buffer, sizeof(buffer), "Size: %dKb", file->size / 1024); + feh_menu_add_entry(mm, buffer, NULL, 0, 0, NULL); snprintf(buffer, sizeof(buffer), "Dimensions: %dx%d", file->info->width, file->info->height); - feh_menu_add_entry(mm, buffer, NULL, NULL, 0, NULL, NULL); + feh_menu_add_entry(mm, buffer, NULL, 0, 0, NULL); snprintf(buffer, sizeof(buffer), "Type: %s", file->info->format); - feh_menu_add_entry(mm, buffer, NULL, NULL, 0, NULL, NULL); + feh_menu_add_entry(mm, buffer, NULL, 0, 0, NULL); } mm->func_free = feh_menu_func_free_info; @@ -1487,20 +1426,23 @@ static feh_menu *feh_menu_func_gen_options(feh_menu * m) mm = feh_menu_new(); mm->name = estrdup("OPTIONS"); mm->fehwin = m->fehwin; - feh_menu_add_toggle_entry(mm, "Auto-Zoom", NULL, NULL, CB_OPT_AUTO_ZOOM, NULL, NULL, opt.auto_zoom); - feh_menu_add_toggle_entry(mm, "Freeze Window Size", NULL, NULL, - CB_OPT_FREEZE_WINDOW, NULL, NULL, opt.geom_flags); - feh_menu_add_toggle_entry(mm, "Fullscreen", NULL, NULL, - CB_OPT_FULLSCREEN, NULL, NULL, m->fehwin->full_screen); - - feh_menu_add_entry(mm, NULL, NULL, NULL, 0, NULL, NULL); - - feh_menu_add_toggle_entry(mm, "Draw Filename", NULL, NULL, - CB_OPT_DRAW_FILENAME, NULL, NULL, opt.draw_filename); - feh_menu_add_toggle_entry(mm, "Draw Actions", NULL, NULL, - CB_OPT_DRAW_ACTIONS, NULL, NULL, opt.draw_actions); - feh_menu_add_toggle_entry(mm, "Keep HTTP Files", NULL, NULL, - CB_OPT_KEEP_HTTP, NULL, NULL, opt.keep_http); + feh_menu_add_toggle_entry(mm, "Auto-Zoom", NULL, CB_OPT_AUTO_ZOOM, + 0, NULL, opt.zoom_mode); + feh_menu_add_toggle_entry(mm, "Freeze Window Size", NULL, + CB_OPT_FREEZE_WINDOW, 0, NULL, opt.geom_flags); + feh_menu_add_toggle_entry(mm, "Fullscreen", NULL, + CB_OPT_FULLSCREEN, 0, NULL, m->fehwin->full_screen); + feh_menu_add_toggle_entry(mm, "Keep viewport zoom & pos", NULL, + CB_OPT_KEEP_ZOOM_VP, 0, NULL, opt.keep_zoom_vp); + + feh_menu_add_entry(mm, NULL, NULL, 0, 0, NULL); + + feh_menu_add_toggle_entry(mm, "Draw Filename", NULL, + CB_OPT_DRAW_FILENAME, 0, NULL, opt.draw_filename); + feh_menu_add_toggle_entry(mm, "Draw Actions", NULL, + CB_OPT_DRAW_ACTIONS, 0, NULL, opt.draw_actions); + feh_menu_add_toggle_entry(mm, "Keep HTTP Files", NULL, + CB_OPT_KEEP_HTTP, 0, NULL, opt.keep_http); mm->func_free = feh_menu_func_free_options; return(mm); } @@ -1,6 +1,7 @@ /* menu.h Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -85,12 +86,11 @@ struct _feh_menu_list { struct _feh_menu_item { int state; - Imlib_Image icon; char *text; char *submenu; int action; void (*func_free) (void *data); - void *data; + unsigned short data; feh_menu_item *next; feh_menu_item *prev; unsigned char is_toggle; @@ -130,12 +130,12 @@ void feh_menu_show_at_submenu(feh_menu * m, feh_menu * parent_m, feh_menu_item * void feh_menu_hide(feh_menu * m, int func_free); void feh_menu_show(feh_menu * m); feh_menu_item *feh_menu_add_entry(feh_menu * m, char *text, - Imlib_Image icon, char *submenu, - int action, void *data, void (*func_free) (void *data)); + char *submenu, + int action, unsigned short data, void (*func_free) (void *data)); feh_menu_item *feh_menu_add_toggle_entry(feh_menu * m, char *text, - Imlib_Image icon, char *submenu, - int action, void *data, void (*func_free) (void *data), int setting); -void feh_menu_entry_get_size(feh_menu * m, feh_menu_item * i, int *w, int *h); + char *submenu, + int action, unsigned short data, void (*func_free) (void *data), int setting); +void feh_menu_entry_get_size(feh_menu_item * i, int *w, int *h); void feh_menu_calc_size(feh_menu * m); void feh_menu_draw_item(feh_menu_item * i, Imlib_Image im, int ox, int oy); void feh_menu_redraw(feh_menu * m); @@ -143,7 +143,6 @@ void feh_menu_move(feh_menu * m, int x, int y); void feh_menu_slide_all_menus_relative(int dx, int dy); void feh_menu_init_main(void); void feh_menu_init_single_win(void); -void feh_menu_init_about_win(void); void feh_menu_init_common(void); void feh_menu_init_thumbnail_viewer(void); void feh_menu_init_thumbnail_win(void); @@ -168,7 +167,6 @@ void feh_menu_select_submenu(feh_menu * selected_menu); extern feh_menu *menu_root; extern feh_menu *menu_single_win; -extern feh_menu *menu_about_win; extern feh_menu *menu_thumbnail_viewer; extern feh_menu *menu_thumbnail_win; extern Window menu_cover; diff --git a/src/multiwindow.c b/src/multiwindow.c index c46e453..abbf6c9 100644 --- a/src/multiwindow.c +++ b/src/multiwindow.c @@ -28,30 +28,20 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "timers.h" #include "filelist.h" #include "options.h" +#include "signals.h" void init_multiwindow_mode(void) { winwidget w = NULL; gib_list *l; - feh_file *file = NULL; + + if (!opt.title) + opt.title = PACKAGE " - %f"; mode = "multiwindow"; for (l = filelist; l; l = l->next) { - char *s = NULL; - int len = 0; - file = FEH_FILE(l->data); - current_file = l; - - if (!opt.title) { - len = strlen(PACKAGE " - ") + strlen(file->filename) + 1; - s = emalloc(len); - snprintf(s, len, PACKAGE " - %s", file->filename); - } else { - s = estrdup(feh_printf(opt.title, file)); - } - - if ((w = winwidget_create_from_file(l, s, WIN_TYPE_SINGLE)) != NULL) { + if ((w = winwidget_create_from_file(l, WIN_TYPE_SINGLE)) != NULL) { winwidget_show(w); if (opt.reload > 0) feh_add_unique_timer(cb_reload_timer, w, opt.reload); @@ -61,7 +51,7 @@ void init_multiwindow_mode(void) D(("EEEK. Couldn't load image in multiwindow mode. " "I 'm not sure if this is a problem\n")); } - free(s); } + return; } diff --git a/src/options.c b/src/options.c index 2206f3e..d38ce45 100644 --- a/src/options.c +++ b/src/options.c @@ -1,6 +1,7 @@ /* options.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 @@ -23,15 +24,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include <strings.h> #include "feh.h" #include "filelist.h" #include "options.h" static void check_options(void); -static void feh_create_default_config(char *rcfile); -static void feh_parse_option_array(int argc, char **argv); -static void feh_parse_environment_options(void); -static void feh_check_theme_options(int arg, char **argv); +static void feh_getopt_theme(int argc, char **argv); +static void feh_parse_option_array(int argc, char **argv, int finalrun); +static void feh_check_theme_options(char **argv); static void feh_parse_options_from_string(char *opts); static void feh_load_options_for_theme(char *theme); static void show_usage(void); @@ -53,52 +54,40 @@ void init_parse_options(int argc, char **argv) opt.display = 1; opt.aspect = 1; opt.slideshow_delay = 0.0; + opt.conversion_timeout = -1; opt.thumb_w = 60; opt.thumb_h = 60; opt.thumb_redraw = 10; + opt.scroll_step = 20; opt.menu_font = estrdup(DEFAULT_MENU_FONT); opt.font = NULL; - opt.image_bg = estrdup("default"); - opt.menu_bg = estrdup(PREFIX "/share/feh/images/menubg_default.png"); - opt.menu_style = estrdup(PREFIX "/share/feh/fonts/menu.style"); + opt.max_height = opt.max_width = UINT_MAX; - opt.reload_button = 0; - opt.pan_button = 1; - opt.zoom_button = 2; - opt.menu_button = 3; - opt.menu_ctrl_mask = 0; - opt.prev_button = 4; - opt.next_button = 5; - - opt.draw_actions = 0; - - opt.rotate_button = 2; - opt.no_rotate_ctrl_mask = 0; - opt.blur_button = 1; - opt.no_blur_ctrl_mask = 0; + opt.zoom_rate = 1.25; opt.start_list_at = NULL; opt.jump_on_resort = 1; - opt.builtin_http = 0; - - opt.xinerama = 0; opt.screen_clip = 1; + opt.cache_size = 4; #ifdef HAVE_LIBXINERAMA /* if we're using xinerama, then enable it by default */ opt.xinerama = 1; + opt.xinerama_index = -1; #endif /* HAVE_LIBXINERAMA */ +#ifdef HAVE_INOTIFY + opt.auto_reload = 1; +#endif /* HAVE_INOTIFY */ + opt.use_conversion_cache = 1; + + feh_getopt_theme(argc, argv); - D(("About to parse env options (if any)\n")); - /* Check for and parse any options in FEH_OPTIONS */ - feh_parse_environment_options(); + D(("About to check for theme configuration\n")); + feh_check_theme_options(argv); D(("About to parse commandline options\n")); /* Parse the cmdline args */ - feh_parse_option_array(argc, argv); - - D(("About to check for theme configuration\n")); - feh_check_theme_options(argc, argv); + feh_parse_option_array(argc, argv, 1); /* If we have a filelist to read, do it now */ if (opt.filelistfile) { @@ -111,9 +100,6 @@ void init_parse_options(int argc, char **argv) D(("Options parsed\n")); - if (opt.bgmode) - return; - filelist_len = gib_list_length(filelist); if (!filelist_len) show_mini_usage(); @@ -124,7 +110,7 @@ void init_parse_options(int argc, char **argv) return; } -static void feh_check_theme_options(int arg, char **argv) +static void feh_check_theme_options(char **argv) { if (!theme) { /* This prevents screw up when running src/feh or ./feh */ @@ -141,53 +127,65 @@ static void feh_check_theme_options(int arg, char **argv) free(theme); return; - arg = 0; } static void feh_load_options_for_theme(char *theme) { FILE *fp = NULL; - char *home; + char *home = getenv("HOME"); char *rcpath = NULL; + char *oldrcpath = NULL; + char *confbase = getenv("XDG_CONFIG_HOME"); + // s, s1 and s2 must always have identical size char s[1024], s1[1024], s2[1024]; int cont = 0; int bspos; - if (opt.rcfile) { - if ((fp = fopen(opt.rcfile, "r")) == NULL) { - weprintf("couldn't load the specified rcfile %s\n", opt.rcfile); - return; - } - } else { - home = getenv("HOME"); - if (!home) - eprintf("D'oh! Please define HOME in your environment! " - "It would really help me out...\n"); - rcpath = estrjoin("/", home, ".fehrc", NULL); - D(("Trying %s for config\n", rcpath)); - fp = fopen(rcpath, "r"); - - if (!fp && ((fp = fopen("/etc/fehrc", "r")) == NULL)) { - feh_create_default_config(rcpath); - - if ((fp = fopen(rcpath, "r")) == NULL) - return; - } - - free(rcpath); + if (confbase) + rcpath = estrjoin("/", confbase, "feh/themes", NULL); + else if (home) + rcpath = estrjoin("/", home, ".config/feh/themes", NULL); + else { + weprintf("You have no HOME, cannot read configuration"); + return; } + oldrcpath = estrjoin("/", home, ".fehrc", NULL); + + fp = fopen(rcpath, "r"); + + free(rcpath); + + if (!fp && ((fp = fopen(oldrcpath, "r")) != NULL)) + weprintf("The theme config file was moved from ~/.fehrc to " + "~/.config/feh/themes. Run\n" + " mkdir -p ~/.config/feh; mv ~/.fehrc ~/.config/feh/themes\n" + "to fix this."); + + free(oldrcpath); + + if (!fp && ((fp = fopen("/etc/feh/themes", "r")) == NULL)) + return; + /* Oooh. We have an options file :) */ for (; fgets(s, sizeof(s), fp);) { s1[0] = '\0'; s2[0] = '\0'; if (cont) { + /* + * fgets ensures that s contains no more than 1023 characters + * (+ 1 null byte) + */ sscanf(s, " %[^\n]\n", (char *) &s2); if (!*s2) break; D(("Got continued options %s\n", s2)); } else { + /* + * fgets ensures that s contains no more than 1023 characters + * (+ 1 null byte) + */ sscanf(s, "%s %[^\n]\n", (char *) &s1, (char *) &s2); if (!(*s1) || (!*s2) || (*s1 == '\n') || (*s1 == '#')) { cont = 0; @@ -221,67 +219,52 @@ static void feh_load_options_for_theme(char *theme) return; } -static void feh_parse_environment_options(void) -{ - char *opts; - - if ((opts = getenv("FEH_OPTIONS")) == NULL) - return; - - weprintf - ("The FEH_OPTIONS configuration method is depreciated and will soon die.\n" - "Use the .fehrc configuration file instead."); - - /* We definitely have some options to parse */ - feh_parse_options_from_string(opts); - return; -} - /* FIXME This function is a crufty bitch ;) */ static void feh_parse_options_from_string(char *opts) { - char **list = NULL; + char *list[sizeof(char *) * 64]; int num = 0; char *s; char *t; char last = 0; - int inquote = 0; + char inquote = 0; int i = 0; /* So we don't reinvent the wheel (not again, anyway), we use the getopt_long function to do this parsing as well. This means it has to look like the real argv ;) */ - list = malloc(sizeof(char *)); - list[num++] = estrdup(PACKAGE); for (s = opts, t = opts;; t++) { - if ((*t == ' ') && !(inquote)) { + + if (num > 64) + eprintf(PACKAGE " does not support more than 64 words per " + "theme definition.\n Please shorten your lines."); + + if ((*t == ' ') && !inquote) { *t = '\0'; num++; - list = erealloc(list, sizeof(char *) * num); list[num - 1] = feh_string_normalize(s); s = t + 1; } else if (*t == '\0') { num++; - list = erealloc(list, sizeof(char *) * num); list[num - 1] = feh_string_normalize(s); break; - } else if (*t == '\"' && last != '\\') - inquote = !(inquote); + } else if ((*t == inquote) && (last != '\\')) { + inquote = 0; + } else if (((*t == '\"') || (*t == '\'')) && (last != '\\') && !inquote) + inquote = *t; last = *t; } - feh_parse_option_array(num, list); + feh_parse_option_array(num, list, 0); for (i = 0; i < num; i++) if (list[i]) free(list[i]); - if (list) - free(list); return; } @@ -301,6 +284,9 @@ char *feh_string_normalize(char *str) else if ((*s == '\"') && (last == '\\')) ret[i++] = '\"'; else if ((*s == '\"') && (last == 0)); + else if ((*s == '\'') && (last == '\\')) + ret[i++] = '\''; + else if ((*s == '\'') && (last == 0)); else if ((*s == ' ') && (last == '\\')) ret[i++] = ' '; else @@ -308,7 +294,7 @@ char *feh_string_normalize(char *str) last = *s; } - if (i && ret[i - 1] == '\"') + if (i && ((ret[i - 1] == '\"') || (ret[i - 1] == '\''))) ret[i - 1] = '\0'; else ret[i] = '\0'; @@ -317,232 +303,249 @@ char *feh_string_normalize(char *str) return(estrdup(ret)); } -static void feh_parse_option_array(int argc, char **argv) +static void feh_getopt_theme(int argc, char **argv) +{ + static char stropts[] = "-T:"; + static struct option lopts[] = { + {"theme", 1, 0, 'T'}, + {0, 0, 0, 0} + }; + int optch = 0, cmdx = 0; + + opterr = 0; + + while ((optch = getopt_long(argc, argv, stropts, lopts, &cmdx)) != EOF) { + if (optch == 'T') + theme = estrdup(optarg); + } + + opterr = 1; + optind = 0; +} + +static void feh_parse_option_array(int argc, char **argv, int finalrun) { + int discard; static char stropts[] = - "a:A:b:B:cC:dD:e:E:f:Fg:GhH:iIj:J:kK:lL:mM:nNo:O:pPqQrR:sS:tT:uUvVwW:xXy:YzZ" - "0:1:2:4:5:8:9:.@:^:~:):|:_:+:"; + "a:A:b:B:C:dD:e:E:f:Fg:GhH:iIj:J:kK:lL:mM:nNo:O:pPqrR:sS:tT:uUvVwW:xXy:YzZ" + ".@:^:~:|:+:<:>:"; /* (*name, has_arg, *flag, val) See: struct option in getopts.h */ static struct option lopts[] = { - {"help" , 0, 0, 'h'}, - {"version" , 0, 0, 'v'}, - {"montage" , 0, 0, 'm'}, - {"collage" , 0, 0, 'c'}, - {"index" , 0, 0, 'i'}, - {"fullindex" , 0, 0, 'I'}, - {"verbose" , 0, 0, 'V'}, - {"borderless" , 0, 0, 'x'}, - {"keep-http" , 0, 0, 'k'}, - {"stretch" , 0, 0, 's'}, - {"multiwindow" , 0, 0, 'w'}, - {"recursive" , 0, 0, 'r'}, - {"randomize" , 0, 0, 'z'}, - {"list" , 0, 0, 'l'}, - {"quiet" , 0, 0, 'q'}, - {"loadable" , 0, 0, 'U'}, - {"unloadable" , 0, 0, 'u'}, - {"no-menus" , 0, 0, 'N'}, - {"full-screen" , 0, 0, 'F'}, /* deprecated */ - {"fullscreen" , 0, 0, 'F'}, - {"auto-zoom" , 0, 0, 'Z'}, - {"ignore-aspect" , 0, 0, 'X'}, - {"draw-filename" , 0, 0, 'd'}, - {"preload" , 0, 0, 'p'}, - {"reverse" , 0, 0, 'n'}, - {"thumbnails" , 0, 0, 't'}, - {"builtin" , 0, 0, 'Q'}, - {"scale-down" , 0, 0, '.'}, - {"no-jump-on-resort", 0, 0, 220}, - {"hide-pointer" , 0, 0, 'Y'}, - {"draw-actions" , 0, 0, 'G'}, - {"cache-thumbnails", 0, 0, 'P'}, - {"cycle-once" , 0, 0, 224}, - {"no-xinerama" , 0, 0, 225}, - {"no-rotate-ctrl-mask", 0, 0, 226}, - {"no-blur-ctrl-mask", 0, 0, 227}, - {"menu-ctrl-mask", 0, 0, 228}, - - {"output" , 1, 0, 'o'}, - {"output-only" , 1, 0, 'O'}, - {"action" , 1, 0, 'A'}, - {"limit-width" , 1, 0, 'W'}, - {"limit-height" , 1, 0, 'H'}, - {"reload" , 1, 0, 'R'}, - {"alpha" , 1, 0, 'a'}, - {"sort" , 1, 0, 'S'}, - {"theme" , 1, 0, 'T'}, - {"filelist" , 1, 0, 'f'}, - {"customlist" , 1, 0, 'L'}, - {"geometry" , 1, 0, 'g'}, - {"menu-font" , 1, 0, 'M'}, - {"thumb-width" , 1, 0, 'y'}, - {"thumb-height" , 1, 0, 'E'}, - {"slideshow-delay",1, 0, 'D'}, - {"font" , 1, 0, 'e'}, - {"title-font" , 1, 0, '@'}, - {"title" , 1, 0, '^'}, - {"thumb-title" , 1, 0, '~'}, - {"bg" , 1, 0, 'b'}, - {"fontpath" , 1, 0, 'C'}, - {"menu-bg" , 1, 0, ')'}, - {"image-bg" , 1, 0, 'B'}, - {"reload-button" , 1, 0, '0'}, - {"pan-button" , 1, 0, '1'}, - {"zoom-button" , 1, 0, '2'}, - {"menu-button" , 1, 0, '3'}, - {"prev-button" , 1, 0, '4'}, - {"next-button" , 1, 0, '5'}, - {"rotate-button" , 1, 0, '8'}, - {"blur-button" , 1, 0, '9'}, - {"start-at" , 1, 0, '|'}, - {"rcfile" , 1, 0, '_'}, - {"debug" , 0, 0, '+'}, - {"output-dir" , 1, 0, 'j'}, - {"bg-tile" , 1, 0, 200}, - {"bg-center" , 1, 0, 201}, - {"bg-scale" , 1, 0, 202}, - {"menu-style" , 1, 0, 204}, - {"zoom" , 1, 0, 205}, - {"no-screen-clip", 0, 0, 206}, - {"caption-path" , 1, 0, 'K'}, - {"action1" , 1, 0, 209}, - {"action2" , 1, 0, 210}, - {"action3" , 1, 0, 211}, - {"action4" , 1, 0, 212}, - {"action5" , 1, 0, 213}, - {"action6" , 1, 0, 214}, - {"action7" , 1, 0, 215}, - {"action8" , 1, 0, 216}, - {"action9" , 1, 0, 217}, - {"bg-fill" , 1, 0, 218}, - {"bg-max" , 1, 0, 219}, - {"index-name" , 1, 0, 230}, - {"index-size" , 1, 0, 231}, - {"index-dim" , 1, 0, 232}, - {"thumb-redraw" , 1, 0, 'J'}, - {"info" , 1, 0, 234}, - + {"debug" , 0, 0, OPTION_debug}, + {"scale-down" , 0, 0, OPTION_scale_down}, + {"max-dimension" , 1, 0, OPTION_max_dimension}, + {"min-dimension" , 1, 0, OPTION_min_dimension}, + {"title-font" , 1, 0, OPTION_title_font}, + {"action" , 1, 0, OPTION_action}, + {"image-bg" , 1, 0, OPTION_image_bg}, + {"fontpath" , 1, 0, OPTION_fontpath}, + {"slideshow-delay",1, 0, OPTION_slideshow_delay}, + {"thumb-height" , 1, 0, OPTION_thumb_height}, + {"full-screen" , 0, 0, OPTION_fullscreen}, /* deprecated */ + {"fullscreen" , 0, 0, OPTION_fullscreen}, + {"draw-actions" , 0, 0, OPTION_draw_actions}, + {"limit-height" , 1, 0, OPTION_limit_height}, + {"fullindex" , 0, 0, OPTION_fullindex}, + {"thumb-redraw" , 1, 0, OPTION_thumb_redraw}, + {"caption-path" , 1, 0, OPTION_caption_path}, + {"customlist" , 1, 0, OPTION_customlist}, + {"menu-font" , 1, 0, OPTION_menu_font}, + {"no-menus" , 0, 0, OPTION_no_menus}, + {"output-only" , 1, 0, OPTION_output_only}, + {"cache-thumbnails", 0, 0, OPTION_cache_thumbnails}, + {"reload" , 1, 0, OPTION_reload}, + {"sort" , 1, 0, OPTION_sort}, + {"theme" , 1, 0, OPTION_theme}, + {"loadable" , 0, 0, OPTION_loadable}, + {"verbose" , 0, 0, OPTION_verbose}, + {"limit-width" , 1, 0, OPTION_limit_width}, + {"ignore-aspect" , 0, 0, OPTION_ignore_aspect}, + {"hide-pointer" , 0, 0, OPTION_hide_pointer}, + {"auto-zoom" , 0, 0, OPTION_auto_zoom}, + {"title" , 1, 0, OPTION_title}, + {"alpha" , 1, 0, OPTION_alpha}, + {"bg" , 1, 0, OPTION_bg}, + {"draw-filename" , 0, 0, OPTION_draw_filename}, + {"font" , 1, 0, OPTION_font}, + {"filelist" , 1, 0, OPTION_filelist}, + {"geometry" , 1, 0, OPTION_geometry}, + {"help" , 0, 0, OPTION_help}, + {"index" , 0, 0, OPTION_index}, + {"output-dir" , 1, 0, OPTION_output_dir}, + {"keep-http" , 0, 0, OPTION_keep_http}, + {"list" , 0, 0, OPTION_list}, + {"montage" , 0, 0, OPTION_montage}, + {"reverse" , 0, 0, OPTION_reverse}, + {"output" , 1, 0, OPTION_output}, + {"preload" , 0, 0, OPTION_preload}, + {"quiet" , 0, 0, OPTION_quiet}, + {"recursive" , 0, 0, OPTION_recursive}, + {"stretch" , 0, 0, OPTION_stretch}, + {"thumbnails" , 0, 0, OPTION_thumbnails}, + {"unloadable" , 0, 0, OPTION_unloadable}, + {"version" , 0, 0, OPTION_version}, + {"multiwindow" , 0, 0, OPTION_multiwindow}, + {"borderless" , 0, 0, OPTION_borderless}, + {"thumb-width" , 1, 0, OPTION_thumb_width}, + {"randomize" , 0, 0, OPTION_randomize}, + {"start-at" , 1, 0, OPTION_start_at}, + {"thumb-title" , 1, 0, OPTION_thumb_title}, + {"bg-tile" , 0, 0, OPTION_bg_title}, + {"bg-center" , 0, 0, OPTION_bg_center}, + {"bg-scale" , 0, 0, OPTION_bg_scale}, + {"zoom" , 1, 0, OPTION_zoom}, + {"zoom-step" , 1, 0, OPTION_zoom_step}, + {"no-screen-clip", 0, 0, OPTION_no_screen_clip}, + {"index-info" , 1, 0, OPTION_index_info}, + {"magick-timeout", 1, 0, OPTION_magick_timeout}, + {"action1" , 1, 0, OPTION_action1}, + {"action2" , 1, 0, OPTION_action2}, + {"action3" , 1, 0, OPTION_action3}, + {"action4" , 1, 0, OPTION_action4}, + {"action5" , 1, 0, OPTION_action5}, + {"action6" , 1, 0, OPTION_action6}, + {"action7" , 1, 0, OPTION_action7}, + {"action8" , 1, 0, OPTION_action8}, + {"action9" , 1, 0, OPTION_action9}, + {"bg-fill" , 0, 0, OPTION_bg_fill}, + {"bg-max" , 0, 0, OPTION_bg_max}, + {"no-jump-on-resort", 0, 0, OPTION_no_jump_on_resort}, + {"edit" , 0, 0, OPTION_edit}, +#ifdef HAVE_LIBEXIF + {"draw-exif" , 0, 0, OPTION_draw_exif}, + {"auto-rotate" , 0, 0, OPTION_auto_rotate}, +#endif + {"no-xinerama" , 0, 0, OPTION_no_xinerama}, + {"draw-tinted" , 0, 0, OPTION_draw_tinted}, + {"info" , 1, 0, OPTION_info}, + {"tap-zones" , 0, 0, OPTION_tap_zones}, + {"force-aliasing", 0, 0, OPTION_force_aliasing}, + {"no-fehbg" , 0, 0, OPTION_no_fehbg}, + {"keep-zoom-vp" , 0, 0, OPTION_keep_zoom_vp}, + {"scroll-step" , 1, 0, OPTION_scroll_step}, + {"xinerama-index", 1, 0, OPTION_xinerama_index}, + {"insecure" , 0, 0, OPTION_insecure}, + {"no-recursive" , 0, 0, OPTION_no_recursive}, + {"cache-size" , 1, 0, OPTION_cache_size}, + {"on-last-slide" , 1, 0, OPTION_on_last_slide}, + {"conversion-timeout" , 1, 0, OPTION_conversion_timeout}, + {"version-sort" , 0, 0, OPTION_version_sort}, + {"offset" , 1, 0, OPTION_offset}, +#ifdef HAVE_INOTIFY + {"auto-reload" , 0, 0, OPTION_auto_reload}, +#endif + {"class" , 1, 0, OPTION_class}, + {"no-conversion-cache", 0, 0, OPTION_no_conversion_cache}, + {"window-id", 1, 0, OPTION_window_id}, {0, 0, 0, 0} }; int optch = 0, cmdx = 0; - int i = 0; - /* Now to pass some optionarinos */ while ((optch = getopt_long(argc, argv, stropts, lopts, &cmdx)) != EOF) { D(("Got option, getopt calls it %d, or %c\n", optch, optch)); switch (optch) { case 0: break; - case 'h': - show_usage(); - break; - case 'v': - show_version(); - break; - case 'm': - opt.index = 1; - opt.index_show_name = 0; - opt.index_show_size = 0; - opt.index_show_dim = 0; + case OPTION_debug: + opt.debug = 1; break; - case 'c': - opt.collage = 1; + case OPTION_max_dimension: + opt.filter_by_dimensions = 1; + XParseGeometry(optarg, &discard, &discard, &opt.max_width, &opt.max_height); + if (opt.max_width == 0) + opt.max_width = UINT_MAX; + if (opt.max_height == 0) + opt.max_height = UINT_MAX; break; - case 'i': - opt.index = 1; - opt.index_show_name = 1; - opt.index_show_size = 0; - opt.index_show_dim = 0; + case OPTION_min_dimension: + opt.filter_by_dimensions = 1; + XParseGeometry(optarg, &discard, &discard, &opt.min_width, &opt.min_height); break; - case '.': + case OPTION_scale_down: opt.scale_down = 1; break; - case 'I': - opt.index = 1; - opt.index_show_name = 1; - opt.index_show_size = 1; - opt.index_show_dim = 1; - break; - case 'l': - opt.list = 1; - opt.display = 0; - break; - case 'Q': - opt.builtin_http = 1; - break; - case 'L': - opt.customlist = estrdup(optarg); - opt.display = 0; - break; - case 'M': - free(opt.menu_font); - opt.menu_font = estrdup(optarg); - break; - case '+': - opt.debug = 1; - break; - case 'n': - opt.reverse = 1; + case OPTION_title_font: + opt.title_font = estrdup(optarg); break; - case 'g': - opt.geom_flags = XParseGeometry(optarg, &opt.geom_x, &opt.geom_y, &opt.geom_w, &opt.geom_h); + case OPTION_action: + opt.actions[0] = estrdup(optarg); break; - case 'N': - opt.no_menus = 1; + case OPTION_image_bg: + opt.image_bg = estrdup(optarg); break; - case 'V': - opt.verbose = 1; + case OPTION_fontpath: + D(("adding fontpath %s\n", optarg)); + imlib_add_path_to_font_path(optarg); break; - case 'q': - opt.quiet = 1; + case OPTION_slideshow_delay: + opt.slideshow_delay = atof(optarg); + if (opt.slideshow_delay < 0.0) { + opt.slideshow_delay *= (-1); + opt.paused = 1; + } else { + opt.paused = 0; + } break; - case 'x': - opt.borderless = 1; + case OPTION_thumb_height: + opt.thumb_h = atoi(optarg); break; - case 'k': - opt.keep_http = 1; + case OPTION_fullscreen: + opt.full_screen = 1; break; - case 's': - opt.stretch = 1; + case OPTION_draw_actions: + opt.draw_actions = 1; break; - case 'w': - opt.multiwindow = 1; + case OPTION_limit_height: + opt.limit_h = atoi(optarg); break; - case 'r': - opt.recursive = 1; + case OPTION_fullindex: + opt.index = 1; + opt.index_info = estrdup("%n\n%S\n%wx%h"); break; - case 'z': - opt.randomize = 1; + case OPTION_thumb_redraw: + opt.thumb_redraw = atoi(optarg); break; - case 'd': - opt.draw_filename = 1; + case OPTION_caption_path: + opt.caption_path = estrdup(optarg); break; - case 'F': - opt.full_screen = 1; + case OPTION_customlist: + opt.customlist = estrdup(optarg); + opt.display = 0; break; - case 'Z': - opt.auto_zoom = 1; + case OPTION_menu_font: + free(opt.menu_font); + opt.menu_font = estrdup(optarg); break; - case 'U': - opt.loadables = 1; - opt.display = 0; + case OPTION_no_menus: + opt.no_menus = 1; break; - case 'u': - opt.unloadables = 1; + case OPTION_output_only: + opt.output = 1; + opt.output_file = estrdup(optarg); opt.display = 0; break; - case 'p': - opt.preload = 1; + case OPTION_cache_thumbnails: + opt.cache_thumbnails = 1; break; - case 'X': - opt.aspect = 0; + case OPTION_reload: + opt.reload = atof(optarg); + opt.use_conversion_cache = 0; +#ifdef HAVE_INOTIFY + opt.auto_reload = 0; +#endif break; - case 'S': + case OPTION_sort: if (!strcasecmp(optarg, "name")) opt.sort = SORT_NAME; + else if (!strcasecmp(optarg, "none")) + opt.sort = SORT_NONE; else if (!strcasecmp(optarg, "filename")) opt.sort = SORT_FILENAME; + else if (!strcasecmp(optarg, "dirname")) + opt.sort = SORT_DIRNAME; + else if (!strcasecmp(optarg, "mtime")) + opt.sort = SORT_MTIME; else if (!strcasecmp(optarg, "width")) opt.sort = SORT_WIDTH; else if (!strcasecmp(optarg, "height")) @@ -558,219 +561,304 @@ static void feh_parse_option_array(int argc, char **argv) "sort by filename", optarg); opt.sort = SORT_FILENAME; } + if (opt.randomize) { + weprintf("commandline contains --randomize and --sort. " + "--randomize has been unset"); + opt.randomize = 0; + } break; - case 'o': - opt.output = 1; - opt.output_file = estrdup(optarg); + case OPTION_theme: + theme = estrdup(optarg); break; - case 'O': - opt.output = 1; - opt.output_file = estrdup(optarg); + case OPTION_loadable: + opt.loadables = 1; opt.display = 0; break; - case 'T': - theme = estrdup(optarg); + case OPTION_verbose: + opt.verbose = 1; break; - case 'C': - D(("adding fontpath %s\n", optarg)); - imlib_add_path_to_font_path(optarg); + case OPTION_limit_width: + opt.limit_w = atoi(optarg); break; - case 'e': - opt.font = estrdup(optarg); + case OPTION_ignore_aspect: + opt.aspect = 0; break; - case '@': - opt.title_font = estrdup(optarg); + case OPTION_hide_pointer: + opt.hide_pointer = 1; + break; + case OPTION_auto_zoom: + opt.zoom_mode = ZOOM_MODE_MAX; break; - case '^': + case OPTION_title: opt.title = estrdup(optarg); break; - case '~': - opt.thumb_title = estrdup(optarg); + case OPTION_alpha: + opt.alpha = 1; + opt.alpha_level = 255 - atoi(optarg); break; - case 'b': + case OPTION_bg: opt.bg = 1; opt.bg_file = estrdup(optarg); break; - case '_': - opt.rcfile = estrdup(optarg); + case OPTION_draw_filename: + opt.draw_filename = 1; break; - case 'A': - opt.actions[0] = estrdup(optarg); + case OPTION_font: + opt.font = estrdup(optarg); break; - case 'W': - opt.limit_w = atoi(optarg); + case OPTION_filelist: + if (!strcmp(optarg, "-")) + opt.filelistfile = estrdup("/dev/stdin"); + else + opt.filelistfile = estrdup(optarg); break; - case 'H': - opt.limit_h = atoi(optarg); + case OPTION_geometry: + opt.geom_enabled = 1; + opt.geom_flags = XParseGeometry(optarg, &opt.geom_x, + &opt.geom_y, &opt.geom_w, &opt.geom_h); break; - case 'y': - opt.thumb_w = atoi(optarg); + case OPTION_help: + show_usage(); break; - case 'E': - opt.thumb_h = atoi(optarg); + case OPTION_index: + opt.index = 1; + opt.index_info = estrdup("%n"); break; - case ')': - free(opt.menu_bg); - opt.menu_bg = estrdup(optarg); + case OPTION_output_dir: + opt.output_dir = estrdup(optarg); break; - case 'B': - free(opt.image_bg); - opt.image_bg = estrdup(optarg); + case OPTION_keep_http: + opt.keep_http = 1; break; - case 'D': - opt.slideshow_delay = atof(optarg); - if (opt.slideshow_delay < 0.0) { - opt.slideshow_delay *= (-1); - opt.paused = 1; - } + case OPTION_list: + opt.list = 1; + opt.display = 0; break; - case 'R': - opt.reload = atoi(optarg); + case OPTION_montage: + opt.index = 1; break; - case 'a': - opt.alpha = 1; - opt.alpha_level = 255 - atoi(optarg); + case OPTION_reverse: + opt.reverse = 1; break; - case 'f': - opt.filelistfile = estrdup(optarg); + case OPTION_output: + opt.output = 1; + opt.output_file = estrdup(optarg); break; - case '0': - opt.reload_button = atoi(optarg); + case OPTION_preload: + opt.preload = 1; break; - case '1': - opt.pan_button = atoi(optarg); + case OPTION_quiet: + opt.quiet = 1; break; - case '2': - opt.zoom_button = atoi(optarg); + case OPTION_recursive: + opt.recursive = 1; break; - case '3': - opt.menu_button = atoi(optarg); + case OPTION_stretch: + opt.stretch = 1; break; - case '4': - opt.prev_button = atoi(optarg); + case OPTION_thumbnails: + opt.thumbs = 1; + opt.index_info = estrdup("%n"); break; - case '5': - opt.next_button = atoi(optarg); + case OPTION_unloadable: + opt.unloadables = 1; + opt.display = 0; break; - case '8': - opt.rotate_button = atoi(optarg); + case OPTION_version: + show_version(); break; - case '9': - opt.blur_button = atoi(optarg); + case OPTION_multiwindow: + opt.multiwindow = 1; break; - case '|': - opt.start_list_at = estrdup(optarg); + case OPTION_borderless: + opt.borderless = 1; break; - case 't': - opt.thumbs = 1; - opt.index_show_name = 1; - opt.index_show_size = 0; - opt.index_show_dim = 0; + case OPTION_thumb_width: + opt.thumb_w = atoi(optarg); break; - case 'j': - opt.output_dir = estrdup(optarg); + case OPTION_randomize: + opt.randomize = 1; + if (opt.sort != SORT_NONE) { + weprintf("commandline contains --sort and --randomize. " + "--sort has been unset"); + opt.sort = SORT_NONE; + } + break; + case OPTION_start_at: + opt.start_list_at = estrdup(optarg); + break; + case OPTION_thumb_title: + opt.thumb_title = estrdup(optarg); break; - case 200: + case OPTION_bg_title: opt.bgmode = BG_MODE_TILE; - opt.output_file = estrdup(optarg); break; - case 201: + case OPTION_bg_center: opt.bgmode = BG_MODE_CENTER; - opt.output_file = estrdup(optarg); break; - case 202: + case OPTION_bg_scale: opt.bgmode = BG_MODE_SCALE; - opt.output_file = estrdup(optarg); - break; - case 218: - opt.bgmode = BG_MODE_FILL; - opt.output_file = estrdup(optarg); - break; - case 219: - opt.bgmode = BG_MODE_MAX; - opt.output_file = estrdup(optarg); break; - case 204: - free(opt.menu_style); - opt.menu_style = estrdup(optarg); + case OPTION_zoom: + if (!strcmp("fill", optarg)) + opt.zoom_mode = ZOOM_MODE_FILL; + else if (!strcmp("max", optarg)) + opt.zoom_mode = ZOOM_MODE_MAX; + else + opt.default_zoom = atoi(optarg); break; - case 205: - opt.default_zoom = atoi(optarg); - break; - case 206: + case OPTION_no_screen_clip: opt.screen_clip = 0; break; - case 'K': - opt.caption_path = estrdup(optarg); + case OPTION_index_info: + opt.index_info = estrdup(optarg); break; - case 209: + case OPTION_magick_timeout: + weprintf("--magick-timeout is deprecated, please use --conversion-timeout instead"); + opt.conversion_timeout = atoi(optarg); + break; + case OPTION_action1: opt.actions[1] = estrdup(optarg); break; - case 210: + case OPTION_action2: opt.actions[2] = estrdup(optarg); break; - case 211: + case OPTION_action3: opt.actions[3] = estrdup(optarg); break; - case 212: + case OPTION_action4: opt.actions[4] = estrdup(optarg); break; - case 213: + case OPTION_action5: opt.actions[5] = estrdup(optarg); break; - case 214: + case OPTION_action6: opt.actions[6] = estrdup(optarg); break; - case 215: + case OPTION_action7: opt.actions[7] = estrdup(optarg); break; - case 216: + case OPTION_action8: opt.actions[8] = estrdup(optarg); break; - case 217: + case OPTION_action9: opt.actions[9] = estrdup(optarg); break; - case 220: - opt.jump_on_resort = 0; + case OPTION_bg_fill: + opt.bgmode = BG_MODE_FILL; break; - case 'Y': - opt.hide_pointer = 1; + case OPTION_bg_max: + opt.bgmode = BG_MODE_MAX; break; - case 'G': - opt.draw_actions = 1; + case OPTION_no_jump_on_resort: + opt.jump_on_resort = 0; break; - case 'P': - opt.cache_thumbnails = 1; + case OPTION_edit: + opt.edit = 1; + break; +#ifdef HAVE_LIBEXIF + case OPTION_draw_exif: + opt.draw_exif = 1; break; - case 224: - opt.cycle_once = 1; + case OPTION_auto_rotate: +#if defined(IMLIB2_VERSION_MAJOR) && defined(IMLIB2_VERSION_MINOR) && defined(IMLIB2_VERSION_MICRO) && (IMLIB2_VERSION_MAJOR > 1 || IMLIB2_VERSION_MINOR > 7 || IMLIB2_VERSION_MICRO >= 5) + weprintf("This feh release was built with Imlib2 version %d.%d.%d, which transparently adjusts for image orientation according to EXIF data.", IMLIB2_VERSION_MAJOR, IMLIB2_VERSION_MINOR, IMLIB2_VERSION_MICRO); + weprintf("--auto-rotate would rotate an already correctly oriented image, resulting in incorrect orientation. It has been disabled in this build. Rebuild feh with Imlib2 <1.7.5 to enable --auto-rotate."); +#else + opt.auto_rotate = 1; +#endif break; - case 225: +#endif + case OPTION_no_xinerama: opt.xinerama = 0; break; - case 226: - opt.no_rotate_ctrl_mask = 1; + case OPTION_draw_tinted: + opt.text_bg = TEXT_BG_TINTED; break; - case 227: - opt.no_blur_ctrl_mask = 1; + case OPTION_info: + opt.info_cmd = estrdup(optarg); + if (opt.info_cmd[0] == ';') { + opt.draw_info = 0; + opt.info_cmd++; + } else { + opt.draw_info = 1; + } break; - case 228: - opt.menu_ctrl_mask = 1; + case OPTION_tap_zones: + opt.tap_zones = 1; break; - case 230: - opt.index_show_name = atoi(optarg); + case OPTION_force_aliasing: + opt.force_aliasing = 1; break; - case 231: - opt.index_show_size = atoi(optarg); + case OPTION_no_fehbg: + opt.no_fehbg = 1; break; - case 232: - opt.index_show_dim = atoi(optarg); + case OPTION_keep_zoom_vp: + opt.keep_zoom_vp = 1; break; - case 'J': - opt.thumb_redraw = atoi(optarg); + case OPTION_scroll_step: + opt.scroll_step = atoi(optarg); break; - case 234: - opt.info_cmd = estrdup(optarg); + case OPTION_xinerama_index: + opt.xinerama_index = atoi(optarg); + break; + case OPTION_insecure: + opt.insecure_ssl = 1; + break; + case OPTION_no_recursive: + opt.recursive = 0; + break; + case OPTION_cache_size: + opt.cache_size = atoi(optarg); + if (opt.cache_size < 0) + opt.cache_size = 0; + if (opt.cache_size > 2048) + opt.cache_size = 2048; + break; + case OPTION_on_last_slide: + if (!strcmp(optarg, "quit")) { + opt.on_last_slide = ON_LAST_SLIDE_QUIT; + } else if (!strcmp(optarg, "hold")) { + opt.on_last_slide = ON_LAST_SLIDE_HOLD; + } else if (!strcmp(optarg, "resume")) { + opt.on_last_slide = ON_LAST_SLIDE_RESUME; + } else { + weprintf("Unrecognized on-last-slide action \"%s\"." + "Supported actions: hold, resume, quit\n", optarg); + } + break; + case OPTION_conversion_timeout: + opt.conversion_timeout = atoi(optarg); + break; + case OPTION_version_sort: + opt.version_sort = 1; + break; + case OPTION_offset: + opt.offset_flags = XParseGeometry(optarg, &opt.offset_x, + &opt.offset_y, (unsigned int *)&discard, (unsigned int *)&discard); + break; +#ifdef HAVE_INOTIFY + case OPTION_auto_reload: + opt.auto_reload = 1; + break; +#endif + case OPTION_class: + opt.x11_class = estrdup(optarg); + break; + case OPTION_no_conversion_cache: + opt.use_conversion_cache = 0; + break; + case OPTION_window_id: + opt.x11_windowid = strtol(optarg, NULL, 0); + break; + case OPTION_zoom_step: + opt.zoom_rate = atof(optarg); + if ((opt.zoom_rate <= 0)) { + weprintf("Zooming disabled due to --zoom-step=%f", opt.zoom_rate); + opt.zoom_rate = 1.0; + } else { + opt.zoom_rate = 1 + ((float)opt.zoom_rate / 100); + } break; default: break; @@ -780,62 +868,90 @@ static void feh_parse_option_array(int argc, char **argv) /* Now the leftovers, which must be files */ if (optind < argc) { while (optind < argc) { + if (opt.reload) + original_file_items = gib_list_add_front(original_file_items, estrdup(argv[optind])); /* If recursive is NOT set, but the only argument is a directory name, we grab all the files in there, but not subdirs */ add_file_to_filelist_recursively(argv[optind++], FILELIST_FIRST); } } - - for (i = 0; i < 10; i++) { - if (opt.actions[i] && !opt.hold_actions[i] && (opt.actions[i][0] == ';')) { - opt.hold_actions[i] = 1; - opt.actions[i] = &opt.actions[i][1]; + else if (finalrun && !opt.filelistfile && !opt.bgmode) { + /* + * if --start-at is a non-local URL (i.e., does not start with file:///), + * behave as if "feh URL" was called (there is no directory we can load) + */ + if (opt.start_list_at && path_is_url(opt.start_list_at) && (strlen(opt.start_list_at) <= 8 || strncmp(opt.start_list_at, "file:///", 8) != 0)) { + add_file_to_filelist_recursively(opt.start_list_at, FILELIST_FIRST); + /* + * Otherwise, make "feh --start-at dir/file.jpg" behave like + * "feh --start-at dir/file.jpg dir". + */ + } else if (opt.start_list_at && strrchr(opt.start_list_at, '/')) { + /* + * feh can't candle urlencoded path components ("some%20dir" etc). + * Use libcurl to unescape them if --start-at is file://... + */ + if (strlen(opt.start_list_at) > 8 && strncmp(opt.start_list_at, "file:///", 8) == 0) { + char *unescaped_path = feh_http_unescape(opt.start_list_at); + if (unescaped_path != NULL) { + free(opt.start_list_at); + opt.start_list_at = estrdup(unescaped_path + 7); + free(unescaped_path); + } else { + char *new_path = estrdup(opt.start_list_at + 7); + free(opt.start_list_at); + opt.start_list_at = new_path; + } + } + char *target_directory = estrdup(opt.start_list_at); + char *filename_start = strrchr(target_directory, '/'); + if (filename_start) { + *filename_start = '\0'; + } + add_file_to_filelist_recursively(target_directory, FILELIST_FIRST); + original_file_items = gib_list_add_front(original_file_items, estrdup(target_directory)); + free(target_directory); + } else { + add_file_to_filelist_recursively(".", FILELIST_FIRST); } } /* So that we can safely be called again */ - optind = 1; + optind = 0; return; } static void check_options(void) { - if ((opt.index + opt.collage) > 1) { - weprintf("you can't use collage mode and index mode together.\n" - " I'm going with index"); - opt.collage = 0; - } + int i; + char *endptr; - if (opt.full_screen && opt.multiwindow) { - weprintf("you shouldn't combine multiwindow mode with full-screen mode,\n" - " Multiwindow mode has been disabled."); - opt.multiwindow = 0; + for (i = 0; i < 10; i++) { + if (opt.actions[i] && !opt.hold_actions[i] && (opt.actions[i][0] == ';')) { + opt.hold_actions[i] = 1; + opt.actions[i] = opt.actions[i] + 1; + } + opt.action_titles[i] = opt.actions[i]; + if (opt.actions[i] && (opt.actions[i][0] == '[')) { + if (((endptr = strchr(opt.actions[i], ']')) != NULL) + && (opt.actions[i][1] != ' ')) { + opt.action_titles[i] = opt.actions[i] + 1; + opt.actions[i] = endptr + 1; + *endptr = 0; + } + } } - if (opt.list && (opt.multiwindow || opt.index || opt.collage)) { - weprintf("list mode can't be combined with other processing modes,\n" - " list mode disabled."); - opt.list = 0; + if (opt.full_screen && opt.multiwindow) { + eprintf("You cannot combine --fullscreen with --multiwindow"); } - if (opt.sort && opt.randomize) { - weprintf("You cant sort AND randomize the filelist...\n" - "randomize mode has been unset\n"); - opt.randomize = 0; + if (opt.list && (opt.multiwindow || opt.index)) { + eprintf("You cannot combine --list with other modes"); } if (opt.loadables && opt.unloadables) { - weprintf("You cant show loadables AND unloadables...\n" - "you might as well use ls ;)\n" - "loadables only will be shown\n"); - opt.unloadables = 0; - } - - if (opt.thumb_title && (!opt.thumbs)) { - weprintf("Doesn't make sense to set thumbnail title when not in\n" - "thumbnail mode.\n"); - free(opt.thumb_title); - opt.thumb_title = NULL; + eprintf("You cannot combine --loadable with --unloadable"); } return; @@ -843,38 +959,70 @@ static void check_options(void) static void show_version(void) { - printf(PACKAGE " version " VERSION "\n"); + puts(PACKAGE " version " VERSION); + puts("Compile-time switches: " + +#ifdef HAVE_LIBCURL + "curl " +#endif + +#ifdef DEBUG + "debug " +#endif + +#ifdef HAVE_LIBEXIF + "exif " +#endif + +#ifdef HAVE_INOTIFY + "inotify " +#endif + +#ifdef INCLUDE_HELP + "help " +#endif + +#ifdef HAVE_LIBMAGIC + "magic " +#endif + +#if _FILE_OFFSET_BITS == 64 + "stat64 " +#endif + +#ifdef HAVE_STRVERSCMP + "verscmp " +#endif + +#ifdef HAVE_LIBXINERAMA + "xinerama " +#endif + + ); + exit(0); } void show_mini_usage(void) { - fprintf(stderr, PACKAGE " - No loadable images specified.\n" - "Use " PACKAGE " --help for detailed usage information\n"); + fputs(PACKAGE ": No loadable images specified.\n" +#ifdef INCLUDE_HELP + "See '" PACKAGE " --help' or 'man " PACKAGE "' for detailed usage information\n", +#else + "See 'man " PACKAGE "' for detailed usage information\n", +#endif + stderr); exit(1); } static void show_usage(void) { fputs( +#ifdef INCLUDE_HELP #include "help.inc" +#else + "See 'man " PACKAGE "'\n" +#endif , stdout); exit(0); } - -static void feh_create_default_config(char *rcfile) -{ - FILE *fp; - - if ((fp = fopen(rcfile, "w")) == NULL) { - weprintf("Unable to create default config file %s\n", rcfile); - return; - } - - fputs( -#include "fehrc.inc" - , fp); - fclose(fp); - - return; -} diff --git a/src/options.h b/src/options.h index e99c14c..74c12cd 100644 --- a/src/options.h +++ b/src/options.h @@ -1,6 +1,7 @@ /* options.h 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 @@ -26,14 +27,16 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef OPTIONS_H #define OPTIONS_H +enum on_last_slide_action { + ON_LAST_SLIDE_RESUME = 0, + ON_LAST_SLIDE_QUIT, + ON_LAST_SLIDE_HOLD +}; + struct __fehoptions { unsigned char multiwindow; unsigned char montage; - unsigned char collage; unsigned char index; - unsigned char index_show_name; - unsigned char index_show_dim; - unsigned char index_show_size; unsigned char thumbs; unsigned char slideshow; unsigned char recursive; @@ -46,12 +49,20 @@ struct __fehoptions { unsigned char aspect; unsigned char stretch; unsigned char keep_http; + unsigned char use_conversion_cache; unsigned char borderless; unsigned char randomize; unsigned char jump_on_resort; unsigned char full_screen; - unsigned char auto_zoom; unsigned char draw_filename; +#ifdef HAVE_LIBEXIF + unsigned char draw_exif; + unsigned char auto_rotate; +#endif +#ifdef HAVE_INOTIFY + unsigned char auto_reload; + int inotify_fd; +#endif unsigned char list; unsigned char quiet; unsigned char preload; @@ -60,76 +71,279 @@ struct __fehoptions { unsigned char reverse; unsigned char no_menus; unsigned char scale_down; - unsigned char builtin_http; unsigned char bgmode; unsigned char xinerama; unsigned char screen_clip; unsigned char hide_pointer; unsigned char draw_actions; + unsigned char draw_info; unsigned char cache_thumbnails; - unsigned char cycle_once; + unsigned char on_last_slide; unsigned char hold_actions[10]; + unsigned char text_bg; + unsigned char no_fehbg; + unsigned char keep_zoom_vp; + unsigned char insecure_ssl; + unsigned char filter_by_dimensions; + unsigned char edit; char *output_file; char *output_dir; char *bg_file; + char *image_bg; char *font; char *title_font; char *title; char *thumb_title; char *actions[10]; + char *action_titles[10]; char *fontpath; char *filelistfile; char *menu_font; char *customlist; - char *menu_bg; - char *image_bg; - char *rcfile; - char *menu_style; char *caption_path; char *start_list_at; char *info_cmd; + char *index_info; - gib_style *menu_style_l; - - unsigned char pan_button; - unsigned char zoom_button; - unsigned char menu_button; - unsigned char menu_ctrl_mask; - unsigned char prev_button; - unsigned char next_button; - - unsigned char rotate_button; - unsigned char blur_button; - unsigned char reload_button; - unsigned char no_rotate_ctrl_mask; - unsigned char no_blur_ctrl_mask; - unsigned char no_pan_ctrl_mask; - + int force_aliasing; + int tap_zones; int thumb_w; int thumb_h; int limit_w; int limit_h; unsigned int thumb_redraw; - int reload; + double reload; int sort; + int version_sort; int debug; + int geom_enabled; int geom_flags; int geom_x; int geom_y; unsigned int geom_w; unsigned int geom_h; + int offset_flags; + int offset_x; + int offset_y; int default_zoom; + int zoom_mode; + double zoom_rate; unsigned char adjust_reload; + int xinerama_index; + char *x11_class; + unsigned long int x11_windowid; + + /* signed in case someone wants to invert scrolling real quick */ + int scroll_step; + + // imlib cache size in mebibytes + int cache_size; + + unsigned int min_width, min_height, max_width, max_height; unsigned char mode; unsigned char paused; double slideshow_delay; + signed int conversion_timeout; + Imlib_Font menu_fn; }; +enum __feh_option { +OPTION_debug = '+', +OPTION_scale_down = '.', +OPTION_max_dimension = '<', +OPTION_min_dimension = '>', +OPTION_title_font = '@', +OPTION_action = 'A', +OPTION_image_bg = 'B', +OPTION_fontpath = 'C', +OPTION_slideshow_delay = 'D', +OPTION_thumb_height = 'E', +OPTION_fullscreen = 'F', +OPTION_draw_actions = 'G', +OPTION_limit_height = 'H', +OPTION_fullindex = 'I', +OPTION_thumb_redraw = 'J', +OPTION_caption_path = 'K', +OPTION_customlist = 'L', +OPTION_menu_font = 'M', +OPTION_no_menus = 'N', +OPTION_output_only = 'O', +OPTION_cache_thumbnails = 'P', +OPTION_reload = 'R', +OPTION_sort = 'S', +OPTION_theme = 'T', +OPTION_loadable = 'U', +OPTION_verbose = 'V', +OPTION_limit_width = 'W', +OPTION_ignore_aspect = 'X', +OPTION_hide_pointer = 'Y', +OPTION_auto_zoom = 'Z', +OPTION_title = '^', +OPTION_alpha = 'a', +OPTION_bg = 'b', +OPTION_draw_filename = 'd', +OPTION_font = 'e', +OPTION_filelist = 'f', +OPTION_geometry = 'g', +OPTION_help = 'h', +OPTION_index = 'i', +OPTION_output_dir = 'j', +OPTION_keep_http = 'k', +OPTION_list = 'l', +OPTION_montage = 'm', +OPTION_reverse = 'n', +OPTION_output = 'o', +OPTION_preload = 'p', +OPTION_quiet = 'q', +OPTION_recursive = 'r', +OPTION_stretch = 's', +OPTION_thumbnails = 't', +OPTION_unloadable = 'u', +OPTION_version = 'v', +OPTION_multiwindow = 'w', +OPTION_borderless = 'x', +OPTION_thumb_width = 'y', +OPTION_randomize = 'z', +OPTION_start_at = '|', +OPTION_thumb_title = '~', +OPTION_bg_title, +OPTION_bg_center, +OPTION_bg_scale, +OPTION_bg_fill, +OPTION_bg_max, +OPTION_zoom, +OPTION_zoom_step, +OPTION_zoom_in_rate, +OPTION_zoom_out_rate, +OPTION_keep_zoom_vp, +OPTION_no_screen_clip, +OPTION_index_info, +OPTION_magick_timeout, +OPTION_action1, +OPTION_action2, +OPTION_action3, +OPTION_action4, +OPTION_action5, +OPTION_action6, +OPTION_action7, +OPTION_action8, +OPTION_action9, +OPTION_no_jump_on_resort, +OPTION_edit, +OPTION_draw_exif, +OPTION_auto_rotate, +OPTION_no_xinerama, +OPTION_draw_tinted, +OPTION_info, +OPTION_tap_zones, +OPTION_force_aliasing, +OPTION_no_fehbg, +OPTION_scroll_step, +OPTION_xinerama_index, +OPTION_insecure, +OPTION_no_recursive, +OPTION_cache_size, +OPTION_on_last_slide, +OPTION_conversion_timeout, +OPTION_version_sort, +OPTION_offset, +OPTION_auto_reload, +OPTION_class, +OPTION_no_conversion_cache, +OPTION_window_id, +}; + +//typedef enum __fehoption fehoption; + +struct __fehkey { + unsigned int keysyms[3]; + unsigned int keystates[3]; + unsigned int state; + unsigned int button; + char *name; +}; + +enum key_action { + EVENT_menu_close = 0, + EVENT_menu_parent, + EVENT_menu_down, + EVENT_menu_up, + EVENT_menu_child, + EVENT_menu_select, + EVENT_scroll_left, + EVENT_scroll_right, + EVENT_scroll_down, + EVENT_scroll_up, + EVENT_scroll_left_page, + EVENT_scroll_right_page, + EVENT_scroll_down_page, + EVENT_scroll_up_page, + EVENT_prev_img, + EVENT_next_img, + EVENT_jump_back, + EVENT_jump_fwd, + EVENT_prev_dir, + EVENT_next_dir, + EVENT_jump_random, + EVENT_quit, + EVENT_close, + EVENT_remove, + EVENT_delete, + EVENT_jump_first, + EVENT_jump_last, + EVENT_action_0, + EVENT_action_1, + EVENT_action_2, + EVENT_action_3, + EVENT_action_4, + EVENT_action_5, + EVENT_action_6, + EVENT_action_7, + EVENT_action_8, + EVENT_action_9, + EVENT_zoom_in, + EVENT_zoom_out, + EVENT_zoom_default, + EVENT_zoom_fit, + EVENT_zoom_fill, + EVENT_size_to_image, + EVENT_render, + EVENT_toggle_actions, + EVENT_toggle_aliasing, + EVENT_toggle_auto_zoom, +#ifdef HAVE_LIBEXIF + EVENT_toggle_exif, +#endif + EVENT_toggle_filenames, + EVENT_toggle_info, + EVENT_toggle_pointer, + EVENT_toggle_caption, + EVENT_toggle_pause, + EVENT_toggle_menu, + EVENT_toggle_fullscreen, + EVENT_reload_image, + EVENT_save_image, + EVENT_save_filelist, + EVENT_orient_1, + EVENT_orient_3, + EVENT_flip, + EVENT_mirror, + EVENT_reload_minus, + EVENT_reload_plus, + EVENT_toggle_keep_vp, + EVENT_toggle_fixed_geometry, + EVENT_pan, + EVENT_zoom, + EVENT_blur, + EVENT_rotate, + EVENT_LIST_END +}; + void init_parse_options(int argc, char **argv); char *feh_string_normalize(char *str); diff --git a/src/signals.c b/src/signals.c index b3e118a..058b8c9 100644 --- a/src/signals.c +++ b/src/signals.c @@ -1,6 +1,6 @@ /* signals.c -Copyright (C) 2010 by Daniel Friesel +Copyright (C) 2010-2023 by 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 @@ -24,20 +24,29 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "feh.h" +#include "filelist.h" #include "winwidget.h" +#include "options.h" void feh_handle_signal(int); +volatile int sig_received = 0; +volatile int sig_exit = 0; -void setup_signal_handlers() +void setup_signal_handlers(void) { struct sigaction feh_sh; sigset_t feh_ss; if ( (sigemptyset(&feh_ss) == -1) || (sigaddset(&feh_ss, SIGUSR1) == -1) || - (sigaddset(&feh_ss, SIGUSR2) == -1)) + (sigaddset(&feh_ss, SIGUSR2) == -1) || + (sigaddset(&feh_ss, SIGALRM) == -1) || + (sigaddset(&feh_ss, SIGTERM) == -1) || + (sigaddset(&feh_ss, SIGQUIT) == -1) || + (sigaddset(&feh_ss, SIGINT) == -1) || + (sigaddset(&feh_ss, SIGTTIN) == -1)) { - weprintf("Failed to set up signal mask, SIGUSR1/2 won't work"); + weprintf("Failed to set up signal masks"); return; } @@ -47,9 +56,14 @@ void setup_signal_handlers() if ( (sigaction(SIGUSR1, &feh_sh, NULL) == -1) || - (sigaction(SIGUSR2, &feh_sh, NULL) == -1)) + (sigaction(SIGUSR2, &feh_sh, NULL) == -1) || + (sigaction(SIGALRM, &feh_sh, NULL) == -1) || + (sigaction(SIGTERM, &feh_sh, NULL) == -1) || + (sigaction(SIGQUIT, &feh_sh, NULL) == -1) || + (sigaction(SIGINT, &feh_sh, NULL) == -1) || + (sigaction(SIGTTIN, &feh_sh, NULL) == -1)) { - weprintf("Failed to set up signal handler, SIGUSR1/2 won't work"); + weprintf("Failed to set up signal handler"); return; } @@ -58,15 +72,23 @@ void setup_signal_handlers() void feh_handle_signal(int signo) { - winwidget winwid - = winwidget_get_first_window_of_type(WIN_TYPE_SLIDESHOW); - - if (winwid) { - if (signo == SIGUSR1) - slideshow_change_image(winwid, SLIDE_NEXT); - else if (signo == SIGUSR2) - slideshow_change_image(winwid, SLIDE_PREV); + switch (signo) { + case SIGALRM: + if (childpid) + killpg(childpid, SIGINT); + return; + case SIGTTIN: + // we were probably backgrounded while we were running + control_via_stdin = 0; + return; + case SIGINT: + case SIGTERM: + case SIGQUIT: + if (childpid) + killpg(childpid, SIGINT); + sig_exit = 128 + signo; + return; } - return; + sig_received = signo; } diff --git a/src/signals.h b/src/signals.h index 526285d..3d78b67 100644 --- a/src/signals.h +++ b/src/signals.h @@ -1,6 +1,6 @@ /* signals.h -Copyright (C) 2010 by Daniel Friesel +Copyright (C) 2010-2023 by 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 @@ -26,6 +26,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef SIGNALS_H #define SIGNALS_H -void setup_signal_handlers(); - +void setup_signal_handlers(void); +extern volatile int sig_exit; +extern volatile int sig_received; #endif diff --git a/src/slideshow.c b/src/slideshow.c index de10300..266cb2e 100644 --- a/src/slideshow.c +++ b/src/slideshow.c @@ -1,6 +1,7 @@ /* slideshow.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 @@ -34,158 +35,179 @@ void init_slideshow_mode(void) { winwidget w = NULL; int success = 0; - char *s = NULL; gib_list *l = filelist, *last = NULL; - feh_file *file = NULL; - for (l = filelist; l && opt.start_list_at; l = l->next) { + /* + * In theory, --start-at FILENAME is simple: Look for a file called + * FILENAME, start the filelist there, done. + * + * In practice, there are cases where this isn't sufficient. For instance, + * a user running 'feh --start-at hello.jpg /tmp' will expect feh to start + * at /tmp/hello.jpg, as if they had used + * 'feh --start-at /tmp/hello.jpg /tmp'. Similarly, XDG Desktop files + * may lead to the invocation 'feh --start-at /tmp/hello.jpg .' in /tmp, + * expecting the behaviour of 'feh --start-at ./hello.jpg .'. + * + * Since a good user experience is not about being technically correct, but + * about delivering the expected behaviour, we do some fuzzy matching + * here. In the worst case, this will cause --start-at to start at the + * wrong file. + */ + + // Try finding an exact filename match first + for (; l && opt.start_list_at; l = l->next) { if (!strcmp(opt.start_list_at, FEH_FILE(l->data)->filename)) { + free(opt.start_list_at); opt.start_list_at = NULL; break; } } + /* + * If it didn't work (opt.start_list_at is still set): Fall back to + * comparing just the filenames without directory prefixes. This may lead + * to false positives, but for now that's just the way it is. + */ + if (opt.start_list_at) { + char *current_filename; + char *start_at_filename = strrchr(opt.start_list_at, '/'); + if (start_at_filename) { + start_at_filename++; // We only care about the part after the '/' + } else { + start_at_filename = opt.start_list_at; + } + for (l = filelist; l && opt.start_list_at; l = l->next) { + current_filename = strrchr(FEH_FILE(l->data)->filename, '/'); + if (current_filename) { + current_filename++; // We only care about the part after the '/' + } else { + current_filename = FEH_FILE(l->data)->filename; + } + if (!strcmp(start_at_filename, current_filename)) { + free(opt.start_list_at); + opt.start_list_at = NULL; + break; + } + } + } + + // If that didn't work either, we're out of luck. if (opt.start_list_at) eprintf("--start-at %s: File not found in filelist", opt.start_list_at); + if (!opt.title) + opt.title = PACKAGE " [%u of %l] - %f"; + mode = "slideshow"; for (; l; l = l->next) { - file = FEH_FILE(l->data); if (last) { filelist = feh_file_remove_from_list(filelist, last); last = NULL; } current_file = l; - s = slideshow_create_name(file); - if ((w = winwidget_create_from_file(l, s, WIN_TYPE_SLIDESHOW)) != NULL) { - free(s); + if ((w = winwidget_create_from_file(l, WIN_TYPE_SLIDESHOW)) != NULL) { success = 1; winwidget_show(w); if (opt.slideshow_delay > 0.0) feh_add_timer(cb_slide_timer, w, opt.slideshow_delay, "SLIDE_CHANGE"); - else if (opt.reload > 0) + if (opt.reload > 0) feh_add_unique_timer(cb_reload_timer, w, opt.reload); break; } else { - free(s); last = l; } } if (!success) show_mini_usage(); - setup_signal_handlers(); - return; } void cb_slide_timer(void *data) { - slideshow_change_image((winwidget) data, SLIDE_NEXT); + slideshow_change_image((winwidget) data, SLIDE_NEXT, 1); return; } void cb_reload_timer(void *data) { - winwidget w = (winwidget) data; + gib_list *l; + char *current_filename; - feh_reload_image(w, 0, 0); - feh_add_unique_timer(cb_reload_timer, w, opt.reload); - return; -} + winwidget w = (winwidget) data; -void feh_reload_image(winwidget w, int resize, int force_new) -{ - char *title, *new_title; - int len; - Imlib_Image tmp; + /* + * multi-window mode has no concept of a "current file" and + * dynamically adding/removing windows is not implemented at the moment. + * So don't reload filelists in multi-window mode. + */ + if (current_file != NULL) { + /* save the current filename for refinding it in new list */ + current_filename = estrdup(FEH_FILE(current_file->data)->filename); - if (!w->file) { - weprintf("couldn't reload, this image has no file associated with it."); - return; - } + for (l = filelist; l; l = l->next) { + feh_file_free(l->data); + l->data = NULL; + } + gib_list_free_and_data(filelist); + filelist = NULL; + filelist_len = 0; + current_file = NULL; + + /* rebuild filelist from original_file_items */ + if (gib_list_length(original_file_items) > 0) + for (l = gib_list_last(original_file_items); l; l = l->prev) + add_file_to_filelist_recursively(l->data, FILELIST_FIRST); + else if (!opt.filelistfile && !opt.bgmode) + add_file_to_filelist_recursively(".", FILELIST_FIRST); + + if (opt.filelistfile) { + filelist = gib_list_cat(filelist, feh_read_filelist(opt.filelistfile)); + } - free(FEH_FILE(w->file->data)->caption); - FEH_FILE(w->file->data)->caption = NULL; + if (!(filelist_len = gib_list_length(filelist))) { + eprintf("No files found to reload."); + } - len = strlen(w->name) + sizeof("Reloading: ") + 1; - new_title = emalloc(len); - snprintf(new_title, len, "Reloading: %s", w->name); - title = estrdup(w->name); - winwidget_rename(w, new_title); + feh_prepare_filelist(); - /* force imlib2 not to cache */ - if (force_new) { - winwidget_free_image(w); - } + /* find the previously current file */ + for (l = filelist; l; l = l->next) + if (strcmp(FEH_FILE(l->data)->filename, current_filename) == 0) { + current_file = l; + break; + } - /* if the image has changed in dimensions - we gotta resize */ - if ((feh_load_image(&tmp, FEH_FILE(w->file->data))) == 0) { - if (force_new) { - eprintf("failed to reload image\n"); - } else { - weprintf("Couldn't reload image. Is it still there?"); - } - winwidget_rename(w, title); - free(title); - free(new_title); - filelist = feh_file_remove_from_list(filelist, w->file); - return; - } - if (force_new) { - w->im = tmp; - resize = 1; - winwidget_reset_image(w); - } else { - if ((gib_imlib_image_get_width(w->im) != gib_imlib_image_get_width(tmp)) - || (gib_imlib_image_get_height(w->im) != gib_imlib_image_get_height(tmp))) { - resize = 1; - winwidget_reset_image(w); - } - winwidget_free_image(w); - w->im = tmp; - } + free(current_filename); - 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); + if (!current_file) + current_file = filelist; + w->file = current_file; } - winwidget_render_image(w, resize, 1); - - winwidget_rename(w, title); - free(title); - free(new_title); + feh_reload_image(w, 1, 0); + feh_add_unique_timer(cb_reload_timer, w, opt.reload); return; } -void slideshow_change_image(winwidget winwid, int change) +void slideshow_change_image(winwidget winwid, int change, int render) { - int success = 0; gib_list *last = NULL; + gib_list *previous_file = current_file; int i = 0; int jmp = 1; /* We can't use filelist_len in the for loop, since that changes when we * encounter invalid images. */ int our_filelist_len = filelist_len; - char *s; + + if (opt.slideshow_delay > 0.0) + feh_add_timer(cb_slide_timer, winwid, opt.slideshow_delay, "SLIDE_CHANGE"); /* Without this, clicking a one-image slideshow reloads it. Not very * intelligent behaviour :-) */ - if (filelist_len < 2 && opt.cycle_once == 0) + if (filelist_len < 2 && opt.on_last_slide != ON_LAST_SLIDE_QUIT) return; /* Ok. I do this in such an odd way to ensure that if the last or first * @@ -195,14 +217,28 @@ void slideshow_change_image(winwidget winwid, int change) if (change == SLIDE_FIRST) { current_file = gib_list_last(filelist); change = SLIDE_NEXT; + previous_file = NULL; } else if (change == SLIDE_LAST) { current_file = filelist; change = SLIDE_PREV; + previous_file = NULL; } /* The for loop prevents us looping infinitely */ for (i = 0; i < our_filelist_len; i++) { winwidget_free_image(winwid); +#ifdef HAVE_LIBEXIF + /* + * An EXIF data chunk requires up to 50 kB of space. For large and + * long-running slideshows, this would acculumate gigabytes of + * EXIF data after a few days. We therefore do not cache EXIF data + * in slideshows. + */ + if (FEH_FILE(winwid->file->data)->ed) { + exif_data_unref(FEH_FILE(winwid->file->data)->ed); + FEH_FILE(winwid->file->data)->ed = NULL; + } +#endif switch (change) { case SLIDE_NEXT: current_file = feh_list_jump(filelist, current_file, FORWARD, 1); @@ -211,8 +247,11 @@ void slideshow_change_image(winwidget winwid, int change) current_file = feh_list_jump(filelist, current_file, BACK, 1); break; case SLIDE_RAND: - current_file = feh_list_jump(filelist, current_file, FORWARD, rand() % filelist_len); - change = SLIDE_NEXT; + if (filelist_len > 1) { + current_file = feh_list_jump(filelist, current_file, FORWARD, + (random() % (filelist_len - 1)) + 1); + change = SLIDE_NEXT; + } break; case SLIDE_JUMP_FWD: if (filelist_len < 5) @@ -242,6 +281,44 @@ void slideshow_change_image(winwidget winwid, int change) try the previous file, not another jmp */ change = SLIDE_NEXT; break; + case SLIDE_JUMP_NEXT_DIR: + { + char old_dir[PATH_MAX], new_dir[PATH_MAX]; + int j; + + feh_file_dirname(old_dir, FEH_FILE(current_file->data), PATH_MAX); + + for (j = 0; j < our_filelist_len; j++) { + current_file = feh_list_jump(filelist, current_file, FORWARD, 1); + feh_file_dirname(new_dir, FEH_FILE(current_file->data), PATH_MAX); + if (strcmp(old_dir, new_dir) != 0) + break; + } + } + change = SLIDE_NEXT; + break; + case SLIDE_JUMP_PREV_DIR: + { + char old_dir[PATH_MAX], new_dir[PATH_MAX]; + int j; + + /* Start the search from the previous file in case we are on + the first file of a directory */ + current_file = feh_list_jump(filelist, current_file, BACK, 1); + feh_file_dirname(old_dir, FEH_FILE(current_file->data), PATH_MAX); + + for (j = 0; j < our_filelist_len; j++) { + current_file = feh_list_jump(filelist, current_file, BACK, 1); + feh_file_dirname(new_dir, FEH_FILE(current_file->data), PATH_MAX); + if (strcmp(old_dir, new_dir) != 0) + break; + } + + /* Next file is the first entry of prev_dir */ + current_file = feh_list_jump(filelist, current_file, FORWARD, 1); + } + change = SLIDE_NEXT; + break; default: eprintf("BUG!\n"); break; @@ -251,23 +328,30 @@ void slideshow_change_image(winwidget winwid, int change) filelist = feh_file_remove_from_list(filelist, last); last = NULL; } - s = slideshow_create_name(FEH_FILE(current_file->data)); - winwidget_rename(winwid, s); - free(s); + if (opt.on_last_slide == ON_LAST_SLIDE_HOLD && previous_file && + ((current_file == filelist && change == SLIDE_NEXT) || + (previous_file == filelist && change == SLIDE_PREV))) { + current_file = previous_file; + } - if ((winwidget_loadimage(winwid, FEH_FILE(current_file->data))) - != 0) { - success = 1; + if (winwidget_loadimage(winwid, FEH_FILE(current_file->data))) { + int w = gib_imlib_image_get_width(winwid->im); + int h = gib_imlib_image_get_height(winwid->im); + if (feh_should_ignore_image(winwid->im)) { + last = current_file; + continue; + } winwid->mode = MODE_NORMAL; winwid->file = current_file; - if ((winwid->im_w != gib_imlib_image_get_width(winwid->im)) - || (winwid->im_h != gib_imlib_image_get_height(winwid->im))) + if ((winwid->im_w != w) || (winwid->im_h != h)) winwid->had_resize = 1; winwidget_reset_image(winwid); - winwid->im_w = gib_imlib_image_get_width(winwid->im); - winwid->im_h = gib_imlib_image_get_height(winwid->im); - winwidget_render_image(winwid, 1, 1); + winwid->im_w = w; + winwid->im_h = h; + if (render) { + winwidget_render_image(winwid, 1, 0); + } break; } else last = current_file; @@ -278,8 +362,6 @@ void slideshow_change_image(winwidget winwid, int change) if (filelist_len == 0) eprintf("No more slides in show"); - if (opt.slideshow_delay > 0.0) - feh_add_timer(cb_slide_timer, winwid, opt.slideshow_delay, "SLIDE_CHANGE"); return; } @@ -294,143 +376,226 @@ void slideshow_pause_toggle(winwidget w) winwidget_rename(w, NULL); } -char *slideshow_create_name(feh_file * file) -{ - char *s = NULL; - int len = 0; - - if (!opt.title) { - len = strlen(PACKAGE " [slideshow mode] - ") + strlen(file->filename) + 1; - s = emalloc(len); - snprintf(s, len, PACKAGE " [%d of %d] - %s", - gib_list_num(filelist, current_file) + 1, gib_list_length(filelist), file->filename); - } else { - s = estrdup(feh_printf(opt.title, file)); - } - - return(s); -} - -void feh_action_run(feh_file * file, char *action) +void feh_action_run(feh_file * file, char *action, winwidget winwid) { if (action) { char *sys; D(("Running action %s\n", action)); - sys = feh_printf(action, file); + sys = feh_printf(action, file, winwid); if (opt.verbose && !opt.list && !opt.customlist) fprintf(stderr, "Running action -->%s<--\n", sys); - system(sys); + if (system(sys) == -1) + perror("running action via system() failed"); } return; } -char *feh_printf(char *str, feh_file * file) +char *format_size(double size) +{ + static char ret[5]; + char units[] = {' ', 'k', 'M', 'G', 'T'}; + unsigned char postfix = 0; + while (size >= 1000) { + size /= 1000; + postfix++; + } + snprintf(ret, 5, "%3.0f%c", size, units[postfix]); + return ret; +} + +char *feh_printf(char *str, feh_file * file, winwidget winwid) { char *c; char buf[20]; static char ret[4096]; + char *filelist_tmppath; ret[0] = '\0'; + filelist_tmppath = NULL; + gib_list *f; for (c = str; *c != '\0'; c++) { - if (*c == '%') { + if ((*c == '%') && (*(c+1) != '\0')) { c++; switch (*c) { + case 'a': + if (opt.paused == 1) { + strncat(ret, "paused", sizeof(ret) - strlen(ret) - 1); + } + else { + strncat(ret, "playing", sizeof(ret) - strlen(ret) - 1); + } + break; case 'f': if (file) - strcat(ret, file->filename); + strncat(ret, file->filename, sizeof(ret) - strlen(ret) - 1); break; - case 'n': + case 'F': if (file) - strcat(ret, file->name); + strncat(ret, shell_escape(file->filename), sizeof(ret) - strlen(ret) - 1); break; - case 'w': - if (file) { - if (!file->info) - feh_file_info_load(file, NULL); - snprintf(buf, sizeof(buf), "%d", file->info->width); - strcat(ret, buf); + case 'g': + if (winwid) { + snprintf(buf, sizeof(buf), "%d,%d", winwid->w, winwid->h); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); } break; case 'h': - if (file) { - if (!file->info) - feh_file_info_load(file, NULL); + if (file && (file->info || !feh_file_info_load(file, NULL))) { snprintf(buf, sizeof(buf), "%d", file->info->height); - strcat(ret, buf); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); } break; - case 's': - if (file) { - if (!file->info) - feh_file_info_load(file, NULL); - snprintf(buf, sizeof(buf), "%d", file->info->size); - strcat(ret, buf); + case 'l': + snprintf(buf, sizeof(buf), "%d", gib_list_length(filelist)); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + break; + case 'L': + if (filelist_tmppath != NULL) { + strncat(ret, filelist_tmppath, sizeof(ret) - strlen(ret) - 1); + } else { + filelist_tmppath = feh_unique_filename("/tmp/","filelist"); + feh_write_filelist(filelist, filelist_tmppath); + strncat(ret, filelist_tmppath, sizeof(ret) - strlen(ret) - 1); + } + break; + case 'm': + strncat(ret, mode, sizeof(ret) - strlen(ret) - 1); + break; + case 'n': + if (file) + strncat(ret, file->name, sizeof(ret) - strlen(ret) - 1); + break; + case 'N': + if (file) + strncat(ret, shell_escape(file->name), sizeof(ret) - strlen(ret) - 1); + break; + case 'o': + if (winwid) { + snprintf(buf, sizeof(buf), "%d,%d", winwid->im_x, + winwid->im_y); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); } break; case 'p': - if (file) { - if (!file->info) - feh_file_info_load(file, NULL); + if (file && (file->info || !feh_file_info_load(file, NULL))) { snprintf(buf, sizeof(buf), "%d", file->info->pixels); - strcat(ret, buf); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + } + break; + case 'P': + if (file && (file->info || !feh_file_info_load(file, NULL))) { + strncat(ret, format_size(file->info->pixels), sizeof(ret) - strlen(ret) - 1); + } + break; + case 'r': + if (winwid) { + snprintf(buf, sizeof(buf), "%.1f", winwid->im_angle); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + } + break; + case 's': + if (file && (file->size >= 0 || !feh_file_stat(file))) { + snprintf(buf, sizeof(buf), "%d", file->size); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + } + break; + case 'S': + if (file && (file->size >= 0 || !feh_file_stat(file))) { + strncat(ret, format_size(file->size), sizeof(ret) - strlen(ret) - 1); } break; case 't': - if (file) { - if (!file->info) - feh_file_info_load(file, NULL); - strcat(ret, file->info->format); + if (file && (file->info || !feh_file_info_load(file, NULL))) { + strncat(ret, file->info->format, sizeof(ret) - strlen(ret) - 1); } break; - case 'P': - strcat(ret, PACKAGE); + case 'u': + f = current_file ? current_file : gib_list_find_by_data(filelist, file); + snprintf(buf, sizeof(buf), "%d", f ? gib_list_num(filelist, f) + 1 : 0); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); break; case 'v': - strcat(ret, VERSION); + strncat(ret, VERSION, sizeof(ret) - strlen(ret) - 1); break; - case 'm': - strcat(ret, mode); + case 'V': + snprintf(buf, sizeof(buf), "%d", getpid()); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); break; - case 'l': - snprintf(buf, sizeof(buf), "%d", gib_list_length(filelist)); - strcat(ret, buf); + case 'w': + if (file && (file->info || !feh_file_info_load(file, NULL))) { + snprintf(buf, sizeof(buf), "%d", file->info->width); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + } break; - case 'u': - snprintf(buf, sizeof(buf), "%d", - current_file != NULL ? gib_list_num(filelist, current_file) - + 1 : 0); - strcat(ret, buf); + case 'W': + if (winwid) { + snprintf(buf, sizeof(buf), "%dx%d+%d+%d", winwid->w, winwid->h, winwid->x, winwid->y); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + } + break; + case 'z': + if (winwid) { + snprintf(buf, sizeof(buf), "%.2f", winwid->zoom); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + } else { + strncat(ret, "1.00", sizeof(ret) - strlen(ret) - 1); + } + break; + case 'Z': + if (winwid) { + snprintf(buf, sizeof(buf), "%f", winwid->zoom); + strncat(ret, buf, sizeof(ret) - strlen(ret) - 1); + } + break; + case '%': + strncat(ret, "%", sizeof(ret) - strlen(ret) - 1); break; default: - strncat(ret, c, 1); + weprintf("Unrecognized format specifier %%%c", *c); + if ((strlen(ret) + 3) < sizeof(ret)) + strncat(ret, c - 1, 2); break; } - } else if (*c == '\\') { + } else if ((*c == '\\') && (*(c+1) != '\0') && ((strlen(ret) + 3) < sizeof(ret))) { c++; switch (*c) { case 'n': strcat(ret, "\n"); break; default: - strncat(ret, c, 1); + strncat(ret, c - 1, 2); break; } - } else + } else if ((strlen(ret) + 2) < sizeof(ret)) strncat(ret, c, 1); } + if (filelist_tmppath != NULL) + free(filelist_tmppath); return(ret); } void feh_filelist_image_remove(winwidget winwid, char do_delete) { if (winwid->type == WIN_TYPE_SLIDESHOW) { - char *s; gib_list *doomed; doomed = current_file; - slideshow_change_image(winwid, SLIDE_NEXT); + /* + * work around feh_list_jump exiting if ON_LAST_SLIDE_QUIT is set + * and no further files are left (we need to delete first) + */ + if (opt.on_last_slide == ON_LAST_SLIDE_QUIT && ! doomed->next && do_delete) { + feh_file_rm_and_free(filelist, doomed); + exit(0); + } + if (doomed->next) { + slideshow_change_image(winwid, SLIDE_NEXT, 0); + } + else { + slideshow_change_image(winwid, SLIDE_PREV, 0); + } if (do_delete) filelist = feh_file_rm_and_free(filelist, doomed); else @@ -440,15 +605,14 @@ void feh_filelist_image_remove(winwidget winwid, char do_delete) winwidget_destroy(winwid); return; } - s = slideshow_create_name(FEH_FILE(winwid->file->data)); - winwidget_rename(winwid, s); - free(s); + winwidget_render_image(winwid, 1, 0); } else if ((winwid->type == WIN_TYPE_SINGLE) || (winwid->type == WIN_TYPE_THUMBNAIL_VIEWER)) { if (do_delete) filelist = feh_file_rm_and_free(filelist, winwid->file); else filelist = feh_file_remove_from_list(filelist, winwid->file); + winwid->file = NULL; winwidget_destroy(winwid); } } @@ -457,24 +621,33 @@ void slideshow_save_image(winwidget win) { char *tmpname; Imlib_Load_Error err; + char *base_dir = ""; + if (opt.output_dir) { + base_dir = estrjoin("", opt.output_dir, "/", NULL); + } if (win->file) { - tmpname = feh_unique_filename("", FEH_FILE(win->file->data)->name); + tmpname = feh_unique_filename(base_dir, FEH_FILE(win->file->data)->name); } else if (mode) { char *tmp; tmp = estrjoin(".", mode, "png", NULL); - tmpname = feh_unique_filename("", tmp); + tmpname = feh_unique_filename(base_dir, tmp); free(tmp); } else { - tmpname = feh_unique_filename("", "noname.png"); + tmpname = feh_unique_filename(base_dir, "noname.png"); } - if (!opt.quiet) - printf("saving image to filename '%s'\n", tmpname); + if (opt.output_dir) { + free(base_dir); + } + + if (opt.verbose) + fprintf(stderr, "saving image to filename '%s'\n", tmpname); gib_imlib_save_image_with_error_return(win->im, tmpname, &err); + if (err) - weprintf("Can't save image %s:", tmpname); + feh_print_load_error(tmpname, win, err, LOAD_ERROR_IMLIB); free(tmpname); return; @@ -497,10 +670,16 @@ gib_list *feh_list_jump(gib_list * root, gib_list * l, int direction, int num) if (ret->next) { ret = ret->next; } else { - if (opt.cycle_once) { + if (opt.on_last_slide == ON_LAST_SLIDE_QUIT) { exit(0); } - ret = root; + if (opt.randomize) { + /* Randomize the filename order */ + filelist = gib_list_randomize(filelist); + ret = filelist; + } else { + ret = root; + } } } else { if (ret->prev) diff --git a/src/structs.h b/src/structs.h index a2d3527..8438930 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1,6 +1,7 @@ /* structs.h Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -33,5 +34,7 @@ typedef struct __feh_file_info feh_file_info; typedef struct __winwidget _winwidget; typedef _winwidget *winwidget; typedef struct __fehoptions fehoptions; +typedef struct __fehkey fehkey; +typedef struct __fehkb fehkb; #endif diff --git a/src/strverscmp.c b/src/strverscmp.c new file mode 100644 index 0000000..ddc6b6d --- /dev/null +++ b/src/strverscmp.c @@ -0,0 +1,57 @@ +/* + * Copyright © 2005-2020 Rich Felker, et al. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#define _GNU_SOURCE +#include <ctype.h> +#include <string.h> + +int strverscmp(const char *l0, const char *r0) +{ + const unsigned char *l = (const void *)l0; + const unsigned char *r = (const void *)r0; + size_t i, dp, j; + int z = 1; + + /* Find maximal matching prefix and track its maximal digit + * suffix and whether those digits are all zeros. */ + for (dp=i=0; l[i]==r[i]; i++) { + int c = l[i]; + if (!c) return 0; + if (!isdigit(c)) dp=i+1, z=1; + else if (c!='0') z=0; + } + + if (l[dp]-'1'<9U && r[dp]-'1'<9U) { + /* If we're looking at non-degenerate digit sequences starting + * with nonzero digits, longest digit string is greater. */ + for (j=i; isdigit(l[j]); j++) + if (!isdigit(r[j])) return 1; + if (isdigit(r[j])) return -1; + } else if (z && dp<i && (isdigit(l[i]) || isdigit(r[i]))) { + /* Otherwise, if common prefix of digit sequence is + * all zeros, digits order less than non-digits. */ + return (unsigned char)(l[i]-'0') - (unsigned char)(r[i]-'0'); + } + + return l[i] - r[i]; +} diff --git a/src/thumbnail.c b/src/thumbnail.c index ed4e0fb..70af5e0 100644 --- a/src/thumbnail.c +++ b/src/thumbnail.c @@ -1,6 +1,7 @@ /* thumbnail.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 @@ -30,10 +31,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "thumbnail.h" #include "md5.h" #include "feh_png.h" +#include "index.h" +#include "signals.h" -static char *create_index_dimension_string(int w, int h); -static char *create_index_size_string(char *file); -static char *create_index_title_string(int num, int w, int h); static gib_list *thumbnails = NULL; static thumbmode_data td; @@ -55,23 +55,24 @@ void init_thumbnail_mode(void) int max_column_w = 0; */ + Imlib_Load_Error err; Imlib_Image im_temp; int ww = 0, hh = 0, www, hhh, xxx, yyy; + int orig_w, orig_h; int x = 0, y = 0; winwidget winwid = NULL; Imlib_Image im_thumb = NULL; unsigned char trans_bg = 0; int title_area_h = 0; int tw = 0, th = 0; - int fw_name, fw_size, fw_dim, fh; + int fw, fh; int thumbnailcount = 0; feh_file *file = NULL; gib_list *l, *last = NULL; - int lines; + int lineno; int index_image_width, index_image_height; - int x_offset_name = 0, x_offset_dim = 0, x_offset_size = 0; - char *s; unsigned int thumb_counter = 0; + gib_list *line, *lines; /* initialize thumbnail mode data */ td.im_main = NULL; @@ -90,6 +91,8 @@ void init_thumbnail_mode(void) td.vertical = 0; td.max_column_w = 0; + if (!opt.thumb_title) + opt.thumb_title = "%n"; mode = "thumbnail"; if (opt.font) @@ -102,6 +105,9 @@ void init_thumbnail_mode(void) int fh, fw; td.font_title = gib_imlib_load_font(opt.title_font); + if (!td.font_title) + td.font_title = gib_imlib_load_font(DEFAULT_FONT_TITLE); + gib_imlib_get_text_size(td.font_title, "W", NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); title_area_h = fh + 4; @@ -114,9 +120,8 @@ void init_thumbnail_mode(void) /* Work out how tall the font is */ gib_imlib_get_text_size(td.font_main, "W", NULL, &tw, &th, IMLIB_TEXT_TO_RIGHT); - /* For now, allow room for the right number of lines with small gaps */ - td.text_area_h = ((th + 2) * (opt.index_show_name + opt.index_show_size + - opt.index_show_dim)) + 5; + get_index_string_dim(NULL, td.font_main, &fw, &fh); + td.text_area_h = fh + 5; /* This includes the text area for index data */ td.thumb_tot_h = opt.thumb_h + td.text_area_h; @@ -140,11 +145,21 @@ void init_thumbnail_mode(void) index_image_width = td.w; index_image_height = td.h + title_area_h; + D(("imlib_create_image(%d, %d)\n", index_image_width, index_image_height)); td.im_main = imlib_create_image(index_image_width, index_image_height); - gib_imlib_image_set_has_alpha(td.im_main, 1); - if (!td.im_main) - eprintf("Imlib error creating index image, are you low on RAM?"); + if (!td.im_main) { + if (index_image_height >= 32768 || index_image_width >= 32768) { + eprintf("Failed to create %dx%d pixels (%d MB) index image.\n" + "This is probably due to Imlib2 issues when dealing with images larger than 32k x 32k pixels.", + index_image_width, index_image_height, index_image_width * index_image_height * 4 / (1024*1024)); + } else { + eprintf("Failed to create %dx%d pixels (%d MB) index image. Do you have enough RAM?", + index_image_width, index_image_height, index_image_width * index_image_height * 4 / (1024*1024)); + } + } + + gib_imlib_image_set_has_alpha(td.im_main, 1); if (td.im_bg) gib_imlib_blend_image_onto_image(td.im_main, td.im_bg, @@ -161,20 +176,13 @@ void init_thumbnail_mode(void) td.h + title_area_h, 0, 0, 0, 255); } - /* Create title now */ - - if (!opt.title) - s = estrdup(PACKAGE " [thumbnail mode]"); - else - s = estrdup(feh_printf(opt.title, NULL)); if (opt.display) { - winwid = winwidget_create_from_image(td.im_main, s, WIN_TYPE_THUMBNAIL); + winwid = winwidget_create_from_image(td.im_main, WIN_TYPE_THUMBNAIL); + winwidget_rename(winwid, PACKAGE " [thumbnail mode]"); winwidget_show(winwid); } - /* make sure we have an ~/.thumbnails/normal directory for storing - permanent thumbnails */ td.cache_thumbnails = opt.cache_thumbnails; if (td.cache_thumbnails) { @@ -183,9 +191,15 @@ void init_thumbnail_mode(void) else td.cache_dim = opt.thumb_h; - if (td.cache_dim > 256) { - /* No caching as specified by standard. Sort of. */ + if (td.cache_dim > 1024) { + /* Not specified by XDG thumbnail standard */ td.cache_thumbnails = 0; + } else if (td.cache_dim > 512) { + td.cache_dim = 1024; + td.cache_dir = estrdup("xx-large"); + } else if (td.cache_dim > 256) { + td.cache_dim = 512; + td.cache_dir = estrdup("x-large"); } else if (td.cache_dim > 128) { td.cache_dim = 256; td.cache_dir = estrdup("large"); @@ -204,7 +218,8 @@ void init_thumbnail_mode(void) } D(("About to load image %s\n", file->filename)); /* if (feh_load_image(&im_temp, file) != 0) */ - if (feh_thumbnail_get_thumbnail(&im_temp, file) != 0) { + if (feh_thumbnail_get_thumbnail(&im_temp, file, &orig_w, &orig_h) + != 0) { if (opt.verbose) feh_display_status('.'); D(("Successfully loaded %s\n", file->filename)); @@ -212,6 +227,12 @@ void init_thumbnail_mode(void) hhh = opt.thumb_h; ww = gib_imlib_image_get_width(im_temp); hh = gib_imlib_image_get_height(im_temp); + + if (!orig_w) { + orig_w = ww; + orig_h = hh; + } + thumbnailcount++; if (gib_imlib_image_has_alpha(im_temp)) imlib_context_set_blend(1); @@ -252,34 +273,18 @@ void init_thumbnail_mode(void) td.text_area_w = opt.thumb_w; /* Now draw on the info text */ - if (opt.index_show_name) { - gib_imlib_get_text_size(td.font_main, file->name, NULL, - &fw_name, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw_name > td.text_area_w) - td.text_area_w = fw_name; - } - if (opt.index_show_dim) { - gib_imlib_get_text_size(td.font_main, - create_index_dimension_string(ww, hh), - NULL, &fw_dim, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw_dim > td.text_area_w) - td.text_area_w = fw_dim; - } - if (opt.index_show_size) { - gib_imlib_get_text_size(td.font_main, - create_index_size_string(file->filename), - NULL, &fw_size, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw_size > td.text_area_w) - td.text_area_w = fw_size; + if (opt.index_info) { + get_index_string_dim(file, td.font_main, &fw, &fh); + if (fw > td.text_area_w) + td.text_area_w = fw; + if (fh > td.text_area_h) { + td.text_area_h = fh + 5; + td.thumb_tot_h = opt.thumb_h + td.text_area_h; + } } if (td.text_area_w > opt.thumb_w) td.text_area_w += 5; - /* offsets for centering text */ - x_offset_name = (td.text_area_w - fw_name) / 2; - x_offset_dim = (td.text_area_w - fw_dim) / 2; - x_offset_size = (td.text_area_w - fw_size) / 2; - if (td.vertical) { if (td.text_area_w > td.max_column_w) td.max_column_w = td.text_area_w; @@ -320,28 +325,23 @@ void init_thumbnail_mode(void) gib_imlib_free_image_and_decache(im_thumb); - lines = 0; - if (opt.index_show_name) - gib_imlib_text_draw(td.im_main, - td.font_main, NULL, - x + x_offset_name, - y + opt.thumb_h + (lines++ * (th + 2)) + 2, - file->name, IMLIB_TEXT_TO_RIGHT, - 255, 255, 255, 255); - if (opt.index_show_dim) - gib_imlib_text_draw(td.im_main, - td.font_main, NULL, - x + x_offset_dim, - y + opt.thumb_h + (lines++ * (th + 2)) + 2, - create_index_dimension_string(ww, hh), - IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); - if (opt.index_show_size) - gib_imlib_text_draw(td.im_main, - td.font_main, NULL, - x + x_offset_size, - y + opt.thumb_h + (lines++ * (th + 2)) + 2, - create_index_size_string(file->filename), - IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + lineno = 0; + if (opt.index_info) { + line = lines = feh_wrap_string(create_index_string(file), + opt.thumb_w * 3, td.font_main, NULL); + + while (line) { + gib_imlib_get_text_size(td.font_main, (char *) line -> data, + NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); + gib_imlib_text_draw(td.im_main, td.font_main, NULL, + x + ((td.text_area_w - fw) >> 1), + y + opt.thumb_h + (lineno++ * (th + 2)) + 2, + (char *) line->data, + IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + line = line->next; + } + gib_list_free_and_data(lines); + } if (td.vertical) y += td.thumb_tot_h; @@ -355,7 +355,7 @@ void init_thumbnail_mode(void) if (opt.display) { /* thumb_counter is unsigned, so no need to catch overflows */ if (++thumb_counter == opt.thumb_redraw) { - winwidget_render_image(winwid, 0, 0); + winwidget_render_image(winwid, 0, 1); thumb_counter = 0; } if (!feh_main_iteration(0)) @@ -364,10 +364,10 @@ void init_thumbnail_mode(void) } if (thumb_counter != 0) - winwidget_render_image(winwid, 0, 0); + winwidget_render_image(winwid, 0, 1); if (opt.verbose) - fprintf(stdout, "\n"); + putc('\n', stderr); if (opt.title_font) { int fw, fh, fx, fy; @@ -380,6 +380,9 @@ void init_thumbnail_mode(void) fy = index_image_height - fh - 2; gib_imlib_text_draw(td.im_main, td.font_title, NULL, fx, fy, s, IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255); + + if (opt.display) + winwidget_render_image(winwid, 0, 1); } if (opt.output && opt.output_file) { @@ -387,16 +390,21 @@ void init_thumbnail_mode(void) if (opt.output_dir) snprintf(output_buf, 1024, "%s/%s", opt.output_dir, opt.output_file); - else - strncpy(output_buf, opt.output_file, 1024); - gib_imlib_save_image(td.im_main, output_buf); - if (opt.verbose) { + else { + strncpy(output_buf, opt.output_file, 1023); + output_buf[1023] = '\0'; + } + gib_imlib_save_image_with_error_return(td.im_main, output_buf, &err); + if (err) { + feh_print_load_error(output_buf, td.im_main, err, LOAD_ERROR_IMLIB); + } + else if (opt.verbose) { int tw, th; tw = gib_imlib_image_get_width(td.im_main); th = gib_imlib_image_get_height(td.im_main); - fprintf(stdout, PACKAGE " - File saved as %s\n", output_buf); - fprintf(stdout, + fprintf(stderr, PACKAGE " - File saved as %s\n", output_buf); + fprintf(stderr, " - Image is %dx%d pixels and contains %d thumbnails\n", tw, th, thumbnailcount); } @@ -404,43 +412,19 @@ void init_thumbnail_mode(void) if (!opt.display) gib_imlib_free_image_and_decache(td.im_main); - - free(s); - return; -} - -static char *create_index_size_string(char *file) -{ - static char str[50]; - int size = 0; - double kbs = 0.0; - struct stat st; - - if (stat(file, &st)) - kbs = 0.0; - else { - size = st.st_size; - kbs = (double) size / 1000; + else if (opt.start_list_at) { + for (l = thumbnails; l; l = l->next) { + if (!strcmp(opt.start_list_at, FEH_THUMB(l->data)->file->filename)) { + free(opt.start_list_at); + opt.start_list_at = NULL; + feh_thumbnail_select(winwid, FEH_THUMB(l->data)); + break; + } + } } - snprintf(str, sizeof(str), "%.2fKb", kbs); - return(str); -} - -static char *create_index_dimension_string(int w, int h) -{ - static char str[50]; - - snprintf(str, sizeof(str), "%dx%d", w, h); - return(str); -} - -static char *create_index_title_string(int num, int w, int h) -{ - static char str[50]; - snprintf(str, sizeof(str), PACKAGE " index - %d thumbnails, %d by %d pixels", num, w, h); - return(str); + return; } feh_thumbnail *feh_thumbnail_new(feh_file * file, int x, int y, int w, int h) @@ -518,24 +502,20 @@ void feh_thumbnail_mark_removed(feh_file * file, int deleted) if (thumb) { w = winwidget_get_first_window_of_type(WIN_TYPE_THUMBNAIL); if (w) { - td.font_main = imlib_load_font(DEFAULT_FONT_TITLE); + int tw, th; if (deleted) gib_imlib_image_fill_rectangle(w->im, thumb->x, thumb->y, thumb->w, thumb->h, 255, 0, 0, 150); else gib_imlib_image_fill_rectangle(w->im, thumb->x, thumb->y, thumb->w, thumb->h, 0, 0, 255, 150); - if (td.font_main) { - int tw, th; - - gib_imlib_get_text_size(td.font_main, "X", NULL, &tw, &th, - IMLIB_TEXT_TO_RIGHT); - gib_imlib_text_draw(w->im, td.font_main, NULL, - thumb->x + ((thumb->w - tw) / 2), - thumb->y + ((thumb->h - th) / 2), "X", - IMLIB_TEXT_TO_RIGHT, 205, 205, 50, 255); - } else - weprintf(DEFAULT_FONT_TITLE); + + gib_imlib_get_text_size(td.font_main, "X", NULL, &tw, &th, + IMLIB_TEXT_TO_RIGHT); + gib_imlib_text_draw(w->im, td.font_main, NULL, + thumb->x + ((thumb->w - tw) / 2), + thumb->y + ((thumb->h - th) / 2), "X", + IMLIB_TEXT_TO_RIGHT, 205, 205, 50, 255); winwidget_render_image(w, 0, 1); } thumb->exists = 0; @@ -545,193 +525,68 @@ void feh_thumbnail_mark_removed(feh_file * file, int deleted) void feh_thumbnail_calculate_geometry(void) { - gib_list *l; - feh_file *file; - - int x = 0, y = 0; - int fw, fh; - if (!opt.limit_w && !opt.limit_h) { if (td.im_bg) { - if (opt.verbose) - fprintf(stdout, - PACKAGE - " - No size restriction specified for index.\n" - " You did specify a background however, so the\n" - " index size has defaulted to the size of the image\n"); opt.limit_w = td.bg_w; opt.limit_h = td.bg_h; - } else { - if (opt.verbose) - fprintf(stdout, - PACKAGE - " - No size restriction specified for index.\n" - " Using defaults (width limited to 640)\n"); - opt.limit_w = 640; - } + } else + opt.limit_w = 800; } /* Here we need to whiz through the files, and look at the filenames and info in the selected font, work out how much space we need, and calculate the size of the image we will require */ - if (opt.limit_w && opt.limit_h) { - int rec_h = 0; - + if (opt.limit_w) { td.w = opt.limit_w; - td.h = opt.limit_h; - - /* Work out if this is big enough, and give a warning if not */ - - /* Pretend we are limiting width by that specified, loop through, and - see it we fit in the height specified. If not, continue the loop, - and recommend the final value instead. Carry on and make the index - anyway. */ - - for (l = filelist; l; l = l->next) { - file = FEH_FILE(l->data); - td.text_area_w = opt.thumb_w; - if (opt.index_show_name) { - gib_imlib_get_text_size(td.font_main, file->name, NULL, - &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - if (opt.index_show_dim) { - gib_imlib_get_text_size(td.font_main, - create_index_dimension_string(1000, 1000), - NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - if (opt.index_show_size) { - gib_imlib_get_text_size(td.font_main, - create_index_size_string(file->filename), - NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - if (td.text_area_w > opt.thumb_w) - td.text_area_w += 5; - if ((x > td.w - td.text_area_w)) { - x = 0; - y += td.thumb_tot_h; - } - - x += td.text_area_w; - } - rec_h = y + td.thumb_tot_h; - - if (td.h < rec_h) { - weprintf - ("The image size you specified (%d by %d) is not large\n" - "enough to hold all the thumnails you specified (%d). To fit all\n" - "the thumnails, either decrease their size, choose a smaller font,\n" - "or use a larger image (may I recommend %d by %d?)", - opt.limit_w, opt.limit_h, filelist_len, opt.limit_w, rec_h); + index_calculate_height(td.font_main, td.w, &td.h, &td.thumb_tot_h); + + if (opt.limit_h) { + if (td.h> opt.limit_h) + weprintf( + "The image size you specified (%dx%d) is not large\n" + "enough to hold all %d thumbnails. To fit all\n" + "the thumnails, either decrease their size, choose a smaller font,\n" + "or use a larger image (like %dx%d)", + opt.limit_w, opt.limit_h, filelist_len, opt.limit_w, td.h); + td.h = opt.limit_h; } } else if (opt.limit_h) { td.vertical = 1; td.h = opt.limit_h; - /* calc w */ - for (l = filelist; l; l = l->next) { - file = FEH_FILE(l->data); - td.text_area_w = opt.thumb_w; - /* Calc width of text */ - if (opt.index_show_name) { - gib_imlib_get_text_size(td.font_main, file->name, NULL, - &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - if (opt.index_show_dim) { - gib_imlib_get_text_size(td.font_main, - create_index_dimension_string(1000, 1000), - NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - if (opt.index_show_size) { - gib_imlib_get_text_size(td.font_main, - create_index_size_string(file->filename), - NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - if (td.text_area_w > opt.thumb_w) - td.text_area_w += 5; - - if (td.text_area_w > td.max_column_w) - td.max_column_w = td.text_area_w; - - if ((y > td.h - td.thumb_tot_h)) { - y = 0; - x += td.max_column_w; - td.max_column_w = 0; - } - - y += td.thumb_tot_h; - } - td.w = x + td.text_area_w; - td.max_column_w = 0; - } else if (opt.limit_w) { - td.w = opt.limit_w; - /* calc h */ - for (l = filelist; l; l = l->next) { - file = FEH_FILE(l->data); - td.text_area_w = opt.thumb_w; - if (opt.index_show_name) { - gib_imlib_get_text_size(td.font_main, file->name, NULL, - &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - if (opt.index_show_dim) { - gib_imlib_get_text_size(td.font_main, - create_index_dimension_string(1000, 1000), - NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - if (opt.index_show_size) { - gib_imlib_get_text_size(td.font_main, - create_index_size_string(file->filename), - NULL, &fw, &fh, IMLIB_TEXT_TO_RIGHT); - if (fw > td.text_area_w) - td.text_area_w = fw; - } - - if (td.text_area_w > opt.thumb_w) - td.text_area_w += 5; - - if ((x > td.w - td.text_area_w)) { - x = 0; - y += td.thumb_tot_h; - } - - x += td.text_area_w; - } - td.h = y + td.thumb_tot_h; + index_calculate_width(td.font_main, &td.w, td.h, &td.thumb_tot_h); } } -int feh_thumbnail_get_thumbnail(Imlib_Image * image, feh_file * file) +int feh_thumbnail_get_thumbnail(Imlib_Image * image, feh_file * file, + int * orig_w, int * orig_h) { int status = 0; char *thumb_file = NULL, *uri = NULL; + *orig_w = 0; + *orig_h = 0; + if (!file || !file->filename) return (0); if (td.cache_thumbnails) { uri = feh_thumbnail_get_name_uri(file->filename); thumb_file = feh_thumbnail_get_name(uri); - status = feh_thumbnail_get_generated(image, file, thumb_file); + + if (thumb_file == NULL) { + free(uri); + return feh_load_image(image, file); + } + + status = feh_thumbnail_get_generated(image, file, thumb_file, + orig_w, orig_h); if (!status) - status = feh_thumbnail_generate(image, file, thumb_file, uri); + status = feh_thumbnail_generate(image, file, thumb_file, uri, + orig_w, orig_h); D(("uri is %s, thumb_file is %s\n", uri, thumb_file)); free(uri); @@ -742,21 +597,39 @@ int feh_thumbnail_get_thumbnail(Imlib_Image * image, feh_file * file) return status; } +static char *feh_thumbnail_get_prefix(void) +{ + char *dir = NULL, *home, *xdg_cache_home; + + // TODO: perhaps make sure that either of those paths aren't /-terminated + + xdg_cache_home = getenv("XDG_CACHE_HOME"); + if (xdg_cache_home && xdg_cache_home[0] == '/') { + dir = estrjoin("/", xdg_cache_home, "thumbnails", td.cache_dir, NULL); + } else { + home = getenv("HOME"); + if (home && home[0] == '/') { + dir = estrjoin("/", home, ".cache/thumbnails", td.cache_dir, NULL); + } + } + + return dir; +} + char *feh_thumbnail_get_name(char *uri) { - char *home = NULL, *thumb_file = NULL, *md5_name = NULL; + char *prefix, *thumb_file = NULL, *md5_name; /* FIXME: make sure original file isn't under ~/.thumbnails */ - md5_name = feh_thumbnail_get_name_md5(uri); - - home = getenv("HOME"); - if (home) { - thumb_file = estrjoin("/", home, ".thumbnails", td.cache_dir, md5_name, NULL); + prefix = feh_thumbnail_get_prefix(); + if (prefix) { + md5_name = feh_thumbnail_get_name_md5(uri); + thumb_file = estrjoin("/", prefix, md5_name, NULL); + free(md5_name); + free(prefix); } - free(md5_name); - return thumb_file; } @@ -765,14 +638,15 @@ char *feh_thumbnail_get_name_uri(char *name) char *cwd, *uri = NULL; /* FIXME: what happens with http, https, and ftp? MTime etc */ - if ((strncmp(name, "http://", 7) != 0) && - (strncmp(name, "https://", 8) != 0) && (strncmp(name, "ftp://", 6) != 0) - && (strncmp(name, "file://", 7) != 0)) { + if (!path_is_url(name)) { - /* make sure it's an absoulte path */ + /* make sure it's an absolute path */ /* FIXME: add support for ~, need to investigate if it's expanded - somewhere else before adding (unecessary) code */ + somewhere else before adding (unnecessary) code */ if (name[0] != '/') { + /* work around /some/path/./image.ext */ + if ((strncmp(name, "./", 2)) == 0) + name += 2; cwd = getcwd(NULL, 0); uri = estrjoin("/", "file:/", cwd, name, NULL); free(cwd); @@ -798,7 +672,7 @@ char *feh_thumbnail_get_name_md5(char *uri) md5_finish(&pms, digest); /* print the md5 as hex to a string */ - md5_name = emalloc(32 + 4 + 1 * sizeof(char)); /* md5 + .png + '\0' */ + md5_name = emalloc(32 + 4 + 1); /* md5 + .png + '\0' */ for (i = 0, pos = md5_name; i < 16; i++, pos += 2) { sprintf(pos, "%02x", digest[i]); } @@ -808,15 +682,18 @@ char *feh_thumbnail_get_name_md5(char *uri) } int feh_thumbnail_generate(Imlib_Image * image, feh_file * file, - char *thumb_file, char *uri) + char *thumb_file, char *uri, int * orig_w, int * orig_h) { int w, h, thumb_w, thumb_h; Imlib_Image im_temp; struct stat sb; + char c_width[8], c_height[8]; + char *tmp_thumb_file, *prefix; + int tmp_fd; if (feh_load_image(&im_temp, file) != 0) { - w = gib_imlib_image_get_width(im_temp); - h = gib_imlib_image_get_height(im_temp); + *orig_w = w = gib_imlib_image_get_width(im_temp); + *orig_h = h = gib_imlib_image_get_height(im_temp); thumb_w = td.cache_dim; thumb_h = td.cache_dim; @@ -826,6 +703,13 @@ int feh_thumbnail_generate(Imlib_Image * image, feh_file * file, thumb_h = td.cache_dim / ratio; else if (ratio != 1.0) thumb_w = td.cache_dim * ratio; + } else { + /* + * The image is smaller than the specified thumbnail size. + * Do not cache or transform it. + */ + *image = im_temp; + return 1; } *image = gib_imlib_create_cropped_scaled_image(im_temp, 0, 0, w, h, @@ -834,31 +718,57 @@ int feh_thumbnail_generate(Imlib_Image * image, feh_file * file, if (!stat(file->filename, &sb)) { char c_mtime[128]; sprintf(c_mtime, "%d", (int)sb.st_mtime); - feh_png_write_png(*image, thumb_file, "Thumb::URI", uri, - "Thumb::MTime", c_mtime); + snprintf(c_width, 8, "%d", w); + snprintf(c_height, 8, "%d", h); + prefix = feh_thumbnail_get_prefix(); + if (prefix == NULL) { + gib_imlib_free_image_and_decache(im_temp); + return 0; + } + tmp_thumb_file = estrjoin("/", prefix, ".feh_thumbnail_XXXXXX", NULL); + free(prefix); + tmp_fd = mkstemp(tmp_thumb_file); + if (!feh_png_write_png_fd(*image, tmp_fd, "Thumb::URI", uri, + "Thumb::MTime", c_mtime, + "Thumb::Image::Width", c_width, + "Thumb::Image::Height", c_height)) { + rename(tmp_thumb_file, thumb_file); + } else { + unlink(tmp_thumb_file); + } + close(tmp_fd); + free(tmp_thumb_file); } gib_imlib_free_image_and_decache(im_temp); - return (1); + return 1; } - return (0); + return 0; } -int feh_thumbnail_get_generated(Imlib_Image * image, feh_file * file, char *thumb_file) +int feh_thumbnail_get_generated(Imlib_Image * image, feh_file * file, + char *thumb_file, int * orig_w, int * orig_h) { struct stat sb; char *c_mtime; + char *c_width, *c_height; time_t mtime = 0; gib_hash *hash; if (!stat(file->filename, &sb)) { hash = feh_png_read_comments(thumb_file); if (hash != NULL) { - c_mtime = (char *) gib_hash_get(hash, "Thumb::MTime"); + c_mtime = (char *) gib_hash_get(hash, "Thumb::MTime"); + c_width = (char *) gib_hash_get(hash, "Thumb::Image::Width"); + c_height = (char *) gib_hash_get(hash, "Thumb::Image::Height"); if (c_mtime != NULL) mtime = (time_t) strtol(c_mtime, NULL, 10); + if (c_width != NULL) + *orig_w = atoi(c_width); + if (c_height != NULL) + *orig_h = atoi(c_height); gib_hash_free_and_data(hash); } @@ -873,34 +783,165 @@ int feh_thumbnail_get_generated(Imlib_Image * image, feh_file * file, char *thum return (0); } +void feh_thumbnail_show_fullsize(feh_file *thumbfile) +{ + winwidget thumbwin = NULL; + gib_list *l; + + for (l = filelist; l; l = l->next) { + if (FEH_FILE(l->data) == thumbfile) { + break; + } + } + if (!l) { + eprintf("Cannot find %s in filelist, wtf", thumbfile->filename); + } + thumbwin = winwidget_get_first_window_of_type(WIN_TYPE_THUMBNAIL_VIEWER); + if (!thumbwin) { + thumbwin = winwidget_create_from_file( + l, + WIN_TYPE_THUMBNAIL_VIEWER); + if (thumbwin) + winwidget_show(thumbwin); + } else if (FEH_FILE(thumbwin->file->data) != thumbfile) { + thumbwin->file = l; +#ifdef HAVE_INOTIFY + winwidget_inotify_remove(thumbwin); +#endif + feh_reload_image(thumbwin, 1, 0); +#ifdef HAVE_INOTIFY + winwidget_inotify_add(thumbwin, thumbfile); +#endif + } +} + +void feh_thumbnail_select(winwidget winwid, feh_thumbnail *thumbnail) +{ + Imlib_Image origwin; + + if (thumbnail == td.selected) + return; + + if (thumbnail) { + origwin = winwid->im; + winwid->im = gib_imlib_clone_image(origwin); + gib_imlib_image_fill_rectangle(winwid->im, + thumbnail->x, thumbnail->y, thumbnail->w, + thumbnail->h, 50, 50, 255, 100); + gib_imlib_image_draw_rectangle(winwid->im, + thumbnail->x, thumbnail->y, thumbnail->w, + thumbnail->h, 255, 255, 255, 255); + gib_imlib_image_draw_rectangle(winwid->im, + thumbnail->x + 1, thumbnail->y + 1, + thumbnail->w - 2, thumbnail->h - 2, + 0, 0, 0, 255); + gib_imlib_image_draw_rectangle(winwid->im, + thumbnail->x + 2, thumbnail->y + 2, + thumbnail->w - 4, thumbnail->h - 4, + 255, 255, 255, 255); + winwidget_render_image(winwid, 0, 0); + gib_imlib_free_image_and_decache(winwid->im); + winwid->im = origwin; + } else + winwidget_render_image(winwid, 0, 0); + + td.selected = thumbnail; +} + +void feh_thumbnail_select_next(winwidget winwid, int jump) +{ + gib_list *l; + feh_thumbnail *thumb; + int len = 0, cur = 0, target = 0; + + for (l = thumbnails; l; l = l->next) { + thumb = FEH_THUMB(l->data); + if (thumb == td.selected) + cur = len; + len++; + } + + target = (cur + len - jump) % len; + + for (l = thumbnails; l; l = l->next) { + if (target-- == 0) { + feh_thumbnail_select(winwid, FEH_THUMB(l->data)); + } + } +} + +void feh_thumbnail_select_prev(winwidget winwid, int jump) +{ + gib_list *l; + feh_thumbnail *thumb; + int len = 0, cur = 0, target = 0; + + for (l = thumbnails; l; l = l->next) { + thumb = FEH_THUMB(l->data); + if (thumb == td.selected) + cur = len; + len++; + } + + target = (cur + jump) % len; + + for (l = thumbnails; l; l = l->next) { + if (target-- == 0) { + feh_thumbnail_select(winwid, FEH_THUMB(l->data)); + break; + } + } +} + +void feh_thumbnail_show_selected(void) +{ + if (td.selected && td.selected->file) + feh_thumbnail_show_fullsize(td.selected->file); +} + +feh_file* feh_thumbnail_get_selected_file(void) +{ + if (td.selected) + return td.selected->file; + return NULL; +} + int feh_thumbnail_setup_thumbnail_dir(void) { int status = 0; struct stat sb; - char *dir, *dir_thumbnails, *home; + char *dir, *p; - home = getenv("HOME"); - if (home != NULL) { - dir = estrjoin("/", home, ".thumbnails", td.cache_dir, NULL); + dir = feh_thumbnail_get_prefix(); + if (dir) { if (!stat(dir, &sb)) { if (S_ISDIR(sb.st_mode)) status = 1; else weprintf("%s should be a directory", dir); } else { - dir_thumbnails = estrjoin("/", home, ".thumbnails", NULL); + for (p = dir + 1; *p; p++) { + if (*p != '/') { + continue; + } - if (stat(dir_thumbnails, &sb) != 0) { - if (mkdir(dir_thumbnails, 0700) == -1) - weprintf("unable to create %s directory", dir_thumbnails); + *p = 0; + if (stat(dir, &sb) != 0) { + if (mkdir(dir, 0700) == -1) { + weprintf("unable to create directory %s", dir); + } + } + *p = '/'; } - if (mkdir(dir, 0700) == -1) - weprintf("unable to create %s directory", dir); - else - status = 1; + if (stat(dir, &sb) != 0) { + if (mkdir(dir, 0700) == -1) { + weprintf("unable to create directory %s", dir); + } + } } + free(dir); } return status; diff --git a/src/thumbnail.h b/src/thumbnail.h index a384d11..e69759f 100644 --- a/src/thumbnail.h +++ b/src/thumbnail.h @@ -1,6 +1,7 @@ /* thumbnail.h Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -43,8 +44,6 @@ typedef struct thumbnail { } feh_thumbnail; typedef struct thumbmode_data { - /* FIXME: text_area_h not really needed, remove? */ - Imlib_Image im_main; /* base image which all thumbnails are rendered on */ Imlib_Image im_bg; /* background for the thumbnails */ @@ -57,11 +56,12 @@ typedef struct thumbmode_data { int text_area_w, text_area_h; /* space needed for thumbnail description */ int max_column_w; /* FIXME: description */ - int vertical; /* FIXME: vertical in what way? */ + int vertical; /* == !opt.limit_w && opt.limit_h */ int cache_thumbnails; /* use cached thumbnails from ~/.thumbnails */ int cache_dim; /* 128 = 128x128 ("normal"), 256 = 256x256 ("large") */ char *cache_dir; /* "normal"/"large" (.thumbnails/...) */ + feh_thumbnail *selected; /* currently selected thumbnail */ } thumbmode_data; @@ -73,12 +73,18 @@ void feh_thumbnail_mark_removed(feh_file * file, int deleted); void feh_thumbnail_calculate_geometry(void); -int feh_thumbnail_get_thumbnail(Imlib_Image * image, feh_file * file); -int feh_thumbnail_generate(Imlib_Image * image, feh_file * file, char *thumb_file, char *uri); -int feh_thumbnail_get_generated(Imlib_Image * image, feh_file * file, char *thumb_file); +int feh_thumbnail_get_thumbnail(Imlib_Image * image, feh_file * file, int * orig_w, int * orig_h); +int feh_thumbnail_generate(Imlib_Image * image, feh_file * file, char *thumb_file, char *uri, int * orig_w, int * orig_h); +int feh_thumbnail_get_generated(Imlib_Image * image, feh_file * file, char * thumb_file, int * orig_w, int * orig_h); char *feh_thumbnail_get_name(char *uri); char *feh_thumbnail_get_name_uri(char *name); char *feh_thumbnail_get_name_md5(char *uri); +void feh_thumbnail_show_fullsize(feh_file *thumbfile); +void feh_thumbnail_select(winwidget winwid, feh_thumbnail *thumbnail); +void feh_thumbnail_select_next(winwidget winwid, int jump); +void feh_thumbnail_select_prev(winwidget winwid, int jump); +void feh_thumbnail_show_selected(void); +feh_file *feh_thumbnail_get_selected_file(void); int feh_thumbnail_setup_thumbnail_dir(void); diff --git a/src/timers.c b/src/timers.c index cba0716..8e42050 100644 --- a/src/timers.c +++ b/src/timers.c @@ -1,6 +1,7 @@ /* timers.c Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -57,7 +58,37 @@ double feh_get_time(void) return((double) timev.tv_sec + (((double) timev.tv_usec) / 1000000)); } -void feh_remove_timer(char *name) +void feh_remove_timer_by_data(void *data) +{ + fehtimer ft, ptr, pptr; + + D(("removing timer for %p\n", data)); + pptr = NULL; + ptr = first_timer; + while (ptr) { + D(("Stepping through event list\n")); + ft = ptr; + if (ft->data == data) { + D(("Found it. Removing\n")); + if (pptr) + pptr->next = ft->next; + else + first_timer = ft->next; + if (ft->next) + ft->next->in += ft->in; + if (ft->name) + free(ft->name); + if (ft) + free(ft); + return; + } + pptr = ptr; + ptr = ptr->next; + } + return; +} + +static void feh_remove_timer(char *name) { fehtimer ft, ptr, pptr; @@ -87,6 +118,7 @@ void feh_remove_timer(char *name) return; } + void feh_add_timer(void (*func) (void *data), void *data, double in, char *name) { fehtimer ft, ptr, pptr; @@ -94,7 +126,7 @@ void feh_add_timer(void (*func) (void *data), void *data, double in, char *name) D(("adding timer %s for %f seconds time\n", name, in)); feh_remove_timer(name); - ft = malloc(sizeof(_fehtimer)); + ft = emalloc(sizeof(_fehtimer)); ft->next = NULL; ft->func = func; ft->data = data; @@ -102,7 +134,6 @@ void feh_add_timer(void (*func) (void *data), void *data, double in, char *name) ft->just_added = 1; ft->in = in; D(("ft->in = %f\n", ft->in)); - tally = 0.0; if (!first_timer) { D(("No first timer\n")); first_timer = ft; diff --git a/src/timers.h b/src/timers.h index a4243ca..e95d9b5 100644 --- a/src/timers.h +++ b/src/timers.h @@ -37,7 +37,7 @@ struct __fehtimer { void feh_handle_timer(void); double feh_get_time(void); -void feh_remove_timer(char *name); +void feh_remove_timer_by_data(void *data); void feh_add_timer(void (*func) (void *data), void *data, double in, char *name); void feh_add_unique_timer(void (*func) (void *data), void *data, double in); diff --git a/src/utils.c b/src/utils.c index 5259af8..eb128a2 100644 --- a/src/utils.c +++ b/src/utils.c @@ -27,16 +27,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "debug.h" #include "options.h" -static char *feh_user_name = NULL; -static char *feh_tmp_dir = NULL; - /* eprintf: print error message and exit */ void eprintf(char *fmt, ...) { va_list args; fflush(stdout); - fprintf(stderr, "%s ERROR: ", PACKAGE); + fputs(PACKAGE " ERROR: ", stderr); va_start(args, fmt); vfprintf(stderr, fmt, args); @@ -44,7 +41,7 @@ void eprintf(char *fmt, ...) if (fmt[0] != '\0' && fmt[strlen(fmt) - 1] == ':') fprintf(stderr, " %s", strerror(errno)); - fprintf(stderr, "\n"); + fputs("\n", stderr); exit(2); } @@ -54,7 +51,7 @@ void weprintf(char *fmt, ...) va_list args; fflush(stdout); - fprintf(stderr, "%s WARNING: ", PACKAGE); + fputs(PACKAGE " WARNING: ", stderr); va_start(args, fmt); vfprintf(stderr, fmt, args); @@ -62,7 +59,7 @@ void weprintf(char *fmt, ...) if (fmt[0] != '\0' && fmt[strlen(fmt) - 1] == ':') fprintf(stderr, " %s", strerror(errno)); - fprintf(stderr, "\n"); + fputs("\n", stderr); } /* estrdup: duplicate a string, report if error */ @@ -125,7 +122,7 @@ char *estrjoin(const char *separator, ...) s = va_arg(args, char *); } va_end(args); - string = malloc(sizeof(char) * (len + 1)); + string = emalloc(sizeof(char) * (len + 1)); *string = 0; va_start(args, separator); @@ -146,24 +143,24 @@ char *estrjoin(const char *separator, ...) return string; } -char *stroflen(char c, int l) -{ - static char buf[1024]; - int i = 0; - - buf[0] = '\0'; - while (l--) - buf[i++] = c; - buf[i] = '\0'; - return buf; +char path_is_url(char *path) { + if ((!strncmp(path, "http://", 7)) + || (!strncmp(path, "https://", 8)) + || (!strncmp(path, "gopher://", 9)) + || (!strncmp(path, "gophers://", 10)) + || (!strncmp(path, "ftp://", 6)) + || (!strncmp(path, "file://", 7))) + return 1; + return 0; } +/* Note: path must end with a trailing / or be an empty string */ /* free the result please */ char *feh_unique_filename(char *path, char *basename) { char *tmpname; char num[10]; - char cppid[10]; + char cppid[12]; static long int i = 1; struct stat st; pid_t ppid; @@ -175,9 +172,11 @@ char *feh_unique_filename(char *path, char *basename) ppid = getpid(); snprintf(cppid, sizeof(cppid), "%06ld", (long) ppid); + tmpname = NULL; /* make sure file doesn't exist */ do { snprintf(num, sizeof(num), "%06ld", i++); + free(tmpname); tmpname = estrjoin("", path, "feh_", cppid, "_", num, "_", basename, NULL); } while (stat(tmpname, &st) == 0); @@ -189,14 +188,14 @@ char *ereadfile(char *path) { char buffer[4096]; FILE *fp; - int count; + size_t count; fp = fopen(path, "r"); if (!fp) return NULL; count = fread(buffer, sizeof(char), sizeof(buffer) - 1, fp); - if (buffer[count - 1] == '\n') + if (count > 0 && buffer[count - 1] == '\n') buffer[count - 1] = '\0'; else buffer[count] = '\0'; @@ -206,31 +205,25 @@ char *ereadfile(char *path) return estrdup(buffer); } -char *feh_get_tmp_dir(void) +char *shell_escape(char *input) { - char *tmp; - if (feh_tmp_dir) - return feh_tmp_dir; - tmp = getenv("TMPDIR"); - if (!tmp) - tmp = getenv("TMP"); - if (!tmp) - tmp = getenv("TEMP"); - if (!tmp) - tmp = "/tmp"; - feh_tmp_dir = estrdup(tmp); - return feh_tmp_dir; -} + static char ret[1024]; + unsigned int out = 0, in = 0; + + ret[out++] = '\''; + for (in = 0; input[in] && (out < (sizeof(ret) - 7)); in++) { + if (input[in] == '\'') { + ret[out++] = '\''; + ret[out++] = '"'; + ret[out++] = '\''; + ret[out++] = '"'; + ret[out++] = '\''; + } + else + ret[out++] = input[in]; + } + ret[out++] = '\''; + ret[out++] = '\0'; -char *feh_get_user_name(void) -{ - struct passwd *pw = NULL; - - if (feh_user_name) - return feh_user_name; - setpwent(); - pw = getpwuid(getuid()); - endpwent(); - feh_user_name = estrdup(pw->pw_name); - return feh_user_name; + return ret; } diff --git a/src/utils.h b/src/utils.h index d0d7665..22cbbf8 100644 --- a/src/utils.h +++ b/src/utils.h @@ -26,6 +26,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef UTILS_H #define UTILS_H +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + #ifndef __GNUC__ # define __attribute__(x) #endif @@ -36,11 +43,10 @@ char *_estrdup(char *s); void *_emalloc(size_t n); void *_erealloc(void *ptr, size_t n); char *estrjoin(const char *separator, ...); -char *stroflen(char, int); +char path_is_url(char *path); char *feh_unique_filename(char *path, char *basename); char *ereadfile(char *path); -char *feh_get_tmp_dir(void); -char *feh_get_user_name(void); +char *shell_escape(char *input); #define ESTRAPPEND(a,b) \ {\ diff --git a/src/support.c b/src/wallpaper.c index afb941b..e2fa67e 100644 --- a/src/support.c +++ b/src/wallpaper.c @@ -1,6 +1,7 @@ -/* support.c +/* wallpaper.c Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -23,11 +24,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include <limits.h> +#include <sys/stat.h> + #include "feh.h" #include "filelist.h" #include "options.h" -#include "support.h" -#include <limits.h> +#include "wallpaper.h" + Window ipc_win = None; Window my_ipc_win = None; Atom ipc_atom = None; @@ -41,48 +45,330 @@ static unsigned char timeout = 0; */ static char e17_fake_ipc = 0; -void feh_wm_set_bg_file(char *file, unsigned char bgmode) +void feh_wm_set_bg_filelist(unsigned char bgmode) { - Imlib_Image im; - feh_file *fil; - - fil = feh_file_new(file); - if (fil) { - if (feh_load_image(&im, fil) == 0) - eprintf("Couldn't load image in order to set bg"); - switch (bgmode) { + if (filelist_len == 0) + eprintf("No files specified for background setting"); + + switch (bgmode) { case BG_MODE_TILE: - feh_wm_set_bg(file, im, 0, 0, 0, 0, 1); + feh_wm_set_bg(NULL, NULL, 0, 0, 0, 0, 1); break; case BG_MODE_SCALE: - feh_wm_set_bg(file, im, 0, 1, 0, 0, 1); + feh_wm_set_bg(NULL, NULL, 0, 1, 0, 0, 1); break; case BG_MODE_FILL: - feh_wm_set_bg(file, im, 0, 0, 1, 0, 1); + feh_wm_set_bg(NULL, NULL, 0, 0, 1, 0, 1); break; case BG_MODE_MAX: - feh_wm_set_bg(file, im, 0, 0, 2, 0, 1); + feh_wm_set_bg(NULL, NULL, 0, 0, 2, 0, 1); break; default: - feh_wm_set_bg(file, im, 1, 0, 0, 0, 1); + feh_wm_set_bg(NULL, NULL, 1, 0, 0, 0, 1); break; + } +} + +static void feh_wm_load_next(Imlib_Image *im) +{ + static gib_list *wpfile = NULL; + + if (wpfile == NULL) + wpfile = filelist; + + if (feh_load_image(im, FEH_FILE(wpfile->data)) == 0) + eprintf("Unable to load image %s", FEH_FILE(wpfile->data)->filename); + if (wpfile->next) + wpfile = wpfile->next; + + return; +} + +static void feh_wm_set_bg_scaled(Pixmap pmap, Imlib_Image im, int use_filelist, + int x, int y, int w, int h) +{ + if (use_filelist) + feh_wm_load_next(&im); + + gib_imlib_render_image_on_drawable_at_size(pmap, im, x, y, w, h, + 1, 1, !opt.force_aliasing); + + if (use_filelist) + gib_imlib_free_image_and_decache(im); + + return; +} + +static void feh_wm_set_bg_centered(Pixmap pmap, Imlib_Image im, int use_filelist, + int x, int y, int w, int h) +{ + int offset_x, offset_y; + + if (use_filelist) + feh_wm_load_next(&im); + + if(opt.geom_flags & XValue) + if(opt.geom_flags & XNegative) + offset_x = (w - gib_imlib_image_get_width(im)) + opt.geom_x; + else + offset_x = opt.geom_x; + else + offset_x = (w - gib_imlib_image_get_width(im)) >> 1; + + if(opt.geom_flags & YValue) + if(opt.geom_flags & YNegative) + offset_y = (h - gib_imlib_image_get_height(im)) + opt.geom_y; + else + offset_y = opt.geom_y; + else + offset_y = (h - gib_imlib_image_get_height(im)) >> 1; + + gib_imlib_render_image_part_on_drawable_at_size(pmap, im, + ((offset_x < 0) ? -offset_x : 0), + ((offset_y < 0) ? -offset_y : 0), + w, + h, + x + ((offset_x > 0) ? offset_x : 0), + y + ((offset_y > 0) ? offset_y : 0), + w, + h, + 1, 1, 0); + + if (use_filelist) + gib_imlib_free_image_and_decache(im); + + return; +} + +static void feh_wm_set_bg_filled(Pixmap pmap, Imlib_Image im, int use_filelist, + int x, int y, int w, int h) +{ + int img_w, img_h, cut_x; + int render_w, render_h, render_x, render_y; + + if (use_filelist) + feh_wm_load_next(&im); + + img_w = gib_imlib_image_get_width(im); + img_h = gib_imlib_image_get_height(im); + + cut_x = (((img_w * h) > (img_h * w)) ? 1 : 0); + + render_w = ( cut_x ? ((img_h * w) / h) : img_w); + render_h = ( !cut_x ? ((img_w * h) / w) : img_h); + + render_x = ( cut_x ? ((img_w - render_w) >> 1) : 0); + render_y = ( !cut_x ? ((img_h - render_h) >> 1) : 0); + + if ((opt.geom_flags & XValue) && cut_x) { + if (opt.geom_flags & XNegative) { + render_x = img_w - render_w + opt.geom_x; + } else { + render_x = opt.geom_x; + } + if (render_x < 0) { + render_x = 0; + } else if (render_x + render_w > img_w) { + render_x = img_w - render_w; } + } + else if ((opt.geom_flags & YValue) && !cut_x) { + if (opt.geom_flags & YNegative) { + render_y = img_h - render_h + opt.geom_y; + } else { + render_y = opt.geom_y; + } + if (render_y < 0) { + render_y = 0; + } else if (render_y + render_h > img_h) { + render_y = img_h - render_h; + } + } + + gib_imlib_render_image_part_on_drawable_at_size(pmap, im, + render_x, render_y, + render_w, render_h, + x, y, w, h, + 1, 1, !opt.force_aliasing); + + if (use_filelist) + gib_imlib_free_image_and_decache(im); + + return; +} + +static void feh_wm_set_bg_maxed(Pixmap pmap, Imlib_Image im, int use_filelist, + int x, int y, int w, int h) +{ + int img_w, img_h, border_x; + int render_w, render_h, render_x, render_y; + int margin_x, margin_y; + + if (use_filelist) + feh_wm_load_next(&im); + + img_w = gib_imlib_image_get_width(im); + img_h = gib_imlib_image_get_height(im); + + border_x = (((img_w * h) > (img_h * w)) ? 0 : 1); + + render_w = ( border_x ? ((img_w * h) / img_h) : w); + render_h = ( !border_x ? ((img_h * w) / img_w) : h); + + if(opt.geom_flags & XValue) + if(opt.geom_flags & XNegative) + margin_x = (w - render_w) + opt.geom_x; + else + margin_x = opt.geom_x; + else + margin_x = (w - render_w) >> 1; + + if(opt.geom_flags & YValue) + if(opt.geom_flags & YNegative) + margin_y = (h - render_h) + opt.geom_y; + else + margin_y = opt.geom_y; + else + margin_y = (h - render_h) >> 1; + + render_x = x + ( border_x ? margin_x : 0); + render_y = y + ( !border_x ? margin_y : 0); + + gib_imlib_render_image_on_drawable_at_size(pmap, im, + render_x, render_y, + render_w, render_h, + 1, 1, !opt.force_aliasing); + + if (use_filelist) gib_imlib_free_image_and_decache(im); - feh_file_free(fil); + + return; +} + +/* +** Creates a script that can be used to create the same background +** as the last time the program was called. +*/ +void feh_wm_gen_bg_script(char* fil, int centered, int scaled, int filled, int use_filelist) { + char * home = getenv("HOME"); + + if (!home) + return; + + FILE *fp; + int fd; + char *path; + char *exec_method; + char *absolute_path; + struct stat s; + gib_list *filelist_pos = filelist; + + if (strchr(cmdargv[0], '/')) + exec_method = feh_absolute_path(cmdargv[0]); + else + exec_method = cmdargv[0]; + + path = estrjoin("/", home, ".fehbg", NULL); + + if ((fp = fopen(path, "w")) == NULL) { + weprintf("Can't write to %s", path); + } else { + fputs("#!/bin/sh\n", fp); + fputs(exec_method, fp); + fputs(" --no-fehbg --bg-", fp); + if (centered) + fputs("center", fp); + else if (scaled) + fputs("scale", fp); + else if (filled == 1) + fputs("fill", fp); + else if (filled == 2) + fputs("max", fp); + else + fputs("tile", fp); + if (opt.image_bg) { + fputs(" --image-bg ", fp); + fputs(shell_escape(opt.image_bg), fp); + } +#ifdef HAVE_LIBXINERAMA + if (opt.xinerama) { + if (opt.xinerama_index >= 0) { + fprintf(fp, " --xinerama-index %d", opt.xinerama_index); + } + } + else { + fputs(" --no-xinerama", fp); + } +#endif /* HAVE_LIBXINERAMA */ + if (opt.geom_flags & XValue) { + fprintf(fp, " --geometry %c%d", + opt.geom_flags & XNegative ? '-' : '+', + opt.geom_flags & XNegative ? abs(opt.geom_x) : opt.geom_x); + if (opt.geom_flags & YValue) { + fprintf(fp, "%c%d", + opt.geom_flags & YNegative ? '-' : '+', + opt.geom_flags & YNegative ? abs(opt.geom_y) : opt.geom_y); + } + } + if (opt.force_aliasing) { + fputs(" --force-aliasing", fp); + } + fputc(' ', fp); + if (use_filelist) { +#ifdef HAVE_LIBXINERAMA + for (int i = 0; (i < opt.xinerama ? num_xinerama_screens : 1) && filelist_pos; i++) +#else + for (int i = 0; (i < 1 ) && filelist_pos; i++) +#endif + { + absolute_path = feh_absolute_path(FEH_FILE(filelist_pos->data)->filename); + fputs(shell_escape(absolute_path), fp); + filelist_pos = filelist_pos->next; + free(absolute_path); + fputc(' ', fp); + } + } else if (fil) { + absolute_path = feh_absolute_path(fil); + fputs(shell_escape(absolute_path), fp); + free(absolute_path); + } + fputc('\n', fp); + fd = fileno(fp); + if (fstat(fd, &s) != 0 || fchmod(fd, s.st_mode | S_IXUSR | S_IXGRP) != 0) { + weprintf("Can't set %s as executable", path); + } + fclose(fp); } + free(path); + + if(exec_method != cmdargv[0]) + free(exec_method); } void feh_wm_set_bg(char *fil, Imlib_Image im, int centered, int scaled, - int filled, int desktop, int set) + int filled, int desktop, int use_filelist) { + XGCValues gcvalues; + XGCValues gcval; + GC gc; char bgname[20]; - int num = (int) rand(); + int num = (int) random(); char bgfil[4096]; char sendbuf[4096]; + /* + * TODO this re-implements mkstemp (badly). However, it is only needed + * for non-file images and enlightenment. Might be easier to just remove + * it. + */ + snprintf(bgname, sizeof(bgname), "FEHBG_%d", num); - if (!fil) { + if (!fil && im) { + if (getenv("HOME") == NULL) { + weprintf("Cannot save wallpaper to temporary file: You have no HOME"); + return; + } snprintf(bgfil, sizeof(bgfil), "%s/.%s.png", getenv("HOME"), bgname); imlib_context_set_image(im); imlib_image_set_format("png"); @@ -90,10 +376,16 @@ void feh_wm_set_bg(char *fil, Imlib_Image im, int centered, int scaled, D(("bg saved as %s\n", bgfil)); fil = bgfil; } - D(("Setting bg %s\n", fil)); if (feh_wm_get_wm_is_e() && (enl_ipc_get_win() != None)) { - snprintf(sendbuf, sizeof(sendbuf), "background %s bg.file %s", bgname, fil); + if (use_filelist) { + feh_wm_load_next(&im); + fil = FEH_FILE(filelist->data)->filename; + } + if ((size_t) snprintf(sendbuf, sizeof(sendbuf), "background %s bg.file %s", bgname, fil) >= sizeof(sendbuf)) { + weprintf("Writing to IPC send buffer was truncated"); + return; + } enl_ipc_send(sendbuf); if (scaled) { @@ -123,171 +415,142 @@ void feh_wm_set_bg(char *fil, Imlib_Image im, int centered, int scaled, enl_ipc_send(sendbuf); } - if (set) { - snprintf(sendbuf, sizeof(sendbuf), "use_bg %s %d", bgname, desktop); - enl_ipc_send(sendbuf); - } + snprintf(sendbuf, sizeof(sendbuf), "use_bg %s %d", bgname, desktop); + enl_ipc_send(sendbuf); enl_ipc_sync(); } else { Atom prop_root, prop_esetroot, type; - int format; + int format, i; unsigned long length, after; - unsigned char *data_root, *data_esetroot; + unsigned char *data_root = NULL, *data_esetroot = NULL; Pixmap pmap_d1, pmap_d2; - /* string for sticking in ~/.fehbg */ - char *fehbg = NULL; - char *home; - char filbuf[PATH_MAX]; /* local display to set closedownmode on */ Display *disp2; Window root2; int depth2; - XGCValues gcvalues; - GC gc; - int in, out, w, h; + int w, h; D(("Falling back to XSetRootWindowPixmap\n")); - /* Put the filename in filbuf between ' and escape ' in the filename */ - out = 0; - filbuf[out++] = '\''; - for (in = 0; fil[in] && out < (PATH_MAX - 4); in++) { - if (fil[in] == '\'') { - filbuf[out++] = '\\'; - } - filbuf[out++] = fil[in]; - } - filbuf[out++] = '\''; - filbuf[out++] = 0; + XColor color; + Colormap cmap = DefaultColormap(disp, DefaultScreen(disp)); + if (opt.image_bg) + XAllocNamedColor(disp, cmap, (char*) opt.image_bg, &color, &color); + else + XAllocNamedColor(disp, cmap, "black", &color, &color); if (scaled) { - w = scr->width; - h = scr->height; + pmap_d1 = XCreatePixmap(disp, root, scr->width, scr->height, depth); + +#ifdef HAVE_LIBXINERAMA + if (opt.xinerama_index >= 0) { + gcval.foreground = color.pixel; + gc = XCreateGC(disp, root, GCForeground, &gcval); + XFillRectangle(disp, pmap_d1, gc, 0, 0, scr->width, scr->height); + XFreeGC(disp, gc); + } -/* disable xinerama check for setting background */ -#if 0 -/* #ifdef HAVE_LIBXINERAMA */ if (opt.xinerama && xinerama_screens) { - w = xinerama_screens[xinerama_screen].width; - h = xinerama_screens[xinerama_screen].height; + for (i = 0; i < num_xinerama_screens; i++) { + if (opt.xinerama_index < 0 || opt.xinerama_index == i) { + feh_wm_set_bg_scaled(pmap_d1, im, use_filelist, + xinerama_screens[i].x_org, xinerama_screens[i].y_org, + xinerama_screens[i].width, xinerama_screens[i].height); + } + } } -#endif /* HAVE_LIBXINERAMA */ - - pmap_d1 = XCreatePixmap(disp, root, w, h, depth); - gib_imlib_render_image_on_drawable_at_size(pmap_d1, im, 0, 0, w, h, 1, 0, 1); - fehbg = estrjoin(" ", "feh --bg-scale", filbuf, NULL); + else +#endif /* HAVE_LIBXINERAMA */ + feh_wm_set_bg_scaled(pmap_d1, im, use_filelist, + 0, 0, scr->width, scr->height); } else if (centered) { - XGCValues gcval; - GC gc; - int x, y; D(("centering\n")); - w = scr->width; - h = scr->height; -/* disable xinerama check for setting background */ -#if 0 -/* #ifdef HAVE_LIBXINERAMA */ + pmap_d1 = XCreatePixmap(disp, root, scr->width, scr->height, depth); + gcval.foreground = color.pixel; + gc = XCreateGC(disp, root, GCForeground, &gcval); + XFillRectangle(disp, pmap_d1, gc, 0, 0, scr->width, scr->height); + +#ifdef HAVE_LIBXINERAMA if (opt.xinerama && xinerama_screens) { - w = xinerama_screens[xinerama_screen].width; - h = xinerama_screens[xinerama_screen].height; + for (i = 0; i < num_xinerama_screens; i++) { + if (opt.xinerama_index < 0 || opt.xinerama_index == i) { + feh_wm_set_bg_centered(pmap_d1, im, use_filelist, + xinerama_screens[i].x_org, xinerama_screens[i].y_org, + xinerama_screens[i].width, xinerama_screens[i].height); + } + } } + else #endif /* HAVE_LIBXINERAMA */ + feh_wm_set_bg_centered(pmap_d1, im, use_filelist, + 0, 0, scr->width, scr->height); - pmap_d1 = XCreatePixmap(disp, root, w, h, depth); - gcval.foreground = BlackPixel(disp, DefaultScreen(disp)); - gc = XCreateGC(disp, root, GCForeground, &gcval); - XFillRectangle(disp, pmap_d1, gc, 0, 0, w, h); - x = (w - gib_imlib_image_get_width(im)) >> 1; - y = (h - gib_imlib_image_get_height(im)) >> 1; - gib_imlib_render_image_on_drawable(pmap_d1, im, x, y, 1, 0, 0); XFreeGC(disp, gc); - fehbg = estrjoin(" ", "feh --bg-center", filbuf, NULL); + } else if (filled == 1) { - int scr_w = scr->width; - int scr_h = scr->height; - int img_w = gib_imlib_image_get_width(im); - int img_h = gib_imlib_image_get_height(im); - int render_x = 0; - int render_y = 0; - - if ((img_w * scr_h) > (scr_w * img_h)) { - h = scr_h; - w = (scr_h * img_w) / img_h; - render_x = (scr_w - w) / 2; - } else { - h = (scr_w * img_h) / img_w; - w = scr_w; - render_y = (scr_h - h) / 2; + + pmap_d1 = XCreatePixmap(disp, root, scr->width, scr->height, depth); + +#ifdef HAVE_LIBXINERAMA + if (opt.xinerama_index >= 0) { + gcval.foreground = color.pixel; + gc = XCreateGC(disp, root, GCForeground, &gcval); + XFillRectangle(disp, pmap_d1, gc, 0, 0, scr->width, scr->height); + XFreeGC(disp, gc); } - pmap_d1 = XCreatePixmap(disp, root, w, h, depth); - gib_imlib_render_image_on_drawable_at_size(pmap_d1, im, - render_x, render_y, w, h, 1, 0, 1); - fehbg = estrjoin(" ", "feh --bg-fill", filbuf, NULL); - } else if (filled == 2) { - int scr_w = scr->width; - int scr_h = scr->height; - int img_w = gib_imlib_image_get_width(im); - int img_h = gib_imlib_image_get_height(im); - int render_x = 0; - int render_y = 0; - XGCValues gcval; - - if (img_w > img_h) { - w = scr_w; - h = (((scr_w * 100) / img_w) * img_h) / 100; - render_y = (scr_h - h) / 2; - if (h > scr_h) { - w = (((scr_h * 100) / h) * w) / 100; - h = scr_h; - render_x = (scr_w - w) / 2; - render_y = 0; - } - } else { - h = scr_h; - w = (((scr_h * 100) / img_h) * img_w) / 100; - render_x = (scr_w - w) / 2; - if (w > scr_w) { - h = (((scr_w * 100) / w) * h) / 100; - w = scr_w; - render_x = 0; - render_y = (scr_h - h) / 2; + + if (opt.xinerama && xinerama_screens) { + for (i = 0; i < num_xinerama_screens; i++) { + if (opt.xinerama_index < 0 || opt.xinerama_index == i) { + feh_wm_set_bg_filled(pmap_d1, im, use_filelist, + xinerama_screens[i].x_org, xinerama_screens[i].y_org, + xinerama_screens[i].width, xinerama_screens[i].height); + } } } + else +#endif /* HAVE_LIBXINERAMA */ + feh_wm_set_bg_filled(pmap_d1, im, use_filelist + , 0, 0, scr->width, scr->height); + + } else if (filled == 2) { - pmap_d1 = XCreatePixmap(disp, root, scr_w, scr_h, depth); - gcval.foreground = BlackPixel(disp, DefaultScreen(disp)); + pmap_d1 = XCreatePixmap(disp, root, scr->width, scr->height, depth); + gcval.foreground = color.pixel; gc = XCreateGC(disp, root, GCForeground, &gcval); - XFillRectangle(disp, pmap_d1, gc, 0, 0, scr_w, scr_h); - gib_imlib_render_image_on_drawable_at_size(pmap_d1, im, - render_x, render_y, w, h, 1, 0, 1); + XFillRectangle(disp, pmap_d1, gc, 0, 0, scr->width, scr->height); + +#ifdef HAVE_LIBXINERAMA + if (opt.xinerama && xinerama_screens) { + for (i = 0; i < num_xinerama_screens; i++) { + if (opt.xinerama_index < 0 || opt.xinerama_index == i) { + feh_wm_set_bg_maxed(pmap_d1, im, use_filelist, + xinerama_screens[i].x_org, xinerama_screens[i].y_org, + xinerama_screens[i].width, xinerama_screens[i].height); + } + } + } + else +#endif /* HAVE_LIBXINERAMA */ + feh_wm_set_bg_maxed(pmap_d1, im, use_filelist, + 0, 0, scr->width, scr->height); + XFreeGC(disp, gc); - fehbg = estrjoin(" ", "feh --bg-max", filbuf, NULL); + } else { + if (use_filelist) + feh_wm_load_next(&im); w = gib_imlib_image_get_width(im); h = gib_imlib_image_get_height(im); pmap_d1 = XCreatePixmap(disp, root, w, h, depth); - gib_imlib_render_image_on_drawable(pmap_d1, im, 0, 0, 1, 0, 0); - fehbg = estrjoin(" ", "feh --bg-tile", filbuf, NULL); + gib_imlib_render_image_on_drawable(pmap_d1, im, 0, 0, 1, 1, 0); } - if (fehbg) { - home = getenv("HOME"); - if (home) { - FILE *fp; - char *path; - path = estrjoin("/", home, ".fehbg", NULL); - if ((fp = fopen(path, "w")) == NULL) { - weprintf("Can't open %s for write", path); - } else { - fprintf(fp, "%s\n", fehbg); - fclose(fp); - } - free(path); - } - free(fehbg); - } + if (!opt.no_fehbg) + feh_wm_gen_bg_script(fil, centered, scaled, filled, use_filelist); /* create new display, copy pixmap to new display */ disp2 = XOpenDisplay(NULL); @@ -325,12 +588,19 @@ void feh_wm_set_bg(char *fil, Imlib_Image im, int centered, int scaled, } } } + + if (data_root) + XFree(data_root); + + if (data_esetroot) + XFree(data_esetroot); + /* This will locate the property, creating it if it doesn't exist */ prop_root = XInternAtom(disp2, "_XROOTPMAP_ID", False); prop_esetroot = XInternAtom(disp2, "ESETROOT_PMAP_ID", False); if (prop_root == None || prop_esetroot == None) - weprintf("creation of pixmap property failed."); + eprintf("creation of pixmap property failed."); XChangeProperty(disp2, root2, prop_root, XA_PIXMAP, 32, PropModeReplace, (unsigned char *) &pmap_d2, 1); XChangeProperty(disp2, root2, prop_esetroot, XA_PIXMAP, 32, @@ -533,11 +803,11 @@ void enl_ipc_send(char *str) return; } -static sighandler_t *enl_ipc_timeout(int sig) +void enl_ipc_timeout(int sig) { - timeout = 1; - return((sighandler_t *) sig); - sig = 0; + if (sig == SIGALRM) + timeout = 1; + return; } char *enl_wait_for_reply(void) @@ -564,7 +834,7 @@ char *enl_ipc_get(const char *msg_data) { static char *message = NULL; - static unsigned short len = 0; + static size_t len = 0; char buff[13], *ret_msg = NULL; register unsigned char i; unsigned char blen; @@ -597,7 +867,8 @@ char *enl_ipc_get(const char *msg_data) char *enl_send_and_wait(char *msg) { char *reply = IPC_TIMEOUT; - sighandler_t old_alrm; + struct sigaction e17_sh, feh_sh; + sigset_t e17_ss; /* * Shortcut this func and return IPC_FAKE @@ -616,7 +887,19 @@ char *enl_send_and_wait(char *msg) sleep(1); } } - old_alrm = (sighandler_t) signal(SIGALRM, (sighandler_t) enl_ipc_timeout); + + if ((sigemptyset(&e17_ss) == -1) || sigaddset(&e17_ss, SIGALRM) == -1) { + weprintf("Failed to set up temporary E17 signal masks"); + return reply; + } + e17_sh.sa_handler = enl_ipc_timeout; + e17_sh.sa_mask = e17_ss; + e17_sh.sa_flags = 0; + if (sigaction(SIGALRM, &e17_sh, &feh_sh) == -1) { + weprintf("Failed to set up temporary E17 signal handler"); + return reply; + } + for (; reply == IPC_TIMEOUT;) { timeout = 0; enl_ipc_send(msg); @@ -628,6 +911,8 @@ char *enl_send_and_wait(char *msg) ipc_win = None; } } - signal(SIGALRM, old_alrm); + if (sigaction(SIGALRM, &feh_sh, NULL) == -1) { + weprintf("Failed to restore signal handler"); + } return(reply); } diff --git a/src/support.h b/src/wallpaper.h index bb17082..c836c0f 100644 --- a/src/support.h +++ b/src/wallpaper.h @@ -1,6 +1,7 @@ -/* support.h +/* wallpaper.h Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -23,8 +24,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef SUPPORT_H -#define SUPPORT_H +#ifndef WALLPAPER_H +#define WALLPAPER_H #include <X11/Xfuncproto.h> #include <X11/Intrinsic.h> /* Xlib, Xutil, Xresource, Xfuncproto */ @@ -41,16 +42,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. extern Window ipc_win; extern Atom ipc_atom; -_XFUNCPROTOBEGIN extern Window enl_ipc_get_win(void); +extern Window enl_ipc_get_win(void); extern void enl_ipc_send(char *); extern char *enl_wait_for_reply(void); extern char *enl_ipc_get(const char *); extern char *enl_send_and_wait(char *); extern void feh_wm_set_bg(char *fil, Imlib_Image im, int centered, int scaled, - int fill, int desktop, int set); + int fill, int desktop, int for_screen); extern int feh_wm_get_num_desks(void); extern signed char feh_wm_get_wm_is_e(void); -void feh_wm_set_bg_file(char *file, unsigned char bgmode); +void feh_wm_set_bg_filelist(unsigned char bgmode); -_XFUNCPROTOEND #endif diff --git a/src/winwidget.c b/src/winwidget.c index 26a43e2..3b90158 100644 --- a/src/winwidget.c +++ b/src/winwidget.c @@ -1,6 +1,7 @@ /* winwidget.c Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2025 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 @@ -27,6 +28,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "filelist.h" #include "winwidget.h" #include "options.h" +#include "events.h" +#include "timers.h" + +#ifdef HAVE_INOTIFY +#include <sys/inotify.h> +#endif static void winwidget_unregister(winwidget win); static void winwidget_register(winwidget win); @@ -41,6 +48,7 @@ static winwidget winwidget_allocate(void) winwidget ret = NULL; ret = emalloc(sizeof(_winwidget)); + memset(ret, 0, sizeof(_winwidget)); ret->win = 0; ret->w = 0; @@ -54,9 +62,11 @@ static winwidget winwidget_allocate(void) ret->im = NULL; ret->name = NULL; ret->file = NULL; + ret->errstr = NULL; ret->type = WIN_TYPE_UNSET; ret->visible = 0; ret->caption_entry = 0; + ret->force_aliasing = opt.force_aliasing; /* Zoom stuff */ ret->mode = MODE_NORMAL; @@ -73,10 +83,14 @@ static winwidget winwidget_allocate(void) ret->click_offset_y = 0; ret->has_rotated = 0; +#ifdef HAVE_INOTIFY + ret->inotify_wd = -1; +#endif + return(ret); } -winwidget winwidget_create_from_image(Imlib_Image im, char *name, char type) +winwidget winwidget_create_from_image(Imlib_Image im, char type) { winwidget ret = NULL; @@ -90,20 +104,20 @@ winwidget winwidget_create_from_image(Imlib_Image im, char *name, char type) ret->w = ret->im_w = gib_imlib_image_get_width(ret->im); ret->h = ret->im_h = gib_imlib_image_get_height(ret->im); - if (name) - ret->name = estrdup(name); - else - ret->name = estrdup(PACKAGE); - - if (opt.full_screen && (type != WIN_TYPE_THUMBNAIL)) + if (opt.full_screen && (type != WIN_TYPE_THUMBNAIL)) { ret->full_screen = True; + } else if (opt.default_zoom) { + ret->zoom = 0.01 * opt.default_zoom; + ret->w *= ret->zoom; + ret->h *= ret->zoom; + } winwidget_create_window(ret, ret->w, ret->h); - winwidget_render_image(ret, 1, 1); + winwidget_render_image(ret, 1, 0); return(ret); } -winwidget winwidget_create_from_file(gib_list * list, char *name, char type) +winwidget winwidget_create_from_file(gib_list * list, char type) { winwidget ret = NULL; feh_file *file = FEH_FILE(list->data); @@ -114,12 +128,8 @@ winwidget winwidget_create_from_file(gib_list * list, char *name, char type) ret = winwidget_allocate(); ret->file = list; ret->type = type; - if (name) - ret->name = estrdup(name); - else - ret->name = estrdup(file->filename); - if (winwidget_loadimage(ret, file) == 0) { + if ((winwidget_loadimage(ret, file) == 0) || feh_should_ignore_image(ret->im)) { winwidget_destroy(ret); return(NULL); } @@ -128,10 +138,15 @@ winwidget winwidget_create_from_file(gib_list * list, char *name, char type) ret->w = ret->im_w = gib_imlib_image_get_width(ret->im); ret->h = ret->im_h = gib_imlib_image_get_height(ret->im); D(("image is %dx%d pixels, format %s\n", ret->w, ret->h, gib_imlib_image_format(ret->im))); - if (opt.full_screen) + if (opt.full_screen) { ret->full_screen = True; + } else if (opt.default_zoom) { + ret->zoom = 0.01 * opt.default_zoom; + ret->w *= ret->zoom; + ret->h *= ret->zoom; + } winwidget_create_window(ret, ret->w, ret->h); - winwidget_render_image(ret, 1, 1); + winwidget_render_image(ret, 1, 0); } return(ret); @@ -139,14 +154,22 @@ winwidget winwidget_create_from_file(gib_list * list, char *name, char type) void winwidget_create_window(winwidget ret, int w, int h) { + XWindowAttributes window_attr; XSetWindowAttributes attr; XEvent ev; XClassHint *xch; MWMHints mwmhints; Atom prop = None; + pid_t pid; int x = 0; int y = 0; char *tmpname; +#ifdef HOST_NAME_MAX + char hostname[HOST_NAME_MAX]; +#else /* ! HOST_NAME_MAX */ + long host_name_max; + char *hostname; +#endif /* HOST_NAME_MAX */ D(("winwidget_create_window %dx%d\n", w, h)); @@ -200,7 +223,6 @@ void winwidget_create_window(winwidget ret, int w, int h) } if (opt.paused) { - printf("name %s\n", ret->name); tmpname = estrjoin(" ", ret->name, "[Paused]", NULL); free(ret->name); ret->name = tmpname; @@ -224,6 +246,7 @@ void winwidget_create_window(winwidget ret, int w, int h) KeyPressMask | KeyReleaseMask | ButtonMotionMask | ExposureMask | FocusChangeMask | PropertyChangeMask | VisibilityChangeMask; + memset(&mwmhints, 0, sizeof(mwmhints)); if (opt.borderless || ret->full_screen) { prop = XInternAtom(disp, "_MOTIF_WM_HINTS", True); if (prop == None) { @@ -236,14 +259,33 @@ void winwidget_create_window(winwidget ret, int w, int h) mwmhints.flags = MWM_HINTS_DECORATIONS; mwmhints.decorations = 0; } - } else - mwmhints.flags = 0; + } - ret->win = - XCreateWindow(disp, DefaultRootWindow(disp), x, y, w, h, 0, - depth, InputOutput, vis, - CWOverrideRedirect | CWSaveUnder | CWBackingStore - | CWColormap | CWBackPixel | CWBorderPixel | CWEventMask, &attr); + if (opt.x11_windowid == 0) { + ret->win = + XCreateWindow(disp, DefaultRootWindow(disp), x, y, w, h, 0, + depth, InputOutput, vis, + CWOverrideRedirect | CWSaveUnder | CWBackingStore + | CWColormap | CWBackPixel | CWBorderPixel | CWEventMask, + &attr); + } else { + /* x11_windowid is a pointer to the window */ + ret->win = (Window) opt.x11_windowid; + + /* set our attributes on the window */ + XChangeWindowAttributes(disp, ret->win, + CWOverrideRedirect | CWSaveUnder | CWBackingStore + | CWColormap | CWBackPixel | CWBorderPixel + | CWEventMask, &attr); + + /* determine the size and visibility of the window */ + XGetWindowAttributes(disp, ret->win, &window_attr); + ret->visible = (window_attr.map_state == IsViewable); + ret->x = window_attr.x; + ret->y = window_attr.y; + ret->w = window_attr.width; + ret->h = window_attr.height; + } if (mwmhints.flags) { XChangeProperty(disp, ret->win, prop, prop, 32, @@ -259,18 +301,43 @@ void winwidget_create_window(winwidget ret, int w, int h) ev.xclient.display = disp; ev.xclient.window = ret->win; ev.xclient.format = 32; - ev.xclient.data.l[0] = (ret->full_screen ? 1 : 0); + ev.xclient.data.l[0] = 1; ev.xclient.data.l[1] = prop_fs; XChangeProperty(disp, ret->win, prop_state, XA_ATOM, 32, PropModeReplace, (unsigned char *) &prop_fs, 1); } + pid = getpid(); + prop = XInternAtom(disp, "_NET_WM_PID", False); + XChangeProperty(disp, ret->win, prop, XA_CARDINAL, sizeof(pid_t) * 8, + PropModeReplace, (const unsigned char *)&pid, 1); + +#ifdef HOST_NAME_MAX + if (gethostname(hostname, HOST_NAME_MAX) == 0) { + hostname[HOST_NAME_MAX-1] = '\0'; + prop = XInternAtom(disp, "WM_CLIENT_MACHINE", False); + XChangeProperty(disp, ret->win, prop, XA_STRING, sizeof(char) * 8, + PropModeReplace, (unsigned char *)hostname, strlen(hostname)); + } +#else /* ! HOST_NAME_MAX */ + if ((host_name_max = sysconf(_SC_HOST_NAME_MAX)) != -1 ) { + if ((hostname = calloc(1, host_name_max + 1)) != NULL ) { + if (gethostname(hostname, host_name_max) == 0) { + prop = XInternAtom(disp, "WM_CLIENT_MACHINE", False); + XChangeProperty(disp, ret->win, prop, XA_STRING, sizeof(char) * 8, + PropModeReplace, (unsigned char *)hostname, strlen(hostname)); + } + free(hostname); + } + } +#endif /* HOST_NAME_MAX */ + XSetWMProtocols(disp, ret->win, &wmDeleteWindow, 1); winwidget_update_title(ret); xch = XAllocClassHint(); xch->res_name = "feh"; - xch->res_class = "feh"; + xch->res_class = opt.x11_class ? opt.x11_class : "feh"; XSetClassHint(disp, ret->win, xch); XFree(xch); @@ -293,18 +360,35 @@ void winwidget_create_window(winwidget ret, int w, int h) XSetCommand(disp, ret->win, cmdargv, cmdargc); winwidget_register(ret); + + /* do not scale down a thumbnail list window, only those created from it */ + if (opt.geom_enabled && (ret->type != WIN_TYPE_THUMBNAIL)) { + opt.geom_w = w; + opt.geom_h = h; + opt.geom_flags |= WidthValue | HeightValue; + } + return; } void winwidget_update_title(winwidget ret) { char *name; + Atom prop_name = XInternAtom(disp, "_NET_WM_NAME", False); + Atom prop_icon = XInternAtom(disp, "_NET_WM_ICON_NAME", False); + Atom prop_utf8 = XInternAtom(disp, "UTF8_STRING", False); D(("winwid->name = %s\n", ret->name)); name = ret->name ? ret->name : "feh"; XStoreName(disp, ret->win, name); XSetIconName(disp, ret->win, name); - /* XFlush(disp); */ + + XChangeProperty(disp, ret->win, prop_name, prop_utf8, 8, + PropModeReplace, (const unsigned char *)name, strlen(name)); + + XChangeProperty(disp, ret->win, prop_icon, prop_utf8, 8, + PropModeReplace, (const unsigned char *)name, strlen(name)); + return; } @@ -338,8 +422,20 @@ void winwidget_setup_pixmaps(winwidget winwid) if (winwid->gc == None) { XGCValues gcval; - gcval.foreground = BlackPixel(disp, DefaultScreen(disp)); - winwid->gc = XCreateGC(disp, winwid->win, GCForeground, &gcval); + if (!opt.image_bg || !strcmp(opt.image_bg, "default")) { + gcval.foreground = BlackPixel(disp, DefaultScreen(disp)); + winwid->gc = XCreateGC(disp, winwid->win, GCForeground, &gcval); + } else if (!strcmp(opt.image_bg, "checks")) { + gcval.tile = feh_create_checks(); + gcval.fill_style = FillTiled; + winwid->gc = XCreateGC(disp, winwid->win, GCTile | GCFillStyle, &gcval); + } else { + XColor color; + Colormap cmap = DefaultColormap(disp, DefaultScreen(disp)); + XAllocNamedColor(disp, cmap, (char*) opt.image_bg, &color, &color); + gcval.foreground = color.pixel; + winwid->gc = XCreateGC(disp, winwid->win, GCForeground, &gcval); + } } winwid->bg_pmap = XCreatePixmap(disp, winwid->win, scr->width, scr->height, depth); } @@ -361,130 +457,76 @@ void winwidget_setup_pixmaps(winwidget winwid) return; } -void winwidget_render_image(winwidget winwid, int resize, int alias) +void winwidget_render_image(winwidget winwid, int resize, int force_alias) { int sx, sy, sw, sh, dx, dy, dw, dh; int calc_w, calc_h; + int antialias = 0; if (!winwid->full_screen && resize) { - winwidget_resize(winwid, winwid->im_w, winwid->im_h); + if (opt.default_zoom) { + winwid->zoom = 0.01 * opt.default_zoom; + winwidget_resize(winwid, winwid->im_w * winwid->zoom, winwid->im_h * winwid->zoom, 0); + } else { + winwidget_resize(winwid, winwid->im_w, winwid->im_h, 0); + } winwidget_reset_image(winwid); } - /* bounds checks for panning */ - if (winwid->im_x > winwid->w) - winwid->im_x = winwid->w; - if (winwid->im_y > winwid->h) - winwid->im_y = winwid->h; + D(("winwidget_render_image resize %d force_alias %d im %dx%d\n", + resize, force_alias, winwid->im_w, winwid->im_h)); - D(("winwidget_render_image resize %d alias %d im %dx%d\n", - resize, alias, winwid->im_w, winwid->im_h)); + /* winwidget_setup_pixmaps(winwid) resets the winwid->had_resize flag */ + int had_resize = winwid->had_resize || resize; winwidget_setup_pixmaps(winwid); - if (!winwid->full_screen && opt.scale_down && ((winwid->w < winwid->im_w) - || (winwid->h < winwid->im_h))) { - D(("scaling down image %dx%d\n", winwid->w, winwid->h)); + if (had_resize && !opt.keep_zoom_vp && (winwid->type != WIN_TYPE_THUMBNAIL)) { + double required_zoom = 1.0; + feh_calc_needed_zoom(&required_zoom, winwid->im_w, winwid->im_h, winwid->w, winwid->h); - feh_calc_needed_zoom(&(winwid->zoom), winwid->im_w, winwid->im_h, winwid->w, winwid->h); - if (resize) - winwidget_resize(winwid, winwid->im_w * winwid->zoom, winwid->im_h * winwid->zoom); - D(("after scaling down image %dx%d\n", winwid->w, winwid->h)); - } + winwid->zoom = opt.default_zoom ? (0.01 * opt.default_zoom) : 1.0; - if (!winwid->full_screen && ((gib_imlib_image_has_alpha(winwid->im)) || (opt.geom_flags) - || (winwid->im_x || winwid->im_y) || (winwid->zoom != 1.0) - || (winwid->w > winwid->im_w || winwid->h > winwid->im_h) - || (winwid->has_rotated))) - feh_draw_checks(winwid); + if ((opt.scale_down || (winwid->full_screen && !opt.default_zoom)) + && winwid->zoom > required_zoom) + winwid->zoom = required_zoom; + else if ((opt.zoom_mode && required_zoom > 1) + && (!opt.default_zoom || required_zoom < winwid->zoom)) + winwid->zoom = required_zoom; - if (resize && (winwid->full_screen || opt.geom_flags)) { - int smaller; /* Is the image smaller than screen? */ - int max_w = 0, max_h = 0; - - if (winwid->full_screen) { - max_w = scr->width; - max_h = scr->height; -#ifdef HAVE_LIBXINERAMA - if (opt.xinerama && xinerama_screens) { - max_w = xinerama_screens[xinerama_screen].width; - max_h = xinerama_screens[xinerama_screen].height; + if (opt.offset_flags & XValue) { + if (opt.offset_flags & XNegative) { + winwid->im_x = winwid->w - (winwid->im_w * winwid->zoom) - opt.offset_x; + } else { + winwid->im_x = - opt.offset_x * winwid->zoom; } -#endif /* HAVE_LIBXINERAMA */ } else { - if (opt.geom_flags & WidthValue) { - max_w = opt.geom_w; - } - if (opt.geom_flags & HeightValue) { - max_h = opt.geom_h; - } + winwid->im_x = (int) (winwid->w - (winwid->im_w * winwid->zoom)) >> 1; } - - D(("Calculating for fullscreen/fixed geom render\n")); - smaller = ((winwid->im_w < max_w) - && (winwid->im_h < max_h)); - - if (!smaller || opt.auto_zoom) { - double ratio = 0.0; - - /* Image is larger than the screen (so wants shrinking), or it's - smaller but wants expanding to fill it */ - ratio = feh_calc_needed_zoom(&(winwid->zoom), winwid->im_w, winwid->im_h, max_w, max_h); - - /* contributed by Jens Laas <jens.laas@data.slu.se> - * What it does: - * zooms images by a fixed amount but never larger than the screen. - * - * Why: - * This is nice if you got a collection of images where some - * are small and can stand a small zoom. Large images are unaffected. - * - * When does it work, and how? - * You have to be in fullscreen mode _and_ have auto-zoom turned on. - * "feh -FZ --zoom 130 imagefile" will do the trick. - * -zoom percent - the new switch. - * 100 = orignal size, - * 130 is 30% larger. - */ - if (opt.default_zoom) { - double old_zoom = winwid->zoom; - - winwid->zoom = 0.01 * opt.default_zoom; - if ((winwid->im_h * winwid->zoom) > max_h) - winwid->zoom = old_zoom; - if ((winwid->im_w * winwid->zoom) > max_w) - winwid->zoom = old_zoom; - - winwid->im_x = ((int) - (max_w - (winwid->im_w * winwid->zoom))) >> 1; - winwid->im_y = ((int) - (max_h - (winwid->im_h * winwid->zoom))) >> 1; + if (opt.offset_flags & YValue) { + if (opt.offset_flags & YNegative) { + winwid->im_y = winwid->h - (winwid->im_h * winwid->zoom) - opt.offset_y; } else { - if (ratio > 1.0) { - /* height is the factor */ - winwid->im_x = 0; - winwid->im_y = ((int) - (max_h - (winwid->im_h * winwid->zoom))) >> 1; - } else { - /* width is the factor */ - winwid->im_x = ((int) - (max_w - (winwid->im_w * winwid->zoom))) >> 1; - winwid->im_y = 0; - } + winwid->im_y = - opt.offset_y * winwid->zoom; } } else { - /* my modification to jens hack, allow --zoom without auto-zoom mode */ - if (opt.default_zoom) { - winwid->zoom = 0.01 * opt.default_zoom; - } else { - winwid->zoom = 1.0; - } - /* Just center the image in the window */ - winwid->im_x = (int) (max_w - (winwid->im_w * winwid->zoom)) >> 1; - winwid->im_y = (int) (max_h - (winwid->im_h * winwid->zoom)) >> 1; + winwid->im_y = (int) (winwid->h - (winwid->im_h * winwid->zoom)) >> 1; } } + winwid->had_resize = 0; + + if (opt.keep_zoom_vp) + winwidget_sanitise_offsets(winwid); + + if (!winwid->full_screen && ((gib_imlib_image_has_alpha(winwid->im)) + || (opt.geom_flags & (WidthValue | HeightValue)) + || (winwid->im_x || winwid->im_y) + || (winwid->w > winwid->im_w * winwid->zoom) + || (winwid->h > winwid->im_h * winwid->zoom) + || (winwid->has_rotated))) + feh_draw_checks(winwid); + /* Now we ensure only to render the area we're looking at */ dx = winwid->im_x; dy = winwid->im_y; @@ -507,10 +549,18 @@ void winwidget_render_image(winwidget winwid, int resize, int alias) calc_h = lround(winwid->im_h * winwid->zoom); dw = (winwid->w - winwid->im_x); dh = (winwid->h - winwid->im_y); - if (calc_w < dw) + if (calc_w < dw) { dw = calc_w; - if (calc_h < dh) + if (!winwid->full_screen) { + dx = 0; + } + } + if (calc_h < dh) { dh = calc_h; + if (!winwid->full_screen) { + dy = 0; + } + } if (dw > winwid->w) dw = winwid->w; if (dh > winwid->h) @@ -522,28 +572,46 @@ void winwidget_render_image(winwidget winwid, int resize, int alias) D(("sx: %d sy: %d sw: %d sh: %d dx: %d dy: %d dw: %d dh: %d zoom: %f\n", sx, sy, sw, sh, dx, dy, dw, dh, winwid->zoom)); + if ((winwid->zoom != 1.0 || winwid->has_rotated) && !force_alias && !winwid->force_aliasing) + antialias = 1; + D(("winwidget_render(): winwid->im_angle = %f\n", winwid->im_angle)); if (winwid->has_rotated) gib_imlib_render_image_part_on_drawable_at_size_with_rotation - (winwid->bg_pmap, winwid->im, sx, sy, sw, sh, dx, dy, dw, dh, winwid->im_angle, 1, 1, alias); + (winwid->bg_pmap, winwid->im, sx, sy, sw, sh, dx, dy, dw, dh, + winwid->im_angle, 1, 1, antialias); else gib_imlib_render_image_part_on_drawable_at_size(winwid->bg_pmap, winwid->im, sx, sy, sw, sh, dx, dy, dw, dh, 1, - gib_imlib_image_has_alpha(winwid->im), alias); + gib_imlib_image_has_alpha(winwid->im), + antialias); if (opt.mode == MODE_NORMAL) { if (opt.caption_path) winwidget_update_caption(winwid); if (opt.draw_filename) feh_draw_filename(winwid); +#ifdef HAVE_LIBEXIF + if (opt.draw_exif) + feh_draw_exif(winwid); +#endif if (opt.draw_actions) feh_draw_actions(winwid); - if (opt.info_cmd) + if (opt.draw_info && opt.info_cmd) feh_draw_info(winwid); - } else if ((opt.mode == MODE_ZOOM) && !alias) + if (winwid->errstr) + feh_draw_errstr(winwid); + if (winwid->file != NULL) { + if (opt.title && winwid->type != WIN_TYPE_THUMBNAIL_VIEWER) { + winwidget_rename(winwid, feh_printf(opt.title, FEH_FILE(winwid->file->data), winwid)); + } else if (opt.thumb_title && winwid->type == WIN_TYPE_THUMBNAIL_VIEWER) { + winwidget_rename(winwid, feh_printf(opt.thumb_title, FEH_FILE(winwid->file->data), winwid)); + } + } + } else if ((opt.mode == MODE_ZOOM) && !antialias) feh_draw_zoom(winwid); XSetWindowBackgroundPixmap(disp, winwid->win, winwid->bg_pmap); @@ -566,7 +634,7 @@ void winwidget_render_image_cached(winwidget winwid) feh_draw_filename(winwid); if (opt.draw_actions) feh_draw_actions(winwid); - if (opt.info_cmd) + if (opt.draw_info && opt.info_cmd) feh_draw_info(winwid); XSetWindowBackgroundPixmap(disp, winwid->win, winwid->bg_pmap); XClearWindow(disp, winwid->win); @@ -578,6 +646,9 @@ double feh_calc_needed_zoom(double *zoom, int orig_w, int orig_h, int dest_w, in ratio = ((double) orig_w / orig_h) / ((double) dest_w / dest_h); + if (opt.zoom_mode == ZOOM_MODE_FILL) + ratio = 1.0 / ratio; + if (ratio > 1.0) *zoom = ((double) dest_w / orig_w); else @@ -592,30 +663,20 @@ Pixmap feh_create_checks(void) Imlib_Image checks = NULL; if (checks_pmap == None) { - int onoff, x, y; - checks = imlib_create_image(16, 16); if (!checks) eprintf("Unable to create a teeny weeny imlib image. I detect problems"); - if (strncmp(opt.image_bg, "white", 5) == 0) - gib_imlib_image_fill_rectangle(checks, 0, 0, 16, 16, 255, 255, 255, 255); - else if (strncmp(opt.image_bg, "black", 5) == 0) - gib_imlib_image_fill_rectangle(checks, 0, 0, 16, 16, 0, 0, 0, 255); - else { - for (y = 0; y < 16; y += 8) { - onoff = (y / 8) & 0x1; - for (x = 0; x < 16; x += 8) { - if (onoff) - gib_imlib_image_fill_rectangle(checks, x, y, 8, 8, 144, 144, 144, 255); - else - gib_imlib_image_fill_rectangle(checks, x, y, 8, 8, 100, 100, 100, 255); - onoff++; - if (onoff == 2) - onoff = 0; - } - } + if (!opt.image_bg || !strcmp(opt.image_bg, "default") || !strcmp(opt.image_bg, "checks")) { + gib_imlib_image_fill_rectangle(checks, 0, 0, 16, 16, 144, 144, 144, 255); + gib_imlib_image_fill_rectangle(checks, 0, 0, 8, 8, 100, 100, 100, 255); + gib_imlib_image_fill_rectangle(checks, 8, 8, 8, 8, 100, 100, 100, 255); + } else { + XColor color; + Colormap cmap = DefaultColormap(disp, DefaultScreen(disp)); + XAllocNamedColor(disp, cmap, (char*) opt.image_bg, &color, &color); + gib_imlib_image_fill_rectangle(checks, 0, 0, 16, 16, color.red, color.green, color.blue, 255); } checks_pmap = XCreatePixmap(disp, root, 16, 16, depth); @@ -625,13 +686,6 @@ Pixmap feh_create_checks(void) return(checks_pmap); } -void winwidget_clear_background(winwidget w) -{ - XSetWindowBackgroundPixmap(disp, w->win, feh_create_checks()); - /* XClearWindow(disp, w->win); */ - return; -} - void feh_draw_checks(winwidget win) { static GC gc = None; @@ -661,13 +715,15 @@ void winwidget_destroy_xwin(winwidget winwid) void winwidget_destroy(winwidget winwid) { +#ifdef HAVE_INOTIFY + winwidget_inotify_remove(winwid); +#endif + if (opt.reload > 0 && opt.multiwindow) { + feh_remove_timer_by_data(winwid); + } winwidget_destroy_xwin(winwid); if (winwid->name) free(winwid->name); - if ((winwid->type == WIN_TYPE_ABOUT) && winwid->file) { - feh_file_free(FEH_FILE(winwid->file->data)); - free(winwid->file); - } if (winwid->gc) XFreeGC(disp, winwid->gc); if (winwid->im) @@ -676,6 +732,76 @@ void winwidget_destroy(winwidget winwid) return; } +#ifdef HAVE_INOTIFY +void winwidget_inotify_remove(winwidget winwid) +{ + if (winwid->inotify_wd >= 0) { + D(("Removing inotify watch\n")); + if (inotify_rm_watch(opt.inotify_fd, winwid->inotify_wd)) + weprintf("inotify_rm_watch failed:"); + winwid->inotify_wd = -1; + } +} +#endif + +#ifdef HAVE_INOTIFY +void winwidget_inotify_add(winwidget winwid, feh_file * file) +{ + if (opt.auto_reload && !path_is_url(file->filename)) { + D(("Adding inotify watch for %s\n", file->filename)); + char dir[PATH_MAX]; + feh_file_dirname(dir, file, PATH_MAX); + + /* + * Handle files without directory part, e.g. "feh somefile.jpg". + * These always reside in the current directory. + */ + if (dir[0] == '\0') { + dir[0] = '.'; + dir[1] = '\0'; + } + winwid->inotify_wd = inotify_add_watch(opt.inotify_fd, dir, IN_CLOSE_WRITE | IN_MOVED_TO); + if (winwid->inotify_wd < 0) + weprintf("inotify_add_watch failed:"); + } +} +#endif + +#ifdef HAVE_INOTIFY +#define INOTIFY_BUFFER_LEN (1024 * (sizeof (struct inotify_event)) + 16) +void feh_event_handle_inotify(void) +{ + D(("Received inotify events\n")); + char buf[INOTIFY_BUFFER_LEN]; + int i = 0; + int len = read (opt.inotify_fd, buf, INOTIFY_BUFFER_LEN); + if (len < 0) { + if (errno != EINTR) + eprintf("inotify event read failed"); + } else if (!len) + eprintf("inotify event read failed"); + while (i < len) { + struct inotify_event *event; + event = (struct inotify_event *) &buf[i]; + for (int j = 0; j < window_num; j++) { + if(windows[j]->inotify_wd == event->wd) { + if (event->mask & IN_IGNORED) { + D(("inotify watch was implicitly removed\n")); + windows[j]->inotify_wd = -1; + } else if (event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)) { + if (strcmp(event->name, FEH_FILE(windows[j]->file->data)->name) == 0) { + D(("inotify says file changed\n")); + feh_reload_image(windows[j], 0, 0); + } + } + break; + } + } + i += sizeof(struct inotify_event) + event->len; + } +} +#endif + void winwidget_destroy_all(void) { int i; @@ -686,13 +812,13 @@ void winwidget_destroy_all(void) return; } -void winwidget_rerender_all(int resize, int alias) +void winwidget_rerender_all(int resize) { int i; /* Have to DESCEND the list here, 'cos of the way _unregister works */ for (i = window_num - 1; i >= 0; i--) - winwidget_render_image(windows[i], resize, alias); + winwidget_render_image(windows[i], resize, 0); return; } @@ -709,7 +835,16 @@ winwidget winwidget_get_first_window_of_type(unsigned int type) int winwidget_loadimage(winwidget winwid, feh_file * file) { D(("filename %s\n", file->filename)); - return(feh_load_image(&(winwid->im), file)); +#ifdef HAVE_INOTIFY + winwidget_inotify_remove(winwid); +#endif + int res = feh_load_image(&(winwid->im), file); +#ifdef HAVE_INOTIFY + if (res) { + winwidget_inotify_add(winwid, file); + } +#endif + return(res); } void winwidget_show(winwidget winwid) @@ -724,6 +859,17 @@ void winwidget_show(winwidget winwid) /* wait for the window to map */ D(("Waiting for window to map\n")); XMaskEvent(disp, StructureNotifyMask, &ev); + winwidget_get_geometry(winwid, NULL); + + /* Unfortunately, StructureNotifyMask does not only mask + * the events of type MapNotify (which we want to mask here) + * but also such of type ConfigureNotify (and others, see + * https://tronche.com/gui/x/xlib/events/processing-overview.html), + * which should be handled, especially on tiling wm's. To + * remedy this, the handler is executed explicitly: + */ + if (ev.type == ConfigureNotify) + feh_event_handle_ConfigureNotify(&ev); D(("Window mapped\n")); winwid->visible = 1; } @@ -733,8 +879,6 @@ void winwidget_show(winwidget winwid) void winwidget_move(winwidget winwid, int x, int y) { if (winwid && ((winwid->x != x) || (winwid->y != y))) { - winwid->x = x; - winwid->y = y; winwid->x = (x > scr->width) ? scr->width : x; winwid->y = (y > scr->height) ? scr->height : y; XMoveWindow(disp, winwid->win, winwid->x, winwid->y); @@ -745,55 +889,87 @@ void winwidget_move(winwidget winwid, int x, int y) return; } -void winwidget_resize(winwidget winwid, int w, int h) +void winwidget_resize(winwidget winwid, int w, int h, int force_resize) { - Window ignored_window; XWindowAttributes attributes; - int tc_x, tc_y; + int tc_x, tc_y, px, py; + int scr_width = scr->width; + int scr_height = scr->height; + + /* discarded */ + Window dw; + int di, i; + unsigned int du; + + XGetWindowAttributes(disp, winwid->win, &attributes); - if (opt.geom_flags) { +#ifdef HAVE_LIBXINERAMA + if (opt.xinerama && xinerama_screens) { + 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; + } + + } + if (opt.xinerama_index >= 0) + xinerama_screen = opt.xinerama_index; + + scr_width = xinerama_screens[xinerama_screen].width; + scr_height = xinerama_screens[xinerama_screen].height; + } +#endif + + + D((" x %d y %d w %d h %d\n", attributes.x, attributes.y, winwid->w, + winwid->h)); + + if ((opt.geom_flags & (WidthValue | HeightValue)) && !force_resize) { winwid->had_resize = 1; return; } if (winwid && ((winwid->w != w) || (winwid->h != h))) { - D(("Really doing a resize\n")); - /* winwidget_clear_background(winwid); */ if (opt.screen_clip) { - winwid->w = (w > scr->width) ? scr->width : w; - winwid->h = (h > scr->height) ? scr->height : h; + double required_zoom = winwid->zoom; + if (opt.scale_down && !opt.keep_zoom_vp) { + int max_w = (w > scr_width) ? scr_width : w; + int max_h = (h > scr_height) ? scr_height : h; + feh_calc_needed_zoom(&required_zoom, winwid->im_w, winwid->im_h, max_w, max_h); + } + int desired_w = winwid->im_w * required_zoom; + int desired_h = winwid->im_h * required_zoom; + winwid->w = (desired_w > scr_width) ? scr_width : desired_w; + winwid->h = (desired_h > scr_height) ? scr_height : desired_h; } - /* XResizeWindow(disp, winwid->win, winwid->w, winwid->h); */ - XGetWindowAttributes(disp, winwid->win, &attributes); - XTranslateCoordinates(disp, winwid->win, attributes.root, - -attributes.border_width - - attributes.x, - -attributes.border_width - attributes.y, &tc_x, &tc_y, &ignored_window); - winwid->x = tc_x; - winwid->y = tc_y; - XMoveResizeWindow(disp, winwid->win, tc_x, tc_y, winwid->w, winwid->h); + if (winwid->full_screen) { + XTranslateCoordinates(disp, winwid->win, attributes.root, + -attributes.border_width - attributes.x, + -attributes.border_width - attributes.y, &tc_x, &tc_y, &dw); + winwid->x = tc_x; + winwid->y = tc_y; + XMoveResizeWindow(disp, winwid->win, tc_x, tc_y, winwid->w, winwid->h); + } else + XResizeWindow(disp, winwid->win, winwid->w, winwid->h); winwid->had_resize = 1; XFlush(disp); -#ifdef HAVE_LIBXINERAMA - if (opt.xinerama && xinerama_screens) { - int i; - - for (i = 0; i < num_xinerama_screens; i++) { - xinerama_screen = 0; - if (XY_IN_RECT(winwid->x, winwid->y, - xinerama_screens[i].x_org, - xinerama_screens[i].y_org, - xinerama_screens[i].width, xinerama_screens[i].height)) { - xinerama_screen = i; - break; - } + winwidget_get_geometry(winwid, NULL); - } - if (getenv("XINERAMA_SCREEN")) - xinerama_screen = atoi(getenv("XINERAMA_SCREEN")); + if (force_resize && (opt.geom_flags & (WidthValue | HeightValue)) + && (winwid->type != WIN_TYPE_THUMBNAIL)) { + opt.geom_w = winwid->w; + opt.geom_h = winwid->h; } -#endif /* HAVE_LIBXINERAMA */ + + D(("-> x %d y %d w %d h %d\n", winwid->x, winwid->y, winwid->w, + winwid->h)); } else { D(("No resize actually needed\n")); @@ -882,8 +1058,9 @@ void winwidget_rename(winwidget winwid, char *newname) void winwidget_free_image(winwidget w) { - if (w->im) - gib_imlib_free_image_and_decache(w->im); + if (w->im) { + gib_imlib_free_image(w->im); + } w->im = NULL; w->im_w = 0; w->im_h = 0; @@ -907,9 +1084,12 @@ void feh_debug_print_winwid(winwidget w) void winwidget_reset_image(winwidget winwid) { - winwid->zoom = 1.0; - winwid->im_x = 0; - winwid->im_y = 0; + if (!opt.keep_zoom_vp) { + winwid->zoom = 1.0; + winwid->old_zoom = 1.0; + winwid->im_x = 0; + winwid->im_y = 0; + } winwid->im_angle = 0.0; winwid->has_rotated = 0; return; @@ -934,11 +1114,11 @@ void winwidget_center_image(winwidget winwid) winwid->im_y = (scr_height - lround(winwid->im_h * winwid->zoom)) >> 1; } else { if (opt.geom_flags & WidthValue) - winwid->im_x = (opt.geom_w - lround(winwid->im_w * winwid->zoom)) >> 1; + winwid->im_x = ((int)opt.geom_w - lround(winwid->im_w * winwid->zoom)) >> 1; else winwid->im_x = 0; if (opt.geom_flags & HeightValue) - winwid->im_y = (opt.geom_h - lround(winwid->im_h * winwid->zoom)) >> 1; + winwid->im_y = ((int)opt.geom_h - lround(winwid->im_h * winwid->zoom)) >> 1; else winwid->im_y = 0; } @@ -980,9 +1160,9 @@ void winwidget_sanitise_offsets(winwidget winwid) void winwidget_size_to_image(winwidget winwid) { - winwidget_resize(winwid, winwid->im_w * winwid->zoom, winwid->im_h * winwid->zoom); + winwidget_resize(winwid, winwid->im_w * winwid->zoom, winwid->im_h * winwid->zoom, 1); winwid->im_x = winwid->im_y = 0; - winwidget_render_image(winwid, 0, 1); + winwidget_render_image(winwid, 0, 0); return; } @@ -1022,8 +1202,11 @@ void winwidget_get_geometry(winwidget winwid, int *rect) { unsigned int bw, bp; Window child; + + int inner_rect[4]; + if (!rect) - return; + rect = inner_rect; XGetGeometry(disp, winwid->win, &root, &(rect[0]), &(rect[1]), (unsigned int *)&(rect[2]), (unsigned int *)&(rect[3]), &bw, &bp); @@ -1045,11 +1228,7 @@ void winwidget_show_menu(winwidget winwid) Window r; XQueryPointer(disp, winwid->win, &r, &r, &x, &y, &b, &b, &c); - if (winwid->type == WIN_TYPE_ABOUT) { - if (!menu_about_win) - feh_menu_init_about_win(); - feh_menu_show_at_xy(menu_about_win, winwid, x, y); - } else if (winwid->type == WIN_TYPE_SINGLE) { + if (winwid->type == WIN_TYPE_SINGLE) { if (!menu_single_win) feh_menu_init_single_win(); feh_menu_show_at_xy(menu_single_win, winwid, x, y); diff --git a/src/winwidget.h b/src/winwidget.h index 012d78f..0894b5a 100644 --- a/src/winwidget.h +++ b/src/winwidget.h @@ -1,6 +1,7 @@ /* winwidget.h Copyright (C) 1999-2003 Tom Gilbert. +Copyright (C) 2010-2020 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 @@ -60,16 +61,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* Motif window hints */ typedef struct _mwmhints { - CARD32 flags; - CARD32 functions; - CARD32 decorations; - INT32 input_mode; - CARD32 status; + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long input_mode; + unsigned long status; } MWMHints; enum win_type { WIN_TYPE_UNSET, WIN_TYPE_SLIDESHOW, WIN_TYPE_SINGLE, - WIN_TYPE_ABOUT, WIN_TYPE_THUMBNAIL, WIN_TYPE_THUMBNAIL_VIEWER }; @@ -81,6 +81,7 @@ struct __winwidget { int h; int im_w; int im_h; + int force_aliasing; double im_angle; enum win_type type; unsigned char had_resize, full_screen; @@ -91,6 +92,7 @@ struct __winwidget { char *name; gib_list *file; unsigned char visible; + char *errstr; /* panning, zooming, etc. */ unsigned char mode; @@ -111,10 +113,20 @@ struct __winwidget { int click_offset_y; int im_click_offset_x; int im_click_offset_y; + time_t click_start_time; unsigned char has_rotated; + +#ifdef HAVE_INOTIFY + int inotify_wd; +#endif }; +#ifdef HAVE_INOTIFY +void winwidget_inotify_remove(winwidget winwid); +void winwidget_inotify_add(winwidget winwid, feh_file * file); +#endif + int winwidget_loadimage(winwidget winwid, feh_file * filename); void winwidget_show(winwidget winwid); void winwidget_show_menu(winwidget winwid); @@ -122,14 +134,14 @@ void winwidget_hide(winwidget winwid); void winwidget_destroy_all(void); void winwidget_free_image(winwidget w); void winwidget_center_image(winwidget w); -void winwidget_render_image(winwidget winwid, int resize, int alias); +void winwidget_render_image(winwidget winwid, int resize, int force_alias); void winwidget_rotate_image(winwidget winid, double angle); void winwidget_move(winwidget winwid, int x, int y); -void winwidget_resize(winwidget winwid, int w, int h); +void winwidget_resize(winwidget winwid, int w, int h, int force_resize); void winwidget_setup_pixmaps(winwidget winwid); void winwidget_update_title(winwidget ret); void winwidget_update_caption(winwidget winwid); -void winwidget_rerender_all(int resize, int alias); +void winwidget_rerender_all(int resize); void winwidget_destroy_xwin(winwidget winwid); void winwidget_set_pointer(winwidget winwid, int visible); @@ -138,12 +150,11 @@ void winwidget_get_geometry(winwidget winwid, int *rect); int winwidget_get_width(winwidget winwid); int winwidget_get_height(winwidget winwid); winwidget winwidget_get_from_window(Window win); -winwidget winwidget_create_from_file(gib_list * filename, char *name, char type); -winwidget winwidget_create_from_image(Imlib_Image im, char *name, char type); +winwidget winwidget_create_from_file(gib_list * filename, char type); +winwidget winwidget_create_from_image(Imlib_Image im, char type); void winwidget_rename(winwidget winwid, char *newname); void winwidget_destroy(winwidget winwid); void winwidget_create_window(winwidget ret, int w, int h); -void winwidget_clear_background(winwidget w); Pixmap feh_create_checks(void); double feh_calc_needed_zoom(double *zoom, int orig_w, int orig_h, int dest_w, int dest_h); void feh_debug_print_winwid(winwidget winwid); diff --git a/test/bg/exact/h/fill b/test/bg/exact/h/fill Binary files differindex 18e1da9..6906f52 100644 --- a/test/bg/exact/h/fill +++ b/test/bg/exact/h/fill diff --git a/test/bg/exact/w/fill b/test/bg/exact/w/fill Binary files differindex eeec8ca..5e5f26c 100644 --- a/test/bg/exact/w/fill +++ b/test/bg/exact/w/fill diff --git a/test/config/keys/feh/keys b/test/config/keys/feh/keys new file mode 100644 index 0000000..7114ecc --- /dev/null +++ b/test/config/keys/feh/keys @@ -0,0 +1,17 @@ +action_1 x +action_2 X +action_3 C-x +action_4 C-X +action_5 1-x +action_6 +action_7 + +next_img a b c +prev_img d e f + +# conflict with next_img/prev_img +toggle_actions +toggle_caption +toggle_filenames +save_filelist +close diff --git a/test/config/themes/feh/themes b/test/config/themes/feh/themes new file mode 100644 index 0000000..d4b7f69 --- /dev/null +++ b/test/config/themes/feh/themes @@ -0,0 +1,5 @@ +test_general --action1 "touch a1" + +test_multiline --action1 "touch a1" \ +--action2 "touch a2" \ + --action3 "touch a3" diff --git a/test/feh-bg.i b/test/feh-bg-i.t index eeff836..1f22c9d 100755 --- a/test/feh-bg.i +++ b/test/feh-bg-i.t @@ -8,10 +8,13 @@ use GD qw/:DEFAULT :cmp/; use Test::More tests => 70; use Time::HiRes qw/sleep/; -my ($pid_xnest, $pid_twm); +my $scr_dir = '/tmp/feh-test-scr'; +my ( $pid_xnest, $pid_twm ); + +$ENV{HOME} = 'test'; sub set_bg { - my ($mode, $file) = @_; + my ( $mode, $file ) = @_; ok( system("feh --bg-${mode} test/bg/${file}") == 0, @@ -20,34 +23,36 @@ sub set_bg { } sub same_files { - my ($one, $two) = @_; + my ( $one, $two ) = @_; my $img_one = GD::Image->new($one); my $img_two = GD::Image->new($two); - return( ! ($img_one->compare($img_two) & GD_CMP_IMAGE)); + return ( !( $img_one->compare($img_two) & GD_CMP_IMAGE ) ); } sub check_bg { my ($file) = @_; - system("import -silent -window root /tmp/feh_${$}.png"); + system("import -silent -window root ${scr_dir}/feh_${$}.png"); - ok( - same_files("test/bg/${file}", "/tmp/feh_${$}.png"), - "Wallpaper is test/bg/${file}" - ); + ok( same_files( "test/bg/${file}", "${scr_dir}/feh_${$}.png" ), + "Wallpaper is test/bg/${file}" ); +} + +if ( not -d $scr_dir ) { + mkdir($scr_dir); } -if (($pid_xnest = fork()) == 0) { - exec(qw( Xnest -geometry 500x500 :7 )); +if ( ( $pid_xnest = fork() ) == 0 ) { + exec(qw( Xephyr -screen 500x500 :7 )); } sleep(0.5); $ENV{'DISPLAY'} = ':7'; -if (($pid_twm = fork()) == 0) { +if ( ( $pid_twm = fork() ) == 0 ) { exec('twm'); } @@ -55,22 +60,23 @@ sleep(0.5); for my $mode (qw( center fill max scale tile )) { - set_bg($mode, 'exact/in'); + set_bg( $mode, 'exact/in' ); check_bg('exact/out'); for my $type (qw( exact small large )) { for my $orientation (qw( w h )) { - set_bg($mode, "${type}/${orientation}/in"); + set_bg( $mode, "${type}/${orientation}/in" ); check_bg("${type}/${orientation}/${mode}"); } } } -kill(15, $pid_twm); +kill( 15, $pid_twm ); sleep(0.2); -kill(15, $pid_xnest); +kill( 15, $pid_xnest ); sleep(0.2); -unlink("/tmp/feh_${$}.png"); +unlink("${scr_dir}/feh_${$}.png"); +unlink('test/.fehbg'); diff --git a/test/feh-i.t b/test/feh-i.t new file mode 100755 index 0000000..24775e3 --- /dev/null +++ b/test/feh-i.t @@ -0,0 +1,463 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use 5.010; + +no if $] >= 5.018, warnings => 'experimental::smartmatch'; + +use Cwd; +use Test::More tests => 103; +use Time::HiRes qw/sleep/; +use X11::GUITest qw/:ALL/; + +my $win; +my ( $width, $height ); +my $pwd = getcwd(); + +$ENV{HOME} = 'test'; + +sub waitfor(&) { + my ($sub) = @_; + my $out; + for ( 1 .. 40 ) { + sleep(0.05); + $out = &{$sub}; + if ($out) { + return $out; + } + } + return 0; +} + +sub feh_start { + my ( $opts, $files ) = @_; + my $id; + + $opts //= q{}; + $files //= 'test/ok/png'; + + StartApp("feh ${opts} ${files}"); + ($id) = WaitWindowViewable(qr{^feh}); + + if ( not $id ) { + BAIL_OUT("Unable to start feh ${opts} ${files}"); + } + + if ( not SetInputFocus($id) ) { + BAIL_OUT("Unable to focus window"); + } + + return $id; +} + +sub feh_stop { + SendKeys('{ESC}'); + if ( not waitfor { not FindWindowLike(qr{^feh}) } ) { + BAIL_OUT("Unclosed feh window still open, cannot continue"); + } +} + +sub test_no_win { + my ($reason) = @_; + + if ( waitfor { not FindWindowLike(qr{^feh}) } ) { + pass("Window closed ($reason)"); + } + else { + fail("Window closed ($reason)"); + BAIL_OUT("unclosed window still open, cannot continue"); + } +} + +sub test_win_title { + my ( $win, $wtitle ) = @_; + my $rtitle; + + if ( waitfor { GetWindowName($win) eq $wtitle } ) { + pass("Window has title: $wtitle"); + } + else { + $rtitle = GetWindowName($win); + fail("Window has title: $wtitle"); + diag("expected: $wtitle"); + diag(" got: $rtitle"); + } +} + +sub slurp { + my ($file) = @_; + my $ret; + local $/ = undef; + open( my $fh, '<', $file ) or die("Can't open $file: $!"); + $ret = <$fh>; + close($fh) or die("Can't close $file: $!"); + return ($ret); +} + +if ( FindWindowLike(qr{^feh}) ) { + BAIL_OUT('It appears you have an open feh window. Please close it.'); +} + +for my $key (qw/q x {ESC}/) { + feh_start(); + SendKeys($key); + test_no_win("$key pressed"); +} + +$win = feh_start( q{}, 'test/ok/png' ); +test_win_title( $win, 'feh [1 of 1] - test/ok/png' ); +feh_stop(); + +$win = feh_start( q{}, 'test/ok/png test/ok/jpg test/ok/gif' ); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('{RIG}'); +test_win_title( $win, 'feh [2 of 3] - test/ok/jpg' ); +SendKeys('n'); +test_win_title( $win, 'feh [3 of 3] - test/ok/gif' ); +SendKeys('{SPA}'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('{LEF}'); +test_win_title( $win, 'feh [3 of 3] - test/ok/gif' ); +SendKeys('p'); +test_win_title( $win, 'feh [2 of 3] - test/ok/jpg' ); +SendKeys('{BAC}'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('p'); +test_win_title( $win, 'feh [3 of 3] - test/ok/gif' ); +SendKeys('{DEL}'); +test_win_title( $win, 'feh [2 of 2] - test/ok/jpg' ); +SendKeys('{DEL}'); +test_win_title( $win, 'feh [1 of 1] - test/ok/png' ); +SendKeys('{DEL}'); +test_no_win("Removed all images from slideshow"); + +$win = feh_start( '--title \'feh %m %u/%l %n\'', + 'test/ok/png test/ok/jpg test/ok/gif' ); +test_win_title( $win, 'feh slideshow 1/3 png' ); +SendKeys('{RIG}'); +test_win_title( $win, 'feh slideshow 2/3 jpg' ); +feh_stop(); + +feh_start( '--on-last-slide=quit', 'test/ok/png test/ok/jpg' ); +for ( 1 .. 2 ) { + SendKeys('{RIG}'); +} +test_no_win("--on-last-slide=quit -> window closed"); + +feh_start( + '--on-last-slide=quit --slideshow-delay 0.5', + 'test/ok/png test/ok/jpg test/ok/gif' +); +sleep(1.5); +test_no_win('on-last-slide=quit + slideshow-delay -> window closed'); + +$win = feh_start( + '--on-last-slide=quit --slideshow-delay -0.01', + 'test/ok/png test/ok/jpg test/ok/gif' +); + +test_win_title( $win, 'feh [1 of 3] - test/ok/png [Paused]' ); + +SendKeys('h'); +test_no_win('on-last-slide=quit + negative delay + [h]'); + +$win = feh_start( q{}, 'test/ok/png test/ok/gif test/ok/gif test/ok/jpg' ); +for ( 1 .. 2 ) { + SendKeys('{END}'); + test_win_title( $win, 'feh [4 of 4] - test/ok/jpg' ); +} +for ( 1 .. 2 ) { + SendKeys('{HOM}'); + test_win_title( $win, 'feh [1 of 4] - test/ok/png' ); +} + +SendKeys('{PGU}'); +test_win_title( $win, 'feh [4 of 4] - test/ok/jpg' ); +SendKeys('{PGD}'); +test_win_title( $win, 'feh [1 of 4] - test/ok/png' ); +SendKeys('{PGD}'); +test_win_title( $win, 'feh [2 of 4] - test/ok/gif' ); + +feh_stop(); + +$win + = feh_start( '--slideshow-delay 1', 'test/ok/png test/ok/gif test/ok/jpg' ); +sleep(1.7); +test_win_title( $win, 'feh [3 of 3] - test/ok/jpg' ); +SendKeys('h'); +test_win_title( $win, 'feh [3 of 3] - test/ok/jpg [Paused]' ); +SendKeys('{RIG}'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png [Paused]' ); +SendKeys('h'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +sleep(0.8); +test_win_title( $win, 'feh [2 of 3] - test/ok/gif' ); +feh_stop(); + +$win = feh_start( + '--action3 ";echo foo" --action7 "echo foo" ' + . '--action8 ";touch feh_test_%u_%l" --action9 "rm feh_test_%u_%l"', + 'test/ok/png test/ok/gif test/ok/jpg' +); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('3'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('7'); +test_win_title( $win, 'feh [2 of 3] - test/ok/gif' ); +SendKeys('8'); +test_win_title( $win, 'feh [2 of 3] - test/ok/gif' ); +ok( -e 'feh_test_2_3', + 'feh action created file with correct format specifiers' ); +SendKeys('9'); +ok( waitfor { not -e 'feh_test_2_3' }, 'feh action removed file' ); +test_win_title( $win, 'feh [3 of 3] - test/ok/jpg' ); +feh_stop(); + +# .config/feh/keys +# Action Unbinding + non-conflicting none/shift/control/meta modifiers + +$ENV{XDG_CONFIG_HOME} = 'test/config/keys'; + +$win = feh_start( + '--action1 "touch a1" --action2 "touch a2" ' + . '--action3 "touch a3" --action4 "touch a4" ' + . '--action5 "touch a5" --action6 "touch a6" ', + 'test/ok/png test/ok/jpg test/ok/pnm' +); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('6'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('{RIG}'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('a'); +test_win_title( $win, 'feh [2 of 3] - test/ok/jpg' ); +SendKeys('b'); +test_win_title( $win, 'feh [3 of 3] - test/ok/pnm' ); +SendKeys('c'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('d'); +test_win_title( $win, 'feh [3 of 3] - test/ok/pnm' ); +SendKeys('e'); +test_win_title( $win, 'feh [2 of 3] - test/ok/jpg' ); +SendKeys('f'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('1'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +SendKeys('x'); +ok( waitfor { -e 'a1' }, 'action 1 = X ok' ); +SendKeys('X'); +ok( waitfor { -e 'a2' }, 'action 2 = Shift+X ok' ); +SendKeys('^(x)'); +ok( waitfor { -e 'a3' }, 'action 3 = Ctrl+X ok' ); +SendKeys('^(X)'); +ok( waitfor { -e 'a4' }, 'action 4 = Ctrl+Shift+X ok' ); +SendKeys('%(x)'); +ok( waitfor { -e 'a5' }, 'action 5 = Alt+X ok' ); + +for my $f (qw(a1 a2 a3 a4 a5)) { + unlink($f); +} +feh_stop(); + +$ENV{XDG_CONFIG_HOME} = 'test/config/themes'; + +$win = feh_start( '-Ttest_general', 'test/ok/png test/ok/jpg' ); +SendKeys('1'); +ok( waitfor { -e 'a1' }, 'theme: action 1 okay' ); +unlink('a1'); +feh_stop(); + +$win = feh_start( '-Ttest_general --action1 "touch c1"', + 'test/ok/png test/ok/jpg' ); +SendKeys('1'); +ok( waitfor { -e 'c1' }, 'theme: commandline overrides theme' ); +unlink('c1'); +feh_stop(); + +$win = feh_start( '-Ttest_multiline', 'test/ok/png test/ok/jpg' ); +SendKeys('1'); +ok( waitfor { -e 'a1' }, 'multiline theme: first line ok' ); +SendKeys('2'); +ok( waitfor { -e 'a2' }, 'multiline theme: second line ok' ); +SendKeys('3'); +ok( waitfor { -e 'a3' }, 'multiline theme: last line ok' ); +for my $f (qw(a1 a2 a3)) { + unlink($f); +} +feh_stop(); + +delete $ENV{XDG_CONFIG_HOME}; + +$win = feh_start( q{}, 'test/ok/png ' x 100 ); +test_win_title( $win, 'feh [1 of 100] - test/ok/png' ); +SendKeys('{PGD}'); +test_win_title( $win, 'feh [6 of 100] - test/ok/png' ); +SendKeys('{PGD}'); +test_win_title( $win, 'feh [11 of 100] - test/ok/png' ); +SendKeys('{HOM PGU}'); +test_win_title( $win, 'feh [96 of 100] - test/ok/png' ); +feh_stop(); + +$win = feh_start( '--thumbnails -H 300 -W 310 --thumb-title "feh [%l] %f"', + 'test/ok/png test/ok/gif test/ok/jpg' ); +test_win_title( $win, 'feh [thumbnail mode]' ); +( $width, $height ) = ( GetWindowPos($win) )[ 2, 3 ]; +is( $width, 310, 'thumbnail win: Set correct width' ); +is( $height, 300, 'thumbnail win: Set correct height' ); +MoveMouseAbs( 30, 30 ); +ClickMouseButton(M_BTN1); +($win) = WaitWindowViewable(qr{test/ok/png$}); +ok( $win, 'Thumbnail mode: Window opened' ); +test_win_title( $win, 'feh [3] test/ok/png' ); +SetInputFocus($win); +SendKeys('x'); +ok( waitfor { not FindWindowLike(qr{^ok/png$}) }, 'Thumbnail mode: closed' ); + +MoveMouseAbs( 90, 30 ); +ClickMouseButton(M_BTN1); +($win) = WaitWindowViewable(qr{test/ok/gif$}); +ok( $win, 'Thumbnail mode: Window opened' ); +test_win_title( $win, 'feh [3] test/ok/gif' ); + +MoveMouseAbs( 150, 30 ); +ClickMouseButton(M_BTN1); +($win) = WaitWindowViewable(qr{test/ok/jpg$}); +ok( $win, 'Thumbnail mode: Other window opened' ); +test_win_title( $win, 'feh [3] test/ok/jpg' ); + +feh_stop(); + +feh_start( '--multiwindow', 'test/ok/png test/ok/gif test/ok/jpg' ); +ok( waitfor { FindWindowLike(qr{^feh - test/ok/png$}) }, 'multiwindow 1/3' ); +ok( waitfor { FindWindowLike(qr{^feh - test/ok/gif$}) }, 'multiwindow 2/3' ); +ok( waitfor { FindWindowLike(qr{^feh - test/ok/jpg$}) }, 'multiwindow 3/3' ); + +($win) = FindWindowLike(qr{^feh - test/ok/gif$}); +SetInputFocus($win); +SendKeys('x'); +ok( waitfor { not FindWindowLike(qr{^feh - test/ok/gif$}) }, 'win 1 closed' ); +ok( FindWindowLike(qr{^feh - test/ok/png$}), 'multiwindow 1/2' ); +ok( FindWindowLike(qr{^feh - test/ok/jpg$}), 'multiwindow 2/2' ); + +($win) = FindWindowLike(qr{^feh - test/ok/jpg$}); +SetInputFocus($win); +SendKeys('x'); +ok( waitfor { not FindWindowLike(qr{^feh - test/ok/jpg$}) }, 'win 2 closed' ); + +($win) = FindWindowLike(qr{^feh - test/ok/png$}); +SetInputFocus($win); +SendKeys('x'); +test_no_win('all multiwindows closed'); + +$win = feh_start( '--start-at test/ok/jpg', + 'test/ok/png test/ok/gif test/ok/jpg' ); +test_win_title( $win, 'feh [3 of 3] - test/ok/jpg' ); +SendKeys('{RIG}'); +test_win_title( $win, 'feh [1 of 3] - test/ok/png' ); +feh_stop(); + +feh_start( '--caption-path .captions', 'test/ok/png' ); +SendKeys('cFoo Bar Quux Moep~'); +feh_stop(); +ok( -d 'test/ok/.captions', 'autocreated captions directory' ); +is( + slurp('test/ok/.captions/png.txt'), + 'Foo Bar Quux Moep', + 'Correct caption saved' +); + +feh_start( '--caption-path .captions', 'test/ok/png' ); +SendKeys('c'); +SendKeys( '{BKS}' x length('Foo Bar Quux Moep') ); +SendKeys('Foo Bar^(~)miep~'); +feh_stop(); +is( + slurp('test/ok/.captions/png.txt'), + "Foo Bar\nmiep", + 'Caption with newline + correct backspace' +); + +unlink('test/ok/.captions/png.txt'); +rmdir('test/ok/.captions'); + +$win = feh_start( '--filelist test/filelist', + 'test/ok/png test/ok/gif test/ok/png test/ok/jpg' ); +SendKeys('{DEL}'); +test_win_title( $win, "feh [1 of 3] - ${pwd}/test/ok/gif" ); +feh_stop(); + +is( slurp('test/filelist'), <<"EOF", 'Filelist saved' ); +${pwd}/test/ok/gif +${pwd}/test/ok/png +${pwd}/test/ok/jpg +EOF + +$win = feh_start( '--filelist test/filelist', q{} ); +test_win_title( $win, "feh [1 of 3] - ${pwd}/test/ok/gif" ); +feh_stop(); +unlink('test/filelist'); + +$win = feh_start('--geometry 423x232'); +( undef, undef, $width, $height ) = GetWindowPos($win); +is( $width, 423, '--geometry: correct width' ); +is( $height, 232, '--geometry: correct height' ); +feh_stop(); + +$win = feh_start('--fullscreen'); +( undef, undef, $width, $height ) = GetWindowPos($win); +ok( [ ( GetWindowPos($win) )[ 2, 3 ] ] ~~ [ GetScreenRes() ], + 'fullscreen uses full screen size' ); +feh_stop(); + +$win = feh_start( q{}, 'test/ok/png ' . 'test/fail/png ' x 7 . 'test/ok/gif' ); +test_win_title( $win, 'feh [1 of 9] - test/ok/png' ); +SendKeys('{RIG}'); +test_win_title( $win, 'feh [2 of 2] - test/ok/gif' ); +SendKeys('{LEF}'); +test_win_title( $win, 'feh [1 of 2] - test/ok/png' ); +SendKeys('{LEF}'); +test_win_title( $win, 'feh [2 of 2] - test/ok/gif' ); +feh_stop(); + +$win = feh_start(); +( undef, undef, $width, $height ) = GetWindowPos($win); +is( $width, 16, 'correct default window width' ); +is( $height, 16, 'correct default window height' ); + +ResizeWindow( $win, 25, 30 ); +( undef, undef, $width, $height ) = GetWindowPos($win); + +SKIP: { + if ( not( [ $width, $height ] ~~ [ 25, 30 ] ) ) { + skip( 'ResizeWindow failed', 2 ); + } + PressKey('w'); + ok( waitfor { [ ( GetWindowPos($win) )[ 2, 3 ] ] ~~ [ 16, 16 ] }, + 'w key resizes correctly' ); +} +feh_stop(); + +$win = feh_start( q{}, 'test/huge.png' ); +ok( + waitfor { + ( GetWindowPos($win) )[2] == ( GetScreenRes() )[0] + || ( GetWindowPos($win) )[3] == ( GetScreenRes() )[1]; + }, + 'Large window limited to screen size' +); +feh_stop(); + +$win = feh_start( '--no-screen-clip', 'test/huge.png' ); +ok( + waitfor { + [ ( GetWindowPos($win) )[ 2, 3 ] ] ~~ [ 4000, 3000 ]; + }, + 'disabled screen clip' +); +feh_stop(); + +# GH-35 "Custom window title crashes feh on unloadable files" +$win = feh_start( '--title "feh %h"', 'test/ok/png test/fail/png test/ok/jpg' ); +SendKeys('{RIG}'); +test_win_title( $win, 'feh 16' ); +feh_stop(); diff --git a/test/feh-scr-i.t b/test/feh-scr-i.t new file mode 100755 index 0000000..36cb494 --- /dev/null +++ b/test/feh-scr-i.t @@ -0,0 +1,326 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use 5.010; +use autodie qw/:all/; + +use Cwd; +use GD qw/:DEFAULT :cmp/; +use Test::More tests => 54; +use Time::HiRes qw/sleep/; +use X11::GUITest qw/:ALL/; + +my ( $pid_xnest, $pid_twm ); +my $win; +my ( $width, $height ); +my $pwd = getcwd(); +my $test_id = 0; +my $scr_dir = '/tmp/feh-test-scr'; + +$ENV{HOME} = 'test'; + +sub waitfor(&) { + my ($sub) = @_; + my $out; + for ( 1 .. 10 ) { + sleep(0.05); + $out = &{$sub}; + if ($out) { + return $out; + } + } + return 0; +} + +sub feh_start { + my ( $opts, $files ) = @_; + my $id; + + $opts //= q{}; + $files //= 'test/ok/png'; + + StartApp("feh ${opts} ${files}"); + ($id) = WaitWindowViewable(qr{^feh}); + + if ( not $id ) { + BAIL_OUT("Unable to start feh ${opts} ${files}"); + } + + if ( not SetInputFocus($id) ) { + BAIL_OUT("Unable to focus window"); + } + + return $id; +} + +sub feh_stop { + SendKeys('{ESC}'); + if ( not waitfor { not FindWindowLike(qr{^feh}) } ) { + BAIL_OUT("Unclosed feh window still open, cannot continue"); + } +} + +sub same_files { + my ( $one, $two ) = @_; + + my $img_one = GD::Image->new($one); + my $img_two = GD::Image->new($two); + + if ( not defined $img_one or not defined $img_two ) { + return 0; + } + + return ( !( $img_one->compare($img_two) & GD_CMP_IMAGE ) ); +} + +sub check_scr { + my ($file) = @_; + + system("import -silent -window root ${scr_dir}/feh_${$}.png"); + + return same_files( "test/scr/${file}", "${scr_dir}/feh_${$}.png" ); +} + +sub test_scr { + my ($file) = @_; + my $msg = "X root window is test/scr/${file}"; + + $test_id++; + + if ( waitfor { check_scr($file) } ) { + pass($msg); + } + else { + fail($msg); + rename( "${scr_dir}/feh_${$}.png", + "${scr_dir}/feh_${$}_${test_id}_${file}.png" ); + } +} + +if ( FindWindowLike(qr{^feh}) ) { + BAIL_OUT('It appears you have an open feh window. Please close it.'); +} + +if ( not -d $scr_dir ) { + mkdir($scr_dir); +} + +feh_start( + "--draw-actions --draw-filename --info 'echo foo; echo bar' " + . '--action quux --action5 baz --action8 "nrm \'%f\'"', + 'test/bg/exact/in test/bg/large/w/in test/bg/large/h/in' +); +test_scr('draw_all_multi'); +feh_stop(); + +feh_start( + "--draw-actions --draw-filename --info 'echo foo; echo bar' " + . '--action quux --action5 baz --action8 "nrm \'%f\'"', + 'test/bg/exact/in' +); +test_scr('draw_all_one'); +feh_stop(); + +feh_start( '--fullscreen', 'test/bg/large/w/in' ); +test_scr('feh_full_lwi'); +feh_stop(); + +feh_start( q{}, 'test/bg/large/w/in' ); +test_scr('feh_lwi'); + +SendKeys('^({RIG})'); +test_scr('feh_lwi_scroll_r'); + +SendKeys('^({DOWN})'); +test_scr('feh_lwi_scroll_rd'); + +SendKeys('^({RIG})'); +test_scr('feh_lwi_scroll_rdr'); + +SendKeys('^({UP})'); +test_scr('feh_lwi_scroll_rdru'); + +SendKeys('^({LEF})'); +test_scr('feh_lwi_scroll_rdrul'); + +feh_stop(); + +feh_start( '--scale-down', 'test/bg/large/w/in' ); +test_scr('feh_scaledown_lwi'); +feh_stop(); + +feh_start( '--thumbnails', 'test/ok/gif test/ok/jpg test/ok/png test/ok/pnm' ); +test_scr('thumbnail_default'); +feh_stop(); + +feh_start( '--index --limit-width 400', + 'test/ok/gif test/ok/jpg test/ok/png test/ok/pnm' ); +test_scr('index_w400'); +feh_stop(); + +feh_start( '--fullindex --limit-width 400', + 'test/ok/gif test/ok/jpg test/ok/png test/ok/pnm' ); +test_scr('index_full_w400'); +feh_stop(); + +feh_start( + '--index --limit-width 400 --index-info "%n\n%S\n%wx%h"', + 'test/ok/gif test/ok/jpg test/ok/png test/ok/pnm' +); +test_scr('index_full_w400'); +feh_stop(); + +feh_start( '--index --limit-height 400', + 'test/ok/gif test/ok/jpg test/ok/png test/ok/pnm' ); +test_scr('index_h400'); +feh_stop(); + +feh_start( '--fullindex --limit-height 400', + 'test/ok/gif test/ok/jpg test/ok/png test/ok/pnm' ); +test_scr('index_full_h400'); +feh_stop(); + +feh_start( '--geometry +10+20', 'test/ok/png' ); +test_scr('geometry_offset_only'); +feh_stop(); + +feh_start( '--caption-path .tc', 'test/bg/exact/in' ); +test_scr('caption_none'); + +SendKeys('c'); +test_scr('caption_new'); + +SendKeys( + 'Picknick im Zenit metaphysischen Wiederscheins der astralen Kuhglocke'); +test_scr('caption_while'); + +SendKeys('~'); +test_scr('caption_done'); + +SendKeys('c'); +test_scr('caption_while'); + +SendKeys( '{BKS}' x 80 ); +test_scr('caption_new'); + +SendKeys('~'); +test_scr('caption_none'); + +SendKeys('cfoobar{ESC}'); +test_scr('caption_none'); + +feh_stop(); + +feh_start( '--info "echo \'%f\n%wx%h\'"', 'test/bg/exact/in' ); +test_scr('draw_info'); +feh_stop(); + +feh_start( '--info "echo \'%f\n%wx%h\'" --draw-tinted', 'test/bg/exact/in' ); +test_scr('draw_info_tinted'); +feh_stop(); + +feh_start( '--draw-actions --action8 "nrm \'%f\'"', 'test/bg/exact/in' ); +test_scr('draw_action'); +feh_stop(); + +feh_start( '--draw-actions --action8 "nrm \'%f\'" --draw-tinted', + 'test/bg/exact/in' ); +test_scr('draw_action_tinted'); +feh_stop(); + +feh_start( '--draw-filename', 'test/bg/exact/in' ); +test_scr('draw_filename'); +feh_stop(); + +feh_start( '--draw-filename --draw-tinted', 'test/bg/exact/in' ); +test_scr('draw_filename_tinted'); +feh_stop(); + +feh_start( '--draw-filename --draw-actions --action8 "nrm \'%f\'"', + 'test/bg/exact/in' ); +test_scr('draw_filename_action'); +feh_stop(); + +feh_start( + '--draw-filename --draw-actions --action8 "nrm \'%f\'" --draw-tinted', + 'test/bg/exact/in' ); +test_scr('draw_filename_action_tinted'); +feh_stop(); + +feh_start( '--action8 "nrm \'%f\'"', 'test/bg/exact/in' ); +test_scr('draw_nothing'); + +SendKeys('d'); +test_scr('draw_filename'); + +SendKeys('da'); +test_scr('draw_action'); + +SendKeys('d'); +test_scr('draw_filename_action'); + +SendKeys('da'); +test_scr('draw_nothing'); + +feh_stop(); + +feh_start( '--draw-tinted', 'test/bg/exact/in' ); +test_scr('draw_nothing'); +feh_stop(); + +feh_start( q{}, 'test/bg/large/h/in' ); +test_scr('feh_lhi'); + +SendKeys('{UP}'); +test_scr('feh_lhi_i'); + +SendKeys('{UP}'); +test_scr('feh_lhi_ii'); + +SendKeys('^({RIG})'); +test_scr('feh_lhi_iir'); + +SendKeys('^({RIG})'); +test_scr('feh_lhi_iirr'); + +SendKeys('{UP}'); +test_scr('feh_lhi_iirri'); + +SendKeys('{DOWN}'); +test_scr('feh_lhi_iirrio'); + +feh_stop(); + +feh_start( q{}, 'test/bg/large/h/in' ); +test_scr('feh_lhi'); + +SendKeys('{DOWN}'); +test_scr('feh_lhi_o'); + +SendKeys('{DOWN}'); +test_scr('feh_lhi_oo'); + +SendKeys('{DOWN}'); +test_scr('feh_lhi_ooo'); + +feh_stop(); + +feh_start( q{}, 'test/bg/transparency' ); +test_scr('feh_ibg_default'); +feh_stop(); + +feh_start( '--image-bg checks', 'test/bg/transparency' ); +test_scr('feh_ibg_default'); +feh_stop(); + +feh_start( '--image-bg black', 'test/bg/transparency' ); +test_scr('feh_ibg_black'); +feh_stop(); + +feh_start( '--image-bg white', 'test/bg/transparency' ); +test_scr('feh_ibg_white'); +feh_stop(); + +unlink('test/bg/exact/.tc/in.txt'); +rmdir('test/bg/exact/.tc'); +unlink("${scr_dir}/feh_${$}.png"); diff --git a/test/feh-scr.i b/test/feh-scr.i deleted file mode 100755 index 098f1c0..0000000 --- a/test/feh-scr.i +++ /dev/null @@ -1,285 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; -use 5.010; -use autodie qw/:all/; - -use Cwd; -use GD qw/:DEFAULT :cmp/; -use Test::More tests => 42; -use Time::HiRes qw/sleep/; -use X11::GUITest qw/:ALL/; - -my ($pid_xnest, $pid_twm); -my $win; -my ($width, $height); -my $pwd = getcwd(); -my $test_id = 0; - -sub waitfor(&) { - my ($sub) = @_; - my $out; - for (1 .. 10) { - sleep(0.05); - $out = &{$sub}; - if ($out) { - return $out; - } - } - return 0; -} - -sub feh_start { - my ($opts, $files) = @_; - my $id; - - $opts //= q{}; - $files //= 'test/ok/png'; - - StartApp("feh ${opts} ${files}"); - ($id) = WaitWindowViewable(qr{^feh}); - - if (not $id) { - BAIL_OUT("Unable to start feh ${opts} ${files}"); - } - - if (not SetInputFocus($id)) { - BAIL_OUT("Unable to focus window"); - } - - return $id; -} - -sub feh_stop { - SendKeys('{ESC}'); - if (not waitfor { not FindWindowLike(qr{^feh}) }) { - BAIL_OUT("Unclosed feh window still open, cannot continue"); - } -} - -sub same_files { - my ($one, $two) = @_; - - my $img_one = GD::Image->new($one); - my $img_two = GD::Image->new($two); - - if (not defined $img_one or not defined $img_two) { - return 0; - } - - return( ! ($img_one->compare($img_two) & GD_CMP_IMAGE)); -} - -sub check_scr { - my ($file) = @_; - - system("import -silent -window root /tmp/feh_${$}.png"); - - return same_files("test/scr/${file}", "/tmp/feh_${$}.png"); -} - -sub test_scr { - my ($file) = @_; - my $msg = "X root window is test/scr/${file}"; - - $test_id++; - - if (waitfor { check_scr($file) }) { - pass($msg); - } - else { - fail($msg); - rename("/tmp/feh_${$}.png", "/tmp/feh_${$}_${test_id}_${file}.png"); - } -} - -if (FindWindowLike(qr{^feh})) { - BAIL_OUT('It appears you have an open feh window. Please close it.'); -} - -feh_start( - "--draw-actions --draw-filename --info 'echo foo; echo bar' " - . '--action quux --action5 baz --action8 "nrm \'%f\'"', - 'test/bg/exact/in test/bg/large/w/in test/bg/large/h/in' -); -test_scr('draw_all_multi'); -feh_stop(); - -feh_start( - "--draw-actions --draw-filename --info 'echo foo; echo bar' " - . '--action quux --action5 baz --action8 "nrm \'%f\'"', - 'test/bg/exact/in' -); -test_scr('draw_all_one'); -feh_stop(); - -feh_start( - '--fullscreen', - 'test/bg/large/w/in' -); -test_scr('feh_full_lwi'); -feh_stop(); - -feh_start( - q{}, - 'test/bg/large/w/in' -); -test_scr('feh_lwi'); - -SendKeys('^({RIG})'); -test_scr('feh_lwi_scroll_r'); - -SendKeys('^({DOW})'); -test_scr('feh_lwi_scroll_rd'); - -SendKeys('^({RIG})'); -test_scr('feh_lwi_scroll_rdr'); - -SendKeys('^({UP})'); -test_scr('feh_lwi_scroll_rdru'); - -SendKeys('^({LEF})'); -test_scr('feh_lwi_scroll_rdrul'); - -feh_stop(); - -feh_start( - '--scale-down', - 'test/bg/large/w/in' -); -test_scr('feh_scaledown_lwi'); -feh_stop(); - -feh_start( - '--thumbnails', - 'test/ok/gif test/ok/jpg test/ok/png test/ok/pnm' -); -test_scr('thumbnail_default'); -feh_stop(); - -feh_start( - '--caption-path .tc', - 'test/bg/exact/in' -); -test_scr('caption_none'); - -SendKeys('c'); -test_scr('caption_new'); - -SendKeys('Picknick im Zenit metaphysischen Wiederscheins der astralen Kuhglocke'); -test_scr('caption_while'); - -SendKeys('~'); -test_scr('caption_done'); - -SendKeys('c'); -test_scr('caption_while'); - -SendKeys('{BKS}' x 80); -test_scr('caption_new'); - -SendKeys('~'); -test_scr('caption_none'); - -SendKeys('cfoobar{ESC}'); -test_scr('caption_none'); - -feh_stop(); - -feh_start( - '--draw-actions --action8 "nrm \'%f\'"', - 'test/bg/exact/in' -); -test_scr('draw_action'); -feh_stop(); - -feh_start( - '--draw-filename', - 'test/bg/exact/in' -); -test_scr('draw_filename'); -feh_stop(); - -feh_start( - '--draw-filename --draw-actions --action8 "nrm \'%f\'"', - 'test/bg/exact/in' -); -test_scr('draw_filename_action'); -feh_stop(); - -feh_start( - '--action8 "nrm \'%f\'"', - 'test/bg/exact/in' -); -test_scr('draw_nothing'); - -SendKeys('d'); -test_scr('draw_filename'); - -SendKeys('da'); -test_scr('draw_action'); - -SendKeys('d'); -test_scr('draw_filename_action'); - -SendKeys('da'); -test_scr('draw_nothing'); - -feh_stop(); - -feh_start(q{}, 'test/bg/large/h/in'); -test_scr('feh_lhi'); - -SendKeys('{UP}'); -test_scr('feh_lhi_i'); - -SendKeys('{UP}'); -test_scr('feh_lhi_ii'); - -SendKeys('^({RIG})'); -test_scr('feh_lhi_iir'); - -SendKeys('^({RIG})'); -test_scr('feh_lhi_iirr'); - -SendKeys('{UP}'); -test_scr('feh_lhi_iirri'); - -SendKeys('{DOW}'); -test_scr('feh_lhi_iirrio'); - -feh_stop(); - -feh_start(q{}, 'test/bg/large/h/in'); -test_scr('feh_lhi'); - -SendKeys('{DOW}'); -test_scr('feh_lhi_o'); - -SendKeys('{DOW}'); -test_scr('feh_lhi_oo'); - -SendKeys('{DOW}'); -test_scr('feh_lhi_ooo'); - -feh_stop(); - -feh_start(q{}, 'test/bg/transparency'); -test_scr('feh_ibg_default'); -feh_stop(); - -feh_start('--image-bg default', 'test/bg/transparency'); -test_scr('feh_ibg_default'); -feh_stop(); - -feh_start('--image-bg black', 'test/bg/transparency'); -test_scr('feh_ibg_black'); -feh_stop(); - -feh_start('--image-bg white', 'test/bg/transparency'); -test_scr('feh_ibg_white'); -feh_stop(); - -unlink('test/bg/exact/.tc/in.txt'); -rmdir('test/bg/exact/.tc'); -unlink("/tmp/feh_${$}.png"); diff --git a/test/feh.i b/test/feh.i deleted file mode 100755 index ffa9c5a..0000000 --- a/test/feh.i +++ /dev/null @@ -1,343 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; -use 5.010; - -use Cwd; -use Test::More tests => 75; -use Time::HiRes qw/sleep/; -use X11::GUITest qw/:ALL/; - -my $win; -my ($width, $height); -my $pwd = getcwd(); - -sub waitfor(&) { - my ($sub) = @_; - my $out; - for (1 .. 40) { - sleep(0.05); - $out = &{$sub}; - if ($out) { - return $out; - } - } - return 0; -} - -sub feh_start { - my ($opts, $files) = @_; - my $id; - - $opts //= q{}; - $files //= 'test/ok/png'; - - StartApp("feh ${opts} ${files}"); - ($id) = WaitWindowViewable(qr{^feh}); - - if (not $id) { - BAIL_OUT("Unable to start feh ${opts} ${files}"); - } - - if (not SetInputFocus($id)) { - BAIL_OUT("Unable to focus window"); - } - - return $id; -} - -sub feh_stop { - SendKeys('{ESC}'); - if (not waitfor { not FindWindowLike(qr{^feh}) }) { - BAIL_OUT("Unclosed feh window still open, cannot continue"); - } -} - -sub test_no_win { - my ($reason) = @_; - - if (waitfor { not FindWindowLike(qr{^feh}) }) { - pass("Window closed ($reason)"); - } - else { - fail("Window closed ($reason)"); - BAIL_OUT("unclosed window still open, cannot continue"); - } -} - -sub test_win_title { - my ($win, $wtitle) = @_; - my $rtitle; - - if (waitfor { GetWindowName($win) eq $wtitle }) { - pass("Window has title: $wtitle"); - } - else { - $rtitle = GetWindowName($win); - fail("Window has title: $wtitle"); - diag("expected: $wtitle"); - diag(" got: $rtitle"); - } -} - -sub slurp { - my ($file) = @_; - my $ret; - local $/ = undef; - open(my $fh, '<', $file) or die("Can't open $file: $!"); - $ret = <$fh>; - close($fh) or die("Can't close $file: $!"); - return($ret); -} - -if (FindWindowLike(qr{^feh})) { - BAIL_OUT('It appears you have an open feh window. Please close it.'); -} - -for my $key (qw/q x {ESC}/) { - feh_start(); - SendKeys($key); - test_no_win("$key pressed"); -} - -$win = feh_start(q{}, 'test/ok/png'); -test_win_title($win, 'feh [1 of 1] - test/ok/png'); -feh_stop(); - -$win = feh_start(q{}, 'test/ok/png test/ok/jpg test/ok/gif'); -test_win_title($win, 'feh [1 of 3] - test/ok/png'); -SendKeys('{RIG}'); -test_win_title($win, 'feh [2 of 3] - test/ok/jpg'); -SendKeys('n'); -test_win_title($win, 'feh [3 of 3] - test/ok/gif'); -SendKeys('{SPA}'); -test_win_title($win, 'feh [1 of 3] - test/ok/png'); -SendKeys('{LEF}'); -test_win_title($win, 'feh [3 of 3] - test/ok/gif'); -SendKeys('p'); -test_win_title($win, 'feh [2 of 3] - test/ok/jpg'); -SendKeys('{BAC}'); -test_win_title($win, 'feh [1 of 3] - test/ok/png'); -SendKeys('p'); -test_win_title($win, 'feh [3 of 3] - test/ok/gif'); -SendKeys('{DEL}'); -test_win_title($win, 'feh [1 of 2] - test/ok/png'); -SendKeys('{DEL}'); -test_win_title($win, 'feh [1 of 1] - test/ok/jpg'); -SendKeys('{DEL}'); -test_no_win("Removed all images from slideshow"); - -$win = feh_start('--title \'feh %m %u/%l %n\'', - 'test/ok/png test/ok/jpg test/ok/gif'); -test_win_title($win, 'feh slideshow 1/3 png'); -SendKeys('{RIG}'); -test_win_title($win, 'feh slideshow 2/3 jpg'); -feh_stop(); - -feh_start('--cycle-once', 'test/ok/png test/ok/jpg'); -for (1 .. 2) { - SendKeys('{RIG}'); -} -test_no_win("--cycle-once -> window closed"); - -feh_start('--cycle-once --slideshow-delay 0.5', - 'test/ok/png test/ok/jpg test/ok/gif'); -sleep(1.5); -test_no_win('cycle-once + slideshow-delay -> window closed'); - -$win = feh_start('--cycle-once --slideshow-delay -0.01', - 'test/ok/png test/ok/jpg test/ok/gif'); - -test_win_title($win, 'feh [1 of 3] - test/ok/png [Paused]'); - -SendKeys('h'); -test_no_win('cycle-once + negative delay + [h]'); - -$win = feh_start(q{}, 'test/ok/png test/ok/gif test/ok/gif test/ok/jpg'); -for (1 .. 2) { - SendKeys('{END}'); - test_win_title($win, 'feh [4 of 4] - test/ok/jpg'); -} -for (1 .. 2) { - SendKeys('{HOM}'); - test_win_title($win, 'feh [1 of 4] - test/ok/png'); -} - -SendKeys('{PGU}'); -test_win_title($win, 'feh [4 of 4] - test/ok/jpg'); -SendKeys('{PGD}'); -test_win_title($win, 'feh [1 of 4] - test/ok/png'); -SendKeys('{PGD}'); -test_win_title($win, 'feh [2 of 4] - test/ok/gif'); - -feh_stop(); - -$win = feh_start('--slideshow-delay 1', 'test/ok/png test/ok/gif test/ok/jpg'); -sleep(1.7); -test_win_title($win, 'feh [3 of 3] - test/ok/jpg'); -SendKeys('h'); -test_win_title($win, 'feh [3 of 3] - test/ok/jpg [Paused]'); -SendKeys('{RIG}'); -test_win_title($win, 'feh [1 of 3] - test/ok/png [Paused]'); -SendKeys('h'); -test_win_title($win, 'feh [1 of 3] - test/ok/png'); -sleep(0.8); -test_win_title($win, 'feh [2 of 3] - test/ok/gif'); -feh_stop(); - -$win = feh_start(q{}, 'test/ok/png ' x 100); -test_win_title($win, 'feh [1 of 100] - test/ok/png'); -SendKeys('{PGD}'); -test_win_title($win, 'feh [6 of 100] - test/ok/png'); -SendKeys('{PGD}'); -test_win_title($win, 'feh [11 of 100] - test/ok/png'); -SendKeys('{HOM PGU}'); -test_win_title($win, 'feh [96 of 100] - test/ok/png'); -feh_stop(); - -$win = feh_start('--thumbnails -H 300 -W 310 --thumb-title "%P [%l] %f"', - 'test/ok/png test/ok/gif test/ok/jpg'); -test_win_title($win, 'feh [thumbnail mode]'); -($width, $height) = (GetWindowPos($win))[2,3]; -is($width, 310, 'thumbnail win: Set correct width'); -is($height, 300, 'thumbnail win: Set correct height'); -MoveMouseAbs(30, 30); -ClickMouseButton(M_BTN1); -($win) = WaitWindowViewable(qr{test/ok/png$}); -ok($win, 'Thumbnail mode: Window opened'); -test_win_title($win, 'feh [3] test/ok/png'); -SetInputFocus($win); -SendKeys('x'); -ok(waitfor { not FindWindowLike(qr{^ok/png$}) }, 'Thumbnail mode: closed'); - -MoveMouseAbs(90, 30); -ClickMouseButton(M_BTN1); -($win) = WaitWindowViewable(qr{test/ok/gif$}); -ok($win, 'Thumbnail mode: Window opened'); -test_win_title($win, 'feh [3] test/ok/gif'); - -MoveMouseAbs(150,30); -ClickMouseButton(M_BTN1); -($win) = WaitWindowViewable(qr{test/ok/jpg$}); -ok($win, 'Thumbnail mode: Other window opened'); -test_win_title($win, 'feh [3] test/ok/jpg'); - -feh_stop(); - -feh_start('--multiwindow', 'test/ok/png test/ok/gif test/ok/jpg'); -ok(waitfor { FindWindowLike(qr{^feh - test/ok/png$}) }, 'multiwindow 1/3'); -ok(waitfor { FindWindowLike(qr{^feh - test/ok/gif$}) }, 'multiwindow 2/3'); -ok(waitfor { FindWindowLike(qr{^feh - test/ok/jpg$}) }, 'multiwindow 3/3'); - -($win) = FindWindowLike(qr{^feh - test/ok/gif$}); -SetInputFocus($win); -SendKeys('x'); -ok(waitfor { not FindWindowLike(qr{^feh - test/ok/gif$}) }, 'win 1 closed'); -ok(FindWindowLike(qr{^feh - test/ok/png$}), 'multiwindow 1/2'); -ok(FindWindowLike(qr{^feh - test/ok/jpg$}), 'multiwindow 2/2'); - -($win) = FindWindowLike(qr{^feh - test/ok/jpg$}); -SetInputFocus($win); -SendKeys('x'); -ok(waitfor { not FindWindowLike(qr{^feh - test/ok/jpg$}) }, 'win 2 closed'); - -($win) = FindWindowLike(qr{^feh - test/ok/png$}); -SetInputFocus($win); -SendKeys('x'); -test_no_win('all multiwindows closed'); - -$win = feh_start('--start-at test/ok/jpg', 'test/ok/png test/ok/gif test/ok/jpg'); -test_win_title($win, 'feh [3 of 3] - test/ok/jpg'); -SendKeys('{RIG}'); -test_win_title($win, 'feh [1 of 3] - test/ok/png'); -feh_stop(); - -feh_start('--caption-path .captions', 'test/ok/png'); -SendKeys('cFoo Bar Quux Moep~'); -feh_stop(); -ok(-d 'test/ok/.captions', 'autocreated captions directory'); -is(slurp('test/ok/.captions/png.txt'), 'Foo Bar Quux Moep', - 'Correct caption saved'); - -feh_start('--caption-path .captions', 'test/ok/png'); -SendKeys('c'); -SendKeys('{BKS}' x length('Foo Bar Quux Moep')); -SendKeys('Foo Bar^(~)miep~'); -feh_stop(); -is(slurp('test/ok/.captions/png.txt'), "Foo Bar\nmiep", - 'Caption with newline + correct backspace'); - -unlink('test/ok/.captions/png.txt'); -rmdir('test/ok/.captions'); - -$win = feh_start('--filelist test/filelist', - 'test/ok/png test/ok/gif test/ok/png test/ok/jpg'); -SendKeys('{DEL}'); -test_win_title($win, "feh [1 of 3] - ${pwd}/test/ok/gif"); -feh_stop(); - -is(slurp('test/filelist'), <<"EOF", 'Filelist saved'); -${pwd}/test/ok/gif -${pwd}/test/ok/png -${pwd}/test/ok/jpg -EOF - -$win = feh_start('--filelist test/filelist', q{}); -test_win_title($win, "feh [1 of 3] - ${pwd}/test/ok/gif"); -feh_stop(); -unlink('test/filelist'); - -$win = feh_start('--geometry 423x232'); -(undef, undef, $width, $height) = GetWindowPos($win); -is($width, 423, '--geometry: correct width'); -is($height, 232, '--geometry: correct height'); -feh_stop(); - -$win = feh_start('--fullscreen'); -(undef, undef, $width, $height) = GetWindowPos($win); -ok([(GetWindowPos($win))[2, 3]] ~~ [GetScreenRes()], - 'fullscreen uses full screen size'); -feh_stop(); - -$win = feh_start(q{}, 'test/ok/png ' . 'test/fail/png ' x 7 . 'test/ok/gif'); -test_win_title($win, 'feh [1 of 9] - test/ok/png'); -SendKeys('{RIG}'); -test_win_title($win, 'feh [2 of 2] - test/ok/gif'); -SendKeys('{LEF}'); -test_win_title($win, 'feh [1 of 2] - test/ok/png'); -SendKeys('{LEF}'); -test_win_title($win, 'feh [2 of 2] - test/ok/gif'); -feh_stop(); - -$win = feh_start(); -(undef, undef, $width, $height) = GetWindowPos($win); -is($width, 16, 'correct default window width'); -is($height, 16, 'correct default window height'); - -ResizeWindow($win, 25, 30); -(undef, undef, $width, $height) = GetWindowPos($win); - -SKIP: { - if (not ([$width, $height] ~~ [25, 30])) { - skip('ResizeWindow failed', 2) - } - PressKey('w'); - ok(waitfor { [(GetWindowPos($win))[2, 3]] ~~ [16, 16] }, - 'w key resizes correctly'); -} -feh_stop(); - -$win = feh_start(q{}, 'test/huge.png'); -ok(waitfor { - (GetWindowPos($win))[2] == (GetScreenRes())[0] - || (GetWindowPos($win))[3] == (GetScreenRes())[1] - }, - 'Large window limited to screen size'); -feh_stop(); - -$win = feh_start('--no-screen-clip', 'test/huge.png'); -ok(waitfor { - [(GetWindowPos($win))[2, 3]] ~~ [4000, 3000] - }, - 'disabled screen clip'); -feh_stop(); @@ -2,111 +2,194 @@ use strict; use warnings; use 5.010; -use Test::Command tests => 48; +use Test::Command tests => 73; -my $fehrc = "/tmp/.fehrc-$$"; -my $feh = "src/feh --rcfile $fehrc"; -my $images = 'test/ok/* test/fail/*'; +$ENV{HOME} = 'test'; -my ($feh_name, $feh_version) = @ENV{'PACKAGE', 'VERSION'}; +my $feh = "src/feh"; +my $images_ok = 'test/ok/gif test/ok/jpg test/ok/png test/ok/pnm'; +my $images_fail = 'test/fail/gif test/fail/jpg test/fail/png test/fail/pnm'; +my $images = "${images_ok} ${images_fail}"; +my $has_help = 0; + +my $feh_name = $ENV{'PACKAGE'}; # These tests are meant to run non-interactively and without X. # make sure they are capable of doing so. delete $ENV{'DISPLAY'}; -# Create empty fehrc so that feh does not create one in $HOME -# (mostly for build servers) -open(my $fh, '>', $fehrc) or die("Can't create $fehrc: $!"); -close($fh) or die("Can't close $fehrc: $!"); - my $err_no_env = <<'EOF'; -Unable to determine feh PACKAGE or VERSION. +Unable to determine feh PACKAGE. This is most likely because you ran 'prove test' or 'perl test/feh.t'. -Sinc this test uses make variables and is therefore designed to be run from +Since this test uses make variables and is therefore designed to be run from the Makefile only, use 'make test' instead. If you absolutely need to run it the other way, use - PACKAGE=feh VERSION=1.5 ${your_command} -(with the appropiate values, of course). + PACKAGE=feh ${your_command} EOF -if (length($feh_name) == 0 or length($feh_version) == 0) { +if ( length($feh_name) == 0 ) { die($err_no_env); } -my $re_warning = - qr{${feh_name} WARNING: test/fail/... \- No Imlib2 loader for that file format\n}; -my $re_loadable = qr{test/ok/...}; -my $re_unloadable = qr{test/fail/...}; -my $re_list_action = qr{test/ok/... 16x16 \(${feh_name}\)}; +# Imlib2 1.6+ reports JPEG file format as 'jpg', older versions use 'jpeg'. +# Determine the output format used in this version with a --customlist call. +my $list_dir = 'list'; +if (qx{$feh --customlist %t test/ok/jpg} =~ m{jpg}) { + $list_dir = 'list_imlib2_1.6'; +} -my $cmd = Test::Command->new(cmd => $feh); +my $version = qx{$feh --version}; +if ( $version =~ m{ Compile-time \s switches : \s .* help }ox ) { + $has_help = 1; +} -# Insufficient Arguments -> Usage should return failure -$cmd->exit_is_num(1, 'missing arguments return 1'); -$cmd->stdout_is_eq('', 'missing arguments print usage (!stdout)'); -$cmd->stderr_is_eq(<<"EOF", 'missing arguments print usage (stderr)'); -${feh_name} - No loadable images specified. -Use ${feh_name} --help for detailed usage information -EOF +# Imlib2 1.8+ returns "Invalid image file" rather than "No Imlib2 loader". +# feh compiled with magic=1 returns "Does not look like an image (magic bytes missing)" +# Here, we accept all three. +my $re_warning + = qr{${feh_name} WARNING: test/fail/... \- (Invalid image file|No Imlib2 loader for that file format|Does not look like an image \(magic bytes missing\))\n}; +my $re_loadable = qr{test/ok/...}; +my $re_unloadable = qr{test/fail/...}; +my $re_list_action = qr{test/ok/... 16x16}; -$cmd = Test::Command->new(cmd => "$feh --version"); +my $cmd = Test::Command->new( cmd => "$feh --version" ); $cmd->exit_is_num(0); -$cmd->stdout_is_eq("${feh_name} version ${feh_version}\n"); $cmd->stderr_is_eq(''); -$cmd = Test::Command->new(cmd => "$feh --loadable $images"); +$cmd = Test::Command->new( cmd => "$feh --loadable $images" ); -$cmd->exit_is_num(0); +$cmd->exit_is_num(1); $cmd->stdout_like($re_loadable); $cmd->stderr_is_eq(''); -$cmd = Test::Command->new(cmd => "$feh --unloadable $images"); +$cmd = Test::Command->new( + cmd => "$feh --loadable --action 'echo touch %f' $images" ); -$cmd->exit_is_num(0); +$cmd->exit_is_num(1); +$cmd->stdout_is_file('test/nx_action/loadable_action'); +$cmd->stderr_is_eq(''); + +$cmd = Test::Command->new( + cmd => "$feh --loadable --action ';echo touch %f' $images" ); + +$cmd->exit_is_num(1); +$cmd->stdout_is_file('test/nx_action/loadable_naction'); +$cmd->stderr_is_eq(''); + +$cmd = Test::Command->new( + cmd => "$feh --unloadable --action 'echo rm %f' $images" ); + +$cmd->exit_is_num(1); +$cmd->stdout_is_file('test/nx_action/unloadable_action'); +$cmd->stderr_is_eq(''); + +$cmd = Test::Command->new( + cmd => "$feh --unloadable --action ';echo rm %f' $images" ); + +$cmd->exit_is_num(1); +$cmd->stdout_is_file('test/nx_action/unloadable_naction'); +$cmd->stderr_is_eq(''); + +$cmd = Test::Command->new( cmd => "$feh --unloadable $images" ); + +$cmd->exit_is_num(1); $cmd->stdout_like($re_unloadable); $cmd->stderr_is_eq(''); -$cmd = Test::Command->new(cmd => "$feh --list $images"); +$cmd = Test::Command->new( cmd => "$feh --list $images" ); $cmd->exit_is_num(0); -$cmd->stdout_is_file('test/list/default'); +$cmd->stdout_is_file("test/${list_dir}/default"); $cmd->stderr_like($re_warning); for my $sort (qw/name filename width height pixels size format/) { - $cmd = Test::Command->new(cmd => "$feh --list $images --sort $sort"); + $cmd = Test::Command->new( cmd => "$feh --list $images --sort $sort" ); $cmd->exit_is_num(0); - $cmd->stdout_is_file("test/list/$sort"); + $cmd->stdout_is_file("test/${list_dir}/$sort"); $cmd->stderr_like($re_warning); } -$cmd = Test::Command->new(cmd => "$feh --list $images --sort format --reverse"); +$cmd + = Test::Command->new( cmd => "$feh --list $images --sort format --reverse" ); $cmd->exit_is_num(0); -$cmd->stdout_is_file('test/list/format_reverse'); +$cmd->stdout_is_file("test/${list_dir}/format_reverse"); $cmd->stderr_like($re_warning); -$cmd = Test::Command->new(cmd => "$feh --customlist '%f; %h; %l; %m; %n; %p; " - . "%s; %t; %u; %w' $images"); +$cmd = Test::Command->new( + cmd => "$feh --list --recursive --sort filename test/ok" ); $cmd->exit_is_num(0); -$cmd->stdout_is_file('test/list/custom'); + +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=813729 +#$cmd->stdout_is_file("test/${list_dir}/filename_recursive"); +#$cmd->stderr_is_eq(''); +# dummy tests to match number of planned tests +$cmd->exit_is_num(0); +$cmd->exit_is_num(0); + +$cmd = Test::Command->new( cmd => "$feh --customlist '%f; %h; %l; %m; %n; %p; " + . "%s; %t; %u; %w' $images" ); + +$cmd->exit_is_num(0); +$cmd->stdout_is_file("test/${list_dir}/custom"); $cmd->stderr_like($re_warning); -$cmd = Test::Command->new(cmd => "$feh --list --quiet $images"); +$cmd = Test::Command->new( cmd => "$feh --list --quiet $images" ); $cmd->exit_is_num(0); -$cmd->stdout_is_file('test/list/default'); +$cmd->stdout_is_file("test/${list_dir}/default"); $cmd->stderr_is_eq(''); -$cmd = Test::Command->new(cmd => - "$feh --quiet --list --action 'echo \"%f %wx%h (%P)\" >&2' $images"); +$cmd = Test::Command->new( + cmd => "$feh --quiet --list --action 'echo \"%f %wx%h\" >&2' $images" ); $cmd->exit_is_num(0); -$cmd->stdout_is_file('test/list/default'); +$cmd->stdout_is_file("test/${list_dir}/default"); $cmd->stderr_like($re_list_action); -unlink($fehrc); +$cmd + = Test::Command->new( cmd => "$feh --list --min-dimension 20x20 $images_ok" ); + +$cmd->exit_is_num(1); +$cmd->stdout_is_eq(''); +if ($has_help) { + $cmd->stderr_is_file('test/no-loadable-files.help'); +} +else { + $cmd->stderr_is_file('test/no-loadable-files'); +} + +$cmd + = Test::Command->new( cmd => "$feh --list --max-dimension 10x10 $images_ok" ); + +$cmd->exit_is_num(1); +$cmd->stdout_is_eq(''); +if ($has_help) { + $cmd->stderr_is_file('test/no-loadable-files.help'); +} +else { + $cmd->stderr_is_file('test/no-loadable-files'); +} + +$cmd + = Test::Command->new( cmd => "$feh --list --min-dimension 16x16 $images_ok" ); + +$cmd->exit_is_num(0); +$cmd->stdout_is_file("test/${list_dir}/default"); +$cmd->stderr_is_eq(''); + +$cmd + = Test::Command->new( cmd => "$feh --list --max-dimension 16x16 $images_ok" ); + +$cmd->exit_is_num(0); +$cmd->stdout_is_file("test/${list_dir}/default"); +$cmd->stderr_is_eq(''); + +$cmd = Test::Command->new( cmd => "$feh --list test/tiny.pbm" ); +$cmd->exit_is_num(0); +$cmd->stderr_is_eq(''); diff --git a/test/imlib2-bug-notice b/test/imlib2-bug-notice new file mode 100644 index 0000000..3cfccf5 --- /dev/null +++ b/test/imlib2-bug-notice @@ -0,0 +1,11 @@ +[!] Possibly broken imlib2 / libgif detected - ignoring test results + +Imlib2 1.4.7 with giflib 5.1.2 is unable to load GIF images. On x86 / x86_64 +this applies to most gifs which are not the first file in the filelist, while +on mips/mipsel and (some?) arm boxes it is unable to load any gif files. Since +feh's tests include gifs, they fail. + +As there's nothing we can do about it (and other image formats still work +fine), we'll just pretend everything's okay. + +See <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=813729> for details. diff --git a/test/list/custom b/test/list/custom index b5ddb32..dbe2074 100644 --- a/test/list/custom +++ b/test/list/custom @@ -1,4 +1,4 @@ -test/ok/gif; 16; 4; list; gif; 256; 953; gif; 0; 16 -test/ok/jpg; 16; 4; list; jpg; 256; 354; jpeg; 0; 16 -test/ok/png; 16; 4; list; png; 256; 403; png; 0; 16 -test/ok/pnm; 16; 4; list; pnm; 256; 269; pnm; 0; 16 +test/ok/gif; 16; 4; list; gif; 256; 953; gif; 1; 16 +test/ok/jpg; 16; 4; list; jpg; 256; 354; jpeg; 2; 16 +test/ok/png; 16; 4; list; png; 256; 403; png; 3; 16 +test/ok/pnm; 16; 4; list; pnm; 256; 269; pnm; 4; 16 diff --git a/test/list/default b/test/list/default index d7b2c12..bc0ef52 100644 --- a/test/list/default +++ b/test/list/default @@ -1,5 +1,5 @@ -NUM FORMAT WIDTH HEIGHT PIXELS SIZE(bytes) ALPHA FILENAME -1 gif 16 16 256 953 - test/ok/gif -2 jpeg 16 16 256 354 - test/ok/jpg -3 png 16 16 256 403 X test/ok/png -4 pnm 16 16 256 269 - test/ok/pnm +NUM FORMAT WIDTH HEIGHT PIXELS SIZE ALPHA FILENAME +1 gif 16 16 256 953 - test/ok/gif +2 jpeg 16 16 256 354 - test/ok/jpg +3 png 16 16 256 403 X test/ok/png +4 pnm 16 16 256 269 - test/ok/pnm diff --git a/test/list/filename_recursive b/test/list/filename_recursive new file mode 100644 index 0000000..6e2de8a --- /dev/null +++ b/test/list/filename_recursive @@ -0,0 +1,7 @@ +NUM FORMAT WIDTH HEIGHT PIXELS SIZE ALPHA FILENAME +1 gif 16 16 256 953 - test/ok/gif +2 jpeg 16 16 256 354 - test/ok/jpg +3 jpeg 16 16 256 9k - test/ok/jpg_exif +4 png 16 16 256 403 X test/ok/png +5 pnm 16 16 256 269 - test/ok/pnm +6 png 16 16 256 403 X test/ok/recursive/png diff --git a/test/list/format_reverse b/test/list/format_reverse index 801b732..9216184 100644 --- a/test/list/format_reverse +++ b/test/list/format_reverse @@ -1,5 +1,5 @@ -NUM FORMAT WIDTH HEIGHT PIXELS SIZE(bytes) ALPHA FILENAME -1 pnm 16 16 256 269 - test/ok/pnm -2 png 16 16 256 403 X test/ok/png -3 jpeg 16 16 256 354 - test/ok/jpg -4 gif 16 16 256 953 - test/ok/gif +NUM FORMAT WIDTH HEIGHT PIXELS SIZE ALPHA FILENAME +1 pnm 16 16 256 269 - test/ok/pnm +2 png 16 16 256 403 X test/ok/png +3 jpeg 16 16 256 354 - test/ok/jpg +4 gif 16 16 256 953 - test/ok/gif diff --git a/test/list/size b/test/list/size index 9e1c3d0..cad60b0 100644 --- a/test/list/size +++ b/test/list/size @@ -1,5 +1,5 @@ -NUM FORMAT WIDTH HEIGHT PIXELS SIZE(bytes) ALPHA FILENAME -1 pnm 16 16 256 269 - test/ok/pnm -2 jpeg 16 16 256 354 - test/ok/jpg -3 png 16 16 256 403 X test/ok/png -4 gif 16 16 256 953 - test/ok/gif +NUM FORMAT WIDTH HEIGHT PIXELS SIZE ALPHA FILENAME +1 pnm 16 16 256 269 - test/ok/pnm +2 jpeg 16 16 256 354 - test/ok/jpg +3 png 16 16 256 403 X test/ok/png +4 gif 16 16 256 953 - test/ok/gif diff --git a/test/list_imlib2_1.6/custom b/test/list_imlib2_1.6/custom new file mode 100644 index 0000000..40ac557 --- /dev/null +++ b/test/list_imlib2_1.6/custom @@ -0,0 +1,4 @@ +test/ok/gif; 16; 4; list; gif; 256; 953; gif; 1; 16 +test/ok/jpg; 16; 4; list; jpg; 256; 354; jpg; 2; 16 +test/ok/png; 16; 4; list; png; 256; 403; png; 3; 16 +test/ok/pnm; 16; 4; list; pnm; 256; 269; pnm; 4; 16 diff --git a/test/list_imlib2_1.6/default b/test/list_imlib2_1.6/default new file mode 100644 index 0000000..e480db3 --- /dev/null +++ b/test/list_imlib2_1.6/default @@ -0,0 +1,5 @@ +NUM FORMAT WIDTH HEIGHT PIXELS SIZE ALPHA FILENAME +1 gif 16 16 256 953 - test/ok/gif +2 jpg 16 16 256 354 - test/ok/jpg +3 png 16 16 256 403 X test/ok/png +4 pnm 16 16 256 269 - test/ok/pnm diff --git a/test/list_imlib2_1.6/filename b/test/list_imlib2_1.6/filename new file mode 120000 index 0000000..331d858 --- /dev/null +++ b/test/list_imlib2_1.6/filename @@ -0,0 +1 @@ +default
\ No newline at end of file diff --git a/test/list_imlib2_1.6/filename_recursive b/test/list_imlib2_1.6/filename_recursive new file mode 100644 index 0000000..e42ce14 --- /dev/null +++ b/test/list_imlib2_1.6/filename_recursive @@ -0,0 +1,7 @@ +NUM FORMAT WIDTH HEIGHT PIXELS SIZE ALPHA FILENAME +1 gif 16 16 256 953 - test/ok/gif +2 jpg 16 16 256 354 - test/ok/jpg +3 jpg 16 16 256 9k - test/ok/jpg_exif +4 png 16 16 256 403 X test/ok/png +5 pnm 16 16 256 269 - test/ok/pnm +6 png 16 16 256 403 X test/ok/recursive/png diff --git a/test/list_imlib2_1.6/format b/test/list_imlib2_1.6/format new file mode 120000 index 0000000..331d858 --- /dev/null +++ b/test/list_imlib2_1.6/format @@ -0,0 +1 @@ +default
\ No newline at end of file diff --git a/test/list_imlib2_1.6/format_reverse b/test/list_imlib2_1.6/format_reverse new file mode 100644 index 0000000..3301f78 --- /dev/null +++ b/test/list_imlib2_1.6/format_reverse @@ -0,0 +1,5 @@ +NUM FORMAT WIDTH HEIGHT PIXELS SIZE ALPHA FILENAME +1 pnm 16 16 256 269 - test/ok/pnm +2 png 16 16 256 403 X test/ok/png +3 jpg 16 16 256 354 - test/ok/jpg +4 gif 16 16 256 953 - test/ok/gif diff --git a/test/list_imlib2_1.6/height b/test/list_imlib2_1.6/height new file mode 120000 index 0000000..331d858 --- /dev/null +++ b/test/list_imlib2_1.6/height @@ -0,0 +1 @@ +default
\ No newline at end of file diff --git a/test/list_imlib2_1.6/name b/test/list_imlib2_1.6/name new file mode 120000 index 0000000..331d858 --- /dev/null +++ b/test/list_imlib2_1.6/name @@ -0,0 +1 @@ +default
\ No newline at end of file diff --git a/test/list_imlib2_1.6/pixels b/test/list_imlib2_1.6/pixels new file mode 120000 index 0000000..331d858 --- /dev/null +++ b/test/list_imlib2_1.6/pixels @@ -0,0 +1 @@ +default
\ No newline at end of file diff --git a/test/list_imlib2_1.6/size b/test/list_imlib2_1.6/size new file mode 100644 index 0000000..7716239 --- /dev/null +++ b/test/list_imlib2_1.6/size @@ -0,0 +1,5 @@ +NUM FORMAT WIDTH HEIGHT PIXELS SIZE ALPHA FILENAME +1 pnm 16 16 256 269 - test/ok/pnm +2 jpg 16 16 256 354 - test/ok/jpg +3 png 16 16 256 403 X test/ok/png +4 gif 16 16 256 953 - test/ok/gif diff --git a/test/list_imlib2_1.6/width b/test/list_imlib2_1.6/width new file mode 120000 index 0000000..331d858 --- /dev/null +++ b/test/list_imlib2_1.6/width @@ -0,0 +1 @@ +default
\ No newline at end of file diff --git a/test/mandoc.t b/test/mandoc.t index d664289..9e7ffc3 100755 --- a/test/mandoc.t +++ b/test/mandoc.t @@ -3,18 +3,25 @@ use strict; use warnings; use 5.010; -use Test::More tests => 3; +use Test::More tests => 1; SKIP: { - qx{mandoc -V}; + my $mandoc_present = 0; - if ($? != 0) { + for my $path (split(qr{:}, $ENV{PATH})) { + if (-x "${path}/mandoc") { + $mandoc_present = 1; + last; + } + } + + if ( not $mandoc_present ) { diag('mandoc not installed, test skipped. This is NOT fatal.'); - skip('mandoc not installed', 3); + skip( 'mandoc not installed', 1 ); } - for my $file ('feh', 'feh-cam', 'gen-cam-menu') { - qx{mandoc -Tlint man/${file}.1}; - is($?, 0, "${file}.1: Valid mdoc syntax"); + for my $file ('feh') { + qx{mandoc -Tlint -Werror man/${file}.1}; + is( $?, 0, "${file}.1: Valid mdoc syntax" ); } } diff --git a/test/no-loadable-files b/test/no-loadable-files new file mode 100644 index 0000000..bc5871d --- /dev/null +++ b/test/no-loadable-files @@ -0,0 +1,2 @@ +feh: No loadable images specified. +See 'man feh' for detailed usage information diff --git a/test/no-loadable-files.help b/test/no-loadable-files.help new file mode 100644 index 0000000..b1b61d8 --- /dev/null +++ b/test/no-loadable-files.help @@ -0,0 +1,2 @@ +feh: No loadable images specified. +See 'feh --help' or 'man feh' for detailed usage information diff --git a/test/nx_action/loadable_action b/test/nx_action/loadable_action new file mode 100644 index 0000000..fbf517b --- /dev/null +++ b/test/nx_action/loadable_action @@ -0,0 +1,8 @@ +test/ok/gif +touch test/ok/gif +test/ok/jpg +touch test/ok/jpg +test/ok/png +touch test/ok/png +test/ok/pnm +touch test/ok/pnm diff --git a/test/nx_action/loadable_naction b/test/nx_action/loadable_naction new file mode 100644 index 0000000..fbf517b --- /dev/null +++ b/test/nx_action/loadable_naction @@ -0,0 +1,8 @@ +test/ok/gif +touch test/ok/gif +test/ok/jpg +touch test/ok/jpg +test/ok/png +touch test/ok/png +test/ok/pnm +touch test/ok/pnm diff --git a/test/nx_action/unloadable_action b/test/nx_action/unloadable_action new file mode 100644 index 0000000..cdf3ed8 --- /dev/null +++ b/test/nx_action/unloadable_action @@ -0,0 +1,8 @@ +test/fail/gif +rm test/fail/gif +test/fail/jpg +rm test/fail/jpg +test/fail/png +rm test/fail/png +test/fail/pnm +rm test/fail/pnm diff --git a/test/nx_action/unloadable_naction b/test/nx_action/unloadable_naction new file mode 100644 index 0000000..cdf3ed8 --- /dev/null +++ b/test/nx_action/unloadable_naction @@ -0,0 +1,8 @@ +test/fail/gif +rm test/fail/gif +test/fail/jpg +rm test/fail/jpg +test/fail/png +rm test/fail/png +test/fail/pnm +rm test/fail/pnm diff --git a/test/ok/jpg_exif b/test/ok/jpg_exif Binary files differnew file mode 100644 index 0000000..34c3846 --- /dev/null +++ b/test/ok/jpg_exif diff --git a/test/ok/recursive/png b/test/ok/recursive/png Binary files differnew file mode 100644 index 0000000..2f06506 --- /dev/null +++ b/test/ok/recursive/png diff --git a/test/run-interactive b/test/run-interactive index fab6517..788ca3b 100755 --- a/test/run-interactive +++ b/test/run-interactive @@ -1,9 +1,11 @@ #!/bin/sh -Xnest -geometry 500x500 :7 > /dev/null 2>&1 & +Xephyr -screen 500x500 :7 > /dev/null 2>&1 & pid=${!} -DISPLAY=:7 prove test/feh-scr.i test/feh.i +sleep 2 + +DISPLAY=:7 prove -j1 test/feh-scr-i.t test/feh-i.t ret=${?} kill ${pid} diff --git a/test/scr/caption_done b/test/scr/caption_done Binary files differindex d178e7e..3353d56 100644 --- a/test/scr/caption_done +++ b/test/scr/caption_done diff --git a/test/scr/caption_new b/test/scr/caption_new Binary files differindex fc438fd..ce44a08 100644 --- a/test/scr/caption_new +++ b/test/scr/caption_new diff --git a/test/scr/caption_none b/test/scr/caption_none Binary files differindex 5903c34..a1993ef 100644 --- a/test/scr/caption_none +++ b/test/scr/caption_none diff --git a/test/scr/caption_while b/test/scr/caption_while Binary files differindex e9db559..b8bbde2 100644 --- a/test/scr/caption_while +++ b/test/scr/caption_while diff --git a/test/scr/draw_action b/test/scr/draw_action Binary files differindex 4a2b06c..668d23f 100644 --- a/test/scr/draw_action +++ b/test/scr/draw_action diff --git a/test/scr/draw_action_tinted b/test/scr/draw_action_tinted Binary files differnew file mode 100644 index 0000000..68ce1cd --- /dev/null +++ b/test/scr/draw_action_tinted diff --git a/test/scr/draw_all_multi b/test/scr/draw_all_multi Binary files differindex d105fbe..768a00b 100644 --- a/test/scr/draw_all_multi +++ b/test/scr/draw_all_multi diff --git a/test/scr/draw_all_one b/test/scr/draw_all_one Binary files differindex 7a3b796..44bb2d1 100644 --- a/test/scr/draw_all_one +++ b/test/scr/draw_all_one diff --git a/test/scr/draw_filename b/test/scr/draw_filename Binary files differindex dc32356..4d7cea5 100644 --- a/test/scr/draw_filename +++ b/test/scr/draw_filename diff --git a/test/scr/draw_filename_action b/test/scr/draw_filename_action Binary files differindex 6039def..2c9bbe6 100644 --- a/test/scr/draw_filename_action +++ b/test/scr/draw_filename_action diff --git a/test/scr/draw_filename_action_tinted b/test/scr/draw_filename_action_tinted Binary files differnew file mode 100644 index 0000000..58c43cd --- /dev/null +++ b/test/scr/draw_filename_action_tinted diff --git a/test/scr/draw_filename_tinted b/test/scr/draw_filename_tinted Binary files differnew file mode 100644 index 0000000..c2a8cef --- /dev/null +++ b/test/scr/draw_filename_tinted diff --git a/test/scr/draw_info b/test/scr/draw_info Binary files differnew file mode 100644 index 0000000..dee6b4a --- /dev/null +++ b/test/scr/draw_info diff --git a/test/scr/draw_info_tinted b/test/scr/draw_info_tinted Binary files differnew file mode 100644 index 0000000..6a11a8e --- /dev/null +++ b/test/scr/draw_info_tinted diff --git a/test/scr/draw_nothing b/test/scr/draw_nothing Binary files differindex 5903c34..58923f6 100644 --- a/test/scr/draw_nothing +++ b/test/scr/draw_nothing diff --git a/test/scr/feh_full_lwi b/test/scr/feh_full_lwi Binary files differindex e46cb05..2601133 100644 --- a/test/scr/feh_full_lwi +++ b/test/scr/feh_full_lwi diff --git a/test/scr/feh_ibg_black b/test/scr/feh_ibg_black Binary files differindex 04f1364..90a666b 100644 --- a/test/scr/feh_ibg_black +++ b/test/scr/feh_ibg_black diff --git a/test/scr/feh_ibg_white b/test/scr/feh_ibg_white Binary files differindex 70138ff..53c084f 100644 --- a/test/scr/feh_ibg_white +++ b/test/scr/feh_ibg_white diff --git a/test/scr/feh_lhi b/test/scr/feh_lhi Binary files differindex f16d150..8ba9a22 100644 --- a/test/scr/feh_lhi +++ b/test/scr/feh_lhi diff --git a/test/scr/feh_lhi_i b/test/scr/feh_lhi_i Binary files differindex 108dfb6..5d2febb 100644 --- a/test/scr/feh_lhi_i +++ b/test/scr/feh_lhi_i diff --git a/test/scr/feh_lhi_ii b/test/scr/feh_lhi_ii Binary files differindex 1fb695f..3d7c2fd 100644 --- a/test/scr/feh_lhi_ii +++ b/test/scr/feh_lhi_ii diff --git a/test/scr/feh_lhi_iir b/test/scr/feh_lhi_iir Binary files differindex 396ee99..a0d4f5e 100644 --- a/test/scr/feh_lhi_iir +++ b/test/scr/feh_lhi_iir diff --git a/test/scr/feh_lhi_iirr b/test/scr/feh_lhi_iirr Binary files differindex 81748a3..c3ac6d5 100644 --- a/test/scr/feh_lhi_iirr +++ b/test/scr/feh_lhi_iirr diff --git a/test/scr/feh_lhi_iirri b/test/scr/feh_lhi_iirri Binary files differindex 43b8fa1..310a238 100644 --- a/test/scr/feh_lhi_iirri +++ b/test/scr/feh_lhi_iirri diff --git a/test/scr/feh_lhi_iirrio b/test/scr/feh_lhi_iirrio Binary files differindex c9e287b..4d064ed 100644 --- a/test/scr/feh_lhi_iirrio +++ b/test/scr/feh_lhi_iirrio diff --git a/test/scr/feh_lhi_o b/test/scr/feh_lhi_o Binary files differindex ec48c01..bdef5db 100644 --- a/test/scr/feh_lhi_o +++ b/test/scr/feh_lhi_o diff --git a/test/scr/feh_lhi_oo b/test/scr/feh_lhi_oo Binary files differindex 784d7d7..9499dfd 100644 --- a/test/scr/feh_lhi_oo +++ b/test/scr/feh_lhi_oo diff --git a/test/scr/feh_lhi_ooo b/test/scr/feh_lhi_ooo Binary files differindex ad14384..ec3eab6 100644 --- a/test/scr/feh_lhi_ooo +++ b/test/scr/feh_lhi_ooo diff --git a/test/scr/feh_lwi b/test/scr/feh_lwi Binary files differindex 16855f7..37fa6c1 100644 --- a/test/scr/feh_lwi +++ b/test/scr/feh_lwi diff --git a/test/scr/feh_lwi_scroll_r b/test/scr/feh_lwi_scroll_r Binary files differindex 5003a88..0baa70d 100644 --- a/test/scr/feh_lwi_scroll_r +++ b/test/scr/feh_lwi_scroll_r diff --git a/test/scr/feh_lwi_scroll_rd b/test/scr/feh_lwi_scroll_rd Binary files differindex 333b9e3..ce3271d 100644 --- a/test/scr/feh_lwi_scroll_rd +++ b/test/scr/feh_lwi_scroll_rd diff --git a/test/scr/feh_lwi_scroll_rdr b/test/scr/feh_lwi_scroll_rdr Binary files differindex 1e921f1..52f6b6f 100644 --- a/test/scr/feh_lwi_scroll_rdr +++ b/test/scr/feh_lwi_scroll_rdr diff --git a/test/scr/feh_lwi_scroll_rdru b/test/scr/feh_lwi_scroll_rdru Binary files differindex 455a2c2..afad9de 100644 --- a/test/scr/feh_lwi_scroll_rdru +++ b/test/scr/feh_lwi_scroll_rdru diff --git a/test/scr/feh_lwi_scroll_rdrul b/test/scr/feh_lwi_scroll_rdrul Binary files differindex 5003a88..7b5196a 100644 --- a/test/scr/feh_lwi_scroll_rdrul +++ b/test/scr/feh_lwi_scroll_rdrul diff --git a/test/scr/feh_scaledown_lwi b/test/scr/feh_scaledown_lwi Binary files differindex a842471..623d63d 100644 --- a/test/scr/feh_scaledown_lwi +++ b/test/scr/feh_scaledown_lwi diff --git a/test/scr/geometry_offset_only b/test/scr/geometry_offset_only Binary files differnew file mode 100644 index 0000000..b1618ec --- /dev/null +++ b/test/scr/geometry_offset_only diff --git a/test/scr/index_full_h400 b/test/scr/index_full_h400 Binary files differnew file mode 100644 index 0000000..50c3c36 --- /dev/null +++ b/test/scr/index_full_h400 diff --git a/test/scr/index_full_w400 b/test/scr/index_full_w400 Binary files differnew file mode 100644 index 0000000..a86b2af --- /dev/null +++ b/test/scr/index_full_w400 diff --git a/test/scr/index_h400 b/test/scr/index_h400 Binary files differnew file mode 100644 index 0000000..bcae91d --- /dev/null +++ b/test/scr/index_h400 diff --git a/test/scr/index_w400 b/test/scr/index_w400 Binary files differnew file mode 100644 index 0000000..1038e59 --- /dev/null +++ b/test/scr/index_w400 diff --git a/test/scr/thumbnail_default b/test/scr/thumbnail_default Binary files differindex 2c52059..8d70a5b 100644 --- a/test/scr/thumbnail_default +++ b/test/scr/thumbnail_default diff --git a/test/status b/test/status new file mode 100644 index 0000000..6db362e --- /dev/null +++ b/test/status @@ -0,0 +1,401 @@ +Overall test status, what's covered / missing + +# Features + +# Options + +--action +--action1 +--action2 +--action3 +--action4 +--action5 +--action6 +--action7 +--action8 +--action9 + + [x] correct command execution + [x] hold-action flag + [x] supports format specifiers + [x] with --list + [x] with --loadable + [x] with --unloadable + +--alpha +--auto-zoom + + [ ] zooms to fullscreen + [ ] with --stretch + [ ] with --ignore-aspect + + +--bg +--bg-center + + [x] sets centered wallpaper + + +--bg-fill + + [x] Sets wallpaper zoomed to fill + + +--bg-max + + [x] Sets wallpaper zoomed to max + + +--bg-scale + + [x] sets scaled wallpaper, ignoring aspect ratio + + +--bg-tile + + [x] sets tiled wallpaper + + +--blur-button +--borderless + + [ ] create borderless window (need test WM with borders first...) + + +--cache-thumbnails + + [ ] ./thumbnails/normal + [ ] ./thumbnails/large + + +--caption-path + + [x] loads/saves captions + [x] autocreates caption dir + [x] caption newline support + [x] correct caption display + + +--customlist + + [x] correct output + [x] format specifiers + + +--on-last-slide=quit + + [x] closes feh window at end of slideshow + [x] combination with --slideshow-delay + + +--draw-actions + + [x] lists correct actions + [x] Alignment with/without --draw-filename + + +--draw-filename + + [x] shows filename + [ ] shows filename and position in fullscreen + + +--filelist + + [x] saves filelist + [x] loads filelist + + +--font + + [ ] Sets different font + + +--fontpath + + [ ] Adds font path + + +--fullindex + + [ ] Shows correct image size + [ ] Shows correct image dimensions + [ ] With --cache-thumbnails + + +--fullscreen + + [x] uses full screen size + + +--geometry + + [x] correct window dimensions + + +--hide-pointer + + [ ] Hides pointer visibility + + +--ignore-aspect +--image-bg + + [x] black + [x] white + [x] default + + +--index +--index-dim +--index-name +--index-size + +--info + + [x] executes commands + [x] correct output + [x] format specifiers + + +--keep-http + + [ ] keeps local file copies + + +--limit-height + + [x] sets correct thumbnail window height + + +--limit-width + + [x] sets correct thumbnail window width + + +--list + + [x] correct output + + +--loadable + + [x] lists loadable images + + +--menu-button +--menu-ctrl-mask +--menu-font + + [ ] changes menu font + + +--montage +--multiwindow + + [x] opens one window per image + + +--next-button +--no-blur-ctrl-mask +--no-jump-on-resort +--no-menus + + [ ] disables menu + + +--no-rotate-ctrl-mask +--no-screen-clip + + [x] creates huge window + + +--no-xinerama + + [ ] disables xinerama support + + +--output +--output-dir + + to be deprecated + + +--output-only +--pan-button +--preload + + [ ] weeds out unloadable images + + +--prev-button +--quiet + + [x] quiet output + + +--randomize + + [ ] random filelist order + + +--recursive + + [x] recurses into subdirectories + + +--reload + + [ ] automatically reloads image + + +--reload-button +--reverse + + [x] reverse sort order + + +--rotate-button +--scale-down + + [x] correct window size + [x] correct zoom level + [ ] correct zoom behaviour + + +--slideshow-delay + + [x] positive delay -> automatic window change + [x] negative delay -> starts paused + + +--sort + + [x] name + [x] filename + [x] width + [x] height + [x] pixels + [x] size + [x] format + + +--start-at + + [x] starts at correct image + + +--stretch +--theme + + [x] loads correct options + [x] commandline overrides theme + [x] multiline theme specifications + + +--thumb-height + + [ ] sets correct thumbnail height + + +--thumbnails + + [x] starts thumbnail mode + [ ] shows correct thumbnail dimensions (GH-29) + + +--thumb-redraw +--thumb-title + + [ ] sets title for windows opened from thumbnail mode + + +--thumb-width + + [ ] sets correct thumbnail width + + +--title + + [x] Sets correct window title + [x] Supports format specifiers + + +--title-font +--unloadable + + [x] lists unloadable images + + +--verbose + + [ ] outputs progress bars etc. + + +--version + + [x] correct output + + +--zoom + + [ ] percent + [ ] max + [ ] fill + + +--zoom-button + +# Keys + +action_0 +action_1 +action_2 +action_3 +action_4 +action_5 +action_6 +action_7 +action_8 +action_9 +[x] close +delete +[x] jump_back +[x] jump_first +[x] jump_fwd +[x] jump_last +jump_random +menu_child +menu_close +menu_down +menu_parent +menu_select +menu_up +[x] next_img +orient_1 +orient_3 +[x] prev_img +[x] quit +reload_image +reload_minus +reload_plus +[x] remove +render +save_filelist +save_image +[x] scroll_down +[x] scroll_left +[x] scroll_right +[x] scroll_up +[x] size_to_image +[x] toggle_actions +[x] toggle_caption +[x] toggle_filenames +toggle_fullscreen +toggle_menu +[x] toggle_pause +toggle_pointer +zoom_default +zoom_fit +[x] zoom_in +[x] zoom_out diff --git a/test/tiny.pbm b/test/tiny.pbm new file mode 100644 index 0000000..3fb3e4e --- /dev/null +++ b/test/tiny.pbm @@ -0,0 +1,4 @@ +P4 +1 1 + + |