pax_global_header00006660000000000000000000000064147477745410014536gustar00rootroot0000000000000052 comment=acc03d640bacf8ec0f5f7692fca29885ae47624f nsxiv-33/000077500000000000000000000000001474777454100124745ustar00rootroot00000000000000nsxiv-33/.editorconfig000066400000000000000000000010531474777454100151500ustar00rootroot00000000000000# EditorConfig # See this if your editor doesn't have built-in editorconfig support: # https://editorconfig.org/#download # apply to all .c and .h files [*.{c,h}] # top-most EditorConfig file root = true # Set default charset charset = utf-8 # Indentation # indent with tabs indent_style = tab # same tab size as kernel style indent_size = 8 # no trailing spaces trim_trailing_whitespace = true # line lenght, same as kernel stipulated max_line_length = 100 # all files have a final line insert_final_newline = true # end of line end_of_line = lf nsxiv-33/.github/000077500000000000000000000000001474777454100140345ustar00rootroot00000000000000nsxiv-33/.github/workflows/000077500000000000000000000000001474777454100160715ustar00rootroot00000000000000nsxiv-33/.github/workflows/build.yml000066400000000000000000000043031474777454100177130ustar00rootroot00000000000000name: Build on: push: branches: [ master ] # NOTE: "stable" tcc is too old and fails at linking. instead fetching a recent known working commit. jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: dep run: | sudo apt-get update sudo apt-get install libimlib2 libimlib2-dev xserver-xorg-core xserver-xorg-dev \ libxft2 libxft-dev libexif12 libexif-dev \ gcc clang git bc TCC_SHA="29ae3ed4d5b83eec43598d6cd7949bccb41c8083" wget "https://github.com/TinyCC/tinycc/archive/${TCC_SHA}.tar.gz" && tar xzf "${TCC_SHA}.tar.gz" ( cd "tinycc-$TCC_SHA" && ./configure && make -j"$(nproc)" && sudo make install; ) - name: build run: | CFLAGS="$(sed '/^#/d' etc/woodpecker/CFLAGS | paste -d ' ' -s)" build () { for cc in "gcc" "clang"; do echo "### $cc - $2 build ###" make clean && make -s -j"$(nproc)" CC="$cc" CFLAGS="$CFLAGS" LDFLAGS="$CFLAGS" OPT_DEP_DEFAULT="$1" done } # try all combination with tcc # etc/woodpecker/try-all-builds.sh # full-build with gcc and clang # build "1" "full" # ensure minimal-build works without opt deps installed sudo apt-get remove libxft2 libxft-dev libexif12 libexif-dev >/dev/null build "0" "minimal" macOS-build: runs-on: macOS-latest steps: - uses: actions/checkout@v2 - name: dep run: | brew update # see: https://github.com/actions/setup-python/issues/577 brew install imlib2 libx11 libxft libexif || true - name: build run: | # libinotify-kqueue isn't available on homebrew make clean && make -s OPT_DEP_DEFAULT=1 HAVE_INOTIFY=0 \ CPPFLAGS="-I/opt/homebrew/include -I/opt/homebrew/include/freetype2" \ LDLIBS="-L/opt/homebrew/lib" # force uninstallation with --ignore-dependencies brew uninstall --ignore-dependencies libxft libexif make clean && make -s OPT_DEP_DEFAULT=0 \ CPPFLAGS="-I/opt/homebrew/include" \ LDLIBS="-L/opt/homebrew/lib" nsxiv-33/.github/workflows/lock.yml000066400000000000000000000012551474777454100175470ustar00rootroot00000000000000name: 'Repo Lockdown' on: pull_request_target: types: opened permissions: pull-requests: write jobs: action: runs-on: ubuntu-latest steps: - uses: dessant/repo-lockdown@v2 with: pr-comment: > Hi, thanks for the Pull-Request. However this repository is a read-only mirror, main development of nsxiv happens over at [Codeberg](https://codeberg.org/nsxiv/nsxiv). Please open your Pull-Request over on the Codeberg Repo. Otherwise you may also e-mail the patch (obtained via `git format-patch`) to any of the [active maintainers](https://nsxiv.codeberg.page/man/#CURRENT%20MAINTAINERS) instead. nsxiv-33/.gitignore000066400000000000000000000000571474777454100144660ustar00rootroot00000000000000config.h version.h *.d *.o nsxiv icon/img2data nsxiv-33/LICENSE000066400000000000000000000432541474777454100135110ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. nsxiv-33/Makefile000066400000000000000000000050471474777454100141420ustar00rootroot00000000000000.POSIX: include config.mk inc_fonts_0 = inc_fonts_1 = -I/usr/include/freetype2 -I$(PREFIX)/include/freetype2 lib_fonts_0 = lib_fonts_1 = -lXft -lfontconfig lib_exif_0 = lib_exif_1 = -lexif nsxiv_cppflags = -D_XOPEN_SOURCE=700 \ -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) -DHAVE_LIBFONTS=$(HAVE_LIBFONTS) \ -DHAVE_INOTIFY=$(HAVE_INOTIFY) $(inc_fonts_$(HAVE_LIBFONTS)) \ $(CPPFLAGS) nsxiv_ldlibs = -lImlib2 -lX11 \ $(lib_exif_$(HAVE_LIBEXIF)) $(lib_fonts_$(HAVE_LIBFONTS)) \ $(LDLIBS) objs = autoreload.o commands.o image.o main.o options.o \ thumbs.o util.o window.o .SUFFIXES: .SUFFIXES: .c .o all: nsxiv nsxiv: $(objs) @echo "LINK $@" $(CC) $(LDFLAGS) -o $@ $(objs) $(nsxiv_ldlibs) .c.o: @echo "CC $@" $(CC) $(CFLAGS) $(nsxiv_cppflags) -c -o $@ $< $(objs): Makefile config.mk nsxiv.h config.h commands.h options.o: version.h optparse.h window.o: icon/data.h utf8.h config.h: @echo "GEN $@" cp config.def.h $@ version.h: config.mk .git/index @echo "GEN $@" v="$$(git describe 2>/dev/null || true)"; \ echo "#define VERSION \"$${v:-$(VERSION)}\"" >$@ .git/index: dump_cppflags: @echo $(nsxiv_cppflags) clean: rm -f *.o nsxiv version.h install-all: install install-desktop install-icon install-desktop: @echo "INSTALL nsxiv.desktop" mkdir -p $(DESTDIR)$(PREFIX)/share/applications cp etc/nsxiv.desktop $(DESTDIR)$(PREFIX)/share/applications install-icon: @echo "INSTALL icon" for f in $(ICONS); do \ dir="$(DESTDIR)$(PREFIX)/share/icons/hicolor/$${f%.png}/apps"; \ mkdir -p "$$dir"; \ cp "icon/$$f" "$$dir/nsxiv.png"; \ chmod 644 "$$dir/nsxiv.png"; \ done uninstall-icon: @echo "REMOVE icon" for f in $(ICONS); do \ dir="$(DESTDIR)$(PREFIX)/share/icons/hicolor/$${f%.png}/apps"; \ rm -f "$$dir/nsxiv.png"; \ done install: all @echo "INSTALL bin/nsxiv" mkdir -p $(DESTDIR)$(PREFIX)/bin cp nsxiv $(DESTDIR)$(PREFIX)/bin/ chmod 755 $(DESTDIR)$(PREFIX)/bin/nsxiv @echo "INSTALL nsxiv.1" mkdir -p $(DESTDIR)$(MANPREFIX)/man1 sed "s!EGPREFIX!$(EGPREFIX)!g; s!PREFIX!$(PREFIX)!g; s!VERSION!$(VERSION)!g" \ etc/nsxiv.1 >$(DESTDIR)$(MANPREFIX)/man1/nsxiv.1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/nsxiv.1 @echo "INSTALL share/nsxiv/" mkdir -p $(DESTDIR)$(EGPREFIX) cp etc/examples/* $(DESTDIR)$(EGPREFIX) chmod 755 $(DESTDIR)$(EGPREFIX)/* uninstall: uninstall-icon @echo "REMOVE bin/nsxiv" rm -f $(DESTDIR)$(PREFIX)/bin/nsxiv @echo "REMOVE nsxiv.1" rm -f $(DESTDIR)$(MANPREFIX)/man1/nsxiv.1 @echo "REMOVE nsxiv.desktop" rm -f $(DESTDIR)$(PREFIX)/share/applications/nsxiv.desktop @echo "REMOVE share/nsxiv/" rm -rf $(DESTDIR)$(EGPREFIX) nsxiv-33/README.md000066400000000000000000000177121474777454100137630ustar00rootroot00000000000000[![nsxiv](https://codeberg.org/nsxiv/pages/raw/branch/master/img/logo.png)](https://codeberg.org/nsxiv/nsxiv) [![Codeberg](https://img.shields.io/badge/Hosted_at-Codeberg-%232185D0?style=flat-square&logo=CodeBerg)](https://codeberg.org/nsxiv/nsxiv) [![tags](https://img.shields.io/github/v/tag/nsxiv/nsxiv?style=flat-square)](https://codeberg.org/nsxiv/nsxiv/tags) [![license](https://img.shields.io/badge/license-GPL--2.0%2B-lightgreen?style=flat-square)](https://codeberg.org/nsxiv/nsxiv/src/branch/master/LICENSE) [![loc](https://img.shields.io/tokei/lines/github/nsxiv/nsxiv?color=red&style=flat-square)](https://codeberg.org/nsxiv/nsxiv) **Neo (or New or Not) Simple (or Small or Suckless) X Image Viewer** -------------------------------------------------------------------- nsxiv is a fork of the now-unmaintained [sxiv](https://github.com/xyb3rt/sxiv) with the purpose of being a (mostly) drop-in replacement for sxiv, maintaining its interface and adding simple, sensible features. nsxiv is free software licensed under GPL-2.0-or-later and aims to be easy to modify and customize. Please file a bug report if something does not work as documented or expected on [Codeberg] after making sure you are using the latest release. If you're looking to migrate from `sxiv`, see [MIGRATION]. Contributions are welcome, see [CONTRIBUTING] to get started. [Codeberg]: https://codeberg.org/nsxiv/nsxiv/issues/new [CONTRIBUTING]: etc/CONTRIBUTING.md#contribution-guideline [MIGRATION]: etc/MIGRATION.md Features -------- * Basic image operations like zooming, panning, rotating * Basic support for animated/multi-frame images (**requires Imlib2 v1.8.0 or above**) * Thumbnail mode: grid of selectable previews of all images * Ability to cache thumbnails for fast re-loading * Automatically refreshing modified images * Customizable keyboard and mouse mappings via `config.h` * Scriptability via `key-handler` * Displaying image information in status bar via `image-info` & `thumb-info` * Customizable window title via `win-title` Screenshots ----------- **Image mode with default colors:** ![Image](https://codeberg.org/nsxiv/pages/raw/branch/master/img/image.png "Image mode") **Thumbnail mode with custom colors:** ![Thumb](https://codeberg.org/nsxiv/pages/raw/branch/master/img/thumb.png "Thumb mode") Installing via package manager ------------------------------ nsxiv is available on the following distributions/repositories. If you don't see your distro listed here, either contact your distro's package maintainer or consider packaging it yourself and adding it to the respective community repo. Dependencies ------------ nsxiv requires the following software to be installed: * X11 * Imlib2 (built with X11 support) The following dependencies are optional: * `inotify`\*: Used for auto-reloading images on change. Disabled via `HAVE_INOTIFY=0`. * `libXft`, `freetype2`, `fontconfig`: Used for the status bar. Disabled via `HAVE_LIBFONTS=0`. * `libexif`: Used for auto-orientation and exif thumbnails. Disable via `HAVE_LIBEXIF=0`. Please make sure to install the corresponding development packages in case that you want to build nsxiv on a distribution with separate runtime and development packages (e.g. \*-dev on Debian). \* [inotify][] is a Linux-specific API for monitoring filesystem changes. It's not natively available on `*BSD` systems but can be enabled via installing and linking against [libinotify-kqueue][]. [inotify]: https://www.man7.org/linux/man-pages/man7/inotify.7.html [libinotify-kqueue]: https://github.com/libinotify-kqueue/libinotify-kqueue Building -------- nsxiv is built using the commands: $ make You can pass `HAVE_X=0` to `make` to disable an optional dependency. For example: $ make HAVE_LIBEXIF=0 will disable `libexif` support. Alternatively they can be disabled via editing `config.mk`. `OPT_DEP_DEFAULT=0` can be used to disable all optional dependencies. Installing nsxiv: # make install Installing desktop entry: # make install-desktop Installing icons: # make install-icon Installing all of the above: # make install-all Please note, that these requires root privileges. By default, nsxiv is installed using the prefix `/usr/local`, so the full path of the executable will be `/usr/local/bin/nsxiv`, the `.desktop` entry will be `/usr/local/share/applications/nsxiv.desktop` and the icon path will be `/usr/local/share/icons/hicolor/{size}/apps/nsxiv.png`. You can install nsxiv into a directory of your choice by changing this command to: $ make PREFIX="/your/dir" install Example scripts are installed using `EGPREFIX` which defaults to `/usr/local/share/doc/nsxiv/examples`. You can change `EGPREFIX` the same way you can change `PREFIX` shown above. The build-time specific settings of nsxiv can be found in the file *config.h*. Please check and change them, so that they fit your needs. If the file *config.h* does not already exist, then you have to create it with the following command: $ make config.h Usage ----- Refer to the man-page for the documentation: $ man nsxiv You may also view the man-page [online](https://nsxiv.codeberg.page/man/). However, note that the online man-page might not accurately represent your local copy. F.A.Q. ------ * Can I open remote urls with nsxiv?
Yes, see [nsxiv-url](https://codeberg.org/nsxiv/nsxiv-extra/src/branch/master/scripts/nsxiv-url) * Can I open all the images in a directory?
Yes, see [nsxiv-rifle](https://codeberg.org/nsxiv/nsxiv-extra/src/branch/master/scripts/nsxiv-rifle) * Can I set default arguments for nsxiv?
Yes, see [nsxiv-env](https://codeberg.org/nsxiv/nsxiv-extra/src/branch/master/scripts/nsxiv-env) * Can I pipe images into nsxiv?
Yes, see [nsxiv-pipe](https://codeberg.org/nsxiv/nsxiv-extra/src/branch/master/scripts/nsxiv-pipe) You may also wish to see the [known issues](https://codeberg.org/nsxiv/nsxiv/issues/242). Customization ------------- The main method of customizing nsxiv is by setting values for the variables in *config.h*, or by using Xresources as explained in the manual. If these options are not sufficient, you may implement your own features by following [this guide](https://codeberg.org/nsxiv/nsxiv-extra/src/branch/master/CUSTOMIZATION.md). Due to our limited [project scope](etc/CONTRIBUTING.md#project-scope), certain features or customization cannot be merged into nsxiv mainline. Following the spirit of suckless software, we host the [nsxiv-extra](https://codeberg.org/nsxiv/nsxiv-extra) repo where users are free to submit whatever patches or scripts they wish. If you think your custom features can be beneficial for the general user base and is within our project scope, please submit it as a pull request on this repository, then we *may* merge it to mainline. Description on how to use or submit patches can be found on nsxiv-extra's [README](https://codeberg.org/nsxiv/nsxiv-extra). Download -------- You can [browse](https://codeberg.org/nsxiv/nsxiv) the source code repository on Codeberg or get a copy using git with the following command: $ git clone https://codeberg.org/nsxiv/nsxiv.git You can view the changelog [here](etc/CHANGELOG.md) Similar projects ---------------- If nsxiv isn't able to fit your needs, check out the image viewer section of **[suckless rocks](https://suckless.org/rocks)** to find other minimal image viewers to try out. Below are a couple other lesser known projects not listed in suckless rocks. * [MAGE](https://codeberg.org/explosion-mental/mage): A smaller/more-suckless version of sxiv. * [div](https://codeberg.org/TAAPArthur/div): Minimal and extensive, aimed at C devs willing to build their own features. * [mpv-image-viewer](https://github.com/occivink/mpv-image-viewer): Lua script to turn mpv into an image viewer. Supports thumbnails via [mpv-gallery-view](https://github.com/occivink/mpv-gallery-view). nsxiv-33/autoreload.c000066400000000000000000000065401474777454100150040ustar00rootroot00000000000000/* Copyright 2017 Max Voit, Bert Muennich * Copyright 2022-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #include "nsxiv.h" #if HAVE_INOTIFY #include #include #include #include #include #include static struct { char *buf; size_t len; } scratch; void arl_init(arl_t *arl) { arl->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); arl->wd_dir = arl->wd_file = -1; if (arl->fd == -1) error(0, 0, "Could not initialize inotify, no automatic image reloading"); } CLEANUP void arl_cleanup(arl_t *arl) { if (arl->fd != -1) close(arl->fd); free(scratch.buf); } static void rm_watch(int fd, int *wd) { if (*wd != -1) { inotify_rm_watch(fd, *wd); *wd = -1; } } static void add_watch(int fd, int *wd, const char *path, uint32_t mask) { *wd = inotify_add_watch(fd, path, mask); if (*wd == -1) error(0, errno, "inotify: %s", path); } static char *arl_scratch_push(const char *filepath, size_t len) { if (scratch.len < len + 1) { scratch.len = len + 1; scratch.buf = erealloc(scratch.buf, scratch.len); } scratch.buf[len] = '\0'; return memcpy(scratch.buf, filepath, len); } void arl_add(arl_t *arl, const char *filepath) { char *base, *dir; if (arl->fd == -1) return; rm_watch(arl->fd, &arl->wd_dir); rm_watch(arl->fd, &arl->wd_file); add_watch(arl->fd, &arl->wd_file, filepath, IN_CLOSE_WRITE | IN_DELETE_SELF); base = strrchr(filepath, '/'); assert(base != NULL && "filepath must be result of realpath(3)"); dir = arl_scratch_push(filepath, MAX(base - filepath, 1)); add_watch(arl->fd, &arl->wd_dir, dir, IN_CREATE | IN_MOVED_TO); arl->filename = arl_scratch_push(base + 1, strlen(base + 1)); } bool arl_handle(arl_t *arl) { bool reload = false; char *ptr; const struct inotify_event *e; /* inotify_event aligned buffer */ static union { char d[4096]; struct inotify_event e; } buf; while (true) { ssize_t len = read(arl->fd, buf.d, sizeof(buf.d)); if (len == -1) { if (errno == EINTR) continue; break; } for (ptr = buf.d; ptr < buf.d + len; ptr += sizeof(*e) + e->len) { e = (const struct inotify_event *)ptr; if (e->wd == arl->wd_file && (e->mask & IN_CLOSE_WRITE)) { reload = true; } else if (e->wd == arl->wd_file && (e->mask & IN_DELETE_SELF)) { rm_watch(arl->fd, &arl->wd_file); } else if (e->wd == arl->wd_dir && (e->mask & (IN_CREATE | IN_MOVED_TO))) { if (STREQ(e->name, arl->filename)) reload = true; } } } return reload; } #else void arl_init(arl_t *arl) { arl->fd = -1; } void arl_cleanup(arl_t *arl) { (void)arl; } void arl_add(arl_t *arl, const char *filepath) { (void)arl; (void)filepath; } bool arl_handle(arl_t *arl) { (void)arl; return false; } #endif /* HAVE_INOTIFY */ nsxiv-33/commands.c000066400000000000000000000220101474777454100144340ustar00rootroot00000000000000/* Copyright 2011-2020 Bert Muennich * Copyright 2021-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #include "nsxiv.h" #include "commands.h" #include #include #include #include #include extern img_t img; extern tns_t tns; extern win_t win; static bool navigate_to(arg_t n) { if (n >= 0 && n < filecnt && n != fileidx) { if (mode == MODE_IMAGE) { load_image(n); } else if (mode == MODE_THUMB) { fileidx = n; tns.dirty = true; } return true; } return false; } bool cg_quit(arg_t status) { unsigned int i; if (options->to_stdout && markcnt > 0) { for (i = 0; i < (unsigned int)filecnt; i++) { if (files[i].flags & FF_MARK) printf("%s%c", files[i].name, options->using_null ? '\0' : '\n'); } } exit(status); return None; /* silence tcc warning */ } bool cg_pick_quit(arg_t status) { if (options->to_stdout && markcnt == 0) printf("%s%c", files[fileidx].name, options->using_null ? '\0' : '\n'); return cg_quit(status); } bool cg_switch_mode(arg_t _) { if (mode == MODE_IMAGE) { if (tns.thumbs == NULL) tns_init(&tns, files, &filecnt, &fileidx, &win); img_close(&img, false); reset_timeout(reset_cursor); if (img.ss.on) { img.ss.on = false; reset_timeout(slideshow); } tns.dirty = true; mode = MODE_THUMB; } else { load_image(fileidx); mode = MODE_IMAGE; } return true; } bool cg_toggle_fullscreen(arg_t _) { win_toggle_fullscreen(&win); /* redraw after next ConfigureNotify event */ set_timeout(redraw, TO_REDRAW_RESIZE, false); if (mode == MODE_IMAGE) img.checkpan = img.dirty = true; else tns.dirty = true; return false; } bool cg_toggle_bar(arg_t _) { win_toggle_bar(&win); if (mode == MODE_IMAGE) img.checkpan = img.dirty = true; else tns.dirty = true; if (win.bar.h > 0) open_info(); else close_info(); return true; } bool cg_prefix_external(arg_t _) { handle_key_handler(true); return false; } bool cg_reload_image(arg_t _) { if (mode == MODE_IMAGE) { load_image(fileidx); } else { win_set_cursor(&win, CURSOR_WATCH); if (!tns_load(&tns, fileidx, true, false)) { remove_file(fileidx, false); tns.dirty = true; } } return true; } bool cg_remove_image(arg_t _) { remove_file(fileidx, true); if (mode == MODE_IMAGE) load_image(fileidx); else tns.dirty = true; return true; } bool cg_first(arg_t _) { return navigate_to(0); } bool cg_n_or_last(arg_t _) { int n = prefix != 0 && prefix - 1 < filecnt ? prefix - 1 : filecnt - 1; return navigate_to(n); } bool cg_scroll_screen(arg_t dir) { if (mode == MODE_IMAGE) return img_pan(&img, dir, -1); else return tns_scroll(&tns, dir, true); } bool cg_zoom(arg_t d) { if (mode == MODE_THUMB) return tns_zoom(&tns, d); else return img_zoom(&img, d); } bool cg_toggle_image_mark(arg_t _) { return mark_image(fileidx, !(files[fileidx].flags & FF_MARK)); } bool cg_reverse_marks(arg_t _) { int i; for (i = 0; i < filecnt; i++) { files[i].flags ^= FF_MARK; markcnt += files[i].flags & FF_MARK ? 1 : -1; } if (mode == MODE_THUMB) tns.dirty = true; return true; } bool cg_mark_range(arg_t _) { int d = markidx < fileidx ? 1 : -1, end, i; bool dirty = false, on = !!(files[markidx].flags & FF_MARK); for (i = markidx + d, end = fileidx + d; i != end; i += d) dirty |= mark_image(i, on); return dirty; } bool cg_unmark_all(arg_t _) { int i; for (i = 0; i < filecnt; i++) files[i].flags &= ~FF_MARK; markcnt = 0; if (mode == MODE_THUMB) tns.dirty = true; return true; } bool cg_navigate_marked(arg_t n) { int d, i; int new = fileidx; if (prefix > 0) n *= prefix; d = n > 0 ? 1 : -1; for (i = fileidx + d; n != 0 && i >= 0 && i < filecnt; i += d) { if (files[i].flags & FF_MARK) { n -= d; new = i; } } return navigate_to(new); } static bool change_color_modifier(arg_t d, int *target) { if (!img_change_color_modifier(&img, d * (prefix > 0 ? prefix : 1), target)) return false; if (mode == MODE_THUMB) tns.dirty = true; return true; } bool cg_change_gamma(arg_t d) { return change_color_modifier(d, &img.gamma); } bool cg_change_brightness(arg_t d) { return change_color_modifier(d, &img.brightness); } bool cg_change_contrast(arg_t d) { return change_color_modifier(d, &img.contrast); } bool ci_navigate(arg_t n) { if (prefix > 0) n *= prefix; n += fileidx; n = MAX(0, MIN(n, filecnt - 1)); if (n != fileidx) { load_image(n); return true; } else { return false; } } bool ci_cursor_navigate(arg_t _) { return ci_navigate(nav_button() - 1); } bool ci_alternate(arg_t _) { load_image(alternate); return true; } bool ci_navigate_frame(arg_t d) { if (prefix > 0) d *= prefix; return !img.multi.animate && img_frame_navigate(&img, d); } bool ci_toggle_animation(arg_t _) { bool dirty = false; if (img.multi.cnt > 0) { img.multi.animate = !img.multi.animate; if (img.multi.animate) { dirty = img_frame_animate(&img); set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); } else { reset_timeout(animate); } } return dirty; } bool ci_scroll(arg_t dir) { return img_pan(&img, dir, prefix); } bool ci_scroll_to_center(arg_t _) { return img_pan_center(&img); } bool ci_scroll_to_edge(arg_t dir) { return img_pan_edge(&img, dir); } bool ci_drag(arg_t drag_mode) { int x, y, ox, oy; float px, py; XEvent e; if ((int)(img.w * img.zoom) <= (int)win.w && (int)(img.h * img.zoom) <= (int)win.h) return false; win_set_cursor(&win, drag_mode == DRAG_ABSOLUTE ? CURSOR_DRAG_ABSOLUTE : CURSOR_DRAG_RELATIVE); win_cursor_pos(&win, &x, &y); ox = x; oy = y; while (true) { if (drag_mode == DRAG_ABSOLUTE) { px = MIN(MAX(0.0, x - win.w * 0.1), win.w * 0.8) / (win.w * 0.8) * (win.w - img.w * img.zoom); py = MIN(MAX(0.0, y - win.h * 0.1), win.h * 0.8) / (win.h * 0.8) * (win.h - img.h * img.zoom); } else { px = img.x + x - ox; py = img.y + y - oy; } if (img_pos(&img, px, py)) { img_render(&img); win_draw(&win); } XMaskEvent(win.env.dpy, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); if (e.type == ButtonPress || e.type == ButtonRelease) break; while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)) ; ox = x; oy = y; x = e.xmotion.x; y = e.xmotion.y; } set_timeout(reset_cursor, TO_CURSOR_HIDE, true); reset_cursor(); return true; } bool ci_set_zoom(arg_t zl) { return img_zoom_to(&img, (prefix ? prefix : zl) / 100.0); } bool ci_fit_to_win(arg_t sm) { return img_fit_win(&img, sm); } bool ci_rotate(arg_t degree) { img_rotate(&img, degree); return true; } bool ci_flip(arg_t dir) { img_flip(&img, dir); return true; } bool ci_toggle_antialias(arg_t _) { img_toggle_antialias(&img); return true; } bool ci_toggle_alpha(arg_t _) { img.alpha_layer = !img.alpha_layer; img.dirty = true; return true; } bool ci_slideshow(arg_t _) { if (prefix > 0) { img.ss.on = true; img.ss.delay = prefix * 10; set_timeout(slideshow, img.ss.delay * 100, true); } else if (img.ss.on) { img.ss.on = false; reset_timeout(slideshow); } else { img.ss.on = true; } return true; } bool ct_move_sel(arg_t dir) { return tns_move_selection(&tns, dir, prefix); } bool ct_reload_all(arg_t _) { tns_free(&tns); tns_init(&tns, files, &filecnt, &fileidx, &win); tns.dirty = true; return true; } bool ct_scroll(arg_t dir) { return tns_scroll(&tns, dir, false); } bool ct_drag_mark_image(arg_t _) { int sel; if ((sel = tns_translate(&tns, xbutton_ev->x, xbutton_ev->y)) >= 0) { XEvent e; bool on = !(files[sel].flags & FF_MARK); while (true) { if (sel >= 0 && mark_image(sel, on)) redraw(); XMaskEvent(win.env.dpy, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); if (e.type == ButtonPress || e.type == ButtonRelease) break; while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)) ; sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y); } } return false; } bool ct_select(arg_t _) { int sel; bool dirty = false; static Time firstclick; if ((sel = tns_translate(&tns, xbutton_ev->x, xbutton_ev->y)) >= 0) { if (sel != fileidx) { tns_highlight(&tns, fileidx, false); tns_highlight(&tns, sel, true); fileidx = sel; firstclick = xbutton_ev->time; dirty = true; } else if (xbutton_ev->time - firstclick <= TO_DOUBLE_CLICK) { mode = MODE_IMAGE; set_timeout(reset_cursor, TO_CURSOR_HIDE, true); load_image(fileidx); dirty = true; } else { firstclick = xbutton_ev->time; } } return dirty; } nsxiv-33/commands.h000066400000000000000000000066631474777454100144610ustar00rootroot00000000000000#ifndef COMMANDS_H #define COMMANDS_H #include /* global */ bool cg_change_gamma(arg_t); bool cg_change_brightness(arg_t); bool cg_change_contrast(arg_t); bool cg_first(arg_t); bool cg_mark_range(arg_t); bool cg_n_or_last(arg_t); bool cg_navigate_marked(arg_t); bool cg_prefix_external(arg_t); bool cg_quit(arg_t); bool cg_pick_quit(arg_t); bool cg_reload_image(arg_t); bool cg_remove_image(arg_t); bool cg_reverse_marks(arg_t); bool cg_scroll_screen(arg_t); bool cg_switch_mode(arg_t); bool cg_toggle_bar(arg_t); bool cg_toggle_fullscreen(arg_t); bool cg_toggle_image_mark(arg_t); bool cg_unmark_all(arg_t); bool cg_zoom(arg_t); /* image mode */ bool ci_alternate(arg_t); bool ci_cursor_navigate(arg_t); bool ci_drag(arg_t); bool ci_fit_to_win(arg_t); bool ci_flip(arg_t); bool ci_navigate(arg_t); bool ci_navigate_frame(arg_t); bool ci_rotate(arg_t); bool ci_scroll(arg_t); bool ci_scroll_to_center(arg_t); bool ci_scroll_to_edge(arg_t); bool ci_set_zoom(arg_t); bool ci_slideshow(arg_t); bool ci_toggle_alpha(arg_t); bool ci_toggle_animation(arg_t); bool ci_toggle_antialias(arg_t); /* thumbnails mode */ bool ct_move_sel(arg_t); bool ct_reload_all(arg_t); bool ct_scroll(arg_t); bool ct_drag_mark_image(arg_t); bool ct_select(arg_t); #ifdef INCLUDE_MAPPINGS_CONFIG /* global */ #define g_change_gamma { cg_change_gamma, MODE_ALL } #define g_change_brightness { cg_change_brightness, MODE_ALL } #define g_change_contrast { cg_change_contrast, MODE_ALL } #define g_first { cg_first, MODE_ALL } #define g_mark_range { cg_mark_range, MODE_ALL } #define g_n_or_last { cg_n_or_last, MODE_ALL } #define g_navigate_marked { cg_navigate_marked, MODE_ALL } #define g_prefix_external { cg_prefix_external, MODE_ALL } #define g_quit { cg_quit, MODE_ALL } #define g_pick_quit { cg_pick_quit, MODE_ALL } #define g_reload_image { cg_reload_image, MODE_ALL } #define g_remove_image { cg_remove_image, MODE_ALL } #define g_reverse_marks { cg_reverse_marks, MODE_ALL } #define g_scroll_screen { cg_scroll_screen, MODE_ALL } #define g_switch_mode { cg_switch_mode, MODE_ALL } #define g_toggle_bar { cg_toggle_bar, MODE_ALL } #define g_toggle_fullscreen { cg_toggle_fullscreen, MODE_ALL } #define g_toggle_image_mark { cg_toggle_image_mark, MODE_ALL } #define g_unmark_all { cg_unmark_all, MODE_ALL } #define g_zoom { cg_zoom, MODE_ALL } /* image mode */ #define i_alternate { ci_alternate, MODE_IMAGE } #define i_cursor_navigate { ci_cursor_navigate, MODE_IMAGE } #define i_drag { ci_drag, MODE_IMAGE } #define i_fit_to_win { ci_fit_to_win, MODE_IMAGE } #define i_flip { ci_flip, MODE_IMAGE } #define i_navigate { ci_navigate, MODE_IMAGE } #define i_navigate_frame { ci_navigate_frame, MODE_IMAGE } #define i_rotate { ci_rotate, MODE_IMAGE } #define i_scroll { ci_scroll, MODE_IMAGE } #define i_scroll_to_center { ci_scroll_to_center, MODE_IMAGE } #define i_scroll_to_edge { ci_scroll_to_edge, MODE_IMAGE } #define i_set_zoom { ci_set_zoom, MODE_IMAGE } #define i_slideshow { ci_slideshow, MODE_IMAGE } #define i_toggle_alpha { ci_toggle_alpha, MODE_IMAGE } #define i_toggle_animation { ci_toggle_animation, MODE_IMAGE } #define i_toggle_antialias { ci_toggle_antialias, MODE_IMAGE } /* thumbnails mode */ #define t_move_sel { ct_move_sel, MODE_THUMB } #define t_reload_all { ct_reload_all, MODE_THUMB } #define t_scroll { ct_scroll, MODE_THUMB } #define t_drag_mark_image { ct_drag_mark_image, MODE_THUMB } #define t_select { ct_select, MODE_THUMB } #endif /* _MAPPINGS_CONFIG */ #endif /* COMMANDS_H */ nsxiv-33/config.def.h000066400000000000000000000253041474777454100146530ustar00rootroot00000000000000#ifdef INCLUDE_WINDOW_CONFIG /* default window dimensions (overwritten via -g option): */ static const int WIN_WIDTH = 800; static const int WIN_HEIGHT = 600; /* colors and font can be overwritten via X resource properties. * See nsxiv(1), X(7) section Resources and xrdb(1) for more information. * X resource value (NULL == default) */ static const char *WIN_BG[] = { "Nsxiv.window.background", "white" }; static const char *WIN_FG[] = { "Nsxiv.window.foreground", "black" }; static const char *MARK_FG[] = { "Nsxiv.mark.foreground", NULL }; #if HAVE_LIBFONTS static const char *BAR_BG[] = { "Nsxiv.bar.background", NULL }; static const char *BAR_FG[] = { "Nsxiv.bar.foreground", NULL }; static const char *BAR_FONT[] = { "Nsxiv.bar.font", "monospace-8" }; /* if true, statusbar appears on top of the window */ static const bool TOP_STATUSBAR = false; #endif /* HAVE_LIBFONTS */ #endif #ifdef INCLUDE_IMAGE_CONFIG /* levels (in percent) to use when zooming via '-' and '+': * (first/last value is used as min/max zoom level) */ static const float zoom_levels[] = { 12.5, 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 400.0, 800.0 }; /* default slideshow delay (in sec, overwritten via -S option): */ static const int SLIDESHOW_DELAY = 5; /* color correction: the user-visible ranges [-CC_STEPS, 0] and * (0, CC_STEPS] are mapped to the ranges [0, 1], and (1, *_MAX]. * Higher step count will have higher granulairy. */ static const int CC_STEPS = 32; static const double GAMMA_MAX = 10.0; static const double BRIGHTNESS_MAX = 2.0; static const double CONTRAST_MAX = 4.0; /* command i_scroll pans image 1/PAN_FRACTION of screen width/height */ static const int PAN_FRACTION = 5; /* percentage of memory to use for imlib2's cache size. * 3 means use 3% of total memory which is about 245MiB on 8GiB machine. * 0 or less means disable cache. * 100 means use all available memory (but not above CACHE_SIZE_LIMIT). * * NOTE: higher cache size means better image reloading performance, but also * higher memory usage. */ static const int CACHE_SIZE_MEM_PERCENTAGE = 3; /* use 3% of total memory for cache */ static const int CACHE_SIZE_LIMIT = 256 * 1024 * 1024; /* but not above 256MiB */ static const int CACHE_SIZE_FALLBACK = 32 * 1024 * 1024; /* fallback to 32MiB if we can't determine total memory */ #endif #ifdef INCLUDE_OPTIONS_CONFIG /* if false, pixelate images at zoom level != 100%, * toggled with 'a' key binding (overwritten via `--anti-alias` option) */ static const bool ANTI_ALIAS = true; /* if true, use a checkerboard background for alpha layer, * toggled with 'A' key binding (overwritten via `--alpha-layer` option) */ static const bool ALPHA_LAYER = false; /* list of whitelisted/blacklisted directory for thumbnail cache * (overwritten via --cache-{allow,deny} option). * see THUMBNAIL CACHING section in nsxiv(1) manpage for more details. */ static const char TNS_FILTERS[] = ""; /* set to true to treat `TNS_FILTERS` as a blacklist instead */ static const bool TNS_FILTERS_IS_BLACKLIST = false; #endif #ifdef INCLUDE_THUMBS_CONFIG /* thumbnail sizes in pixels (width == height): */ static const int thumb_sizes[] = { 32, 64, 96, 128, 160 }; /* thumbnail size at startup, index into thumb_sizes[]: */ static const int THUMB_SIZE = 3; #endif #ifdef INCLUDE_MAPPINGS_CONFIG /* these modifiers will be used when processing keybindings */ static const unsigned int USED_MODMASK = ShiftMask | ControlMask | Mod1Mask; /* abort the keyhandler */ static const KeySym KEYHANDLER_ABORT = XK_Escape; /* keyboard mappings for image and thumbnail mode: */ static const keymap_t keys[] = { /* modifiers key function argument */ { 0, XK_q, g_quit, 0 }, { 0, XK_Q, g_pick_quit, 0 }, { 0, XK_Return, g_switch_mode, None }, { 0, XK_f, g_toggle_fullscreen, None }, { 0, XK_b, g_toggle_bar, None }, { ControlMask, XK_x, g_prefix_external, None }, { 0, XK_g, g_first, None }, { 0, XK_G, g_n_or_last, None }, { 0, XK_r, g_reload_image, None }, { 0, XK_D, g_remove_image, None }, { ControlMask, XK_h, g_scroll_screen, DIR_LEFT }, { ControlMask, XK_Left, g_scroll_screen, DIR_LEFT }, { ControlMask, XK_j, g_scroll_screen, DIR_DOWN }, { ControlMask, XK_Down, g_scroll_screen, DIR_DOWN }, { ControlMask, XK_k, g_scroll_screen, DIR_UP }, { ControlMask, XK_Up, g_scroll_screen, DIR_UP }, { ControlMask, XK_l, g_scroll_screen, DIR_RIGHT }, { ControlMask, XK_Right, g_scroll_screen, DIR_RIGHT }, { 0, XK_plus, g_zoom, +1 }, { 0, XK_KP_Add, g_zoom, +1 }, { 0, XK_minus, g_zoom, -1 }, { 0, XK_KP_Subtract, g_zoom, -1 }, { 0, XK_m, g_toggle_image_mark, None }, { 0, XK_M, g_mark_range, None }, { ControlMask, XK_m, g_reverse_marks, None }, { ControlMask, XK_u, g_unmark_all, None }, { 0, XK_N, g_navigate_marked, +1 }, { 0, XK_P, g_navigate_marked, -1 }, { 0, XK_braceleft, g_change_gamma, -1 }, { 0, XK_braceright, g_change_gamma, +1 }, { ControlMask, XK_g, g_change_gamma, 0 }, { ControlMask, XK_bracketright, g_change_brightness, +1 }, { ControlMask, XK_bracketleft, g_change_brightness, -1 }, { 0, XK_parenleft, g_change_contrast, -1 }, { 0, XK_parenright, g_change_contrast, +1 }, { 0, XK_h, t_move_sel, DIR_LEFT }, { 0, XK_Left, t_move_sel, DIR_LEFT }, { 0, XK_j, t_move_sel, DIR_DOWN }, { 0, XK_Down, t_move_sel, DIR_DOWN }, { 0, XK_k, t_move_sel, DIR_UP }, { 0, XK_Up, t_move_sel, DIR_UP }, { 0, XK_l, t_move_sel, DIR_RIGHT }, { 0, XK_Right, t_move_sel, DIR_RIGHT }, { 0, XK_R, t_reload_all, None }, { 0, XK_n, i_navigate, +1 }, { 0, XK_n, i_scroll_to_edge, DIR_LEFT | DIR_UP }, { 0, XK_space, i_navigate, +1 }, { 0, XK_p, i_navigate, -1 }, { 0, XK_p, i_scroll_to_edge, DIR_LEFT | DIR_UP }, { 0, XK_BackSpace, i_navigate, -1 }, { 0, XK_bracketright, i_navigate, +10 }, { 0, XK_bracketleft, i_navigate, -10 }, { ControlMask, XK_6, i_alternate, None }, { ControlMask, XK_n, i_navigate_frame, +1 }, { ControlMask, XK_p, i_navigate_frame, -1 }, { ControlMask, XK_space, i_toggle_animation, None }, { ControlMask, XK_a, i_toggle_animation, None }, { 0, XK_h, i_scroll, DIR_LEFT }, { 0, XK_Left, i_scroll, DIR_LEFT }, { 0, XK_j, i_scroll, DIR_DOWN }, { 0, XK_Down, i_scroll, DIR_DOWN }, { 0, XK_k, i_scroll, DIR_UP }, { 0, XK_Up, i_scroll, DIR_UP }, { 0, XK_l, i_scroll, DIR_RIGHT }, { 0, XK_Right, i_scroll, DIR_RIGHT }, { 0, XK_H, i_scroll_to_edge, DIR_LEFT }, { 0, XK_J, i_scroll_to_edge, DIR_DOWN }, { 0, XK_K, i_scroll_to_edge, DIR_UP }, { 0, XK_L, i_scroll_to_edge, DIR_RIGHT }, { 0, XK_z, i_scroll_to_center, None }, { 0, XK_equal, i_set_zoom, 100 }, { 0, XK_w, i_fit_to_win, SCALE_DOWN }, { 0, XK_W, i_fit_to_win, SCALE_FIT }, { 0, XK_F, i_fit_to_win, SCALE_FILL }, { 0, XK_e, i_fit_to_win, SCALE_WIDTH }, { 0, XK_E, i_fit_to_win, SCALE_HEIGHT }, { 0, XK_less, i_rotate, DEGREE_270 }, { 0, XK_greater, i_rotate, DEGREE_90 }, { 0, XK_question, i_rotate, DEGREE_180 }, { 0, XK_bar, i_flip, FLIP_HORIZONTAL }, { 0, XK_underscore, i_flip, FLIP_VERTICAL }, { 0, XK_a, i_toggle_antialias, None }, { 0, XK_A, i_toggle_alpha, None }, { 0, XK_s, i_slideshow, None }, }; /* mouse button mappings for image mode: */ static const button_t buttons_img[] = { /* modifiers button function argument */ { 0, 1, i_cursor_navigate, None }, { ControlMask, 1, i_drag, DRAG_RELATIVE }, { 0, 2, i_drag, DRAG_ABSOLUTE }, { 0, 3, g_switch_mode, None }, { 0, 4, g_zoom, +1 }, { 0, 5, g_zoom, -1 }, }; /* mouse button mappings for thumbnail mode: */ static const button_t buttons_tns[] = { /* modifiers button function argument */ { 0, 1, t_select, None }, { 0, 3, t_drag_mark_image, None }, { 0, 4, t_scroll, DIR_UP }, { 0, 5, t_scroll, DIR_DOWN }, { ControlMask, 4, g_scroll_screen, DIR_UP }, { ControlMask, 5, g_scroll_screen, DIR_DOWN }, }; /* true means NAV_WIDTH is relative (33%), false means absolute (33 pixels) */ static const bool NAV_IS_REL = true; /* width of navigation area, 0 disables cursor navigation, */ static const unsigned int NAV_WIDTH = 33; /* mouse cursor on left, middle and right part of the window */ static const cursor_t imgcursor[3] = { CURSOR_LEFT, CURSOR_ARROW, CURSOR_RIGHT }; #endif nsxiv-33/config.mk000066400000000000000000000021541474777454100142740ustar00rootroot00000000000000# nsxiv version VERSION = 33 # PREFIX for install PREFIX = /usr/local MANPREFIX = $(PREFIX)/share/man EGPREFIX = $(PREFIX)/share/doc/nsxiv/examples # default value for optional dependencies. 1 = enabled, 0 = disabled OPT_DEP_DEFAULT = 1 # autoreload backend: 1 = inotify, 0 = none HAVE_INOTIFY = $(OPT_DEP_DEFAULT) # optional dependencies, see README for more info HAVE_LIBFONTS = $(OPT_DEP_DEFAULT) HAVE_LIBEXIF = $(OPT_DEP_DEFAULT) # CFLAGS, any additional compiler flags goes here CFLAGS = -Wall -pedantic -O2 -DNDEBUG # Uncomment for a debug build using gcc/clang # CFLAGS = -Wall -pedantic -DDEBUG -g3 -fsanitize=address,undefined # LDFLAGS = $(CFLAGS) # icons that will be installed via `make icon` ICONS = 16x16.png 32x32.png 48x48.png 64x64.png 128x128.png # Uncomment on OpenBSD # HAVE_INOTIFY = 0 # lib_fonts_bsd_0 = # lib_fonts_bsd_1 = -lfreetype -L/usr/X11R6/lib/freetype2 # inc_fonts_bsd_0 = # inc_fonts_bsd_1 = -I/usr/X11R6/include/freetype2 # CPPFLAGS = -I/usr/X11R6/include -I/usr/local/include $(inc_fonts_bsd_$(HAVE_LIBFONTS)) # LDLIBS = -L/usr/X11R6/lib -L/usr/local/lib $(lib_fonts_bsd_$(HAVE_LIBFONTS)) nsxiv-33/etc/000077500000000000000000000000001474777454100132475ustar00rootroot00000000000000nsxiv-33/etc/.clang-format000066400000000000000000000040261474777454100156240ustar00rootroot00000000000000# clang-format doesn't dictate the project's code style and can mess up a # couple edge cases. However it comes quite close and can be used for fixing # most style issues automatically on new changes via `git-clang-format`. --- Standard: c++03 ColumnLimit: 0 AccessModifierOffset: -8 ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 IndentCaseLabels: false IndentGotoLabels: false IndentPPDirectives: None IndentWidth: 8 IndentWrappedFunctionNames: false NamespaceIndentation: None TabWidth: 8 UseTab: AlignWithSpaces AlignAfterOpenBracket: true AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: false AlignOperands: true AlignTrailingComments: false DerivePointerAlignment: true PointerAlignment: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true BreakBeforeBraces: Custom BraceWrapping: AfterControlStatement: MultiLine AfterEnum: false AfterExternBlock: false AfterFunction: true AfterStruct: false AfterUnion: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true BreakBeforeBinaryOperators: None BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: false BreakConstructorInitializers: BeforeComma BreakConstructorInitializersBeforeComma: false BreakStringLiterals: true Cpp11BracedListStyle: false MaxEmptyLinesToKeep: 1 ReflowComments: false SortIncludes: true SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false ... nsxiv-33/etc/CHANGELOG.md000066400000000000000000000516311474777454100150660ustar00rootroot00000000000000nsxiv - Changelog ----------------- **[git](https://codeberg.org/nsxiv/nsxiv.git)** Changes will only be documented on stable releases. If you're on git/master then there may be more changes. Please use `git log` to view them. - - - **[v33](https://codeberg.org/nsxiv/nsxiv/archive/v33.tar.gz)** *(January 26, 2025)* * Changes: * Moved key-handler prompts to the right side bar [#481][] * Rename `--class` to `--name` [#515][] * Print usage output to `stderr` on usage error [#510][] * Added: * Added cli flags `--cache-{allow,deny}` to white/black list directories from thumbnail caching [#461][] * Print ellipsis when text is truncated in the statusbar [#501][] * Allow disabling thumbnail mode via `--thumbnail=no` [#502][] * Allow disabling animation playback via `-A 0` [#502][] * Add `--bar` to enable statusbar [#502][] * Updated `rotate()` in key-handler example to support `nsxiv -0` [#521][] * Fixes: * Unresponsive UI when animation is too fast [#489][] * Fixed image placement when rotating [#493][] * More reliable reading of various script output [#490][] * Statusbar getting stuck at "Running key-handler..." [#481][] * Fixed autoreload when directory is root [#498][] * Fixed `-A` not working [#503][] * Selecting incorrect thumbnail when clicking outside the thumbnail area [#517][] * Fix exiv2 argument order in key-handler example [#518][] [#461]: https://codeberg.org/nsxiv/nsxiv/pulls/461 [#481]: https://codeberg.org/nsxiv/nsxiv/pulls/481 [#489]: https://codeberg.org/nsxiv/nsxiv/pulls/489 [#490]: https://codeberg.org/nsxiv/nsxiv/pulls/490 [#493]: https://codeberg.org/nsxiv/nsxiv/pulls/493 [#498]: https://codeberg.org/nsxiv/nsxiv/pulls/498 [#501]: https://codeberg.org/nsxiv/nsxiv/pulls/501 [#502]: https://codeberg.org/nsxiv/nsxiv/pulls/502 [#503]: https://codeberg.org/nsxiv/nsxiv/pulls/503 [#510]: https://codeberg.org/nsxiv/nsxiv/pulls/510 [#515]: https://codeberg.org/nsxiv/nsxiv/pulls/515 [#517]: https://codeberg.org/nsxiv/nsxiv/pulls/517 [#518]: https://codeberg.org/nsxiv/nsxiv/pulls/518 [#521]: https://codeberg.org/nsxiv/nsxiv/pulls/521 - - - **[v32](https://codeberg.org/nsxiv/nsxiv/archive/v32.tar.gz)** *(October 01, 2023)* * Changes: * Removed legacy multi-frame loaders. Animated image support now requires Imlib2 v1.8.0 or above. * Move loading/caching messages to right side bar [#446] * Set a default delay if delay is 0 in a multi-frame image [#445] * `config.mk`: default to `-O2` [#435] * `config.mk` no longer explicitly sets `CC` to `c99` [#455] * Assertions are now opt-in and requires explicitly defining `DEBUG` [#447] * Added: * Added a pick-quit key-binding [#432] * Ability to configure Xresources class name in `config.h` [#427] * `--version` output now also includes compiled-in feature list [#462] * Document handling of empty X resources values [#428] * Experimental flag `--bg-cache` to generate thumbnail cache in a background process [#438] * Fixes: * Changing brightness/contrast on multi-frame images [#440] * Brightness keybindings on manpage [#467] * Various autoreload bugs [#437], [#459], [#460] * `*-info` scripts not updating when selecting thumbnail with mouse [#477] * Updated openbsd configuration in `config.mk` [#453] * Memory leak in `win_draw_bar` [#444] * Thumbnail leak when removing the last file [#423] [#423]: https://codeberg.org/nsxiv/nsxiv/pulls/423 [#427]: https://codeberg.org/nsxiv/nsxiv/pulls/427 [#428]: https://codeberg.org/nsxiv/nsxiv/pulls/428 [#432]: https://codeberg.org/nsxiv/nsxiv/pulls/432 [#435]: https://codeberg.org/nsxiv/nsxiv/pulls/435 [#437]: https://codeberg.org/nsxiv/nsxiv/pulls/437 [#438]: https://codeberg.org/nsxiv/nsxiv/pulls/438 [#440]: https://codeberg.org/nsxiv/nsxiv/pulls/440 [#444]: https://codeberg.org/nsxiv/nsxiv/pulls/444 [#445]: https://codeberg.org/nsxiv/nsxiv/pulls/445 [#446]: https://codeberg.org/nsxiv/nsxiv/pulls/446 [#447]: https://codeberg.org/nsxiv/nsxiv/pulls/447 [#453]: https://codeberg.org/nsxiv/nsxiv/pulls/453 [#455]: https://codeberg.org/nsxiv/nsxiv/pulls/455 [#459]: https://codeberg.org/nsxiv/nsxiv/pulls/459 [#460]: https://codeberg.org/nsxiv/nsxiv/pulls/460 [#462]: https://codeberg.org/nsxiv/nsxiv/pulls/462 [#467]: https://codeberg.org/nsxiv/nsxiv/pulls/467 [#477]: https://codeberg.org/nsxiv/nsxiv/pulls/477 - - - **[v31](https://codeberg.org/nsxiv/nsxiv/archive/v31.tar.gz)** *(January 28, 2023)* * Changes: * Uncritical files moved to `etc/`. [#350] * Empty Xresource entry will now be ignored. [#340] * `win-title` will be read in a non-blocking manner. [#314] * Added: * Support for multi-frame images via `Imlib2`. [#373] * Support for long-opts. [#332] * Cli flag `--anti-alias` to enable/disable anti-aliasing. [#361] * Cli flag `--alpha-layer` to enable/disable checkerboard background. [#408] * Accept directory via stdin (-i) [#383] * Support for modifying brightness and contrast [#396] * Fixes: * Build failure when `_SC_PHYS_PAGES` is not defined. [#334] * Various statusbar issues. [#353],[#341] * Crashes due to faulty signal-handler. [#411] * Potential memory leak in `r_readdir()`. [#319] * Potentially printing incorrect error message. [#321] * Wrong slideshow length on animated webp. [#381] * Document missing Ctrl+6 binding in the manpage. [#347] [#314]: https://codeberg.org/nsxiv/nsxiv/pulls/314 [#319]: https://codeberg.org/nsxiv/nsxiv/pulls/319 [#321]: https://codeberg.org/nsxiv/nsxiv/pulls/321 [#332]: https://codeberg.org/nsxiv/nsxiv/pulls/332 [#334]: https://codeberg.org/nsxiv/nsxiv/pulls/334 [#340]: https://codeberg.org/nsxiv/nsxiv/pulls/340 [#341]: https://codeberg.org/nsxiv/nsxiv/pulls/341 [#347]: https://codeberg.org/nsxiv/nsxiv/pulls/347 [#350]: https://codeberg.org/nsxiv/nsxiv/pulls/350 [#353]: https://codeberg.org/nsxiv/nsxiv/pulls/353 [#361]: https://codeberg.org/nsxiv/nsxiv/pulls/361 [#373]: https://codeberg.org/nsxiv/nsxiv/pulls/373 [#381]: https://codeberg.org/nsxiv/nsxiv/pulls/381 [#383]: https://codeberg.org/nsxiv/nsxiv/pulls/383 [#396]: https://codeberg.org/nsxiv/nsxiv/pulls/396 [#408]: https://codeberg.org/nsxiv/nsxiv/pulls/408 [#411]: https://codeberg.org/nsxiv/nsxiv/pulls/411 - - - ***SPECIAL NOTE***: Due to [this incident](https://codeberg.org/nsxiv/nsxiv-tmp/issues/1) we have moved development over to [Codeberg](https://codeberg.org/nsxiv/nsxiv). A lot of the references *below* may now be 404 on GitHub. Any threads which survived the wipe have been migrated over to [nsxiv-record]. All of the references *above* can be found on the new main nsxiv repository on Codeberg. [nsxiv-record]: https://codeberg.org/nsxiv/nsxiv-record/ - - - **[v30](https://codeberg.org/nsxiv/nsxiv/archive/v30.tar.gz)** *(June 15, 2022)* * Changes: * Development and main repository moved over to Codeberg. See the special note above for more info. * `autoreload_{inotify,nop}.c` merged into a single file, `autoreload.c`. [#263] * Moved all configuration related macros to `config.mk`. [#264] * `win-title` is now called only when there's change rather than being called on each redraw. [#266] * Added: * Added more mimetypes to the `.desktop` entry. [#260] * Added `thumb-info` for customizing the statusbar in thumbnail-mode. [#265] * Added comments for building on OpenBSD. [#264] * Fixes: * "Too many open file" error due to not closing the win-title script. [#245] * `-f` now directly starts in fullscreen mode rather than opening a normal window and then going fullscreen. [#251] * Broken slideshow on slow systems or fast animations. [#282] * Memory leak when removing an image in thumbnail mode. [#247] * Correctly setting `_NET_WM_PID`. [#251] * Don't override statusbar if info script doesn't exist. [#271] * Potential misbehavior regarding font. [#250] [#245]: https://codeberg.org/nsxiv/nsxiv-record/pulls/245 [#247]: https://codeberg.org/nsxiv/nsxiv-record/pulls/247 [#250]: https://codeberg.org/nsxiv/nsxiv-record/pulls/250 [#251]: https://codeberg.org/nsxiv/nsxiv-record/pulls/251 [#263]: https://codeberg.org/nsxiv/nsxiv-record/pulls/263 [#264]: https://codeberg.org/nsxiv/nsxiv-record/pulls/264 [#260]: https://codeberg.org/nsxiv/nsxiv-record/pulls/260 [#264]: https://codeberg.org/nsxiv/nsxiv-record/pulls/264 [#265]: https://codeberg.org/nsxiv/nsxiv-record/pulls/265 [#266]: https://codeberg.org/nsxiv/nsxiv-record/pulls/266 [#271]: https://codeberg.org/nsxiv/nsxiv-record/pulls/271 [#282]: https://codeberg.org/nsxiv/nsxiv-record/pulls/282 - - - **[v29](https://github.com/nsxiv/nsxiv/archive/v29.tar.gz)** *(March 03, 2022)* * Changes: * Window title is now customizable via `win-title`, cli flag `-T` and related config.h options are removed. See `WINDOW TITLE` section of the manpage for more info. [#213] * Imlib2 cache size is now set based on total memory percentage, by default set to 3%. [#184] * Removed some non-POSIX extensions in the Makefile. [#225] * Added: * Ability to customize thumbnail mode mouse-bindings via `config.h`. [#167] * Option to set statusbar position to top via `config.h`. [#231] * New keybinding z to scroll to center. [#203] * Fixes: * Manpage cleanup: avoid confusing wording and document thumbnail mode mouse-bindings. [#186] * Wrong jpeg exif orientation with Imlib2 v1.7.5 (and above). [#188] * Animation slowdown when zoomed in. [#200] * Reset statusbar after failed keyhandler. [#191] * Window title not working on certain WMs. [#234] * Various compiler warnings. [#197] [#167]: https://codeberg.org/nsxiv/nsxiv-record/pulls/167 [#184]: https://codeberg.org/nsxiv/nsxiv-record/pulls/184 [#186]: https://codeberg.org/nsxiv/nsxiv-record/pulls/186 [#188]: https://codeberg.org/nsxiv/nsxiv-record/pulls/188 [#191]: https://codeberg.org/nsxiv/nsxiv-record/pulls/191 [#197]: https://codeberg.org/nsxiv/nsxiv-record/pulls/197 [#200]: https://codeberg.org/nsxiv/nsxiv-record/pulls/200 [#203]: https://codeberg.org/nsxiv/nsxiv-record/pulls/203 [#213]: https://codeberg.org/nsxiv/nsxiv-record/pulls/213 [#225]: https://codeberg.org/nsxiv/nsxiv-record/pulls/225 [#231]: https://codeberg.org/nsxiv/nsxiv-record/pulls/231 [#234]: https://codeberg.org/nsxiv/nsxiv-record/pulls/234 - - - **[v28](https://github.com/nsxiv/nsxiv/archive/v28.tar.gz)** *(December 12, 2021)* * Changes: * Statusbar made optional via `HAVE_LIBFONTS`. [#95] * Remove library auto-detection, use `OPT_DEP_DEFAULT` instead. [#71] * Example scripts will now be installed into `EGPREFIX` (`$(PREFIX)/share/doc/nsxiv/examples` by default). See README for more info. [#86] * Added: * Animated webp support (optional via `HAVE_LIBWEBP`). [#20] * New mouse-binding Ctrl-Button1 for relative drag. [#117] * Ability to configure colors and fonts in `config.h`. [#115] * Ability to configure navigation width area in `config.h`. [#155] * Ability to customize the set of modifiers used when processing keybindings in `config.h` via `USED_MODMASK`. [#150] * Ability to configure Imlib2's cache size for better image (re)loading performance in `config.h`. [#171] * Cli flag `-0` for sending null-seperated file-list to standard out (`-o`), and key-handler and receiving null-seperated file-list via stdin (`-i`). [#68],[#141],[#164] * Export environment variable `NSXIV_USING_NULL` to key-handler. [#164] * Embed new nsxiv icon. [#163] * `make install-icon` to install icons. [#80],[#96] * `make install-desktop` to install .desktop entry. [#80],[#96] * `make install-all` to install everything. [#80],[#96] * Configurable `KEYHANDLER_ABORT` in `config.h`. [#91],[#172] * Statusbar message upon key-handler activation. [#98] * Ability to write custom C functions in `config.h` and use them via keybindings. [#76] * Fixes: * Not able to use `KEYHANDLER_ABORT` key (Escape by default) in regular keybindings. [#91] * Memory leak related to Xresources. [#134] * Memory leak in gif loader. [#165] * Better handle gif colormap and prevent out-of-bound access. [#165] * Prevent crash when zooming out in very small images. [#178] * Removed non-POSIX commands and extensions from `Makefile`. [#71] * Regression where nsxiv wouldn't run on non-TrueColor X server. [#114] * Wrong comments in `config.h` and description in `manpage`. [#105],[#106],[#152] [#20]: https://codeberg.org/nsxiv/nsxiv-record/pulls/20 [#68]: https://codeberg.org/nsxiv/nsxiv-record/pulls/68 [#71]: https://codeberg.org/nsxiv/nsxiv-record/pulls/71 [#76]: https://codeberg.org/nsxiv/nsxiv-record/pulls/76 [#80]: https://codeberg.org/nsxiv/nsxiv-record/pulls/80 [#86]: https://codeberg.org/nsxiv/nsxiv-record/pulls/86 [#91]: https://codeberg.org/nsxiv/nsxiv-record/pulls/91 [#95]: https://codeberg.org/nsxiv/nsxiv-record/pulls/95 [#96]: https://codeberg.org/nsxiv/nsxiv-record/pulls/96 [#98]: https://codeberg.org/nsxiv/nsxiv-record/pulls/98 [#105]: https://codeberg.org/nsxiv/nsxiv-record/pulls/105 [#106]: https://codeberg.org/nsxiv/nsxiv-record/pulls/106 [#114]: https://codeberg.org/nsxiv/nsxiv-record/pulls/114 [#115]: https://codeberg.org/nsxiv/nsxiv-record/pulls/115 [#117]: https://codeberg.org/nsxiv/nsxiv-record/pulls/117 [#134]: https://codeberg.org/nsxiv/nsxiv-record/pulls/134 [#141]: https://codeberg.org/nsxiv/nsxiv-record/pulls/141 [#150]: https://codeberg.org/nsxiv/nsxiv-record/pulls/150 [#152]: https://codeberg.org/nsxiv/nsxiv-record/pulls/152 [#155]: https://codeberg.org/nsxiv/nsxiv-record/pulls/155 [#163]: https://codeberg.org/nsxiv/nsxiv-record/pulls/163 [#164]: https://codeberg.org/nsxiv/nsxiv-record/pulls/164 [#165]: https://codeberg.org/nsxiv/nsxiv-record/pulls/165 [#171]: https://codeberg.org/nsxiv/nsxiv-record/pulls/171 [#172]: https://codeberg.org/nsxiv/nsxiv-record/pulls/172 [#178]: https://codeberg.org/nsxiv/nsxiv-record/pulls/178 - - - **[v27.1](https://github.com/nsxiv/nsxiv/archive/v27.1.tar.gz)** *(September 16, 2021)* * Fixes: * Source tarball failing build [#66] [#66]: https://codeberg.org/nsxiv/nsxiv-record/pulls/66 - - - **[v27](https://github.com/nsxiv/nsxiv/archive/v27.tar.gz)** *(September 16, 2021)* * Changes: * Re-release under the name nsxiv. * Xresources `Sxiv.foreground` and `Sxiv.background` changed to `Nsxiv.window.foreground` and `Nsxiv.window.background`. * Xresources `Sxiv.font` changed to `Nsxiv.bar.font`. * Rework the build system. Now by default we'll build with only optional dependencies that are already installed. [#19] * Added: * Fill scale mode. [#2] * Configurable X window title (via `config.h` and the `-T` flag). [#23] * Support custom bar colors via Xresources. [#19] * Support custom mark color via Xresources. [#51] * Toggle animation playback with Ctrl-a. [#33] * Set `_NET_WM_PID` and `WM_CLIENT_MACHINE` X properties. [#13] * Set `ICCCM WM manager` hints. [#12] * Fixes: * Cli flag `-G` not initially setting gamma. [#31] * Wrong keybinding description in the manpage. [#14] * .desktop entry not advertising webp support. [#15] * Prevent crash when embedded into transparent window. [#3] * Small memory leak. [#57] * Rare crash when showing some GIFs. [#41] * Rare event where nsxiv wouldn't close after window being destroyed. [#53] [#2]: https://codeberg.org/nsxiv/nsxiv-record/pulls/2 [#3]: https://codeberg.org/nsxiv/nsxiv-record/pulls/3 [#12]: https://codeberg.org/nsxiv/nsxiv-record/pulls/12 [#13]: https://codeberg.org/nsxiv/nsxiv-record/pulls/13 [#14]: https://codeberg.org/nsxiv/nsxiv-record/pulls/14 [#15]: https://codeberg.org/nsxiv/nsxiv-record/pulls/15 [#19]: https://codeberg.org/nsxiv/nsxiv-record/pulls/19 [#23]: https://codeberg.org/nsxiv/nsxiv-record/pulls/23 [#31]: https://codeberg.org/nsxiv/nsxiv-record/pulls/31 [#33]: https://codeberg.org/nsxiv/nsxiv-record/pulls/33 [#41]: https://codeberg.org/nsxiv/nsxiv-record/pulls/41 [#51]: https://codeberg.org/nsxiv/nsxiv-record/pulls/51 [#53]: https://codeberg.org/nsxiv/nsxiv-record/pulls/53 [#57]: https://codeberg.org/nsxiv/nsxiv-record/pulls/57 sxiv ---- **Stable releases** **[v26](https://github.com/nsxiv/nsxiv/archive/v26.tar.gz)** *(January 16, 2020)* * Maintenance release **[v25](https://github.com/nsxiv/nsxiv/archive/v25.tar.gz)** *(January 26, 2019)* * Support font fallback for missing glyphs * Fix busy loop when built without inotify * Use background/foreground colors from X resource database **[v24](https://github.com/nsxiv/nsxiv/archive/v24.tar.gz)** *(October 27, 2017)* * Automatically reload the current image whenever it changes * Support embedding into other X windows with -e (e.g. tabbed) * New option -p prevents sxiv from creating cache and temporary files * Simpler mouse mappings, the most basic features are accessible with the mouse only (navigate, zoom, pan) **[v1.3.2](https://github.com/nsxiv/nsxiv/archive/v1.3.2.tar.gz)** *(December 20, 2015)* * external key handler gets file paths on stdin, not as arguments * Cache out-of-view thumbnails in the background * Apply gamma correction to thumbnails **[v1.3.1](https://github.com/nsxiv/nsxiv/archive/v1.3.1.tar.gz)** *(November 16, 2014)* * Fixed build error, caused by delayed config.h creation * Fixed segfault when run with -c **[v1.3](https://github.com/nsxiv/nsxiv/archive/v1.3.tar.gz)** *(October 24, 2014)* * Extract thumbnails from EXIF tags (requires libexif) * Zoomable thumbnails, supported sizes defined in config.h * Fixed build error with giflib version >= 5.1.0 **[v1.2](https://github.com/nsxiv/nsxiv/archive/v1.2.tar.gz)** *(April 24, 2014)* * Added external key handler, called on keys prefixed with `Ctrl-x` * New keybinding `{`/`}` to change gamma (by András Mohari) * Support for slideshows, enabled with `-S` option & toggled with `s` * Added application icon (created by 0ion9) * Checkerboard background for alpha layer * Option `-o` only prints files marked with `m` key * Fixed rotation/flipping of multi-frame images (gifs) **[v1.1.1](https://github.com/nsxiv/nsxiv/archive/v1.1.1.tar.gz)** *(June 2, 2013)* * Various bug fixes **[v1.1](https://github.com/nsxiv/nsxiv/archive/v1.1.tar.gz)** *(March 30, 2013)* * Added status bar on bottom of window with customizable content * New keyboard shortcuts `\`/`|`: flip image vertically/horizontally * New keyboard shortcut `Ctrl-6`: go to last/alternate image * Added own EXIF orientation handling, removed dependency on libexif * Fixed various bugs **[v1.0](https://github.com/nsxiv/nsxiv/archive/v1.0.tar.gz)** *(October 31, 2011)* * Support for multi-frame images & GIF animations * POSIX compliant (IEEE Std 1003.1-2001) **[v0.9](https://github.com/nsxiv/nsxiv/archive/v0.9.tar.gz)** *(August 17, 2011)* * Made key and mouse mappings fully configurable in config.h * Complete code refactoring **[v0.8.2](https://github.com/nsxiv/nsxiv/archive/v0.8.2.tar.gz)** *(June 29, 2011)* * POSIX-compliant Makefile; compiles under NetBSD **[v0.8.1](https://github.com/nsxiv/nsxiv/archive/v0.8.1.tar.gz)** *(May 8, 2011)* * Fixed fullscreen under window managers, which are not fully EWMH-compliant **[v0.8](https://github.com/nsxiv/nsxiv/archive/v0.8.tar.gz)** *(April 18, 2011)* * Support for thumbnail caching * Ability to run external commands (e.g. jpegtran, convert) on current image **[v0.7](https://github.com/nsxiv/nsxiv/archive/v0.7.tar.gz)** *(February 26, 2011)* * Sort directory entries when using `-r` command line option * Hide cursor in image mode * Full functional thumbnail mode, use Return key to switch between image and thumbnail mode **[v0.6](https://github.com/nsxiv/nsxiv/archive/v0.6.tar.gz)** *(February 16, 2011)* * Bug fix: Correctly display filenames with umlauts in window title * Basic support of thumbnails **[v0.5](https://github.com/nsxiv/nsxiv/archive/v0.5.tar.gz)** *(February 6, 2011)* * New command line option: `-r`: open all images in given directories * New key shortcuts: `w`: resize image to fit into window; `W`: resize window to fit to image **[v0.4](https://github.com/nsxiv/nsxiv/archive/v0.4.tar.gz)** *(February 1, 2011)* * New command line option: `-F`, `-g`: use fixed window dimensions and apply a given window geometry * New key shortcut: `r`: reload current image **[v0.3.1](https://github.com/nsxiv/nsxiv/archive/v0.3.1.tar.gz)** *(January 30, 2011)* * Bug fix: Do not set setuid bit on executable when using `make install` * Pan image with mouse while pressing middle mouse button **[v0.3](https://github.com/nsxiv/nsxiv/archive/v0.3.tar.gz)** *(January 29, 2011)* * New command line options: `-d`, `-f`, `-p`, `-s`, `-v`, `-w`, `-Z`, `-z` * More mouse mappings: Go to next/previous image with left/right click, scroll image with mouse wheel (horizontally if Shift key is pressed), zoom image with mouse wheel if Ctrl key is pressed **[v0.2](https://github.com/nsxiv/nsxiv/archive/v0.2.tar.gz)** *(January 23, 2011)* * Bug fix: Handle window resizes correctly * New keyboard shortcuts: `g`/`G`: go to first/last image; `[`/`]`: go 10 images back/forward * Support for mouse wheel zooming (by Dave Reisner) * Added fullscreen mode **[v0.1](https://github.com/nsxiv/nsxiv/archive/v0.1.tar.gz)** *(January 21, 2011)* * Initial release nsxiv-33/etc/CONTRIBUTING.md000066400000000000000000000115721474777454100155060ustar00rootroot00000000000000Project Scope ------------- * Bug fixes and maintenance * Prioritize extensibility and simplicity * Do not make the codebase more complex, keep it simple to hack on * Do not add extra dependency (if we do, add compile time switch to disable it) * New features may be added if it cannot be achieved (easily) via a shell script, doesn't break backwards compatibility and doesn't violate any of the above rules. Note: Since we aim to be a drop-in replacement for sxiv, we intend to keep all sxiv's behaviors/features even in cases where removing them would make the code-base simpler. Also note that (n)sxiv uses `imlib2` for loading images. Thus any request or patches for adding support for new image formats should go into [imlib2's repo](https://git.enlightenment.org/old/legacy-imlib2) instead. Contribution Guideline ---------------------- When contributing, make sure: * Your contribution falls under nsxiv's scope and aim * You follow the existing code style (see the "Code Style" section below) * You open the pull request from a new branch, not from master * To avoid using force pushes, especially for bigger patches. Only use them when there's merge conflicts. If your contribution is not suitable for general use, it will not be included in nsxiv. For changes that are very much up to preference, such as changing values in config.h, please do not open a pull request unless you have an objective explanation. See the [open issues](https://codeberg.org/nsxiv/nsxiv/issues) to find something to work on. You can also filter the issues via label: * [Good first issue](https://codeberg.org/nsxiv/nsxiv/issues?labels=49698): (Easy) Issues which do not require much if any experience. * [Up for grabs](https://codeberg.org/nsxiv/nsxiv/issues?labels=49705): (Intermediate) Issues which are free for anyone who wants to pick it up. Might require some experience. * [Help wanted](https://codeberg.org/nsxiv/nsxiv/issues?labels=49699): (Intermediate/Experienced) Issues where we require some help. Code Style ---------- `nsxiv` mostly follows the [suckless code-style][sl], with a few exceptions. If your editor supports [.editorconfig](../.editorconfig) then you'll already be off to a good start without needing much manual intervention. Additionally we provide a [clang-format](./.clang-format) configuration for reference, which you may use via [`git-clang-format`][cf] to format the changes you've made (please do not run it globally on the entire code-base since clang-format gets a decent amount of edge cases wrong). [sl]: https://suckless.org/coding_style/ [cf]: https://clang.llvm.org/docs/ClangFormat.html#git-integration Development workflow for maintainers ------------------------------------ If we notice you contributing and/or showing interest in issues/pull requests, we may invite you to join the nsxiv org as a member. Being a member simply means you will be able to approve, disapprove and merge pull requests. Our workflow regarding pull requests is the following: * Code related changes require two approvals, but documentation related changes (e.g. typo) can be merged with just one. * If a pull request has a single approval, no objections and has been open for more than 7 days, then it may be force-merged. * Always prefer squashing when merging. In the case a PR makes more than one significant change, use the "don't squash" tag and rebase instead. * When merging, make sure the commit message is cleaned up properly so that it reflects the current intention of the PR. For releases, the process is the following: * Tag the release with a "vN" tag, where N is the version number. Also set the commit message and tag description for the release commit to "Release version N". Make sure to use an annotated tag. * Update `VERSION` macro in `config.mk`. * Update the changelog (`etc/CHANGELOG.md`): * Include link to the release tarball and add the release date. * Document only the changes or fixes between releases. Don't document changes which never made it into a release. * Use the "Changes" section to document behavior changes since the last release, the "Added" section for new features, and the "Fixes" section for fixed bugs or regressions. * Include pull request IDs with reference style links. Mirroring to GitHub: Assuming `origin` is the name of the codeberg remote and `github` is the name of the github remote; run the following commands to mirror the codeberg repo to github: ```console $ git fetch --prune origin $ git push --prune github '+refs/remotes/origin/*:refs/heads/*' '+refs/tags/*:refs/tags/*' ``` The first command updates the local repo and the second command pushes everything on `origin` without pushing any of the local branches. - - - For mundane development related talks which don't warrant their own issue, use the [general discussion](https://codeberg.org/nsxiv/nsxiv/issues/294) thread. nsxiv-33/etc/MIGRATION.md000066400000000000000000000036511474777454100151270ustar00rootroot00000000000000# Migrating from `sxiv` `nsxiv` is *mostly* a drop-in replacement for `sxiv`, but not fully. This document outlines some key differences to be aware of if you're migrating from `sxiv`. ### Configuration directory `sxiv` looks for config files under the directory `${XDG_CONFIG_HOME:-${HOME}/.config}/sxiv`. E.g `~/.config/sxiv/exec/key-handler`. `nsxiv` uses the same logic to find the config dir but uses the name "nsxiv". E.g `~/.config/nsxiv/...`. The "exec" scripts such as `key-handler` and `image-info` in `nsxiv` has some more features, but all previous argument order are preserved. And so if you have any exec scripts, you can simply copy them over and they should just work. ### Xresources The xresources config for `nsxiv` is under the "Nsxiv" namespace whereas `sxiv` uses the "Sxiv" namespace. Some of the variables are also different between `nsxiv` and `sxiv`, below is a table that shows the old and new names: | sxiv | nsxiv | | :-- | :-- | | Sxiv.background | Nsxiv.window.background | | Sxiv.foreground | Nsxiv.window.foreground | | Sxiv.barBackground | Nsxiv.bar.background | | Sxiv.barForeground | Nsxiv.bar.foreground | | Sxiv.font | Nsxiv.bar.font | ### Default window class The window class of `nsxiv` is set to "Nsxiv" by default (can be overwritten via `-N` flag). This usually shouldn't matter, unless you have scripts that search for "Sxiv" window class. ### Thumbnail cache directory Similar to config dir, the thumbnail cache dir of `nsxiv` is under the "nsxiv" name instead of "sxiv". E.g `~/.cache/nsxiv`. The "caching structure" in `nsxiv` is the same as `sxiv`. Which means that you can simply rename the directory to `nsxiv`: ```console $ mv ~/.cache/sxiv ~/.cache/nsxiv ``` If you want to have both `sxiv` and `nsxiv` installed at the same time, you can even use symlink to avoid duplicate cache. nsxiv-33/etc/examples/000077500000000000000000000000001474777454100150655ustar00rootroot00000000000000nsxiv-33/etc/examples/image-info000077500000000000000000000007661474777454100170370ustar00rootroot00000000000000#!/bin/sh # Example for $XDG_CONFIG_HOME/nsxiv/exec/image-info # Called by nsxiv(1) whenever an image gets loaded. # The output is displayed in nsxiv's status bar. # Arguments: # $1: path to image file (as provided by the user) # $2: image width # $3: image height # $4: fully resolved path to the image file s=" " # field separator exec 2>/dev/null filename=$(basename -- "$1") filesize=$(du -Hh -- "$1" | cut -f 1) geometry="${2}x${3}" echo "${filesize}${s}${geometry}${s}${filename}" nsxiv-33/etc/examples/key-handler000077500000000000000000000035541474777454100172250ustar00rootroot00000000000000#!/bin/sh # Example for $XDG_CONFIG_HOME/nsxiv/exec/key-handler # Called by nsxiv(1) after the external prefix key (C-x by default) is pressed. # The next key combo is passed as its first argument. Passed via stdin are the # images to act upon: all marked images, if in thumbnail mode and at least one # image has been marked, otherwise the current image. nsxiv(1) will block until # the handler terminates. It then checks which images have been modified and # reloads them. # The key combo argument has the following form: "[C-][M-][S-]KEY", # where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X # keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. # If KEY has an uppercase equivalent, S-KEY is resolved into it. For instance, # K replaces S-k and Scedilla replaces S-scedilla, but S-Delete is sent as-is. # By default nsxiv(1) will send one image per-line to stdin, however when using # -0 the image list will be NULL separated and the environment variable # "$NSXIV_USING_NULL" will be set to 1. [ "$NSXIV_USING_NULL" = 1 ] && DELIM='\0' || DELIM='\n' rotate() { tr "$DELIM" '\0' | xargs -0 realpath -z | sort -z -u | xargs -0 -I {} sh -c ' degree="$1" case "$(file -b -i "{}")" in image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "{}" "{}" ;; *) mogrify -rotate "$degree" "{}" ;; esac ' -- "$1" } case "$1" in "C-x") xclip -in -filter | tr '\n' ' ' | xclip -in -selection clipboard ;; "C-c") while read file; do xclip -selection clipboard -target image/png "$file"; done ;; "C-e") while read file; do urxvt -bg "#444" -fg "#eee" -sl 0 -title "$file" -e sh -c "exiv2 -q -pa pr '$file' | less" & done ;; "C-g") tr '\n' '\0' | xargs -0 gimp & ;; "C-r") while read file; do rawtherapee "$file" & done ;; "C-comma") rotate 270 ;; "C-period") rotate 90 ;; "C-slash") rotate 180 ;; esac nsxiv-33/etc/examples/thumb-info000077500000000000000000000007171474777454100170700ustar00rootroot00000000000000#!/bin/sh # Example for $XDG_CONFIG_HOME/nsxiv/exec/thumb-info # Called by nsxiv(1) whenever the selected thumbnail changes. # The output is displayed in nsxiv's status bar. # Arguments: # $1: path to image file (as provided by the user) # $2: empty # $3: empty # $4: fully resolved path to the image file s=" " # field separator exec 2>/dev/null filename=$(basename -- "$4") filesize=$(du -Hh -- "$4" | cut -f 1) echo "${filesize}${s}${filename}" nsxiv-33/etc/examples/win-title000077500000000000000000000016031474777454100167270ustar00rootroot00000000000000#!/bin/sh # Example for $XDG_CONFIG_HOME/nsxiv/exec/win-title # Called by nsxiv(1) whenever any of the relevant information changes. # The output is set as nsxiv's window title. # # Arguments, "Optional" arguments might be empty: # $1: resolved absolute path of the current file # $2: current file number # $3: total file number # $4: image width (Optional: Disabled on thumbnails mode) # $5: image height (Optional: Disabled on thumbnails mode) # $6: current zoom (Optional: Disabled on thumbnails mode) # # The term file is used rather than image as nsxiv does not # precheck that the input files are valid images. Total file # count may be different from the actual count of valid images. exec 2>/dev/null filename="${1##*/}" if [ -n "$4" ]; then # image mode printf "%s" "nsxiv - ${filename} | ${4}x${5} ${6}% [${2}/${3}]" else printf "%s" "nsxiv - ${filename} [${2}/${3}]" fi nsxiv-33/etc/nsxiv.1000066400000000000000000000326611474777454100145100ustar00rootroot00000000000000.TH NSXIV 1 nsxiv\-VERSION .SH NAME nsxiv \- Neo Simple X Image Viewer .SH SYNOPSIS .B nsxiv .RB [ \-abcfhiopqrtvZ0 ] .RB [ \-A .IR FRAMERATE ] .RB [ \-e .IR WID ] .RB [ \-G .IR GAMMA ] .RB [ \-g .IR GEOMETRY ] .RB [ \-N .IR NAME ] .RB [ \-n .IR NUM ] .RB [ \-S .IR DELAY ] .RB [ \-s .IR MODE ] .RB [ \-z .IR ZOOM ] .IR FILE ... .SH DESCRIPTION nsxiv is a simple image viewer for X. .P It has two modes of operation: image and thumbnail mode. The default is image mode, in which only the current image is shown. In thumbnail mode a grid of small previews is displayed, making it easy to choose an image to open. .P Please note, that the fullscreen mode requires an EWMH/NetWM-compliant window manager. .SH OPTIONS .TP .BI "\-A, \-\-framerate " FRAMERATE Play animations with a constant frame rate set to .IR FRAMERATE . If .I FRAMERATE is set to 0 then animation playback is disabled. .TP .B "\-a, \-\-animate" Play animations of multi-frame images. .TP .B "\-b" Do not show statusbar. .TP .B "\-\-bar, \-\-no\-bar" Enables or disables statusbar. .B "\-\-no\-bar" is equivalent to .BR "\-b" . .TP .B "\-c, \-\-clean\-cache" Remove all orphaned cache files from the thumbnail cache directory and exit. .TP .BI "\-e, \-\-embed " WID Embed nsxiv's window into window whose ID is .IR WID . .TP .B "\-f, \-\-fullscreen" Start in fullscreen mode. .TP .BI "\-G, \-\-gamma " GAMMA Set image gamma to GAMMA (\-32..32). .TP .BI "\-g, \-\-geometry " GEOMETRY Set window position and size. See section GEOMETRY SPECIFICATIONS of X(7) for more information on GEOMETRY argument. .TP .B "\-h, \-\-help" Print brief usage information to standard output and exit. .TP .B "\-i, \-\-stdin" Read names of files to open from standard input. Also done if FILE is `-'. .TP .BI "\-N, \-\-name " NAME Set the window name (i.e the "Instance" name of WM_CLASS as described in ICCCM) to NAME. .TP .BI "\-n, \-\-start\-at " NUM Start at picture number NUM. .TP .B "\-o, \-\-stdout" Write list of all marked files to standard output when quitting. In combination with .B "\-i, \-\-stdin" nsxiv can be used as a visual filter/pipe. .TP .B "\-p, \-\-private" Enable private mode, in which nsxiv does not write any cache or temporary files. .TP .B "\-q, \-\-quiet" Be quiet, and disable warnings to standard error stream. .TP .B "\-r, \-\-recursive" Search the given directories recursively for images to view. .TP .BI "\-S, \-\-ss\-delay " DELAY Start in slideshow mode. Set the delay between images to .I DELAY seconds. .I DELAY may be a floating-point number. .TP .BI "\-s, \-\-scale\-mode " MODE Set scale mode according to MODE character. Supported modes are: [d]own, [f]it, [F]ill, [w]idth, [h]eight. .TP .BR "\-t, \-\-thumbnail" [=no] Start in thumbnail mode. The long option accepts an optional "no" to disable it. .TP .B "\-v, \-\-version" Print version information to standard output and exit. .TP .B "\-Z, \-\-zoom\-100" The same as `\-z 100'. .TP .BI "\-z, \-\-zoom " ZOOM Set zoom level to ZOOM percent. .TP .B "\-0, \-\-null" Use NULL-separator. With this option, output of \-o and file-list sent to the key-handler and the input of \-i will be separated by a NULL character. .TP .BI "\-\-anti\-alias" [=no] Enables anti-aliasing, when given .I no as an argument, disables it instead. .TP .BI "\-\-alpha\-layer" [=no] Enables checkerboard background for alpha layer, when given .I no as an argument, disables it instead. .TP .BI "\-\-cache\-allow, \-\-cache\-deny " "CACHE\-PATHS" List of directories to whitelist or blacklist for thumbnail caching. See .I "THUMBNAIL CACHING" section for more information on the argument format. These options are mutually exclusive, if they are specified more than once then only the final one takes effect. .SH KEYBOARD COMMANDS .SS General The following keyboard commands are available in both image and thumbnail modes: .TP .BR 0 \- 9 Prefix the next command with a number (denoted via .IR count ). .TP .B q Quit nsxiv. .TP .B Q Quit nsxiv, but additionally print the current filename when \-o is active and no files have been marked. .TP .B Return Switch to thumbnail mode / open selected image in image mode. .TP .B f Toggle fullscreen mode. .TP .B b Toggle visibility of statusbar at the bottom of the window. .TP .B Ctrl-x Send the next key to the external key-handler. See section EXTERNAL KEY HANDLER for more information. .TP .B g Go to the first image. .TP .B G Go to the last image, or image number .IR count . .TP .B r Reload image. .TP .B D Remove current image from file list and go to next image. .TP .BR Ctrl-h ", " Ctrl-Left Scroll left one screen width. .TP .BR Ctrl-j ", " Ctrl-Down Scroll down one screen height. .TP .BR Ctrl-k ", " Ctrl-Up Scroll up one screen height. .TP .BR Ctrl-l ", " Ctrl-Right Scroll right one screen width. .TP .BR + Zoom in. .TP .B \- Zoom out. .TP .B m Mark/unmark the current image. .TP .B M Mark/unmark all images starting from the last marked/unmarked image up to the current one. .TP .B Ctrl-m Reverse all image marks. .TP .B Ctrl-u Remove all image marks. .TP .B N Go .I count marked images forward. .TP .B P Go .I count marked images backward. .TP .B { Decrease gamma correction by .I count steps. .TP .B } Increase gamma correction by .I count steps. .TP .B Ctrl-g Reset gamma correction. .TP .B Ctrl-[ Decrease brightness correction by .I count steps. .TP .B Ctrl-] Increase brightness correction by .I count steps. .TP .B ( Decrease contrast by .I count steps. .TP .B ) Increase contrast by .I count steps. .SS Thumbnail mode The following keyboard commands are only available in thumbnail mode: .TP .BR h ", " Left Move selection left .I count times. .TP .BR j ", " Down Move selection down .I count times. .TP .BR k ", " Up Move selection up .I count times. .TP .BR l ", " Right Move selection right .I count times. .TP .B R Reload all thumbnails. .SS Image mode The following keyboard commands are only available in image mode: .TP Navigate image list: .TP .BR n ", " Space Go .I count images forward. .TP .BR p ", " Backspace Go .I count images backward. .TP .B [ Go .I count * 10 images backward. .TP .B ] Go .I count * 10 images forward. .TP .B Ctrl-6 Go to the previously viewed image. .TP Handle multi-frame images: .TP .B Ctrl-n Go .I count frames of a multi-frame image forward. .TP .B Ctrl-p Go .I count frames of a multi-frame image backward. .TP .BR Ctrl-a ", " Ctrl-Space Play/stop animations of multi-frame images. .TP Panning: .TP .BR h ", " Left Scroll image 1/5 of window width or .I count pixels left. .TP .BR j ", " Down Scroll image 1/5 of window height or .I count pixels down. .TP .BR k ", " Up Scroll image 1/5 of window height or .I count pixels up. .TP .BR l ", " Right Scroll image 1/5 of window width or .I count pixels right. .TP .B H Scroll to left image edge. .TP .B J Scroll to bottom image edge. .TP .B K Scroll to top image edge. .TP .B L Scroll to right image edge. .TP .B z Scroll to image center. .TP Zooming: .TP .B = Set zoom level to 100%, or .IR count %. .TP .B w Set zoom level to 100%, but fit large images into window. .TP .B W Fit image to window. .TP .B F Fill image to window. .TP .B e Fit image to window width. .TP .B E Fit image to window height. .TP Rotation: .TP .B < Rotate image counter-clockwise by 90 degrees. .TP .B > Rotate image clockwise by 90 degrees. .TP .B ? Rotate image by 180 degrees. .TP Flipping: .TP .B | Flip image horizontally. .TP .B _ Flip image vertically. .TP Miscellaneous: .TP .B a Toggle anti-aliasing. .TP .B A Toggle visibility of alpha-channel, i.e. image transparency. .TP .B s Toggle slideshow mode and/or set the delay between images to .I count seconds. .SH MOUSE COMMANDS .SS Thumbnail mode The following mouse mappings are available in thumbnail mode: .TP .B Button1 Select the image. Goes into image mode if double\-clicked. .TP .B Button3 Mark/unmark the image. Dragging while holding down Button3 will mark/unmark multiple images. .TP .B Button4 Scroll up by one row. .TP .B Button5 Scroll down by one row. .TP .B Ctrl-Button4 Scroll up by one page. .TP .B Ctrl-Button5 Scroll down by one page. .SS Image mode The following mouse mappings are available in image mode: .TP .B Button1 Go to the next image if the mouse cursor is in the right part of the window or to the previous image if it is in the left part. .TP .B Ctrl-Button1 Pan the image relative to the mouse cursor. .TP .B Button2 Pan the image according to the mouse cursor position in the window while keeping this button pressed down. .TP .B Button3 Switch to thumbnail mode. .TP .B Button4 Zoom in. .TP .B Button5 Zoom out. .SH CONFIGURATION The following X resources are supported under "Nsxiv" (e.g. .B Nsxiv.bar.font ): .TP .B window.background Color of the window background .TP .B window.foreground Color of the window foreground .TP .B bar.font Name of Xft bar font .TP .B bar.background Color of the bar background. Defaults to window.background .TP .B bar.foreground Color of the bar foreground. Defaults to window.foreground .TP .B mark.foreground Color of the mark foreground. Defaults to window.foreground .TP Please see xrdb(1) on how to change them. .LP An X resources entry with an empty value means the default (defined in config.h) will be used. .SH WINDOW TITLE The window title can be replaced with the output of a user-provided script, which is called by nsxiv whenever any of the relevant information changes. The path of this script is .I $XDG_CONFIG_HOME/nsxiv/exec/win-title and the arguments given to it (where "Optional" arguments might be empty) are: .IP $1 4 resolved absolute path of the current file .IP $2 4 current file number .IP $3 4 total file count .IP $4 4 image width (Optional: Disabled on thumbnails mode) .IP $5 4 image height (Optional: Disabled on thumbnails mode) .IP $6 4 current zoom (Optional: Disabled on thumbnails mode) .P The term file is used rather than image as nsxiv does not precheck that the input files are valid images. Total file count may be different from the actual count of valid images. .P There is also an example script installed together with nsxiv as .IR EGPREFIX/win-title . .SH STATUS BAR The information displayed on the left side of the status bar can be replaced with the output of user-provided script. .P The script that is called by nsxiv whenever an image gets loaded is located at .I $XDG_CONFIG_HOME/nsxiv/exec/image-info and the arguments given to it are: .IP $1 4 path to image file (as provided by the user) .IP $2 4 image width .IP $3 4 image height .IP $4 4 fully resolved path to the image file .P In thumbnail mode, the script that is called is located at .I $XDG_CONFIG_HOME/nsxiv/exec/thumb-info and the arguments given to it are: .IP $1 4 path to image file (as provided by the user) .IP $2 4 empty .IP $3 4 empty .IP $4 4 fully resolved path to the image file .P There are also example scripts installed together with nsxiv as .IR EGPREFIX/image-info and .IR EGPREFIX/thumb-info . .SH EXTERNAL KEY HANDLER Additional external keyboard commands can be defined using a handler program located in .IR $XDG_CONFIG_HOME/nsxiv/exec/key-handler . The handler is invoked by pressing .BR Ctrl-x . The next key combo is passed as its first argument. Passed via stdin are the images to act upon: all marked images, if in thumbnail mode and at least one image has been marked, otherwise the current image. nsxiv(1) will block until the handler terminates. It then checks which images have been modified and reloads them. By default nsxiv(1) will send one image per-line to stdin, however when using \-0 the image list will be NULL separated and the environment variable "$NSXIV_USING_NULL" will be set to 1. The key combo argument has the following form: "[C-][M-][S-]KEY", where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. If KEY has an uppercase equivalent, S-KEY is resolved into it. For instance, K replaces S-k and Scedilla replaces S-scedilla, but S-Delete is sent as-is. There is also an example script installed together with nsxiv as .IR EGPREFIX/key-handler . .SH THUMBNAIL CACHING nsxiv stores all thumbnails under .IR $XDG_CACHE_HOME/nsxiv/ . .P Use the command line option .I \-c to remove all orphaned cache files. Additionally, run the following command afterwards inside the cache directory to remove empty subdirectories: .P .RS find . \-depth \-type d \-empty ! \-name '.' \-exec rmdir {} \\; .RE .P The option .BR "\-\-cache\-allow " or " \-\-cache\-deny" may be used to whitelists or blacklist certain directories from being cached. The argument is a .B : separated list of paths. A .B * at the beginning of the path indicates that path should be matched recursively. .P For example: .B \-\-cache\-allow \(dq/user/pictures:*/media/pictures\(dq whitelists \(dq/user/pictures\(dq directory non-recursively and \(dq/media/pictures\(dq directory recursively. Nothing outside these two directories will be cached. And .B \-\-cache\-deny \(dq*/secret\(dq will enable blacklist mode and will not cache anything inside \(dq/secret\(dq or it's subdirectories. .SH ORIGINAL AUTHOR .EX Bert Muennich .EE .SH CURRENT MAINTAINERS .EX NRK Berke Kocaoğlu TAAPArthur eylles Stein Gunnar Bakkeby explosion-mental .EE .SH CONTRIBUTORS .EX For a list of contributors, run `git shortlog -s` in the nsxiv git repository. .EE .SH HOMEPAGE .TP Website: .EE https://nsxiv.codeberg.page/ .EX .TP Source code: .EE https://codeberg.org/nsxiv/nsxiv .EX .SH SEE ALSO .BR X (7), .BR xrdb (1) nsxiv-33/etc/nsxiv.desktop000066400000000000000000000006131474777454100160110ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=nsxiv GenericName=Image Viewer Exec=nsxiv %F MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-tga;image/x-xpixmap;image/webp;image/heic;image/svg+xml;application/postscript;image/jp2;image/jxl;image/avif;image/heif; NoDisplay=true Icon=nsxiv nsxiv-33/etc/woodpecker/000077500000000000000000000000001474777454100154115ustar00rootroot00000000000000nsxiv-33/etc/woodpecker/CFLAGS000066400000000000000000000007031474777454100162730ustar00rootroot00000000000000# vanilla flags -std=c99 -Wall -pedantic # optimizations: enables extra warnings and deeper analysis thus catches more errors/warnings -O3 -flto # treat warnings as errors -Werror # extra flags -Wextra -Wshadow -Wvla -Wpointer-arith -Wundef -Wstrict-overflow=4 -Wwrite-strings -Wunreachable-code -Wbad-function-cast -Wdeclaration-after-statement -Wmissing-prototypes -Wstrict-prototypes # silence -Wno-unused-parameter -Wno-missing-field-initializers nsxiv-33/etc/woodpecker/analysis.sh000077500000000000000000000015041474777454100175730ustar00rootroot00000000000000#!/bin/sh -e std="c99" NProc=$(( $(nproc) / 4 )) if [ -z "$NProc" ] || [ "$NProc" -lt 1 ]; then NProc="1"; fi run_cppcheck() { cppcheck --std="$std" --enable=performance,portability \ --force --quiet --inline-suppr --error-exitcode=1 \ --max-ctu-depth=8 -j"$NProc" \ --check-level=exhaustive \ $(make OPT_DEP_DEFAULT="$1" dump_cppflags) -DDEBUG \ --suppress=varFuncNullUB --suppress=uninitvar \ $(git ls-files *.c) } run_tidy() { checks="$(sed '/^#/d' etc/woodpecker/clang-tidy-checks | paste -d ',' -s)" git ls-files *.c | xargs -P"$NProc" -I{} clang-tidy --quiet \ --warnings-as-errors="*" --checks="$checks" {} \ -- -std="$std" $(make OPT_DEP_DEFAULT="$1" dump_cppflags) -DDEBUG } run_cppcheck "0" & run_cppcheck "1" & run_tidy "0" & run_tidy "1"; wait nsxiv-33/etc/woodpecker/analysis.yml000066400000000000000000000004401474777454100177550ustar00rootroot00000000000000when: branch: master steps: analysis: image: alpine commands: | apk add --no-cache build-base cppcheck clang-extra-tools git \ imlib2-dev xorgproto \ libxft-dev libexif-dev >/dev/null make config.h version.h ./etc/woodpecker/analysis.sh nsxiv-33/etc/woodpecker/build.yml000066400000000000000000000023651474777454100172410ustar00rootroot00000000000000when: branch: master # NOTE: "stable" tcc is too old and fails at linking. instead fetching a recent known working commit. steps: build: image: alpine environment: - TCC_SHA=29ae3ed4d5b83eec43598d6cd7949bccb41c8083 commands: | apk add --no-cache \ imlib2 imlib2-dev xorgproto \ libxft libxft-dev libexif libexif-dev \ gcc clang llvm llvm-dev build-base wget ca-certificates bc >/dev/null wget "https://github.com/TinyCC/tinycc/archive/$TCC_SHA.tar.gz" >/dev/null tar xzf "$TCC_SHA.tar.gz" >/dev/null ( cd "tinycc-$TCC_SHA" && ./configure --config-musl && make -s -j"$(nproc)" && make install; ) >/dev/null CFLAGS="$(sed '/^#/d' etc/woodpecker/CFLAGS | paste -d ' ' -s)" build () { for cc in "gcc" "clang"; do echo "### $cc - $2 build ###" make clean && make -s -j"$(nproc)" CC="$cc" CFLAGS="$CFLAGS" LDFLAGS="$CFLAGS" OPT_DEP_DEFAULT="$1" done } # try all combination with tcc # etc/woodpecker/try-all-builds.sh # full-build with gcc and clang # build "1" "full" # ensure minimal-build works without opt deps installed apk del libxft libxft-dev libexif libexif-dev >/dev/null build "0" "minimal" nsxiv-33/etc/woodpecker/clang-tidy-checks000066400000000000000000000013511474777454100206250ustar00rootroot00000000000000# checks clang-analyzer-*,clang-diagnostic-*,bugprone-*,performance-*,modernize-* misc-*,android-cloexec-*,llvm-include-order -readability-*,readability-duplicate-include,readability-misleading-indentation # silence -misc-unused-parameters,-misc-include-cleaner,-misc-no-recursion -bugprone-easily-swappable-parameters,-bugprone-narrowing-conversions,-bugprone-incorrect-roundings -bugprone-implicit-widening-of-multiplication-result,-bugprone-integer-division -android-cloexec-fopen,-android-cloexec-pipe,-cert-err33-c -bugprone-assignment-in-if-condition -bugprone-suspicious-realloc-usage -bugprone-switch-missing-default-case -bugprone-multi-level-implicit-pointer-conversion # false positive warnings -clang-analyzer-valist.Uninitialized nsxiv-33/etc/woodpecker/spell.yml000066400000000000000000000005311474777454100172520ustar00rootroot00000000000000when: branch: master # NOTE: codespell not available on stable alpine, grab it from pip steps: spell-check: image: alpine:edge commands: | apk add --no-cache python3 py3-pip git python3 -m venv ~/py3-venv . ~/py3-venv/bin/activate pip install codespell git ls-files | sed '/\.png$/d' | xargs codespell nsxiv-33/etc/woodpecker/try-all-builds.sh000077500000000000000000000016231474777454100206160ustar00rootroot00000000000000#!/bin/sh # # Shell script that checks for all possible build combination with TCC. # Usage: call the script while in the nsxiv root directory set -- $(grep -o '^HAVE_[[:alpha:]]* ' config.mk) CFLAGS="$(sed '/^#/d' etc/woodpecker/CFLAGS | paste -d ' ' -s)" z=$(echo "2 ^ $#" | bc) print_opt_name() { shift "$(( $1 + 1 ))" printf "%s=" "$1" } print_opt_arg() { bn=$(echo "$1 / (2 ^ $2)" | bc) printf "%d " $(( bn % 2 )) } n=0 while [ "$n" -lt "$z" ]; do i=0 while [ "$i" -lt "$#" ]; do print_opt_name "$i" "$@" print_opt_arg "$n" "$i" i=$((i + 1)) done | tee "/dev/stderr" | ( make clean if ! xargs make -j"$(nproc)" CC=tcc CFLAGS="$CFLAGS" LDFLAGS="$CFLAGS"; then echo "[FAILED]" >&2 exit 1 else echo "[SUCCESS]" >&2 fi ) [ "$?" -ne 0 ] && exit "$?" n=$((n + 1)) done >/dev/null nsxiv-33/icon/000077500000000000000000000000001474777454100134245ustar00rootroot00000000000000nsxiv-33/icon/128x128.png000066400000000000000000000020521474777454100150660ustar00rootroot00000000000000PNG  IHDRL\ pHYs  ~IDATxϋMa;ǯ(`0#BMl,Ŭ($I$?גXΩS{~=yyd}=s\cӽ祑ߵsϐh. @@Dz2Es2MS\ԟt#uUW@6=bE\M[M uo(>U9[g>\@ <6a@=@p1[Ͻ4p2u*"@DLW~T?N!@XՏ |,e7\:u,5fqJD"l+E,ޖo]9`؍>r 8FeVaG e3g@.^( >ПB7C&a0g@ 40|đAxφXgH-GM nV6e(z>-k` `;m J4wz JmbaΨ`Ve7F]H;M!VNmY`Kv@Wn܍X`P6b$}-`V}%?&HX`\}?&HXz]]PPP$@{k %`:sJXtQ/S`@n8iOIENDB`nsxiv-33/icon/16x16.png000066400000000000000000000004061474777454100147170ustar00rootroot00000000000000PNG  IHDRh6 pHYs  ~IDAT(cTR0a ?h 21 ڀ&$I|~? { "dB8 cЀABUC%&<t eN]kT̝YjzB2HhҤGpś- BhU5 甇ȱ Aqz9RDZ z !2X%@lMIENDB`nsxiv-33/icon/32x32.png000066400000000000000000000006451474777454100147200ustar00rootroot00000000000000PNG  IHDR  pHYs  ~WIDATHcTR0aܽH*+R@c@s ) -`dIʼnd sc!2XVCT | 3I9ab/:|@䤢nڂ,b/ʜԚ{6;YjzN ÊwLd 1FaĎO6EDK}o>8(@hd7yCL n+\T ǹU3.d" AoG4q 6/jbٕ<#D8`a[ŭcB k!Cl>z"Sx5;CXr21'C6]WՐ€{ڹKa|̴D'H XϛIENDB`nsxiv-33/icon/48x48.png000066400000000000000000000007731474777454100147400ustar00rootroot00000000000000PNG  IHDR00`n pHYs  ~IDATXcTR0a%{4hJz&AF4HD=eA,h&h$![d4ʆX8 qY`I%atxx |auĀ9k b\\V$rhAHdnSQK wcS*Z8}ӫMC!Jas d"B>({D]w@` -tA$[>؄&%ZbEGĻW~q (<WW[GTa8{fZnn+?v3FO!u@"}̣R--s!Ҕ#}|M[;H0H܎0^uwsJːHd\_Olz~F5(C.7#==}v}Km^-2hٵ.ץPC4`|=*}@mSlW?#42D%wk@d?^R o% SgVcA n6\?̰ i Vi!t ])mJHOK>Ltj IENDB`nsxiv-33/icon/README.md000066400000000000000000000017321474777454100147060ustar00rootroot00000000000000img2data -------- img2data is a helper program to convert images to binary data for embedding in software. The generated data is used in nsxiv to set the window icons. How It Works ------------ Each given image is compressed using run-length encoding and a data array is generated. In these arrays, the four high bits of each byte are the run length minus one and the lowest four bits are the data, which are indices for the color array. See `data.gen.h` or the output of img2data for a better understanding. Dependencies ------------ img2data requires Imlib2 to be installed. Building -------- img2data is built using the command: $ CC -Wall -std=c89 -pedantic -lImlib2 img2data.c -o img2data where `CC` is a C compiler such as `gcc`. Usage ----- img2data is used as the following: $ ./img2data 16x16.png 32x32.png 48x48.png 64x64.png 128x128.png You may replace or omit any image, but you must have a `data.gen.h` with at least 1 image for nsxiv to compile. nsxiv-33/icon/data.gen.h000066400000000000000000000412061474777454100152610ustar00rootroot00000000000000static const unsigned char icon_data_16[] = { 0xa0, 0x01, 0xe0, 0x01, 0xf0, 0x40, 0x21, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x10, 0x01, 0x10, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x01, 0x00, 0x51, 0x02, 0x00, 0x01, 0x00, 0x21, 0x00, 0x01, 0x00, 0x51, 0x02, 0x80, 0x03, 0x52, 0x04, 0x50, 0x43, 0x05, 0x22, 0x14, 0x06, 0x10, 0x47, 0x35, 0x08, 0x02, 0x14, 0x06, 0x09, 0x22, 0x47, 0x25, 0x08, 0x14, 0x06, 0x09, 0x32, 0x47, 0x08, 0x05, 0x08, 0x04, 0x16, 0x19, 0x32, 0x47, 0x18, 0x16, 0x29, 0x32, 0x47, 0x18, 0x16, 0x29, 0x42, 0x47, 0x08, }; static const unsigned char icon_data_32[] = { 0xf0, 0x40, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x90, 0x51, 0x10, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x51, 0x10, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x51, 0x10, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x02, 0x00, 0x11, 0x10, 0x11, 0x10, 0x11, 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x12, 0x11, 0x02, 0x00, 0x11, 0x10, 0x11, 0x10, 0x11, 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x12, 0x11, 0x02, 0x00, 0x11, 0x10, 0x11, 0x10, 0x11, 0x30, 0x11, 0x30, 0x11, 0x12, 0x11, 0x12, 0x11, 0x02, 0x00, 0x11, 0x10, 0x51, 0x10, 0x11, 0x10, 0xb1, 0x22, 0x00, 0x11, 0x10, 0x51, 0x10, 0x11, 0x10, 0xb1, 0x22, 0x00, 0x11, 0x10, 0x51, 0x10, 0x11, 0x10, 0xb1, 0x22, 0xf0, 0x10, 0xd2, 0xf0, 0x00, 0xe2, 0xf0, 0x53, 0x92, 0x14, 0xc0, 0x83, 0x05, 0x62, 0x24, 0x06, 0x80, 0x93, 0x35, 0x42, 0x34, 0x06, 0x02, 0x30, 0x77, 0x95, 0x08, 0x22, 0x34, 0x16, 0x12, 0xc7, 0x75, 0x08, 0x12, 0x34, 0x16, 0x09, 0x52, 0x97, 0x65, 0x08, 0x02, 0x34, 0x16, 0x19, 0x62, 0x97, 0x45, 0x18, 0x24, 0x26, 0x19, 0x72, 0x97, 0x08, 0x25, 0x18, 0x24, 0x26, 0x29, 0x72, 0x97, 0x48, 0x14, 0x36, 0x29, 0x72, 0x97, 0x48, 0x04, 0x36, 0x39, 0x82, 0x97, 0x38, 0x46, 0x39, 0x82, 0x97, 0x38, 0x36, 0x59, 0x82, 0x97, 0x28, 0x26, 0x69, 0x82, 0x97, 0x28, 0x26, 0x69, 0x82, 0x97, 0x28, }; static const unsigned char icon_data_48[] = { 0xf0, 0xf0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xe0, 0x81, 0x20, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x81, 0x20, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x81, 0x20, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x81, 0x20, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x02, 0x10, 0x81, 0x20, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x00, 0x0a, 0x02, 0x21, 0x02, 0x10, 0x21, 0x20, 0x21, 0x20, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, 0x22, 0x21, 0x02, 0x10, 0x21, 0x20, 0x21, 0x20, 0x21, 0x50, 0x21, 0x50, 0x21, 0x10, 0x02, 0x21, 0x22, 0x21, 0x02, 0x10, 0x21, 0x20, 0x21, 0x20, 0x21, 0x50, 0x21, 0x50, 0x21, 0x00, 0x12, 0x21, 0x22, 0x21, 0x02, 0x10, 0x21, 0x20, 0x21, 0x20, 0x21, 0x50, 0x21, 0x50, 0x21, 0x22, 0x21, 0x22, 0x21, 0x02, 0x10, 0x21, 0x20, 0x81, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x32, 0x10, 0x21, 0x20, 0x81, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x32, 0x10, 0x21, 0x20, 0x81, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x32, 0x10, 0x21, 0x20, 0x81, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x32, 0x10, 0x21, 0x20, 0x81, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x32, 0xf0, 0xa0, 0xf2, 0x42, 0xf0, 0x90, 0xf2, 0x52, 0xf0, 0x90, 0x33, 0xf2, 0x12, 0xf0, 0x70, 0x93, 0xd2, 0x24, 0xf0, 0x20, 0xc3, 0x15, 0xa2, 0x34, 0xf0, 0x00, 0xd3, 0x35, 0x82, 0x44, 0x06, 0xb0, 0xe3, 0x75, 0x62, 0x54, 0x16, 0x02, 0x50, 0xb7, 0xe5, 0x08, 0x42, 0x54, 0x16, 0x09, 0x02, 0x30, 0xe7, 0xd5, 0x08, 0x32, 0x54, 0x26, 0x09, 0x22, 0xf7, 0x37, 0xa5, 0x18, 0x12, 0x54, 0x26, 0x19, 0x72, 0xf7, 0x85, 0x28, 0x02, 0x54, 0x26, 0x19, 0x92, 0xe7, 0x85, 0x28, 0x54, 0x26, 0x29, 0xa2, 0xe7, 0x65, 0x28, 0x44, 0x36, 0x29, 0xb2, 0xe7, 0x18, 0x35, 0x28, 0x44, 0x36, 0x39, 0xa2, 0xe7, 0x28, 0x15, 0x38, 0x34, 0x46, 0x39, 0xb2, 0xe7, 0x78, 0x24, 0x56, 0x49, 0xb2, 0xe7, 0x68, 0x14, 0x56, 0x59, 0xb2, 0xe7, 0x68, 0x04, 0x66, 0x59, 0xc2, 0xe7, 0x58, 0x66, 0x69, 0xc2, 0xe7, 0x58, 0x66, 0x69, 0xc2, 0xe7, 0x58, 0x56, 0x79, 0xd2, 0xe7, 0x48, 0x46, 0x89, 0xd2, 0xe7, 0x48, 0x46, 0x89, 0xe2, 0xe7, 0x38, 0x46, 0x89, 0xe2, 0xe7, 0x38, }; static const unsigned char icon_data_64[] = { 0xf0, 0xf0, 0x90, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0xb1, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0xb1, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0xb1, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0xb1, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0xb1, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x00, 0x0b, 0x10, 0xb1, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x12, 0x10, 0x31, 0x30, 0x31, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x10, 0x12, 0x31, 0x12, 0x10, 0x31, 0x30, 0x31, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x32, 0x31, 0x12, 0x10, 0x31, 0x30, 0x31, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x32, 0x31, 0x12, 0x10, 0x31, 0x30, 0x31, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x20, 0x02, 0x31, 0x32, 0x31, 0x12, 0x10, 0x31, 0x30, 0x31, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x00, 0x22, 0x31, 0x32, 0x31, 0x12, 0x10, 0x31, 0x30, 0x31, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x32, 0x31, 0x32, 0x31, 0x12, 0x10, 0x31, 0x30, 0xb1, 0x30, 0x31, 0x30, 0xf1, 0x71, 0x52, 0x10, 0x31, 0x30, 0xb1, 0x30, 0x31, 0x30, 0xf1, 0x71, 0x52, 0x10, 0x31, 0x30, 0xb1, 0x30, 0x31, 0x30, 0xf1, 0x71, 0x52, 0x10, 0x31, 0x30, 0xb1, 0x30, 0x31, 0x30, 0xf1, 0x71, 0x52, 0x10, 0x31, 0x30, 0xb1, 0x30, 0x31, 0x30, 0xf1, 0x71, 0x52, 0x10, 0x31, 0x30, 0xb1, 0x30, 0x31, 0x30, 0xf1, 0x71, 0x52, 0xf0, 0xf0, 0x40, 0xf2, 0xa2, 0xf0, 0xf0, 0x30, 0xf2, 0xb2, 0xf0, 0xf0, 0x20, 0xf2, 0xc2, 0xf0, 0xf0, 0x10, 0xf2, 0xd2, 0xf0, 0xf0, 0x10, 0x83, 0xf2, 0x42, 0xf0, 0xf0, 0xd3, 0xf2, 0x12, 0x24, 0xf0, 0xa0, 0xf3, 0x03, 0x15, 0xe2, 0x44, 0xf0, 0x70, 0xf3, 0x13, 0x35, 0xc2, 0x54, 0x06, 0xf0, 0x40, 0xf3, 0x13, 0x65, 0xa2, 0x64, 0x16, 0xf0, 0xf3, 0x33, 0x95, 0x82, 0x64, 0x26, 0x02, 0xa0, 0x77, 0xb3, 0xd5, 0x08, 0x62, 0x74, 0x26, 0x12, 0x60, 0xf7, 0x17, 0xf5, 0x25, 0x08, 0x52, 0x74, 0x26, 0x09, 0x32, 0xf7, 0x87, 0xf5, 0x05, 0x18, 0x32, 0x74, 0x36, 0x09, 0x42, 0xf7, 0x97, 0xe5, 0x18, 0x22, 0x74, 0x36, 0x19, 0x92, 0xf7, 0x57, 0xc5, 0x28, 0x12, 0x74, 0x46, 0x19, 0xb2, 0xf7, 0x47, 0xa5, 0x38, 0x02, 0x74, 0x46, 0x19, 0xd2, 0xf7, 0x37, 0x08, 0x95, 0x38, 0x74, 0x46, 0x29, 0xd2, 0xf7, 0x37, 0x08, 0x85, 0x38, 0x64, 0x56, 0x29, 0xe2, 0xf7, 0x37, 0x18, 0x65, 0x38, 0x64, 0x56, 0x39, 0xe2, 0xf7, 0x37, 0x28, 0x45, 0x38, 0x54, 0x66, 0x39, 0xf2, 0xf7, 0x37, 0x38, 0x15, 0x48, 0x44, 0x66, 0x59, 0xf2, 0xf7, 0x37, 0x98, 0x34, 0x76, 0x59, 0xf2, 0xf7, 0x37, 0x98, 0x24, 0x86, 0x59, 0xf2, 0x02, 0xf7, 0x37, 0x88, 0x14, 0x86, 0x79, 0xf2, 0xf7, 0x37, 0x88, 0x04, 0x96, 0x79, 0xf2, 0x02, 0xf7, 0x37, 0x78, 0x96, 0x89, 0xf2, 0x02, 0xf7, 0x37, 0x78, 0x96, 0x89, 0xf2, 0x12, 0xf7, 0x37, 0x68, 0x86, 0x99, 0xf2, 0x12, 0xf7, 0x37, 0x68, 0x76, 0xa9, 0xf2, 0x12, 0xf7, 0x37, 0x68, 0x66, 0xb9, 0xf2, 0x22, 0xf7, 0x37, 0x58, 0x66, 0xb9, 0xf2, 0x22, 0xf7, 0x37, 0x58, 0x56, 0xc9, 0xf2, 0x22, 0xf7, 0x37, 0x58, 0x56, 0xc9, 0xf2, 0x22, 0xf7, 0x37, 0x58, }; static const unsigned char icon_data_128[] = { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x10, 0x0b, 0x0a, 0x30, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x32, 0x30, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x32, 0x30, 0xf1, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x40, 0x0a, 0x12, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x20, 0x42, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x00, 0x62, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x60, 0x02, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x40, 0x22, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x20, 0x42, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x00, 0x62, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x72, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x72, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x72, 0x71, 0x72, 0x71, 0x32, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0x30, 0x71, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xb2, 0xf0, 0xf0, 0xf0, 0xf0, 0x90, 0xf2, 0xf2, 0xf2, 0x52, 0xf0, 0xf0, 0xf0, 0xf0, 0x80, 0xf2, 0xf2, 0xf2, 0x62, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0xf2, 0xf2, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, 0x60, 0xf2, 0xf2, 0xf2, 0x82, 0xf0, 0xf0, 0xf0, 0xf0, 0x50, 0xf2, 0xf2, 0xf2, 0x92, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf2, 0xf2, 0xf2, 0xa2, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0xf2, 0xf2, 0xf2, 0xb2, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0x12, 0xa3, 0xf2, 0xf2, 0xe2, 0xf0, 0xf0, 0xf0, 0xf0, 0x20, 0xf3, 0x13, 0xf2, 0xf2, 0xa2, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0xf3, 0x63, 0xf2, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xe0, 0xf3, 0xb3, 0xf2, 0xf2, 0x42, 0xf0, 0xf0, 0xf0, 0xd0, 0xf3, 0xd3, 0x15, 0xf2, 0xf2, 0x12, 0x44, 0xf0, 0xf0, 0xf0, 0x60, 0xf3, 0xf3, 0x03, 0x35, 0xf2, 0xe2, 0x64, 0xf0, 0xf0, 0xf0, 0x30, 0xf3, 0xf3, 0x23, 0x45, 0xf2, 0xc2, 0x84, 0xf0, 0xf0, 0xf0, 0x00, 0xf3, 0xf3, 0x33, 0x65, 0xf2, 0xa2, 0x94, 0xf0, 0xf0, 0xe0, 0xf3, 0xf3, 0x33, 0x95, 0xf2, 0x82, 0xa4, 0x16, 0xf0, 0xf0, 0x90, 0xf3, 0xf3, 0x43, 0xc5, 0xf2, 0x62, 0xb4, 0x26, 0xf0, 0xf0, 0x40, 0xf3, 0xf3, 0x63, 0xf5, 0xf2, 0x42, 0xc4, 0x36, 0xf0, 0xf0, 0xf3, 0xf3, 0x73, 0xf5, 0x35, 0xf2, 0x22, 0xd4, 0x46, 0xf0, 0xa0, 0xf3, 0xf3, 0x83, 0xf5, 0x75, 0xf2, 0x02, 0xd4, 0x56, 0x02, 0xf0, 0x50, 0xf7, 0x07, 0xf3, 0x73, 0xf5, 0xb5, 0x08, 0xe2, 0xe4, 0x56, 0x12, 0xf0, 0x10, 0xf7, 0xd7, 0xf5, 0xf5, 0x95, 0x18, 0xc2, 0xe4, 0x56, 0x09, 0x22, 0xd0, 0xf7, 0xf7, 0x37, 0xf5, 0xf5, 0x65, 0x18, 0xb2, 0xf4, 0x56, 0x09, 0x32, 0x80, 0xf7, 0xf7, 0x97, 0xf5, 0xf5, 0x45, 0x18, 0xa2, 0xf4, 0x56, 0x19, 0x72, 0xf7, 0xf7, 0xf7, 0x07, 0xf5, 0xf5, 0x25, 0x28, 0x82, 0xf4, 0x66, 0x19, 0x72, 0xf7, 0xf7, 0xf7, 0x27, 0xf5, 0xf5, 0x05, 0x38, 0x62, 0xf4, 0x66, 0x29, 0x82, 0xf7, 0xf7, 0xf7, 0x37, 0xf5, 0xd5, 0x48, 0x52, 0xf4, 0x76, 0x29, 0xc2, 0xf7, 0xf7, 0xf7, 0x07, 0xf5, 0xb5, 0x58, 0x42, 0xf4, 0x76, 0x39, 0xf2, 0x22, 0xf7, 0xf7, 0xb7, 0xf5, 0xa5, 0x58, 0x32, 0xf4, 0x76, 0x49, 0xf2, 0x52, 0xf7, 0xf7, 0x97, 0xf5, 0x85, 0x68, 0x22, 0xf4, 0x86, 0x39, 0xf2, 0x72, 0xf7, 0xf7, 0x97, 0xf5, 0x65, 0x78, 0x12, 0xf4, 0x86, 0x49, 0xf2, 0x82, 0xf7, 0xf7, 0x97, 0xf5, 0x55, 0x78, 0x02, 0xf4, 0x86, 0x49, 0xf2, 0xa2, 0xf7, 0xf7, 0x87, 0x08, 0xf5, 0x35, 0x88, 0xe4, 0x96, 0x59, 0xf2, 0xb2, 0xf7, 0xf7, 0x77, 0x08, 0xf5, 0x25, 0x88, 0xe4, 0x96, 0x59, 0xf2, 0xc2, 0xf7, 0xf7, 0x77, 0x18, 0xf5, 0x05, 0x88, 0xd4, 0xa6, 0x69, 0xf2, 0xc2, 0xf7, 0xf7, 0x77, 0x28, 0xf5, 0x78, 0xd4, 0xa6, 0x69, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, 0x38, 0xd5, 0x78, 0xc4, 0xb6, 0x79, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, 0x48, 0xa5, 0x88, 0xc4, 0xb6, 0x79, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x58, 0x85, 0x88, 0xb4, 0xc6, 0x89, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x68, 0x55, 0x98, 0xb4, 0xc6, 0x89, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0x78, 0x25, 0xa8, 0xa4, 0xc6, 0xa9, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x94, 0xd6, 0xa9, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x84, 0xe6, 0xb9, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x38, 0x74, 0xf6, 0xb9, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x38, 0x64, 0xf6, 0xc9, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, 0xf8, 0x28, 0x54, 0xf6, 0x06, 0xc9, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, 0xf8, 0x28, 0x44, 0xf6, 0x06, 0xe9, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, 0xf8, 0x18, 0x34, 0xf6, 0x16, 0xe9, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, 0xf8, 0x18, 0x24, 0xf6, 0x16, 0xf9, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, 0xf8, 0x08, 0x14, 0xf6, 0x26, 0xf9, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, 0xf8, 0x08, 0x04, 0xf6, 0x26, 0xf9, 0x09, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 0xf8, 0xf6, 0x36, 0xf9, 0x09, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 0xf8, 0xf6, 0x26, 0xf9, 0x19, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 0xf8, 0xf6, 0x26, 0xf9, 0x19, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf6, 0x16, 0xf9, 0x29, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf6, 0x06, 0xf9, 0x39, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf6, 0xf9, 0x59, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xe6, 0xf9, 0x69, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xd6, 0xf9, 0x79, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xd6, 0xf9, 0x79, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xc6, 0xf9, 0x89, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xc6, 0xf9, 0x89, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xb6, 0xf9, 0x99, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb6, 0xf9, 0x99, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb6, 0xf9, 0x99, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb6, 0xf9, 0x99, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb6, 0xf9, 0x99, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, }; static const unsigned long icon_colors[] = { 0xff222034, 0xffffffff, 0xff306082, 0xfffbf236, 0xff76428a, 0xff99e550, 0xffac3232, 0xff37946e, 0xff6abe30, 0xffd95763, 0xff26324a, 0xff23253a, }; static const icon_data_t icons[] = { ICON_(16), ICON_(32), ICON_(48), ICON_(64), ICON_(128), }; nsxiv-33/icon/data.h000066400000000000000000000003761474777454100145140ustar00rootroot00000000000000#ifndef ICON_DATA_H #define ICON_DATA_H typedef struct { unsigned int size; unsigned int cnt; const unsigned char *data; } icon_data_t; #define ICON_(s) { s, ARRLEN(icon_data_##s), icon_data_##s } #include "data.gen.h" #endif /* ICON_DATA_H */ nsxiv-33/icon/img2data.c000066400000000000000000000064341474777454100152670ustar00rootroot00000000000000#include #include #include #include #include /* macros */ #define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) /* globals */ static unsigned long palette[16] = {0}; static unsigned int palette_size = 0; static unsigned int run_column = 0; static unsigned int icon_sizes[16] = {0}; static unsigned int icon_sizes_size = 0; /* functions */ static void error(int eval, int err, const char *fmt, ...) { va_list ap; fflush(stdout); va_start(ap, fmt); if (fmt) vfprintf(stderr, fmt, ap); va_end(ap); if (err) fprintf(stderr, "%s%s", fmt ? ": " : "", strerror(err)); fputc('\n', stderr); if (eval) exit(eval); } static unsigned long color_to_ulong(Imlib_Color color) { return (color.alpha << 24 & 0xff000000) | (color.red << 16 & 0x00ff0000) | (color.green << 8 & 0x0000ff00) | (color.blue & 0x000000ff); } static int to_palette(unsigned long color) { unsigned int i; for (i = 0; i < palette_size; i++) { if (palette[i] == color) return i; } if (palette_size + 1 == 16) error(EXIT_FAILURE, 0, "Error: More than 16 colors in palette"); palette[palette_size] = color; return palette_size++; } static void print_run(int color, unsigned int run_length) { while (run_length > 0) { int x = run_length / 16 >= 1 ? 16 : run_length; printf("0x%02x, ", (x - 1) << 4 | color); run_length -= x; if (++run_column % 12 == 0) printf("\n\t"); } } static void print_palette(void) { unsigned int i; printf("static const unsigned long icon_colors[] = {\n\t"); for (i = 0; i < palette_size; i++) { printf("0x%08lx, ", palette[i]); if (i % 4 == 3) printf("\n\t"); } printf("\n};\n\n"); } static void print_icon_array(void) { unsigned int i; printf("static const icon_data_t icons[] = {\n"); for (i = 0; i < icon_sizes_size; i++) printf("\tICON_(%d),\n", icon_sizes[i]); printf("};\n\n"); } static unsigned int print_encoded_image(const char *path) { Imlib_Image image; Imlib_Color color; unsigned int width, height, x, y; unsigned int run_length = 1; int currentcolor = 0; int lastcolor = -1; image = imlib_load_image(path); if (image == NULL) error(EXIT_FAILURE, 0, "Error loading image: %s", path); imlib_context_set_image(image); width = imlib_image_get_width(); height = imlib_image_get_height(); if (width != height) error(EXIT_FAILURE, 0, "Image is not square: %s", path); printf("static const unsigned char icon_data_%d[] = {\n\t", width); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { imlib_image_query_pixel(x, y, &color); currentcolor = to_palette(color_to_ulong(color)); if (currentcolor != lastcolor) { if (lastcolor != -1) print_run(lastcolor, run_length); run_length = 1; } else { run_length++; } lastcolor = currentcolor; } } print_run(lastcolor, run_length); printf("\n};\n\n"); imlib_free_image(); return width; } int main(int argc, char **argv) { unsigned int img_size = 0; unsigned int i; if (argc < 2) error(EXIT_FAILURE, 0, "No icons provided"); else if (argc-1 > ARRLEN(icon_sizes)) error(EXIT_FAILURE, 0, "Too many icons"); for (i = 1; i < argc; i++) { img_size = print_encoded_image(argv[i]); run_column = 0; icon_sizes[icon_sizes_size++] = img_size; } print_palette(); print_icon_array(); return EXIT_SUCCESS; } nsxiv-33/image.c000066400000000000000000000457331474777454100137360ustar00rootroot00000000000000/* Copyright 2011-2020 Bert Muennich * Copyright 2021-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #include "nsxiv.h" #define INCLUDE_IMAGE_CONFIG #include "config.h" #include #include #include #include #include #include #include #if HAVE_LIBEXIF #include #endif #if HAVE_IMLIB2_MULTI_FRAME enum { DEF_ANIM_DELAY = 75 }; #endif #define ZOOM_MIN (zoom_levels[0] / 100) #define ZOOM_MAX (zoom_levels[ARRLEN(zoom_levels) - 1] / 100) static int calc_cache_size(void) { long cache, pages = -1, page_size = -1; if (CACHE_SIZE_MEM_PERCENTAGE <= 0) return 0; #ifdef _SC_PHYS_PAGES /* _SC_PHYS_PAGES isn't POSIX */ pages = sysconf(_SC_PHYS_PAGES); page_size = sysconf(_SC_PAGE_SIZE); #endif if (pages < 0 || page_size < 0) return CACHE_SIZE_FALLBACK; cache = (pages / 100) * CACHE_SIZE_MEM_PERCENTAGE; cache *= page_size; return MIN(cache, CACHE_SIZE_LIMIT); } void img_init(img_t *img, win_t *win) { imlib_context_set_display(win->env.dpy); imlib_context_set_visual(win->env.vis); imlib_context_set_colormap(win->env.cmap); imlib_set_cache_size(calc_cache_size()); img->im = NULL; img->win = win; img->scalemode = options->scalemode; img->zoom = options->zoom; img->zoom = MAX(img->zoom, ZOOM_MIN); img->zoom = MIN(img->zoom, ZOOM_MAX); img->checkpan = false; img->dirty = false; img->anti_alias = options->anti_alias; img->alpha_layer = options->alpha_layer; img->autoreload_pending = false; img->multi.cap = img->multi.cnt = 0; img->multi.animate = options->animate; img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0; img->multi.length = 0; img->cmod = imlib_create_color_modifier(); imlib_context_set_color_modifier(img->cmod); img->brightness = 0; img->contrast = 0; img_change_color_modifier(img, options->gamma, &img->gamma); img->ss.on = options->slideshow > 0; img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10u; } #if HAVE_LIBEXIF void exif_auto_orientate(const fileinfo_t *file) { ExifData *ed; ExifEntry *entry; int byte_order, orientation = 0; if ((ed = exif_data_new_from_file(file->path)) == NULL) return; byte_order = exif_data_get_byte_order(ed); entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); if (entry != NULL) orientation = exif_get_short(entry->data, byte_order); exif_data_unref(ed); switch (orientation) { case 5: imlib_image_orientate(1); /* fall through */ case 2: imlib_image_flip_vertical(); break; case 3: imlib_image_orientate(2); break; case 7: imlib_image_orientate(1); /* fall through */ case 4: imlib_image_flip_horizontal(); break; case 6: imlib_image_orientate(1); break; case 8: imlib_image_orientate(3); break; } } #endif #if HAVE_IMLIB2_MULTI_FRAME static void img_area_clear(int x, int y, int w, int h) { assert(x >= 0 && y >= 0); assert(w > 0 && h > 0); imlib_image_set_has_alpha(1); imlib_context_set_blend(0); imlib_context_set_color(0, 0, 0, 0); imlib_image_fill_rectangle(x, y, w, h); } static bool img_load_multiframe(img_t *img, const fileinfo_t *file) { unsigned int n, fcnt; Imlib_Image blank; Imlib_Frame_Info finfo; int px, py, pw, ph, pflag; multi_img_t *m = &img->multi; imlib_context_set_image(img->im); imlib_image_get_frame_info(&finfo); if ((fcnt = finfo.frame_count) <= 1 || !(finfo.frame_flags & IMLIB_IMAGE_ANIMATED)) return false; img->w = finfo.canvas_w; img->h = finfo.canvas_h; if (fcnt > m->cap) { m->cap = fcnt; m->frames = erealloc(m->frames, m->cap * sizeof(*m->frames)); } if ((blank = imlib_create_image(img->w, img->h)) == NULL) { error(0, 0, "%s: couldn't create image", file->name); return false; } imlib_context_set_image(blank); img_area_clear(0, 0, img->w, img->h); imlib_context_set_dither(0); imlib_context_set_anti_alias(0); imlib_context_set_color_modifier(NULL); imlib_context_set_operation(IMLIB_OP_COPY); /* * Imlib2 gives back a "raw frame", we need to blend it on top of the * previous frame ourselves if necessary to get the fully decoded frame. */ pflag = m->length = m->cnt = m->sel = 0; px = py = pw = ph = 0; for (n = 1; n <= fcnt; ++n) { Imlib_Image frame, canvas; int sx, sy, sw, sh; bool has_alpha; imlib_context_set_image(m->cnt < 1 ? blank : m->frames[m->cnt - 1].im); canvas = imlib_clone_image(); if ((frame = imlib_load_image_frame(file->path, n)) != NULL) { imlib_context_set_image(frame); imlib_image_set_changes_on_disk(); /* see img_load() for rationale */ imlib_image_get_frame_info(&finfo); } /* NOTE: the underlying file can end up changing during load. * so check if frame_count, w, h are all still the same or not. */ if (canvas == NULL || frame == NULL || finfo.frame_count != (int)fcnt || finfo.canvas_w != img->w || finfo.canvas_h != img->h) { img_free(frame, false); img_free(canvas, false); error(0, 0, "%s: failed to load frame %d", file->name, n); break; } sx = finfo.frame_x; sy = finfo.frame_y; sw = finfo.frame_w; sh = finfo.frame_h; has_alpha = imlib_image_has_alpha(); imlib_context_set_image(canvas); /* the dispose flags are explained in Imlib2's header */ if (pflag & IMLIB_FRAME_DISPOSE_CLEAR) { img_area_clear(px, py, pw, ph); } else if (pflag & IMLIB_FRAME_DISPOSE_PREV) { Imlib_Image p = m->cnt < 2 ? blank : m->frames[m->cnt - 2].im; assert(m->cnt > 0); img_area_clear(0, 0, img->w, img->h); imlib_blend_image_onto_image(p, 1, px, py, pw, ph, px, py, pw, ph); } pflag = finfo.frame_flags; if (pflag & (IMLIB_FRAME_DISPOSE_CLEAR | IMLIB_FRAME_DISPOSE_PREV)) { /* remember these so we can "dispose" them before blending next frame */ px = sx; py = sy; pw = sw; ph = sh; } assert(imlib_context_get_operation() == IMLIB_OP_COPY); imlib_image_set_has_alpha(has_alpha); imlib_context_set_blend(!!(finfo.frame_flags & IMLIB_FRAME_BLEND)); imlib_blend_image_onto_image(frame, has_alpha, 0, 0, sw, sh, sx, sy, sw, sh); m->frames[m->cnt].im = canvas; m->frames[m->cnt].delay = m->framedelay ? m->framedelay : (finfo.frame_delay ? finfo.frame_delay : DEF_ANIM_DELAY); m->length += m->frames[m->cnt].delay; m->cnt++; img_free(frame, false); } img_free(blank, false); imlib_context_set_color_modifier(img->cmod); /* restore cmod */ if (m->cnt > 1) { img_free(img->im, false); img->im = m->frames[0].im; } else if (m->cnt == 1) { img_free(m->frames[0].im, false); m->cnt = 0; } imlib_context_set_image(img->im); return m->cnt > 0; } #endif /* HAVE_IMLIB2_MULTI_FRAME */ Imlib_Image img_open(const fileinfo_t *file) { struct stat st; Imlib_Image im = NULL; if (access(file->path, R_OK) == 0 && stat(file->path, &st) == 0 && S_ISREG(st.st_mode) && #if HAVE_IMLIB2_MULTI_FRAME (im = imlib_load_image_frame(file->path, 1)) != NULL) #else (im = imlib_load_image_immediately(file->path)) != NULL) #endif { imlib_context_set_image(im); } /* UPGRADE: Imlib2 v1.10.0: better error reporting with * imlib_get_error() + imlib_strerror() */ if (im == NULL && (file->flags & FF_WARN)) error(0, 0, "%s: Error opening image", file->name); return im; } bool img_load(img_t *img, const fileinfo_t *file) { const char *fmt; bool animated = false; if ((img->im = img_open(file)) == NULL) return false; /* ensure that the image's timestamp is checked when loading from cache * to avoid issues like: https://codeberg.org/nsxiv/nsxiv/issues/436 */ imlib_image_set_changes_on_disk(); /* UPGRADE: Imlib2 v1.7.5: remove these exif related ifdefs */ /* since v1.7.5, Imlib2 can parse exif orientation from jpeg files. * this version also happens to be the first one which defines the * IMLIB2_VERSION macro. */ #if HAVE_LIBEXIF && !defined(IMLIB2_VERSION) exif_auto_orientate(file); #endif #if HAVE_IMLIB2_MULTI_FRAME animated = img_load_multiframe(img, file); #endif (void)fmt; /* maybe unused */ #if HAVE_LIBEXIF && defined(IMLIB2_VERSION) if ((fmt = imlib_image_format()) != NULL) { if (!STREQ(fmt, "jpeg") && !STREQ(fmt, "jpg")) exif_auto_orientate(file); } #endif /* for animated images, we want the _canvas_ width/height, which * img_load_multiframe() sets already. */ if (!animated) { img->w = imlib_image_get_width(); img->h = imlib_image_get_height(); } img->checkpan = true; img->dirty = true; return true; } CLEANUP void img_free(Imlib_Image im, bool decache) { if (im != NULL) { imlib_context_set_image(im); decache ? imlib_free_image_and_decache() : imlib_free_image(); } } CLEANUP void img_close(img_t *img, bool decache) { unsigned int i; if (img->multi.cnt > 0) { for (i = 0; i < img->multi.cnt; i++) img_free(img->multi.frames[i].im, decache); /* NOTE: the above only decaches the "composed frames", * and not the "raw frame" that's associated with the file. * which leads to issues like: https://codeberg.org/nsxiv/nsxiv/issues/456 */ #if HAVE_IMLIB2_MULTI_FRAME #if IMLIB2_VERSION >= IMLIB2_VERSION_(1, 12, 0) if (decache) imlib_image_decache_file(files[fileidx].path); #else /* UPGRADE: Imlib2 v1.12.0: remove this hack */ /* HACK: try to reload all the frames and forcefully decache them * if imlib_image_decache_file() isn't available. */ for (i = 0; decache && i < img->multi.cnt; i++) img_free(imlib_load_image_frame(files[fileidx].path, i + 1), true); #endif #endif img->multi.cnt = 0; img->im = NULL; } else if (img->im != NULL) { img_free(img->im, decache); img->im = NULL; } } static void img_check_pan(img_t *img, bool moved) { win_t *win; float w, h, ox, oy; win = img->win; w = img->w * img->zoom; h = img->h * img->zoom; ox = img->x; oy = img->y; if (w < win->w) img->x = (win->w - w) / 2; else if (img->x > 0) img->x = 0; else if (img->x + w < win->w) img->x = win->w - w; if (h < win->h) img->y = (win->h - h) / 2; else if (img->y > 0) img->y = 0; else if (img->y + h < win->h) img->y = win->h - h; if (!moved && (ox != img->x || oy != img->y)) img->dirty = true; } static bool img_fit(img_t *img) { float z, zw, zh; if (img->scalemode == SCALE_ZOOM) return false; zw = (float)img->win->w / (float)img->w; zh = (float)img->win->h / (float)img->h; switch (img->scalemode) { case SCALE_FILL: z = MAX(zw, zh); break; case SCALE_WIDTH: z = zw; break; case SCALE_HEIGHT: z = zh; break; default: z = MIN(zw, zh); break; } z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : ZOOM_MAX); if (ABS(img->zoom - z) > 1.0 / MAX(img->w, img->h)) { img->zoom = z; img->dirty = true; return true; } else { return false; } } void img_render(img_t *img) { win_t *win; int sx, sy, sw, sh; int dx, dy, dw, dh; Imlib_Image bg; win = img->win; img_fit(img); if (img->checkpan) { img_check_pan(img, false); img->checkpan = false; } if (!img->dirty) return; /* calculate source and destination offsets: * - part of image drawn on full window, or * - full image drawn on part of window */ if (img->x <= 0) { sx = -img->x / img->zoom + 0.5; sw = win->w / img->zoom; dx = 0; dw = win->w; } else { sx = 0; sw = img->w; dx = img->x; dw = MAX(img->w * img->zoom, 1); } if (img->y <= 0) { sy = -img->y / img->zoom + 0.5; sh = win->h / img->zoom; dy = win->bar.top ? win->bar.h : 0; dh = win->h; } else { sy = 0; sh = img->h; dy = img->y + (win->bar.top ? win->bar.h : 0); dh = MAX(img->h * img->zoom, 1); } win_clear(win); imlib_context_set_image(img->im); imlib_context_set_anti_alias(img->anti_alias); imlib_context_set_drawable(win->buf.pm); /* manual blending, for performance reasons. * see https://phab.enlightenment.org/T8969#156167 for more details. */ if (imlib_image_has_alpha()) { if ((bg = imlib_create_image(dw, dh)) == NULL) { error(0, ENOMEM, "Failed to create image"); goto fallback; } imlib_context_set_image(bg); imlib_image_set_has_alpha(0); if (img->alpha_layer) { int i, c, r; uint32_t col[2] = { 0xFF666666, 0xFF999999 }; uint32_t *data = imlib_image_get_data(); for (r = 0; r < dh; r++) { i = r * dw; if (r == 0 || r == 8) { for (c = 0; c < dw; c++) data[i++] = col[!(c & 8) ^ !r]; } else { memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0])); } } imlib_image_put_back_data(data); } else { XColor c = win->win_bg; imlib_context_set_color(c.red >> 8, c.green >> 8, c.blue >> 8, 0xFF); imlib_image_fill_rectangle(0, 0, dw, dh); } imlib_context_set_blend(1); imlib_context_set_operation(IMLIB_OP_COPY); imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh); imlib_context_set_color_modifier(NULL); imlib_render_image_on_drawable(dx, dy); imlib_free_image(); imlib_context_set_color_modifier(img->cmod); } else { fallback: imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh); } img->dirty = false; } bool img_fit_win(img_t *img, scalemode_t sm) { float oz; oz = img->zoom; img->scalemode = sm; if (img_fit(img)) { img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz; img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz; img->checkpan = true; return true; } else { return false; } } bool img_zoom_to(img_t *img, float z) { int x, y; if (ZOOM_MIN <= z && z <= ZOOM_MAX) { win_cursor_pos(img->win, &x, &y); if (x < 0 || (unsigned int)x >= img->win->w || y < 0 || (unsigned int)y >= img->win->h) { x = img->win->w / 2; y = img->win->h / 2; } img->x = x - (x - img->x) * z / img->zoom; img->y = y - (y - img->y) * z / img->zoom; img->zoom = z; img->scalemode = SCALE_ZOOM; img->dirty = img->checkpan = true; return true; } else { return false; } } bool img_zoom(img_t *img, int d) { int i = d > 0 ? 0 : (int)ARRLEN(zoom_levels) - 1; while (i >= 0 && i < (int)ARRLEN(zoom_levels) && (d > 0 ? zoom_levels[i] / 100 <= img->zoom : zoom_levels[i] / 100 >= img->zoom)) { i += d; } i = MIN(MAX(i, 0), (int)ARRLEN(zoom_levels) - 1); return img_zoom_to(img, zoom_levels[i] / 100); } bool img_pos(img_t *img, float x, float y) { float ox, oy; ox = img->x; oy = img->y; img->x = x; img->y = y; img_check_pan(img, true); if (ox != img->x || oy != img->y) { img->dirty = true; return true; } else { return false; } } static bool img_move(img_t *img, float dx, float dy) { return img_pos(img, img->x + dx, img->y + dy); } bool img_pan(img_t *img, direction_t dir, int d) { /* d < 0: screen-wise * d = 0: 1/PAN_FRACTION of screen * d > 0: num of pixels */ float x, y; if (d > 0) { x = y = MAX(1, (float)d * img->zoom); } else { x = img->win->w / (d < 0 ? 1 : PAN_FRACTION); y = img->win->h / (d < 0 ? 1 : PAN_FRACTION); } switch (dir) { case DIR_LEFT: return img_move(img, x, 0.0); case DIR_RIGHT: return img_move(img, -x, 0.0); case DIR_UP: return img_move(img, 0.0, y); case DIR_DOWN: return img_move(img, 0.0, -y); } return false; } bool img_pan_center(img_t *img) { float x, y; x = (img->win->w - img->w * img->zoom) / 2.0; y = (img->win->h - img->h * img->zoom) / 2.0; return img_pos(img, x, y); } bool img_pan_edge(img_t *img, direction_t dir) { float ox, oy; ox = img->x; oy = img->y; if (dir & DIR_LEFT) img->x = 0; if (dir & DIR_RIGHT) img->x = img->win->w - img->w * img->zoom; if (dir & DIR_UP) img->y = 0; if (dir & DIR_DOWN) img->y = img->win->h - img->h * img->zoom; img_check_pan(img, true); if (ox != img->x || oy != img->y) { img->dirty = true; return true; } else { return false; } } void img_rotate(img_t *img, degree_t d) { unsigned int i, tmp; float ox, oy; imlib_context_set_image(img->im); imlib_image_orientate(d); for (i = 0; i < img->multi.cnt; i++) { if (i != img->multi.sel) { imlib_context_set_image(img->multi.frames[i].im); imlib_image_orientate(d); } } if (d == DEGREE_90 || d == DEGREE_270) { ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom; oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom; img->x = oy + (int)(img->win->w - img->win->h) / 2; img->y = ox + (int)(img->win->h - img->win->w) / 2; tmp = img->w; img->w = img->h; img->h = tmp; img->checkpan = true; } img->dirty = true; } void img_flip(img_t *img, flipdir_t d) { unsigned int i; void (*imlib_flip_op[3])(void) = { imlib_image_flip_horizontal, imlib_image_flip_vertical, imlib_image_flip_diagonal }; d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1; if (d < 0 || d >= ARRLEN(imlib_flip_op)) return; imlib_context_set_image(img->im); imlib_flip_op[d](); for (i = 0; i < img->multi.cnt; i++) { if (i != img->multi.sel) { imlib_context_set_image(img->multi.frames[i].im); imlib_flip_op[d](); } } img->dirty = true; } void img_toggle_antialias(img_t *img) { img->anti_alias = !img->anti_alias; imlib_context_set_image(img->im); imlib_context_set_anti_alias(img->anti_alias); img->dirty = true; } static double steps_to_range(int d, double max, double offset) { return offset + d * ((d <= 0 ? 1.0 : (max - 1.0)) / CC_STEPS); } void img_update_color_modifiers(img_t *img) { assert(imlib_context_get_color_modifier() == img->cmod); imlib_reset_color_modifier(); if (img->gamma != 0) imlib_modify_color_modifier_gamma(steps_to_range(img->gamma, GAMMA_MAX, 1.0)); if (img->brightness != 0) imlib_modify_color_modifier_brightness(steps_to_range(img->brightness, BRIGHTNESS_MAX, 0.0)); if (img->contrast != 0) imlib_modify_color_modifier_contrast(steps_to_range(img->contrast, CONTRAST_MAX, 1.0)); img->dirty = true; } bool img_change_color_modifier(img_t *img, int d, int *target) { int value = d == 0 ? 0 : MIN(MAX(*target + d, -CC_STEPS), CC_STEPS); if (*target == value) return false; *target = value; img_update_color_modifiers(img); return true; } static bool img_frame_goto(img_t *img, int n) { if (n < 0 || (unsigned int)n >= img->multi.cnt || (unsigned int)n == img->multi.sel) return false; img->multi.sel = n; img->im = img->multi.frames[n].im; imlib_context_set_image(img->im); img->w = imlib_image_get_width(); img->h = imlib_image_get_height(); img->checkpan = true; img->dirty = true; return true; } bool img_frame_navigate(img_t *img, int d) { if (img->multi.cnt == 0 || d == 0) return false; d += img->multi.sel; d = MAX(0, MIN(d, (int)img->multi.cnt - 1)); return img_frame_goto(img, d); } bool img_frame_animate(img_t *img) { if (img->multi.cnt > 0) return img_frame_goto(img, (img->multi.sel + 1) % img->multi.cnt); else return false; } nsxiv-33/main.c000066400000000000000000000550061474777454100135720ustar00rootroot00000000000000/* Copyright 2011-2020 Bert Muennich * Copyright 2021-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #include "nsxiv.h" #define INCLUDE_MAPPINGS_CONFIG #include "commands.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MODMASK(mask) (USED_MODMASK & (mask)) #define BAR_SEP " " #define TV_DIFF(t1,t2) (((t1)->tv_sec - (t2)->tv_sec ) * 1000 + \ ((t1)->tv_usec - (t2)->tv_usec) / 1000) #define TV_ADD_MSEC(tv, t) \ do { \ (tv)->tv_sec += (t) / 1000; \ (tv)->tv_usec += (t) % 1000 * 1000; \ } while (0) typedef struct { int err; char *cmd; } extcmd_t; /* these are not declared in nsxiv.h, as it causes too many -Wshadow warnings */ arl_t arl; img_t img; tns_t tns; win_t win; appmode_t mode; fileinfo_t *files; int filecnt, fileidx; int alternate; int markcnt; int markidx; int prefix; const XButtonEvent *xbutton_ev; static void autoreload(void); static bool extprefix; static bool resized = false; static struct { extcmd_t f, ft; int fd; pid_t pid; } info, wintitle; static struct { extcmd_t f; bool warned; } keyhandler; static struct { timeout_f handler; struct timeval when; bool active; } timeouts[] = { { autoreload }, { redraw }, { reset_cursor }, { slideshow }, { animate }, { clear_resize }, }; /* * function implementations */ static void cleanup(void) { img_close(&img, false); arl_cleanup(&arl); tns_free(&tns); win_close(&win); } static bool xgetline(char **lineptr, size_t *n) { ssize_t len = getdelim(lineptr, n, options->using_null ? '\0' : '\n', stdin); if (!options->using_null && len > 0 && (*lineptr)[len - 1] == '\n') (*lineptr)[len - 1] = '\0'; return len > 0; } static int fncmp(const void *a, const void *b) { return strcoll(((fileinfo_t *)a)->name, ((fileinfo_t *)b)->name); } static void check_add_file(const char *filename, bool given) { char *path; if (*filename == '\0') return; if (access(filename, R_OK) < 0 || (path = realpath(filename, NULL)) == NULL) { if (given) error(0, errno, "%s", filename); return; } if (fileidx == filecnt) { filecnt *= 2; files = erealloc(files, filecnt * sizeof(*files)); memset(&files[filecnt / 2], 0, filecnt / 2 * sizeof(*files)); } files[fileidx].name = estrdup(filename); files[fileidx].path = path; if (given) files[fileidx].flags |= FF_WARN; fileidx++; } static void add_entry(const char *entry_name) { int start; char *filename; struct stat fstats; r_dir_t dir; if (stat(entry_name, &fstats) < 0) { error(0, errno, "%s", entry_name); return; } if (!S_ISDIR(fstats.st_mode)) { check_add_file(entry_name, true); } else { if (r_opendir(&dir, entry_name, options->recursive) < 0) { error(0, errno, "%s", entry_name); return; } start = fileidx; while ((filename = r_readdir(&dir, true)) != NULL) { check_add_file(filename, false); free(filename); } r_closedir(&dir); if (fileidx - start > 1) qsort(files + start, fileidx - start, sizeof(*files), fncmp); } } void remove_file(int n, bool manual) { if (n < 0 || n >= filecnt) return; if (filecnt == 1) { if (!manual) fprintf(stderr, "%s: no more files to display, aborting\n", progname); exit(manual ? EXIT_SUCCESS : EXIT_FAILURE); } if (files[n].flags & FF_MARK) markcnt--; if (files[n].path != files[n].name) free((void *)files[n].path); free((void *)files[n].name); if (tns.thumbs != NULL) tns_unload(&tns, n); if (n + 1 < filecnt) { if (tns.thumbs != NULL) { memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) * sizeof(*tns.thumbs)); memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs)); } memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files)); } filecnt--; if (fileidx > n || fileidx == filecnt) fileidx--; if (alternate > n || alternate == filecnt) alternate--; if (markidx > n || markidx == filecnt) markidx--; } void set_timeout(timeout_f handler, int time, bool overwrite) { unsigned int i; for (i = 0; i < ARRLEN(timeouts); i++) { if (timeouts[i].handler == handler) { if (!timeouts[i].active || overwrite) { gettimeofday(&timeouts[i].when, 0); TV_ADD_MSEC(&timeouts[i].when, time); timeouts[i].active = true; } return; } } } void reset_timeout(timeout_f handler) { unsigned int i; for (i = 0; i < ARRLEN(timeouts); i++) { if (timeouts[i].handler == handler) { timeouts[i].active = false; return; } } } static bool check_timeouts(int *t) { int i = 0, tdiff, tmin; struct timeval now; for (i = 0; i < (int)ARRLEN(timeouts); ++i) { if (timeouts[i].active) { gettimeofday(&now, 0); tdiff = TV_DIFF(&timeouts[i].when, &now); if (tdiff <= 0) { timeouts[i].active = false; if (timeouts[i].handler != NULL) timeouts[i].handler(); } } } tmin = INT_MAX; gettimeofday(&now, 0); for (i = 0; i < (int)ARRLEN(timeouts); ++i) { if (timeouts[i].active) { tdiff = TV_DIFF(&timeouts[i].when, &now); tmin = MIN(tmin, tdiff); } } if (tmin != INT_MAX && t != NULL) *t = MAX(tmin, 0); return tmin != INT_MAX; } static void autoreload(void) { if (img.autoreload_pending) { img_close(&img, true); /* load_image() sets autoreload_pending to false */ load_image(fileidx); redraw(); } else { assert(!"unreachable"); } } static void kill_close(pid_t pid, int *fd) { if (fd != NULL && *fd != -1) { kill(pid, SIGTERM); close(*fd); *fd = -1; } } static void close_title(void) { kill_close(wintitle.pid, &wintitle.fd); } static void read_title(void) { ssize_t n; char buf[512]; if ((n = read(wintitle.fd, buf, sizeof(buf) - 1)) > 0) { buf[n] = '\0'; win_set_title(&win, buf, n); } close_title(); } static void open_title(void) { char *argv[8]; char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12]; if (wintitle.f.err) return; close_title(); if (mode == MODE_IMAGE) { snprintf(w, ARRLEN(w), "%d", img.w); snprintf(h, ARRLEN(h), "%d", img.h); snprintf(z, ARRLEN(z), "%d", (int)(img.zoom * 100)); } snprintf(fidx, ARRLEN(fidx), "%d", fileidx + 1); snprintf(fcnt, ARRLEN(fcnt), "%d", filecnt); construct_argv(argv, ARRLEN(argv), wintitle.f.cmd, files[fileidx].path, fidx, fcnt, w, h, z, NULL); wintitle.pid = spawn(&wintitle.fd, NULL, O_NONBLOCK, argv); } void close_info(void) { kill_close(info.pid, &info.fd); } void open_info(void) { char *argv[6], w[12] = "", h[12] = ""; char *cmd = mode == MODE_IMAGE ? info.f.cmd : info.ft.cmd; bool ferr = mode == MODE_IMAGE ? info.f.err : info.ft.err; if (ferr || info.fd >= 0 || win.bar.h == 0) return; win.bar.l.buf[0] = '\0'; if (mode == MODE_IMAGE) { snprintf(w, sizeof(w), "%d", img.w); snprintf(h, sizeof(h), "%d", img.h); } construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h, files[fileidx].path, NULL); info.pid = spawn(&info.fd, NULL, O_NONBLOCK, argv); } static void read_info(void) { ssize_t i, n; if ((n = read(info.fd, win.bar.l.buf, win.bar.l.size - 1)) > 0) { win.bar.l.buf[n] = '\0'; for (i = 0; i < n; ++i) { if (win.bar.l.buf[i] == '\n') win.bar.l.buf[i] = ' '; } win_draw(&win); } close_info(); } void load_image(int new) { bool prev = new < fileidx; static int current; if (new < 0 || new >= filecnt) return; if (win.xwin != None) win_set_cursor(&win, CURSOR_WATCH); reset_timeout(autoreload); reset_timeout(slideshow); if (new != current) { alternate = current; img.autoreload_pending = false; } img_close(&img, false); while (!img_load(&img, &files[new])) { remove_file(new, false); if (new >= filecnt) new = filecnt - 1; else if (new > 0 && prev) new -= 1; } files[new].flags &= ~FF_WARN; fileidx = current = new; arl_add(&arl, files[fileidx].path); if (img.multi.cnt > 0 && img.multi.animate) set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); else reset_timeout(animate); } bool mark_image(int n, bool on) { markidx = n; if (!!(files[n].flags & FF_MARK) != on) { files[n].flags ^= FF_MARK; markcnt += on ? 1 : -1; if (mode == MODE_THUMB) tns_mark(&tns, n, on); return true; } return false; } static void bar_put(win_bar_t *bar, const char *fmt, ...) { size_t len = bar->size - (bar->p - bar->buf), n; va_list ap; va_start(ap, fmt); n = vsnprintf(bar->p, len, fmt, ap); bar->p += MIN(len, n); va_end(ap); } static void update_info(void) { unsigned int i, fn, fw; const char *mark; win_bar_t *l = &win.bar.l, *r = &win.bar.r; static struct { const char *filepath; int fileidx; float zoom; appmode_t mode; } prev; if (prev.fileidx != fileidx || prev.mode != mode || (prev.filepath == NULL || !STREQ(prev.filepath, files[fileidx].path))) { close_info(); open_info(); open_title(); } else if (mode == MODE_IMAGE && prev.zoom != img.zoom) { open_title(); } if (win.bar.h == 0 || extprefix) return; free((char *)prev.filepath); prev.filepath = estrdup(files[fileidx].path); prev.fileidx = fileidx; prev.zoom = img.zoom; prev.mode = mode; for (fw = 0, i = filecnt; i > 0; fw++, i /= 10) ; mark = files[fileidx].flags & FF_MARK ? "* " : ""; l->p = l->buf; r->p = r->buf; if (mode == MODE_THUMB) { if (tns.loadnext < tns.end) bar_put(r, "Loading... %0*d | ", fw, tns.loadnext + 1); else if (tns.initnext < filecnt) bar_put(r, "Caching... %0*d | ", fw, tns.initnext + 1); bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt); if (info.ft.err) strncpy(l->buf, files[fileidx].name, l->size); } else { bar_put(r, "%s", mark); if (img.ss.on) { if (img.ss.delay % 10 != 0) bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10); else bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10); } if (img.gamma) bar_put(r, "G%+d" BAR_SEP, img.gamma); if (img.brightness) bar_put(r, "B%+d" BAR_SEP, img.brightness); if (img.contrast) bar_put(r, "C%+d" BAR_SEP, img.contrast); bar_put(r, "%3d%%" BAR_SEP, (int)(img.zoom * 100.0)); if (img.multi.cnt > 0) { for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10) ; bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt); } bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt); if (info.f.err) strncpy(l->buf, files[fileidx].name, l->size); } } int nav_button(void) { int x, y, nw; if (NAV_WIDTH == 0) return 1; win_cursor_pos(&win, &x, &y); nw = NAV_IS_REL ? win.w * NAV_WIDTH / 100 : NAV_WIDTH; nw = MIN(nw, ((int)win.w + 1) / 2); if (x < nw) return 0; else if (x < (int)win.w - nw) return 1; else return 2; } void redraw(void) { int t; if (mode == MODE_IMAGE) { img_render(&img); if (img.ss.on) { t = img.ss.delay * 100; if (img.multi.cnt > 0 && img.multi.animate) t = MAX(t, img.multi.length); set_timeout(slideshow, t, false); } } else { tns_render(&tns); } update_info(); win_draw(&win); reset_timeout(redraw); reset_cursor(); } void reset_cursor(void) { int c; unsigned int i; cursor_t cursor = CURSOR_NONE; if (mode == MODE_IMAGE) { for (i = 0; i < ARRLEN(timeouts); i++) { if (timeouts[i].handler == reset_cursor) { if (timeouts[i].active) { c = nav_button(); c = MAX(fileidx > 0 ? 0 : 1, c); c = MIN(fileidx + 1 < filecnt ? 2 : 1, c); cursor = imgcursor[c]; } break; } } } else { if (tns.loadnext < tns.end || tns.initnext < filecnt) cursor = CURSOR_WATCH; else cursor = CURSOR_ARROW; } win_set_cursor(&win, cursor); } void animate(void) { if (img_frame_animate(&img)) { set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); redraw(); } } void slideshow(void) { load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0); redraw(); } void clear_resize(void) { resized = false; } static Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg) { return ev->type == ButtonPress || ev->type == KeyPress; } void handle_key_handler(bool init) { extprefix = init; if (win.bar.h == 0) return; if (init) { snprintf(win.bar.r.buf, win.bar.r.size, "Getting key handler input (%s to abort)...", XKeysymToString(KEYHANDLER_ABORT)); } else { /* abort */ update_info(); } win_draw(&win); } static bool run_key_handler(const char *key, unsigned int mask) { FILE *pfs; bool marked = mode == MODE_THUMB && markcnt > 0; bool changed = false; pid_t pid; int writefd, f, i; int fcnt = marked ? markcnt : 1; char kstr[32]; struct stat *oldst, st; XEvent dump; char *argv[3]; if (keyhandler.f.err) { if (!keyhandler.warned) { error(0, keyhandler.f.err, "%s", keyhandler.f.cmd); keyhandler.warned = true; } return false; } if (key == NULL) return false; strncpy(win.bar.r.buf, "Running key handler...", win.bar.r.size); win_draw(&win); win_set_cursor(&win, CURSOR_WATCH); setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1); snprintf(kstr, sizeof(kstr), "%s%s%s%s", mask & ControlMask ? "C-" : "", mask & Mod1Mask ? "M-" : "", mask & ShiftMask ? "S-" : "", key); construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL); if ((pid = spawn(NULL, &writefd, 0x0, argv)) < 0) return false; if ((pfs = fdopen(writefd, "w")) == NULL) { error(0, errno, "open pipe"); close(writefd); return false; } oldst = emalloc(fcnt * sizeof(*oldst)); for (f = i = 0; f < fcnt; i++) { if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { stat(files[i].path, &oldst[f]); fprintf(pfs, "%s%c", files[i].name, options->using_null ? '\0' : '\n'); f++; } } fclose(pfs); while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) ; for (f = i = 0; f < fcnt; i++) { if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { if (stat(files[i].path, &st) != 0 || memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0) { if (tns.thumbs != NULL) { tns_unload(&tns, i); tns.loadnext = MIN(tns.loadnext, i); } changed = true; } f++; } } /* drop user input events that occurred while running the key handler */ while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL)) ; if (mode == MODE_IMAGE && changed) { img_close(&img, true); load_image(fileidx); } else { update_info(); } free(oldst); reset_cursor(); return true; } static bool process_bindings(const keymap_t *bindings, unsigned int len, KeySym ksym_or_button, unsigned int state, unsigned int implicit_mod) { unsigned int i; bool dirty = false; for (i = 0; i < len; i++) { if (bindings[i].ksym_or_button == ksym_or_button && MODMASK(bindings[i].mask | implicit_mod) == MODMASK(state) && bindings[i].cmd.func != NULL && (bindings[i].cmd.mode == MODE_ALL || bindings[i].cmd.mode == mode)) { if (bindings[i].cmd.func(bindings[i].arg)) dirty = true; } } return dirty; } static void on_keypress(XKeyEvent *kev) { unsigned int sh = 0; KeySym ksym, shksym; char dummy, key; bool dirty = false; XLookupString(kev, &key, 1, &ksym, NULL); if (kev->state & ShiftMask) { kev->state &= ~ShiftMask; XLookupString(kev, &dummy, 1, &shksym, NULL); kev->state |= ShiftMask; if (ksym != shksym) sh = ShiftMask; } if (IsModifierKey(ksym)) return; if (extprefix && ksym == KEYHANDLER_ABORT && MODMASK(kev->state) == 0) { handle_key_handler(false); } else if (extprefix) { if ((dirty = run_key_handler(XKeysymToString(ksym), kev->state & ~sh))) extprefix = false; else handle_key_handler(false); } else if (key >= '0' && key <= '9') { /* number prefix for commands */ prefix = prefix * 10 + (int)(key - '0'); return; } else { dirty = process_bindings(keys, ARRLEN(keys), ksym, kev->state, sh); } if (dirty) redraw(); prefix = 0; } static void on_buttonpress(const XButtonEvent *bev) { bool dirty = false; if (mode == MODE_IMAGE) { set_timeout(reset_cursor, TO_CURSOR_HIDE, true); reset_cursor(); dirty = process_bindings(buttons_img, ARRLEN(buttons_img), bev->button, bev->state, 0); } else { /* thumbnail mode */ dirty = process_bindings(buttons_tns, ARRLEN(buttons_tns), bev->button, bev->state, 0); } if (dirty) redraw(); prefix = 0; } static void run(void) { enum { FD_X, FD_INFO, FD_TITLE, FD_ARL, FD_CNT }; struct pollfd pfd[FD_CNT]; int timeout = 0; bool discard, init_thumb, load_thumb, to_set; XEvent ev, nextev; xbutton_ev = &ev.xbutton; while (true) { to_set = check_timeouts(&timeout); init_thumb = mode == MODE_THUMB && tns.initnext < filecnt; load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end; if ((init_thumb || load_thumb || to_set || info.fd != -1 || arl.fd != -1) && XPending(win.env.dpy) == 0) { if (load_thumb) { set_timeout(redraw, TO_REDRAW_THUMBS, false); if (!tns_load(&tns, tns.loadnext, false, false)) { remove_file(tns.loadnext, false); tns.dirty = true; } if (tns.loadnext >= tns.end) { open_info(); redraw(); } } else if (init_thumb) { set_timeout(redraw, TO_REDRAW_THUMBS, false); if (!tns_load(&tns, tns.initnext, false, true)) remove_file(tns.initnext, false); } else { pfd[FD_X].fd = ConnectionNumber(win.env.dpy); pfd[FD_INFO].fd = info.fd; pfd[FD_TITLE].fd = wintitle.fd; pfd[FD_ARL].fd = arl.fd; pfd[FD_X].events = pfd[FD_ARL].events = POLLIN; pfd[FD_INFO].events = pfd[FD_TITLE].events = 0; if (poll(pfd, ARRLEN(pfd), to_set ? timeout : -1) < 0) continue; if (pfd[FD_INFO].revents & POLLHUP) read_info(); if (pfd[FD_TITLE].revents & POLLHUP) read_title(); if ((pfd[FD_ARL].revents & POLLIN) && arl_handle(&arl)) { img.autoreload_pending = true; set_timeout(autoreload, TO_AUTORELOAD, true); } } continue; } do { XNextEvent(win.env.dpy, &ev); discard = false; if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) { XPeekEvent(win.env.dpy, &nextev); switch (ev.type) { case ConfigureNotify: case MotionNotify: discard = ev.type == nextev.type; break; case KeyPress: discard = (nextev.type == KeyPress || nextev.type == KeyRelease) && ev.xkey.keycode == nextev.xkey.keycode; break; } } } while (discard); switch (ev.type) { case ButtonPress: on_buttonpress(&ev.xbutton); break; case ClientMessage: if ((Atom)ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW]) cg_quit(EXIT_SUCCESS); break; case DestroyNotify: cg_quit(EXIT_FAILURE); break; case ConfigureNotify: if (win_configure(&win, &ev.xconfigure)) { if (mode == MODE_IMAGE) { img.dirty = true; img.checkpan = true; } else { tns.dirty = true; } if (!resized) { redraw(); set_timeout(clear_resize, TO_REDRAW_RESIZE, false); resized = true; } else { set_timeout(redraw, TO_REDRAW_RESIZE, false); } } break; case KeyPress: on_keypress(&ev.xkey); break; case MotionNotify: if (mode == MODE_IMAGE) { set_timeout(reset_cursor, TO_CURSOR_HIDE, true); reset_cursor(); } break; } } } static void setup_signal(int sig, void (*handler)(int sig), int flags) { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = flags; if (sigaction(sig, &sa, NULL) < 0) error(EXIT_FAILURE, errno, "signal %d", sig); } int main(int argc, char *argv[]) { int i; size_t n; const char *homedir, *dsuffix = ""; setup_signal(SIGCHLD, SIG_DFL, SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT); setup_signal(SIGPIPE, SIG_IGN, 0); setlocale(LC_COLLATE, ""); parse_options(argc, argv); if (options->clean_cache) { tns_init(&tns, NULL, NULL, NULL, NULL); tns_clean_cache(); exit(EXIT_SUCCESS); } if (options->filecnt == 0 && !options->from_stdin) { print_usage(stderr); exit(EXIT_FAILURE); } if (options->recursive || options->from_stdin) filecnt = 1024; else filecnt = options->filecnt; files = ecalloc(filecnt, sizeof(*files)); fileidx = 0; if (options->from_stdin) { char *filename = NULL; n = 0; while (xgetline(&filename, &n)) add_entry(filename); free(filename); } for (i = 0; i < options->filecnt; i++) add_entry(options->filenames[i]); if (fileidx == 0) error(EXIT_FAILURE, 0, "No valid image file given, aborting"); filecnt = fileidx; fileidx = options->startnum < filecnt ? options->startnum : 0; if (options->background_cache && !options->private_mode) { pid_t ppid = getpid(); /* to check if parent is still alive or not */ switch (fork()) { case 0: tns_init(&tns, files, &filecnt, &fileidx, NULL); while (filecnt > 0 && getppid() == ppid) { tns_load(&tns, filecnt - 1, false, true); remove_file(filecnt - 1, true); } exit(0); break; case -1: error(0, errno, "fork failed"); break; } } win_init(&win); img_init(&img, &win); arl_init(&arl); if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') { homedir = getenv("HOME"); dsuffix = "/.config"; } if (homedir != NULL) { extcmd_t *cmd[] = { &info.f, &info.ft, &keyhandler.f, &wintitle.f }; const char *name[] = { "image-info", "thumb-info", "key-handler", "win-title" }; const char *s = "/nsxiv/exec/"; for (i = 0; i < (int)ARRLEN(cmd); i++) { n = strlen(homedir) + strlen(dsuffix) + strlen(s) + strlen(name[i]) + 1; cmd[i]->cmd = emalloc(n); snprintf(cmd[i]->cmd, n, "%s%s%s%s", homedir, dsuffix, s, name[i]); if (access(cmd[i]->cmd, X_OK) != 0) cmd[i]->err = errno; } } else { error(0, 0, "Exec directory not found"); } wintitle.fd = info.fd = -1; if (options->thumb_mode) { mode = MODE_THUMB; tns_init(&tns, files, &filecnt, &fileidx, &win); while (!tns_load(&tns, fileidx, false, false)) remove_file(fileidx, false); } else { mode = MODE_IMAGE; tns.thumbs = NULL; load_image(fileidx); } win_open(&win); win_set_cursor(&win, CURSOR_WATCH); atexit(cleanup); set_timeout(redraw, 25, false); run(); return 0; } nsxiv-33/nsxiv.h000066400000000000000000000212621474777454100140170ustar00rootroot00000000000000/* Copyright 2011-2020 Bert Muennich * Copyright 2021-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #ifndef NSXIV_H #define NSXIV_H #if !defined(DEBUG) && !defined(NDEBUG) #define NDEBUG #endif #include #include #include #include #include /* * Annotation for functions called in cleanup(). * These functions are not allowed to call error(!0, ...) or exit(). */ #define CLEANUP #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define ABS(a) ((a) > 0 ? (a) : -(a)) #define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) #define STREQ(s1,s2) (strcmp((s1), (s2)) == 0) typedef enum { MODE_ALL, MODE_IMAGE, MODE_THUMB } appmode_t; typedef enum { DIR_LEFT = 1, DIR_RIGHT = 2, DIR_UP = 4, DIR_DOWN = 8 } direction_t; typedef enum { DEGREE_90 = 1, DEGREE_180 = 2, DEGREE_270 = 3 } degree_t; typedef enum { FLIP_HORIZONTAL = 1, FLIP_VERTICAL = 2 } flipdir_t; typedef enum { SCALE_DOWN, SCALE_FIT, SCALE_FILL, SCALE_WIDTH, SCALE_HEIGHT, SCALE_ZOOM } scalemode_t; typedef enum { DRAG_RELATIVE, DRAG_ABSOLUTE } dragmode_t; typedef enum { CURSOR_ARROW, CURSOR_DRAG_ABSOLUTE, CURSOR_DRAG_RELATIVE, CURSOR_WATCH, CURSOR_LEFT, CURSOR_RIGHT, CURSOR_NONE, CURSOR_COUNT } cursor_t; typedef enum { FF_WARN = 1, FF_MARK = 2, FF_TN_INIT = 4 } fileflags_t; typedef struct { const char *name; /* as given by user */ const char *path; /* always absolute, result of realpath(3) */ fileflags_t flags; } fileinfo_t; /* timeouts in milliseconds: */ enum { TO_AUTORELOAD = 128, TO_REDRAW_RESIZE = 75, TO_REDRAW_THUMBS = 200, TO_CURSOR_HIDE = 1200, TO_DOUBLE_CLICK = 300 }; typedef void (*timeout_f)(void); typedef struct arl arl_t; typedef struct img img_t; typedef struct opt opt_t; typedef struct tns tns_t; typedef struct win win_t; /* autoreload.c */ struct arl { int fd; int wd_dir; int wd_file; const char *filename; }; void arl_init(arl_t*); void arl_cleanup(arl_t*); void arl_add(arl_t*, const char* /* result of realpath(3) */); bool arl_handle(arl_t*); /* commands.c */ typedef int arg_t; typedef bool (*cmd_f)(arg_t); typedef struct { cmd_f func; appmode_t mode; } cmd_t; typedef struct { unsigned int mask; KeySym ksym_or_button; cmd_t cmd; arg_t arg; } keymap_t; typedef keymap_t button_t; /* image.c */ #ifdef IMLIB2_VERSION /* UPGRADE: Imlib2 v1.8.0: remove all HAVE_IMLIB2_MULTI_FRAME ifdefs */ #if IMLIB2_VERSION >= IMLIB2_VERSION_(1, 8, 0) #define HAVE_IMLIB2_MULTI_FRAME 1 #endif #endif #ifndef HAVE_IMLIB2_MULTI_FRAME #define HAVE_IMLIB2_MULTI_FRAME 0 #endif typedef struct { Imlib_Image im; unsigned int delay; } img_frame_t; typedef struct { img_frame_t *frames; unsigned int cap; unsigned int cnt; unsigned int sel; bool animate; int framedelay; int length; } multi_img_t; struct img { Imlib_Image im; int w; int h; win_t *win; float x; float y; Imlib_Color_Modifier cmod; int gamma; int brightness; int contrast; scalemode_t scalemode; float zoom; bool checkpan; bool dirty; bool anti_alias; bool alpha_layer; bool autoreload_pending; struct { bool on; int delay; } ss; multi_img_t multi; }; void img_init(img_t*, win_t*); bool img_load(img_t*, const fileinfo_t*); CLEANUP void img_free(Imlib_Image, bool); CLEANUP void img_close(img_t*, bool); void img_render(img_t*); bool img_fit_win(img_t*, scalemode_t); bool img_zoom(img_t*, int); bool img_zoom_to(img_t*, float); bool img_pos(img_t*, float, float); bool img_pan(img_t*, direction_t, int); bool img_pan_center(img_t*); bool img_pan_edge(img_t*, direction_t); void img_rotate(img_t*, degree_t); void img_flip(img_t*, flipdir_t); void img_toggle_antialias(img_t*); void img_update_color_modifiers(img_t*); bool img_change_color_modifier(img_t*, int, int*); bool img_frame_navigate(img_t*, int); bool img_frame_animate(img_t*); Imlib_Image img_open(const fileinfo_t*); #if HAVE_LIBEXIF void exif_auto_orientate(const fileinfo_t*); #endif /* options.c */ struct opt { /* file list: */ char **filenames; bool from_stdin; bool to_stdout; bool using_null; bool recursive; int filecnt; int startnum; /* image: */ scalemode_t scalemode; float zoom; bool animate; bool anti_alias; bool alpha_layer; int gamma; unsigned int slideshow; int framerate; /* window: */ bool fullscreen; bool hide_bar; Window embed; char *geometry; char *res_name; /* misc flags: */ const char *tns_filters; bool tns_filters_is_blacklist; bool quiet; bool thumb_mode; bool clean_cache; bool private_mode; bool background_cache; }; extern const opt_t *options; void print_usage(FILE *stream); void parse_options(int, char**); /* thumbs.c */ typedef struct { Imlib_Image im; int w; int h; int x; int y; } thumb_t; typedef struct { const char *path; int len; bool recursive; } thumb_filter_t; struct tns { fileinfo_t *files; thumb_t *thumbs; const int *cnt; int *sel; int initnext; int loadnext; int first, end; int r_first, r_end; win_t *win; int x; int y; int cols; int rows; int zl; int bw; int dim; thumb_filter_t *filters; int filters_cnt; bool dirty; bool filters_is_blacklist; }; void tns_clean_cache(void); void tns_init(tns_t*, fileinfo_t*, const int*, int*, win_t*); CLEANUP void tns_free(tns_t*); bool tns_load(tns_t*, int, bool, bool); void tns_unload(tns_t*, int); void tns_render(tns_t*); void tns_mark(tns_t*, int, bool); void tns_highlight(tns_t*, int, bool); bool tns_move_selection(tns_t*, direction_t, int); bool tns_scroll(tns_t*, direction_t, bool); bool tns_zoom(tns_t*, int); int tns_translate(tns_t*, int, int); /* util.c */ #include typedef struct { DIR *dir; char *name; int d; bool recursive; char **stack; int stcap; int stlen; } r_dir_t; extern const char *progname; void* emalloc(size_t); void* ecalloc(size_t, size_t); void* erealloc(void*, size_t); char* estrdup(const char*); void error(int, int, const char*, ...); int r_opendir(r_dir_t*, const char*, bool); int r_closedir(r_dir_t*); char* r_readdir(r_dir_t*, bool); int r_mkdir(char*); void construct_argv(char**, unsigned int, ...); pid_t spawn(int*, int*, int, char *const []); /* window.c */ #include #if HAVE_LIBFONTS #include #endif enum { ATOM_WM_DELETE_WINDOW, ATOM__NET_WM_NAME, ATOM__NET_WM_ICON_NAME, ATOM__NET_WM_ICON, ATOM__NET_WM_STATE, ATOM__NET_WM_PID, ATOM__NET_WM_STATE_FULLSCREEN, ATOM_UTF8_STRING, ATOM_WM_NAME, ATOM_WM_ICON_NAME, ATOM_COUNT }; typedef struct { Display *dpy; int scr; int scrw, scrh; Visual *vis; Colormap cmap; int depth; } win_env_t; typedef struct { size_t size; char *p; char *buf; } win_bar_t; struct win { Window xwin; win_env_t env; XColor win_bg; XColor win_fg; XColor mrk_fg; #if HAVE_LIBFONTS XftColor bar_bg; XftColor bar_fg; #endif int x; int y; unsigned int w; unsigned int h; /* = win height - bar height */ unsigned int bw; struct { unsigned int w; unsigned int h; Pixmap pm; } buf; struct { unsigned int h; bool top; win_bar_t l; win_bar_t r; } bar; }; extern Atom atoms[ATOM_COUNT]; void win_init(win_t*); void win_open(win_t*); CLEANUP void win_close(win_t*); bool win_configure(win_t*, XConfigureEvent*); void win_toggle_fullscreen(win_t*); void win_toggle_bar(win_t*); void win_clear(win_t*); void win_draw(win_t*); void win_draw_rect(win_t*, int, int, int, int, bool, int, unsigned long); void win_set_title(win_t*, const char*, size_t); void win_set_cursor(win_t*, cursor_t); void win_cursor_pos(win_t*, int*, int*); /* main.c */ /* timeout handler functions: */ void redraw(void); void reset_cursor(void); void animate(void); void slideshow(void); void clear_resize(void); void remove_file(int, bool); void set_timeout(timeout_f, int, bool); void reset_timeout(timeout_f); void close_info(void); void open_info(void); void load_image(int); bool mark_image(int, bool); int nav_button(void); void handle_key_handler(bool); extern appmode_t mode; extern const XButtonEvent *xbutton_ev; extern fileinfo_t *files; extern int filecnt, fileidx; extern int alternate; extern int markcnt; extern int markidx; extern int prefix; #endif /* NSXIV_H */ nsxiv-33/options.c000066400000000000000000000210371474777454100143360ustar00rootroot00000000000000/* Copyright 2011-2020 Bert Muennich * Copyright 2021-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #include "nsxiv.h" #include "version.h" #define INCLUDE_OPTIONS_CONFIG #include "config.h" #include #include #include #include #include #include #define OPTPARSE_IMPLEMENTATION #define OPTPARSE_API static #pragma GCC diagnostic push /* also works on clang */ #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wunused-function" #include "optparse.h" #pragma GCC diagnostic pop const opt_t *options; void print_usage(FILE *stream) { fprintf(stream, "usage: %s [-abcfhiopqrtvZ0] [-A FRAMERATE] [-e WID] [-G GAMMA] " "[-g GEOMETRY] [-N NAME] [-n NUM] [-S DELAY] [-s MODE] " "[-z ZOOM] FILES...\n", progname); } static void print_version(void) { printf("%s %s\n", progname, VERSION); fputs("features: " #if HAVE_INOTIFY "+inotify " #endif #if HAVE_LIBFONTS "+statusbar " #endif #if HAVE_LIBEXIF "+exif " #endif #if HAVE_IMLIB2_MULTI_FRAME "+multiframe " #endif "\n", stdout); } static bool parse_optional_no(const char *flag, const char *arg) { if (arg != NULL && !STREQ(arg, "no")) error(EXIT_FAILURE, 0, "Invalid argument for option --%s: %s", flag, arg); return arg == NULL; } void parse_options(int argc, char **argv) { enum { /* ensure these can't be represented in a single byte in order * to avoid conflicts with short opts */ OPT_START = UCHAR_MAX, OPT_AA, OPT_AL, OPT_THUMB, OPT_BAR, OPT_BG, OPT_CA, OPT_CD, OPT_CLASS }; static const struct optparse_long longopts[] = { { "framerate", 'A', OPTPARSE_REQUIRED }, { "animate", 'a', OPTPARSE_NONE }, { "no-bar", 'b', OPTPARSE_NONE }, { "bar", OPT_BAR, OPTPARSE_NONE }, { "clean-cache", 'c', OPTPARSE_NONE }, { "embed", 'e', OPTPARSE_REQUIRED }, { "fullscreen", 'f', OPTPARSE_NONE }, { "gamma", 'G', OPTPARSE_REQUIRED }, { "geometry", 'g', OPTPARSE_REQUIRED }, { "help", 'h', OPTPARSE_NONE }, { "stdin", 'i', OPTPARSE_NONE }, { "name", 'N', OPTPARSE_REQUIRED }, { "class", OPT_CLASS, OPTPARSE_REQUIRED }, { "start-at", 'n', OPTPARSE_REQUIRED }, { "stdout", 'o', OPTPARSE_NONE }, { "private", 'p', OPTPARSE_NONE }, { "quiet", 'q', OPTPARSE_NONE }, { "recursive", 'r', OPTPARSE_NONE }, { "ss-delay", 'S', OPTPARSE_REQUIRED }, { "scale-mode", 's', OPTPARSE_REQUIRED }, /* short opt `-t` doesn't accept optional arg for backwards compatibility reasons */ { NULL, 't', OPTPARSE_NONE }, { "thumbnail", OPT_THUMB, OPTPARSE_OPTIONAL }, { "version", 'v', OPTPARSE_NONE }, { "zoom-100", 'Z', OPTPARSE_NONE }, { "zoom", 'z', OPTPARSE_REQUIRED }, { "null", '0', OPTPARSE_NONE }, { "anti-alias", OPT_AA, OPTPARSE_OPTIONAL }, { "alpha-layer", OPT_AL, OPTPARSE_OPTIONAL }, /* TODO: document this when it's stable */ { "bg-cache", OPT_BG, OPTPARSE_OPTIONAL }, { "cache-allow", OPT_CA, OPTPARSE_REQUIRED }, { "cache-deny", OPT_CD, OPTPARSE_REQUIRED }, { 0 }, /* end */ }; long n, opt; float f; char *end, *s; struct optparse op; const char scalemodes[] = "dfFwh"; /* must be sorted according to scalemode_t */ static opt_t _options; options = &_options; _options.from_stdin = false; _options.to_stdout = false; _options.using_null = false; _options.recursive = false; _options.startnum = 0; _options.scalemode = SCALE_DOWN; _options.zoom = 1.0; _options.anti_alias = ANTI_ALIAS; _options.alpha_layer = ALPHA_LAYER; _options.animate = false; _options.gamma = 0; _options.slideshow = 0; _options.framerate = 0; _options.fullscreen = false; _options.embed = 0; _options.hide_bar = false; _options.geometry = NULL; _options.res_name = NULL; _options.tns_filters = TNS_FILTERS; _options.tns_filters_is_blacklist = TNS_FILTERS_IS_BLACKLIST; _options.quiet = false; _options.thumb_mode = false; _options.clean_cache = false; _options.private_mode = false; _options.background_cache = false; if (argc > 0) { s = strrchr(argv[0], '/'); progname = s != NULL && s[1] != '\0' ? s + 1 : argv[0]; } optparse_init(&op, argv); while ((opt = optparse_long(&op, longopts, NULL)) != -1) { for (n = 0; n < (int)ARRLEN(longopts); ++n) { /* clang-tidy finds some non-sensical branch and thinks optarg == NULL is possible */ if (opt == longopts[n].shortname && longopts[n].argtype == OPTPARSE_REQUIRED) assert(op.optarg != NULL); } switch (opt) { case '?': fprintf(stderr, "%s\n", op.errmsg); print_usage(stderr); exit(EXIT_FAILURE); case 'A': n = strtol(op.optarg, &end, 0); if (*end != '\0' || n < 0 || n > INT_MAX) error(EXIT_FAILURE, 0, "Invalid framerate: %s", op.optarg); _options.framerate = n; _options.animate = n > 0; break; case 'a': _options.animate = true; break; case 'b': case OPT_BAR: _options.hide_bar = (opt == 'b'); break; case 'c': _options.clean_cache = true; break; case 'e': n = strtol(op.optarg, &end, 0); if (*end != '\0') error(EXIT_FAILURE, 0, "Invalid window id: %s", op.optarg); _options.embed = n; break; case 'f': _options.fullscreen = true; break; case 'G': n = strtol(op.optarg, &end, 0); if (*end != '\0' || n < INT_MIN || n > INT_MAX) error(EXIT_FAILURE, 0, "Invalid gamma: %s", op.optarg); _options.gamma = n; break; case 'g': _options.geometry = op.optarg; break; case 'h': print_usage(stdout); exit(EXIT_SUCCESS); case 'i': _options.from_stdin = true; break; case 'n': n = strtol(op.optarg, &end, 0); if (*end != '\0' || n <= 0 || n > INT_MAX) error(EXIT_FAILURE, 0, "Invalid starting number: %s", op.optarg); _options.startnum = n - 1; break; case OPT_CLASS: error(0, 0, "--class is deprecated, use --name instead"); /* fallthrough */ case 'N': _options.res_name = op.optarg; break; case 'o': _options.to_stdout = true; break; case 'p': _options.private_mode = true; break; case 'q': _options.quiet = true; break; case 'r': _options.recursive = true; break; case 'S': f = strtof(op.optarg, &end) * 10.0f; if (*end != '\0' || f <= 0 || f >= (float)UINT_MAX) error(EXIT_FAILURE, 0, "Invalid slideshow delay: %s", op.optarg); _options.slideshow = (unsigned int)f; break; case 's': s = strchr(scalemodes, op.optarg[0]); if (s == NULL || *s == '\0' || strlen(op.optarg) != 1) error(EXIT_FAILURE, 0, "Invalid scale mode: %s", op.optarg); _options.scalemode = s - scalemodes; break; case 't': _options.thumb_mode = true; break; case 'v': print_version(); exit(EXIT_SUCCESS); case 'Z': _options.scalemode = SCALE_ZOOM; _options.zoom = 1.0f; break; case 'z': n = strtol(op.optarg, &end, 0); if (*end != '\0' || n <= 0) error(EXIT_FAILURE, 0, "Invalid zoom level: %s", op.optarg); _options.scalemode = SCALE_ZOOM; _options.zoom = (float)n / 100.0f; break; case '0': _options.using_null = true; break; case OPT_AA: _options.anti_alias = parse_optional_no("anti-alias", op.optarg); break; case OPT_AL: _options.alpha_layer = parse_optional_no("alpha-layer", op.optarg); break; case OPT_THUMB: _options.thumb_mode = parse_optional_no("thumbnail", op.optarg); break; case OPT_BG: _options.background_cache = parse_optional_no("bg-cache", op.optarg); break; case OPT_CA: case OPT_CD: _options.tns_filters = op.optarg; _options.tns_filters_is_blacklist = (opt == OPT_CD); break; } } _options.filenames = argv + op.optind; _options.filecnt = argc - op.optind; if (_options.filecnt == 1 && STREQ(_options.filenames[0], "-")) { _options.filenames++; _options.filecnt--; _options.from_stdin = true; } } nsxiv-33/optparse.h000066400000000000000000000274511474777454100145130ustar00rootroot00000000000000/* Optparse --- portable, reentrant, embeddable, getopt-like option parser * * This is free and unencumbered software released into the public domain. * * To get the implementation, define OPTPARSE_IMPLEMENTATION. * Optionally define OPTPARSE_API to control the API's visibility * and/or linkage (static, __attribute__, __declspec). * * The POSIX getopt() option parser has three fatal flaws. These flaws * are solved by Optparse. * * 1) Parser state is stored entirely in global variables, some of * which are static and inaccessible. This means only one thread can * use getopt(). It also means it's not possible to recursively parse * nested sub-arguments while in the middle of argument parsing. * Optparse fixes this by storing all state on a local struct. * * 2) The POSIX standard provides no way to properly reset the parser. * This means for portable code that getopt() is only good for one * run, over one argv with one option string. It also means subcommand * options cannot be processed with getopt(). Most implementations * provide a method to reset the parser, but it's not portable. * Optparse provides an optparse_arg() function for stepping over * subcommands and continuing parsing of options with another option * string. The Optparse struct itself can be passed around to * subcommand handlers for additional subcommand option parsing. A * full reset can be achieved by with an additional optparse_init(). * * 3) Error messages are printed to stderr. This can be disabled with * opterr, but the messages themselves are still inaccessible. * Optparse solves this by writing an error message in its errmsg * field. The downside to Optparse is that this error message will * always be in English rather than the current locale. * * Optparse should be familiar with anyone accustomed to getopt(), and * it could be a nearly drop-in replacement. The option string is the * same and the fields have the same names as the getopt() global * variables (optarg, optind, optopt). * * Optparse also supports GNU-style long options with optparse_long(). * The interface is slightly different and simpler than getopt_long(). * * By default, argv is permuted as it is parsed, moving non-option * arguments to the end. This can be disabled by setting the `permute` * field to 0 after initialization. */ #ifndef OPTPARSE_H #define OPTPARSE_H #ifndef OPTPARSE_API # define OPTPARSE_API #endif struct optparse { char **argv; int permute; int optind; int optopt; char *optarg; char errmsg[64]; int subopt; }; enum optparse_argtype { OPTPARSE_NONE, OPTPARSE_REQUIRED, OPTPARSE_OPTIONAL }; struct optparse_long { const char *longname; int shortname; enum optparse_argtype argtype; }; /** * Initializes the parser state. */ OPTPARSE_API void optparse_init(struct optparse *options, char **argv); /** * Read the next option in the argv array. * @param optstring a getopt()-formatted option string. * @return the next option character, -1 for done, or '?' for error * * Just like getopt(), a character followed by no colons means no * argument. One colon means the option has a required argument. Two * colons means the option takes an optional argument. */ OPTPARSE_API int optparse(struct optparse *options, const char *optstring); /** * Handles GNU-style long options in addition to getopt() options. * This works a lot like GNU's getopt_long(). The last option in * longopts must be all zeros, marking the end of the array. The * longindex argument may be NULL. */ OPTPARSE_API int optparse_long(struct optparse *options, const struct optparse_long *longopts, int *longindex); /** * Used for stepping over non-option arguments. * @return the next non-option argument, or NULL for no more arguments * * Argument parsing can continue with optparse() after using this * function. That would be used to parse the options for the * subcommand returned by optparse_arg(). This function allows you to * ignore the value of optind. */ OPTPARSE_API char *optparse_arg(struct optparse *options); /* Implementation */ #ifdef OPTPARSE_IMPLEMENTATION #define OPTPARSE_MSG_INVALID "invalid option" #define OPTPARSE_MSG_MISSING "option requires an argument" #define OPTPARSE_MSG_TOOMANY "option takes no arguments" static int optparse_error(struct optparse *options, const char *msg, const char *data) { unsigned p = 0; const char *sep = " -- '"; while (*msg) options->errmsg[p++] = *msg++; while (*sep) options->errmsg[p++] = *sep++; while (p < sizeof(options->errmsg) - 2 && *data) options->errmsg[p++] = *data++; options->errmsg[p++] = '\''; options->errmsg[p++] = '\0'; return '?'; } OPTPARSE_API void optparse_init(struct optparse *options, char **argv) { options->argv = argv; options->permute = 1; options->optind = argv[0] != 0; options->subopt = 0; options->optarg = 0; options->errmsg[0] = '\0'; } static int optparse_is_dashdash(const char *arg) { return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; } static int optparse_is_shortopt(const char *arg) { return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; } static int optparse_is_longopt(const char *arg) { return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; } static void optparse_permute(struct optparse *options, int index) { char *nonoption = options->argv[index]; int i; for (i = index; i < options->optind - 1; i++) options->argv[i] = options->argv[i + 1]; options->argv[options->optind - 1] = nonoption; } static int optparse_argtype(const char *optstring, char c) { int count = OPTPARSE_NONE; if (c == ':') return -1; for (; *optstring && c != *optstring; optstring++); if (!*optstring) return -1; if (optstring[1] == ':') count += optstring[2] == ':' ? 2 : 1; return count; } OPTPARSE_API int optparse(struct optparse *options, const char *optstring) { int type; char *next; char *option = options->argv[options->optind]; options->errmsg[0] = '\0'; options->optopt = 0; options->optarg = 0; if (option == 0) { return -1; } else if (optparse_is_dashdash(option)) { options->optind++; /* consume "--" */ return -1; } else if (!optparse_is_shortopt(option)) { if (options->permute) { int index = options->optind++; int r = optparse(options, optstring); optparse_permute(options, index); options->optind--; return r; } else { return -1; } } option += options->subopt + 1; options->optopt = option[0]; type = optparse_argtype(optstring, option[0]); next = options->argv[options->optind + 1]; switch (type) { case -1: { char str[2] = {0, 0}; str[0] = option[0]; options->optind++; return optparse_error(options, OPTPARSE_MSG_INVALID, str); } case OPTPARSE_NONE: if (option[1]) { options->subopt++; } else { options->subopt = 0; options->optind++; } return option[0]; case OPTPARSE_REQUIRED: options->subopt = 0; options->optind++; if (option[1]) { options->optarg = option + 1; } else if (next != 0) { options->optarg = next; options->optind++; } else { char str[2] = {0, 0}; str[0] = option[0]; options->optarg = 0; return optparse_error(options, OPTPARSE_MSG_MISSING, str); } return option[0]; case OPTPARSE_OPTIONAL: options->subopt = 0; options->optind++; if (option[1]) options->optarg = option + 1; else options->optarg = 0; return option[0]; } return 0; } OPTPARSE_API char * optparse_arg(struct optparse *options) { char *option = options->argv[options->optind]; options->subopt = 0; if (option != 0) options->optind++; return option; } static int optparse_longopts_end(const struct optparse_long *longopts, int i) { return !longopts[i].longname && !longopts[i].shortname; } static void optparse_from_long(const struct optparse_long *longopts, char *optstring) { char *p = optstring; int i; for (i = 0; !optparse_longopts_end(longopts, i); i++) { if (longopts[i].shortname && longopts[i].shortname < 127) { int a; *p++ = longopts[i].shortname; for (a = 0; a < (int)longopts[i].argtype; a++) *p++ = ':'; } } *p = '\0'; } /* Unlike strcmp(), handles options containing "=". */ static int optparse_longopts_match(const char *longname, const char *option) { const char *a = option, *n = longname; if (longname == 0) return 0; for (; *a && *n && *a != '='; a++, n++) if (*a != *n) return 0; return *n == '\0' && (*a == '\0' || *a == '='); } /* Return the part after "=", or NULL. */ static char * optparse_longopts_arg(char *option) { for (; *option && *option != '='; option++); if (*option == '=') return option + 1; else return 0; } static int optparse_long_fallback(struct optparse *options, const struct optparse_long *longopts, int *longindex) { int result; char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ optparse_from_long(longopts, optstring); result = optparse(options, optstring); if (longindex != 0) { *longindex = -1; if (result != -1) { int i; for (i = 0; !optparse_longopts_end(longopts, i); i++) if (longopts[i].shortname == options->optopt) *longindex = i; } } return result; } OPTPARSE_API int optparse_long(struct optparse *options, const struct optparse_long *longopts, int *longindex) { int i; char *option = options->argv[options->optind]; if (option == 0) { return -1; } else if (optparse_is_dashdash(option)) { options->optind++; /* consume "--" */ return -1; } else if (optparse_is_shortopt(option)) { return optparse_long_fallback(options, longopts, longindex); } else if (!optparse_is_longopt(option)) { if (options->permute) { int index = options->optind++; int r = optparse_long(options, longopts, longindex); optparse_permute(options, index); options->optind--; return r; } else { return -1; } } /* Parse as long option. */ options->errmsg[0] = '\0'; options->optopt = 0; options->optarg = 0; option += 2; /* skip "--" */ options->optind++; for (i = 0; !optparse_longopts_end(longopts, i); i++) { const char *name = longopts[i].longname; if (optparse_longopts_match(name, option)) { char *arg; if (longindex) *longindex = i; options->optopt = longopts[i].shortname; arg = optparse_longopts_arg(option); if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); } if (arg != 0) { options->optarg = arg; } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { options->optarg = options->argv[options->optind]; if (options->optarg == 0) return optparse_error(options, OPTPARSE_MSG_MISSING, name); else options->optind++; } return options->optopt; } } return optparse_error(options, OPTPARSE_MSG_INVALID, option); } #endif /* OPTPARSE_IMPLEMENTATION */ #endif /* OPTPARSE_H */ nsxiv-33/thumbs.c000066400000000000000000000400141474777454100141410ustar00rootroot00000000000000/* Copyright 2011-2020 Bert Muennich * Copyright 2021-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #include "nsxiv.h" #define INCLUDE_THUMBS_CONFIG #include "config.h" #include #include #include #include #include #include #include #include #if HAVE_LIBEXIF #include #endif static char *cache_dir; static char *cache_tmpfile, *cache_tmpfile_base; static const char TMP_NAME[] = "/nsxiv-XXXXXX"; static char *tns_cache_filepath(const char *filepath) { size_t len; char *cfile = NULL; assert(*filepath == '/' && "filepath must be result of realpath(3)"); if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) { /* don't cache images inside the cache directory! */ len = strlen(cache_dir) + strlen(filepath) + 2; cfile = emalloc(len); snprintf(cfile, len, "%s/%s", cache_dir, filepath + 1); } return cfile; } static Imlib_Image tns_cache_load(const char *filepath, bool *outdated) { char *cfile; struct stat cstats, fstats; Imlib_Image im = NULL; if (stat(filepath, &fstats) < 0) return NULL; if ((cfile = tns_cache_filepath(filepath)) != NULL) { if (stat(cfile, &cstats) == 0) { if (cstats.st_mtime == fstats.st_mtime) im = imlib_load_image(cfile); else *outdated = true; } free(cfile); } return im; } static bool tns_cache_whitelisted(tns_t *tns, const char *filepath) { ptrdiff_t i, dir_len = strrchr(filepath, '/') - filepath; dir_len = MAX(dir_len, 1); /* account for "/file" */ if (tns->filters_cnt == 0) return true; /* no cache list, cache everything */ for (i = 0; i < tns->filters_cnt; ++i) { thumb_filter_t *f = tns->filters + i; if ((f->recursive ? (dir_len >= f->len) : (dir_len == f->len)) && memcmp(filepath, f->path, f->len) == 0) { /* in blacklist mode, finding a match means we shouldn't cache */ return tns->filters_is_blacklist ? false : true; } } return tns->filters_is_blacklist; /* no match */ } static void tns_cache_write(tns_t *tns, Imlib_Image im, const char *filepath, bool force) { char *cfile, *dirend; int tmpfd; struct stat cstats, fstats; struct utimbuf times; Imlib_Load_Error err; if (options->private_mode || !tns_cache_whitelisted(tns, filepath)) return; if (stat(filepath, &fstats) < 0) return; if ((cfile = tns_cache_filepath(filepath)) != NULL) { if (force || stat(cfile, &cstats) < 0 || cstats.st_mtime != fstats.st_mtime) { if ((dirend = strrchr(cfile, '/')) != NULL) { *dirend = '\0'; if (r_mkdir(cfile) < 0) goto end; *dirend = '/'; } imlib_context_set_image(im); if (imlib_image_has_alpha()) { imlib_image_set_format("png"); imlib_image_attach_data_value("compression", NULL, 8, NULL); } else { imlib_image_set_format("jpg"); imlib_image_attach_data_value("quality", NULL, 90, NULL); } memcpy(cache_tmpfile_base, TMP_NAME, sizeof(TMP_NAME)); if ((tmpfd = mkstemp(cache_tmpfile)) < 0) goto end; close(tmpfd); /* UPGRADE: Imlib2 v1.11.0: use imlib_save_image_fd() */ imlib_save_image_with_error_return(cache_tmpfile, &err); times.actime = fstats.st_atime; times.modtime = fstats.st_mtime; utime(cache_tmpfile, ×); if (err || rename(cache_tmpfile, cfile) < 0) unlink(cache_tmpfile); } end: free(cfile); } } void tns_clean_cache(void) { int dirlen; char *cfile, *filename; r_dir_t dir; if (r_opendir(&dir, cache_dir, true) < 0) { error(0, errno, "%s", cache_dir); return; } dirlen = strlen(cache_dir); while ((cfile = r_readdir(&dir, false)) != NULL) { filename = cfile + dirlen; if (access(filename, F_OK) < 0) { if (unlink(cfile) < 0) error(0, errno, "%s", cfile); } free(cfile); } r_closedir(&dir); } void tns_init(tns_t *tns, fileinfo_t *tns_files, const int *cnt, int *sel, win_t *win) { int len; const char *homedir, *dsuffix = ""; if (cnt != NULL && *cnt > 0) tns->thumbs = ecalloc(*cnt, sizeof(*tns->thumbs)); else tns->thumbs = NULL; tns->files = tns_files; tns->cnt = cnt; tns->initnext = tns->loadnext = 0; tns->first = tns->end = tns->r_first = tns->r_end = 0; tns->sel = sel; tns->win = win; tns->dirty = false; tns->zl = THUMB_SIZE; tns_zoom(tns, 0); tns->filters = NULL; tns->filters_cnt = 0; tns->filters_is_blacklist = options->tns_filters_is_blacklist; if (options->tns_filters != NULL && options->tns_filters[0] != '\0') { int allocated = 0; char *save, *tok, *s = estrdup(options->tns_filters); for (tok = strtok_r(s, ":", &save); tok != NULL; tok = strtok_r(NULL, ":", &save)) { thumb_filter_t *f; if (tns->filters_cnt == allocated) { allocated = allocated > 0 ? (allocated * 2) : 4; tns->filters = erealloc(tns->filters, allocated * sizeof(*tns->filters)); } f = tns->filters + tns->filters_cnt++; f->recursive = *tok == '*'; f->path = realpath(tok + f->recursive, NULL); if (f->path == NULL) { error(EXIT_FAILURE, errno, "--cache-%s: `%s`", tns->filters_is_blacklist ? "deny" : "allow", tok + f->recursive); } f->len = strlen(f->path); } free(s); } if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') { homedir = getenv("HOME"); dsuffix = "/.cache"; } if (homedir != NULL) { const char *s = "/nsxiv"; free(cache_dir); len = strlen(homedir) + strlen(dsuffix) + strlen(s) + 1; cache_dir = emalloc(len); snprintf(cache_dir, len, "%s%s%s", homedir, dsuffix, s); cache_tmpfile = emalloc(len + sizeof(TMP_NAME)); memcpy(cache_tmpfile, cache_dir, len - 1); cache_tmpfile_base = cache_tmpfile + len - 1; } else { error(EXIT_FAILURE, 0, "Cache directory not found"); } } CLEANUP void tns_free(tns_t *tns) { int i; if (tns->thumbs != NULL) { for (i = 0; i < *tns->cnt; i++) img_free(tns->thumbs[i].im, false); free(tns->thumbs); tns->thumbs = NULL; } for (i = 0; i < tns->filters_cnt; ++i) free((void *)tns->filters[i].path); tns->filters_cnt = 0; free(tns->filters); tns->filters = NULL; free(cache_dir); cache_dir = NULL; free(cache_tmpfile); cache_tmpfile = cache_tmpfile_base = NULL; } static Imlib_Image tns_scale_down(Imlib_Image im, int dim) { int w, h; float z, zw, zh; imlib_context_set_image(im); w = imlib_image_get_width(); h = imlib_image_get_height(); zw = (float)dim / (float)w; zh = (float)dim / (float)h; z = MIN(zw, zh); z = MIN(z, 1.0); if (z < 1.0) { imlib_context_set_anti_alias(1); im = imlib_create_cropped_scaled_image(0, 0, w, h, MAX(z * w, 1), MAX(z * h, 1)); if (im == NULL) error(EXIT_FAILURE, ENOMEM, NULL); imlib_free_image_and_decache(); } return im; } bool tns_load(tns_t *tns, int n, bool force, bool cache_only) { int maxwh = thumb_sizes[ARRLEN(thumb_sizes) - 1]; bool cache_hit = false; char *cfile; thumb_t *t; fileinfo_t *file; Imlib_Image im = NULL; if (n < 0 || n >= *tns->cnt) return false; file = &tns->files[n]; if (file->name == NULL || file->path == NULL) return false; t = &tns->thumbs[n]; img_free(t->im, false); t->im = NULL; if (!force) { if ((im = tns_cache_load(file->path, &force)) != NULL) { imlib_context_set_image(im); if (imlib_image_get_width() < maxwh && imlib_image_get_height() < maxwh) { if ((cfile = tns_cache_filepath(file->path)) != NULL) { unlink(cfile); free(cfile); } imlib_free_image_and_decache(); im = NULL; } else { cache_hit = true; } #if HAVE_LIBEXIF } else if (!force && !options->private_mode) { int pw = 0, ph = 0, w, h, x = 0, y = 0; bool err; float zw, zh; ExifData *ed; ExifEntry *entry; ExifContent *ifd; ExifByteOrder byte_order; int tmpfd; char tmppath[] = "/tmp/nsxiv-XXXXXX"; Imlib_Image tmpim; /* UPGRADE: Imlib2 v1.10.0: avoid tempfile and use imlib_load_image_mem() */ if ((ed = exif_data_new_from_file(file->path)) != NULL) { if (ed->data != NULL && ed->size > 0 && (tmpfd = mkstemp(tmppath)) >= 0) { err = write(tmpfd, ed->data, ed->size) != ed->size; close(tmpfd); if (!err && (tmpim = imlib_load_image(tmppath)) != NULL) { byte_order = exif_data_get_byte_order(ed); ifd = ed->ifd[EXIF_IFD_EXIF]; entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_X_DIMENSION); if (entry != NULL) pw = exif_get_long(entry->data, byte_order); entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_Y_DIMENSION); if (entry != NULL) ph = exif_get_long(entry->data, byte_order); imlib_context_set_image(tmpim); w = imlib_image_get_width(); h = imlib_image_get_height(); if (pw > w && ph > h && (pw - ph >= 0) == (w - h >= 0)) { zw = (float)pw / (float)w; zh = (float)ph / (float)h; if (zw < zh) { pw /= zh; x = (w - pw) / 2; w = pw; } else if (zw > zh) { ph /= zw; y = (h - ph) / 2; h = ph; } } if (w >= maxwh || h >= maxwh) { if ((im = imlib_create_cropped_image(x, y, w, h)) == NULL) error(0, 0, "%s: error generating thumbnail", file->name); } imlib_free_image_and_decache(); } unlink(tmppath); } exif_data_unref(ed); } #endif /* HAVE_LIBEXIF */ } } if (im == NULL) { if ((im = img_open(file)) == NULL) return false; } imlib_context_set_image(im); if (!cache_hit) { #if HAVE_LIBEXIF exif_auto_orientate(file); #endif im = tns_scale_down(im, maxwh); imlib_context_set_image(im); if (imlib_image_get_width() == maxwh || imlib_image_get_height() == maxwh) tns_cache_write(tns, im, file->path, true); } if (cache_only) { imlib_free_image_and_decache(); } else { t->im = tns_scale_down(im, thumb_sizes[tns->zl]); imlib_context_set_image(t->im); t->w = imlib_image_get_width(); t->h = imlib_image_get_height(); tns->dirty = true; } file->flags |= FF_TN_INIT; if (n == tns->initnext) { while (++tns->initnext < *tns->cnt && ((++file)->flags & FF_TN_INIT)) ; } if (n == tns->loadnext && !cache_only) { while (++tns->loadnext < tns->end && (++t)->im != NULL) ; } return true; } void tns_unload(tns_t *tns, int n) { thumb_t *t; assert(n >= 0 && n < *tns->cnt); t = &tns->thumbs[n]; img_free(t->im, false); t->im = NULL; } static void tns_check_view(tns_t *tns, bool scrolled) { int r; assert(tns != NULL); tns->first -= tns->first % tns->cols; r = *tns->sel % tns->cols; if (scrolled) { /* move selection into visible area */ if (*tns->sel >= tns->first + tns->cols * tns->rows) *tns->sel = tns->first + r + tns->cols * (tns->rows - 1); else if (*tns->sel < tns->first) *tns->sel = tns->first + r; } else { /* scroll to selection */ if (tns->first + tns->cols * tns->rows <= *tns->sel) { tns->first = *tns->sel - r - tns->cols * (tns->rows - 1); tns->dirty = true; } else if (tns->first > *tns->sel) { tns->first = *tns->sel - r; tns->dirty = true; } } } void tns_render(tns_t *tns) { thumb_t *t; win_t *win; int i, cnt, r, x, y; if (!tns->dirty) return; win = tns->win; win_clear(win); imlib_context_set_drawable(win->buf.pm); tns->cols = MAX(1, win->w / tns->dim); tns->rows = MAX(1, win->h / tns->dim); if (*tns->cnt < tns->cols * tns->rows) { tns->first = 0; cnt = *tns->cnt; } else { tns_check_view(tns, false); cnt = tns->cols * tns->rows; if ((r = tns->first + cnt - *tns->cnt) >= tns->cols) tns->first -= r - r % tns->cols; if (r > 0) cnt -= r % tns->cols; } r = cnt % tns->cols ? 1 : 0; tns->x = x = (win->w - MIN(cnt, tns->cols) * tns->dim) / 2 + tns->bw + 3; tns->y = y = (win->h - (cnt / tns->cols + r) * tns->dim) / 2 + tns->bw + 3 + (win->bar.top ? win->bar.h : 0); tns->loadnext = *tns->cnt; tns->end = tns->first + cnt; for (i = tns->r_first; i < tns->r_end; i++) { if ((i < tns->first || i >= tns->end) && tns->thumbs[i].im != NULL) tns_unload(tns, i); } tns->r_first = tns->first; tns->r_end = tns->end; for (i = tns->first; i < tns->end; i++) { t = &tns->thumbs[i]; if (t->im != NULL) { t->x = x + (thumb_sizes[tns->zl] - t->w) / 2; t->y = y + (thumb_sizes[tns->zl] - t->h) / 2; imlib_context_set_image(t->im); imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h); if (tns->files[i].flags & FF_MARK) tns_mark(tns, i, true); } else { tns->loadnext = MIN(tns->loadnext, i); } if ((i + 1) % tns->cols == 0) { x = tns->x; y += tns->dim; } else { x += tns->dim; } } tns->dirty = false; tns_highlight(tns, *tns->sel, true); } void tns_mark(tns_t *tns, int n, bool mark) { if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { win_t *win = tns->win; thumb_t *t = &tns->thumbs[n]; unsigned long col = win->win_bg.pixel; int x = t->x + t->w, y = t->y + t->h; win_draw_rect(win, x - 1, y + 1, 1, tns->bw, true, 1, col); win_draw_rect(win, x + 1, y - 1, tns->bw, 1, true, 1, col); if (mark) col = win->mrk_fg.pixel; win_draw_rect(win, x, y, tns->bw + 2, tns->bw + 2, true, 1, col); if (!mark && n == *tns->sel) tns_highlight(tns, n, true); } } void tns_highlight(tns_t *tns, int n, bool hl) { if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { win_t *win = tns->win; thumb_t *t = &tns->thumbs[n]; unsigned long col = hl ? win->win_fg.pixel : win->win_bg.pixel; int oxy = (tns->bw + 1) / 2 + 1, owh = tns->bw + 2; win_draw_rect(win, t->x - oxy, t->y - oxy, t->w + owh, t->h + owh, false, tns->bw, col); if (tns->files[n].flags & FF_MARK) tns_mark(tns, n, true); } } bool tns_move_selection(tns_t *tns, direction_t dir, int cnt) { int old, max; old = *tns->sel; cnt = cnt > 1 ? cnt : 1; switch (dir) { case DIR_UP: *tns->sel = MAX(*tns->sel - cnt * tns->cols, *tns->sel % tns->cols); break; case DIR_DOWN: max = tns->cols * ((*tns->cnt - 1) / tns->cols) + MIN((*tns->cnt - 1) % tns->cols, *tns->sel % tns->cols); *tns->sel = MIN(*tns->sel + cnt * tns->cols, max); break; case DIR_LEFT: *tns->sel = MAX(*tns->sel - cnt, 0); break; case DIR_RIGHT: *tns->sel = MIN(*tns->sel + cnt, *tns->cnt - 1); break; } if (*tns->sel != old) { tns_highlight(tns, old, false); tns_check_view(tns, false); if (!tns->dirty) tns_highlight(tns, *tns->sel, true); } return *tns->sel != old; } bool tns_scroll(tns_t *tns, direction_t dir, bool screen) { int d, max, old; old = tns->first; d = tns->cols * (screen ? tns->rows : 1); if (dir == DIR_DOWN) { max = *tns->cnt - tns->cols * tns->rows; if (*tns->cnt % tns->cols != 0) max += tns->cols - *tns->cnt % tns->cols; tns->first = MIN(tns->first + d, max); } else if (dir == DIR_UP) { tns->first = MAX(tns->first - d, 0); } if (tns->first != old) { tns_check_view(tns, true); tns->dirty = true; } return tns->first != old; } bool tns_zoom(tns_t *tns, int d) { int i, oldzl; oldzl = tns->zl; tns->zl += -(d < 0) + (d > 0); tns->zl = MAX(tns->zl, 0); tns->zl = MIN(tns->zl, (int)ARRLEN(thumb_sizes) - 1); tns->bw = ((thumb_sizes[tns->zl] - 1) >> 5) + 1; tns->bw = MIN(tns->bw, 4); tns->dim = thumb_sizes[tns->zl] + 2 * tns->bw + 6; if (tns->zl != oldzl) { for (i = 0; i < *tns->cnt; i++) tns_unload(tns, i); tns->dirty = true; } return tns->zl != oldzl; } int tns_translate(tns_t *tns, int x, int y) { int n; int x_max = tns->x + tns->dim * tns->cols; int y_max = tns->y + tns->dim * tns->rows; if (x < tns->x || y < tns->y || x > x_max || y > y_max) return -1; n = tns->first + (y - tns->y) / tns->dim * tns->cols + (x - tns->x) / tns->dim; if (n >= *tns->cnt) n = -1; return n; } nsxiv-33/utf8.h000066400000000000000000000044541474777454100135420ustar00rootroot00000000000000/* Branchless UTF-8 decoder * * This is free and unencumbered software released into the public domain. */ #ifndef UTF8_H #define UTF8_H #include /* Decode the next character, C, from BUF, reporting errors in E. * * Since this is a branchless decoder, four bytes will be read from the * buffer regardless of the actual length of the next character. This * means the buffer _must_ have at least three bytes of zero padding * following the end of the data stream. * * Errors are reported in E, which will be non-zero if the parsed * character was somehow invalid: invalid byte sequence, non-canonical * encoding, or a surrogate half. * * The function returns a pointer to the next character. When an error * occurs, this pointer will be a guess that depends on the particular * error, but it will always advance at least one byte. */ static void * utf8_decode(void *buf, uint32_t *c, int *e) { static const char lengths[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 }; static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; static const int shiftc[] = {0, 18, 12, 6, 0}; static const int shifte[] = {0, 6, 4, 2, 0}; unsigned char *s = buf; int len = lengths[s[0] >> 3]; /* Compute the pointer to the next character early so that the next * iteration can start working on the next character. Neither Clang * nor GCC figure out this reordering on their own. */ unsigned char *next = s + len + !len; /* Assume a four-byte character and load four bytes. Unused bits are * shifted out. */ *c = (uint32_t)(s[0] & masks[len]) << 18; *c |= (uint32_t)(s[1] & 0x3f) << 12; *c |= (uint32_t)(s[2] & 0x3f) << 6; *c |= (uint32_t)(s[3] & 0x3f) << 0; *c >>= shiftc[len]; /* Accumulate the various error conditions. */ *e = (*c < mins[len]) << 6; /* non-canonical encoding */ *e |= ((*c >> 11) == 0x1b) << 7; /* surrogate half? */ *e |= (*c > 0x10FFFF) << 8; /* out of range? */ *e |= (s[1] & 0xc0) >> 2; *e |= (s[2] & 0xc0) >> 4; *e |= (s[3] ) >> 6; *e ^= 0x2a; /* top two bits of each tail byte correct? */ *e >>= shifte[len]; return next; } #endif nsxiv-33/util.c000066400000000000000000000145261474777454100136250ustar00rootroot00000000000000/* Copyright 2011-2020 Bert Muennich * Copyright 2021-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #include "nsxiv.h" #include #include #include #include #include #include #include #include #include #include extern char **environ; const char *progname = "nsxiv"; void *emalloc(size_t size) { void *ptr; ptr = malloc(size); if (ptr == NULL) error(EXIT_FAILURE, errno, NULL); return ptr; } void *ecalloc(size_t nmemb, size_t size) { void *ptr; ptr = calloc(nmemb, size); if (ptr == NULL) error(EXIT_FAILURE, errno, NULL); return ptr; } void *erealloc(void *ptr, size_t size) { ptr = realloc(ptr, size); if (ptr == NULL) error(EXIT_FAILURE, errno, NULL); return ptr; } char *estrdup(const char *s) { size_t n = strlen(s) + 1; return memcpy(emalloc(n), s, n); } void error(int eval, int err, const char *fmt, ...) { va_list ap; if (eval == 0 && options->quiet) return; fflush(stdout); fprintf(stderr, "%s: ", progname); va_start(ap, fmt); if (fmt != NULL) vfprintf(stderr, fmt, ap); va_end(ap); if (err != 0) fprintf(stderr, "%s%s", fmt != NULL ? ": " : "", strerror(err)); fputc('\n', stderr); if (eval != 0) exit(eval); } int r_opendir(r_dir_t *rdir, const char *dirname, bool recursive) { if (*dirname == '\0') return -1; if ((rdir->dir = opendir(dirname)) == NULL) { rdir->name = NULL; rdir->stack = NULL; return -1; } rdir->stcap = 512; rdir->stack = emalloc(rdir->stcap * sizeof(*rdir->stack)); rdir->stlen = 0; rdir->name = (char *)dirname; rdir->d = 0; rdir->recursive = recursive; return 0; } int r_closedir(r_dir_t *rdir) { int ret = 0; if (rdir->stack != NULL) { while (rdir->stlen > 0) free(rdir->stack[--rdir->stlen]); free(rdir->stack); rdir->stack = NULL; } if (rdir->dir != NULL) { if ((ret = closedir(rdir->dir)) == 0) rdir->dir = NULL; } if (rdir->d != 0) { free(rdir->name); rdir->name = NULL; } return ret; } char *r_readdir(r_dir_t *rdir, bool skip_dotfiles) { size_t len; char *filename; struct dirent *dentry; struct stat fstats; while (true) { if (rdir->dir != NULL && (dentry = readdir(rdir->dir)) != NULL) { if (dentry->d_name[0] == '.') { if (skip_dotfiles) continue; if (dentry->d_name[1] == '\0') continue; if (dentry->d_name[1] == '.' && dentry->d_name[2] == '\0') continue; } len = strlen(rdir->name) + strlen(dentry->d_name) + 2; filename = emalloc(len); snprintf(filename, len, "%s%s%s", rdir->name, rdir->name[strlen(rdir->name) - 1] == '/' ? "" : "/", dentry->d_name); if (stat(filename, &fstats) < 0) { free(filename); continue; } if (S_ISDIR(fstats.st_mode)) { /* put subdirectory on the stack */ if (rdir->stlen == rdir->stcap) { rdir->stcap *= 2; rdir->stack = erealloc(rdir->stack, rdir->stcap * sizeof(*rdir->stack)); } rdir->stack[rdir->stlen++] = filename; continue; } return filename; } if (rdir->recursive && rdir->stlen > 0) { /* open next subdirectory */ assert(rdir->dir != NULL); closedir(rdir->dir); if (rdir->d != 0) free(rdir->name); rdir->name = rdir->stack[--rdir->stlen]; rdir->d = 1; if ((rdir->dir = opendir(rdir->name)) == NULL) error(0, errno, "%s", rdir->name); continue; } /* no more entries */ break; } return NULL; } int r_mkdir(char *path) { int rc = 0; char c, *s = path; struct stat st; while (*s != '\0' && rc == 0) { if (*s == '/') { s++; continue; } for (; *s != '\0' && *s != '/'; s++) ; c = *s; *s = '\0'; if (mkdir(path, 0755) == -1) { if (errno != EEXIST || stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) { error(0, errno, "%s", path); rc = -1; } } *s = c; } return rc; } void construct_argv(char **argv, unsigned int len, ...) { unsigned int i; va_list args; va_start(args, len); for (i = 0; i < len; ++i) argv[i] = va_arg(args, char *); va_end(args); assert(argv[len - 1] == NULL && "argv should be NULL terminated"); } static int mkspawn_pipe(posix_spawn_file_actions_t *fa, const char *cmd, int *pfd, int dupidx, int pipeflags) { int err = 0; if (pipe(pfd) < 0) { error(0, errno, "pipe: %s", cmd); return -1; } if (pipeflags && (fcntl(pfd[0], F_SETFL, pipeflags) < 0 || fcntl(pfd[1], F_SETFL, pipeflags) < 0)) err = errno; err = err ? err : posix_spawn_file_actions_adddup2(fa, pfd[dupidx], dupidx); err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[0]); err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[1]); if (err) { error(0, err, "mkspawn_pipe: %s", cmd); close(pfd[0]); close(pfd[1]); } return err ? -1 : 0; } pid_t spawn(int *readfd, int *writefd, int pipeflags, char *const argv[]) { pid_t pid = -1; const char *cmd; int err, pfd_read[2], pfd_write[2]; posix_spawn_file_actions_t fa; assert(argv != NULL && argv[0] != NULL); cmd = argv[0]; if ((err = posix_spawn_file_actions_init(&fa)) != 0) { error(0, err, "spawn: %s", cmd); return pid; } if (readfd != NULL && mkspawn_pipe(&fa, cmd, pfd_read, 1, pipeflags) < 0) goto err_destroy_fa; if (writefd != NULL && mkspawn_pipe(&fa, cmd, pfd_write, 0, pipeflags) < 0) goto err_close_readfd; if ((err = posix_spawnp(&pid, cmd, &fa, NULL, argv, environ)) != 0) { error(0, err, "spawn: %s", cmd); } else { if (readfd != NULL) *readfd = pfd_read[0]; if (writefd != NULL) *writefd = pfd_write[1]; } if (writefd != NULL) { close(pfd_write[0]); if (pid < 0) close(pfd_write[1]); } err_close_readfd: if (readfd != NULL) { if (pid < 0) close(pfd_read[0]); close(pfd_read[1]); } err_destroy_fa: posix_spawn_file_actions_destroy(&fa); return pid; } nsxiv-33/window.c000066400000000000000000000365731474777454100141650ustar00rootroot00000000000000/* Copyright 2011-2020 Bert Muennich * Copyright 2021-2023 nsxiv contributors * * This file is a part of nsxiv. * * nsxiv is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * nsxiv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nsxiv. If not, see . */ #include "nsxiv.h" #define INCLUDE_WINDOW_CONFIG #include "config.h" #include "icon/data.h" #include #include #include #include #include #include #include #include #if HAVE_LIBFONTS #include "utf8.h" #define UTF8_PADDING 4 /* utf8_decode requires 4 bytes of zero padding */ #define TEXTWIDTH(win, text, len) \ win_draw_text(win, NULL, NULL, 0, 0, text, len, 0) #endif #define RES_CLASS "Nsxiv" #define INIT_ATOM_(atom) \ atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False); enum { H_TEXT_PAD = 5, V_TEXT_PAD = 1 }; Atom atoms[ATOM_COUNT]; static GC gc; static int barheight; static struct { int name; Cursor icon; } cursors[CURSOR_COUNT] = { { XC_left_ptr }, { XC_dotbox }, { XC_fleur }, { XC_watch }, { XC_sb_left_arrow }, { XC_sb_right_arrow } }; #if HAVE_LIBFONTS static XftFont *font; static double fontsize; /* utf8 ellipsis "…". the buffer must be at least 4 bytes for utf8_decode() */ enum { ELLIPSIS_LEN = 3 }; static const unsigned char ellipsis[4] = { 0xe2, 0x80, 0xa6, 0x0 }; static int ellipsis_w; static int win_draw_text(win_t *, XftDraw *, const XftColor *, int, int, char *, int, int); #endif #if HAVE_LIBFONTS static void win_init_font(const win_env_t *e, const char *fontstr) { int fontheight = 0; if ((font = XftFontOpenName(e->dpy, e->scr, fontstr)) == NULL) error(EXIT_FAILURE, 0, "Error loading font '%s'", fontstr); fontheight = font->ascent + font->descent; FcPatternGetDouble(font->pattern, FC_SIZE, 0, &fontsize); barheight = fontheight + 2 * V_TEXT_PAD; } static void xft_alloc_color(const win_env_t *e, const char *name, XftColor *col) { if (!XftColorAllocName(e->dpy, e->vis, e->cmap, name, col)) error(EXIT_FAILURE, 0, "Error allocating color '%s'", name); } #endif /* HAVE_LIBFONTS */ static void win_alloc_color(const win_env_t *e, const char *name, XColor *col) { XColor screen; if (!XAllocNamedColor(e->dpy, e->cmap, name, &screen, col)) error(EXIT_FAILURE, 0, "Error allocating color '%s'", name); } static const char *win_res(XrmDatabase db, const char *name, const char *def) { char *type; XrmValue ret; if (db != NULL && XrmGetResource(db, name, name, &type, &ret) && STREQ(type, "String") && *ret.addr != '\0') { return ret.addr; } else { return def; } } void win_init(win_t *win) { win_env_t *e; const char *win_bg, *win_fg, *mrk_fg; char *res_man; XrmDatabase db; #if HAVE_LIBFONTS const char *bar_fg, *bar_bg, *f; static char lbuf[512 + UTF8_PADDING], rbuf[64 + UTF8_PADDING]; #endif memset(win, 0, sizeof(*win)); e = &win->env; if ((e->dpy = XOpenDisplay(NULL)) == NULL) error(EXIT_FAILURE, 0, "Error opening X display"); e->scr = DefaultScreen(e->dpy); e->scrw = DisplayWidth(e->dpy, e->scr); e->scrh = DisplayHeight(e->dpy, e->scr); e->depth = DefaultDepth(e->dpy, e->scr); e->vis = DefaultVisual(e->dpy, e->scr); e->cmap = DefaultColormap(e->dpy, e->scr); if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0) error(0, 0, "No locale support"); XrmInitialize(); res_man = XResourceManagerString(e->dpy); db = res_man == NULL ? NULL : XrmGetStringDatabase(res_man); win_bg = win_res(db, WIN_BG[0], WIN_BG[1] ? WIN_BG[1] : "white"); win_fg = win_res(db, WIN_FG[0], WIN_FG[1] ? WIN_FG[1] : "black"); mrk_fg = win_res(db, MARK_FG[0], MARK_FG[1] ? MARK_FG[1] : win_fg); win_alloc_color(e, win_bg, &win->win_bg); win_alloc_color(e, win_fg, &win->win_fg); win_alloc_color(e, mrk_fg, &win->mrk_fg); #if HAVE_LIBFONTS bar_bg = win_res(db, BAR_BG[0], BAR_BG[1] ? BAR_BG[1] : win_bg); bar_fg = win_res(db, BAR_FG[0], BAR_FG[1] ? BAR_FG[1] : win_fg); xft_alloc_color(e, bar_bg, &win->bar_bg); xft_alloc_color(e, bar_fg, &win->bar_fg); f = win_res(db, BAR_FONT[0], BAR_FONT[1] ? BAR_FONT[1] : "monospace-8"); win_init_font(e, f); ellipsis_w = TEXTWIDTH(win, (char *)ellipsis, ELLIPSIS_LEN); win->bar.l.buf = lbuf; win->bar.r.buf = rbuf; win->bar.l.size = sizeof(lbuf) - UTF8_PADDING; win->bar.r.size = sizeof(rbuf) - UTF8_PADDING; win->bar.h = options->hide_bar ? 0 : barheight; win->bar.top = TOP_STATUSBAR; #endif /* HAVE_LIBFONTS */ XrmDestroyDatabase(db); INIT_ATOM_(WM_DELETE_WINDOW); INIT_ATOM_(_NET_WM_NAME); INIT_ATOM_(_NET_WM_ICON_NAME); INIT_ATOM_(_NET_WM_ICON); INIT_ATOM_(_NET_WM_STATE); INIT_ATOM_(_NET_WM_PID); INIT_ATOM_(_NET_WM_STATE_FULLSCREEN); INIT_ATOM_(UTF8_STRING); INIT_ATOM_(WM_NAME); INIT_ATOM_(WM_ICON_NAME); } void win_open(win_t *win) { int c, i, j, n; Window parent; win_env_t *e; XClassHint classhint; unsigned long *icon_data; XColor col; Cursor *cnone = &cursors[CURSOR_NONE].icon; char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; Pixmap none; int gmask; XSizeHints sizehints; XWMHints hints; long pid; char hostname[256]; XSetWindowAttributes attrs; char res_class[] = RES_CLASS; char res_name[] = "nsxiv"; e = &win->env; parent = options->embed ? options->embed : RootWindow(e->dpy, e->scr); sizehints.flags = PWinGravity; sizehints.win_gravity = NorthWestGravity; /* determine window offsets, width & height */ if (options->geometry == NULL) gmask = 0; else gmask = XParseGeometry(options->geometry, &win->x, &win->y, &win->w, &win->h); if (gmask & WidthValue) sizehints.flags |= USSize; else win->w = WIN_WIDTH; if (gmask & HeightValue) sizehints.flags |= USSize; else win->h = WIN_HEIGHT; if (gmask & XValue) { if (gmask & XNegative) { win->x += e->scrw - win->w; sizehints.win_gravity = NorthEastGravity; } sizehints.flags |= USPosition; } else { win->x = 0; } if (gmask & YValue) { if (gmask & YNegative) { win->y += e->scrh - win->h; sizehints.win_gravity = sizehints.win_gravity == NorthEastGravity ? SouthEastGravity : SouthWestGravity; } sizehints.flags |= USPosition; } else { win->y = 0; } attrs.colormap = e->cmap; attrs.border_pixel = 0; win->xwin = XCreateWindow(e->dpy, parent, win->x, win->y, win->w, win->h, 0, e->depth, InputOutput, e->vis, CWColormap | CWBorderPixel, &attrs); if (win->xwin == None) error(EXIT_FAILURE, 0, "Error creating X window"); /* set the _NET_WM_PID */ pid = getpid(); XChangeProperty(e->dpy, win->xwin, atoms[ATOM__NET_WM_PID], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); if (gethostname(hostname, ARRLEN(hostname)) == 0) { XTextProperty tp; tp.value = (unsigned char *)hostname; tp.nitems = strnlen(hostname, ARRLEN(hostname)); tp.encoding = XA_STRING; tp.format = 8; XSetWMClientMachine(e->dpy, win->xwin, &tp); } XSelectInput(e->dpy, win->xwin, ButtonReleaseMask | ButtonPressMask | KeyPressMask | PointerMotionMask | StructureNotifyMask); for (i = 0; i < (int)ARRLEN(cursors); i++) { if (i != CURSOR_NONE) cursors[i].icon = XCreateFontCursor(e->dpy, cursors[i].name); } if (XAllocNamedColor(e->dpy, e->cmap, "black", &col, &col) == 0) error(EXIT_FAILURE, 0, "Error allocating color 'black'"); none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8); *cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0); gc = XCreateGC(e->dpy, win->xwin, 0, None); n = icons[ARRLEN(icons) - 1].size; icon_data = emalloc((n * n + 2) * sizeof(*icon_data)); for (i = 0; i < (int)ARRLEN(icons); i++) { n = 0; icon_data[n++] = icons[i].size; icon_data[n++] = icons[i].size; for (j = 0; j < (int)icons[i].cnt; j++) { for (c = icons[i].data[j] >> 4; c >= 0; c--) icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F]; } XChangeProperty(e->dpy, win->xwin, atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32, i == 0 ? PropModeReplace : PropModeAppend, (unsigned char *)icon_data, n); } free(icon_data); win_set_title(win, res_name, strlen(res_name)); classhint.res_class = res_class; classhint.res_name = options->res_name != NULL ? options->res_name : res_name; XSetClassHint(e->dpy, win->xwin, &classhint); XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1); sizehints.width = win->w; sizehints.height = win->h; sizehints.x = win->x; sizehints.y = win->y; XSetWMNormalHints(win->env.dpy, win->xwin, &sizehints); hints.flags = InputHint | StateHint; hints.input = 1; hints.initial_state = NormalState; XSetWMHints(win->env.dpy, win->xwin, &hints); if (options->fullscreen) { XChangeProperty(e->dpy, win->xwin, atoms[ATOM__NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char *)&atoms[ATOM__NET_WM_STATE_FULLSCREEN], 1); } win->h -= win->bar.h; win->buf.w = e->scrw; win->buf.h = e->scrh; win->buf.pm = XCreatePixmap(e->dpy, win->xwin, win->buf.w, win->buf.h, e->depth); XSetForeground(e->dpy, gc, win->win_bg.pixel); XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); XSetWindowBackgroundPixmap(e->dpy, win->xwin, win->buf.pm); XMapWindow(e->dpy, win->xwin); XFlush(e->dpy); } CLEANUP void win_close(win_t *win) { unsigned int i; for (i = 0; i < ARRLEN(cursors); i++) XFreeCursor(win->env.dpy, cursors[i].icon); XFreeGC(win->env.dpy, gc); #if HAVE_LIBFONTS XftFontClose(win->env.dpy, font); #endif XDestroyWindow(win->env.dpy, win->xwin); XCloseDisplay(win->env.dpy); } bool win_configure(win_t *win, XConfigureEvent *c) { bool changed; changed = win->w != (unsigned int)c->width || win->h + win->bar.h != (unsigned int)c->height; win->x = c->x; win->y = c->y; win->w = c->width; win->h = c->height - win->bar.h; win->bw = c->border_width; return changed; } void win_toggle_fullscreen(win_t *win) { XEvent ev; XClientMessageEvent *cm; memset(&ev, 0, sizeof(ev)); ev.type = ClientMessage; cm = &ev.xclient; cm->window = win->xwin; cm->message_type = atoms[ATOM__NET_WM_STATE]; cm->format = 32; cm->data.l[0] = 2; /* toggle */ cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN]; XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); } void win_toggle_bar(win_t *win) { if (win->bar.h != 0) { win->h += win->bar.h; win->bar.h = 0; } else { win->bar.h = barheight; win->h -= win->bar.h; } } void win_clear(win_t *win) { win_env_t *e = &win->env; if (win->w > win->buf.w || win->h + win->bar.h > win->buf.h) { XFreePixmap(e->dpy, win->buf.pm); win->buf.w = MAX(win->buf.w, win->w); win->buf.h = MAX(win->buf.h, win->h + win->bar.h); win->buf.pm = XCreatePixmap(e->dpy, win->xwin, win->buf.w, win->buf.h, e->depth); } XSetForeground(e->dpy, gc, win->win_bg.pixel); XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); } #if HAVE_LIBFONTS static int win_draw_text(win_t *win, XftDraw *d, const XftColor *color, int x, int y, char *text, int len, int w) { int err, tw = 0, warned = 0, danger_zone = w - ellipsis_w; char *t, *next; uint32_t rune; XftFont *f; FcCharSet *fccharset; XGlyphInfo ext; for (t = text; t - text < len; t = next) { err = 0; next = utf8_decode(t, &rune, &err); if (err) { if (!warned) error(0, 0, "error decoding utf8 status-bar text"); warned = 1; continue; } if (XftCharExists(win->env.dpy, font, rune)) { f = font; } else { /* fallback font */ fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, rune); f = XftFontOpen(win->env.dpy, win->env.scr, FC_CHARSET, FcTypeCharSet, fccharset, FC_SCALABLE, FcTypeBool, FcTrue, FC_SIZE, FcTypeDouble, fontsize, NULL); FcCharSetDestroy(fccharset); } XftTextExtentsUtf8(win->env.dpy, f, (XftChar8 *)t, next - t, &ext); if (d != NULL && tw + ext.xOff >= danger_zone) { int remaining_width = TEXTWIDTH(win, t, (text + len) - t); if (tw + remaining_width > w) { /* overflow, print ellipsis */ if (tw + ellipsis_w <= w) { win_draw_text(win, d, color, x, y, (char *)ellipsis, ELLIPSIS_LEN, ellipsis_w); tw += ellipsis_w; } if (f != font) XftFontClose(win->env.dpy, f); return tw; } /* text will fit, no need to check again on next iterations */ danger_zone = INT_MAX; } tw += ext.xOff; if (tw <= w) { XftDrawStringUtf8(d, color, f, x, y, (XftChar8 *)t, next - t); x += ext.xOff; } if (f != font) XftFontClose(win->env.dpy, f); } return tw; } static void win_draw_bar(win_t *win) { int len, x, y, w, tw; win_env_t *e; win_bar_t *l, *r; XftDraw *d; e = &win->env; l = &win->bar.l; r = &win->bar.r; assert(l->buf != NULL && r->buf != NULL); y = (win->bar.top ? 0 : win->h) + font->ascent + V_TEXT_PAD; w = win->w - 2 * H_TEXT_PAD; d = XftDrawCreate(e->dpy, win->buf.pm, e->vis, e->cmap); XSetForeground(e->dpy, gc, win->bar_bg.pixel); XFillRectangle(e->dpy, win->buf.pm, gc, 0, win->bar.top ? 0 : win->h, win->w, win->bar.h); XSetForeground(e->dpy, gc, win->win_bg.pixel); XSetBackground(e->dpy, gc, win->bar_bg.pixel); if ((len = strlen(r->buf)) > 0) { if ((tw = TEXTWIDTH(win, r->buf, len)) <= w) { x = win->w - tw - H_TEXT_PAD; win_draw_text(win, d, &win->bar_fg, x, y, r->buf, len, tw); } /* remaining width, also keeping gap between left and right parts */ w -= tw + (2 * H_TEXT_PAD); } if ((len = strlen(l->buf)) > 0 && w > 0) { x = H_TEXT_PAD; win_draw_text(win, d, &win->bar_fg, x, y, l->buf, len, w); } XftDrawDestroy(d); } #else static void win_draw_bar(win_t *win) { (void)win; } #endif /* HAVE_LIBFONTS */ void win_draw(win_t *win) { if (win->bar.h > 0) win_draw_bar(win); XSetWindowBackgroundPixmap(win->env.dpy, win->xwin, win->buf.pm); XClearWindow(win->env.dpy, win->xwin); XFlush(win->env.dpy); } void win_draw_rect(win_t *win, int x, int y, int w, int h, bool fill, int lw, unsigned long col) { XGCValues gcval; gcval.line_width = lw; gcval.foreground = col; XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval); if (fill) XFillRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); else XDrawRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); } void win_set_title(win_t *win, const char *title, size_t len) { int i, targets[] = { ATOM_WM_NAME, ATOM_WM_ICON_NAME, ATOM__NET_WM_NAME, ATOM__NET_WM_ICON_NAME }; for (i = 0; i < (int)ARRLEN(targets); ++i) { XChangeProperty(win->env.dpy, win->xwin, atoms[targets[i]], atoms[ATOM_UTF8_STRING], 8, PropModeReplace, (unsigned char *)title, len); } } void win_set_cursor(win_t *win, cursor_t cursor) { if (cursor >= 0 && cursor < ARRLEN(cursors)) { XDefineCursor(win->env.dpy, win->xwin, cursors[cursor].icon); XFlush(win->env.dpy); } } void win_cursor_pos(win_t *win, int *x, int *y) { int i; unsigned int ui; Window w; if (!XQueryPointer(win->env.dpy, win->xwin, &w, &w, &i, &i, x, y, &ui)) *x = *y = 0; }