pax_global_header00006660000000000000000000000064121526142700014512gustar00rootroot0000000000000052 comment=b4346a93971c0d5766eb64445d38ad5c1cc07562 sxiv-1.1.1/000077500000000000000000000000001215261427000125035ustar00rootroot00000000000000sxiv-1.1.1/.gitignore000066400000000000000000000000221215261427000144650ustar00rootroot00000000000000config.h *.o sxiv sxiv-1.1.1/LICENSE000066400000000000000000000432541215261427000135200ustar00rootroot00000000000000 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. sxiv-1.1.1/Makefile000066400000000000000000000021361215261427000141450ustar00rootroot00000000000000VERSION = 1.1.1 PREFIX = /usr/local MANPREFIX = $(PREFIX)/share/man CC = gcc CFLAGS = -std=c99 -Wall -pedantic -O2 -I$(PREFIX)/include -DHAVE_GIFLIB LDFLAGS = -L$(PREFIX)/lib LIBS = -lX11 -lImlib2 -lgif SRC = commands.c exif.c image.c main.c options.c thumbs.c util.c window.c OBJ = $(SRC:.c=.o) all: sxiv $(OBJ): Makefile config.h .c.o: $(CC) $(CFLAGS) -DVERSION=\"$(VERSION)\" -c -o $@ $< config.h: cp config.def.h $@ sxiv: $(OBJ) $(CC) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) clean: rm -f $(OBJ) sxiv install: all mkdir -p $(DESTDIR)$(PREFIX)/bin cp sxiv $(DESTDIR)$(PREFIX)/bin/ chmod 755 $(DESTDIR)$(PREFIX)/bin/sxiv mkdir -p $(DESTDIR)$(MANPREFIX)/man1 sed "s!PREFIX!$(PREFIX)!g; s!VERSION!$(VERSION)!g" sxiv.1 > $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 mkdir -p $(DESTDIR)$(PREFIX)/share/sxiv/exec cp image-info $(DESTDIR)$(PREFIX)/share/sxiv/exec/image-info chmod 755 $(DESTDIR)$(PREFIX)/share/sxiv/exec/image-info uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/sxiv rm -f $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 rm -rf $(DESTDIR)$(PREFIX)/share/sxiv sxiv-1.1.1/README.md000066400000000000000000000212331215261427000137630ustar00rootroot00000000000000sxiv ==== **Simple X Image Viewer** sxiv is an alternative to feh and qiv. Its only dependencies besides xlib are imlib2 and giflib. The primary goal for writing sxiv is to create an image viewer, which only has the most basic features required for fast image viewing (the ones I want). It has vi key bindings and works nicely with tiling window managers. Its code base should be kept small and clean to make it easy for you to dig into it and customize it for your needs. Features -------- * Basic image operations, e.g. zooming, panning, rotating * Customizable key and mouse button mappings (in *config.h*) * Thumbnail mode: grid of selectable previews of all images * Ability to cache thumbnails for fast re-loading * Basic support for multi-frame images * Load all frames from GIF files and play GIF animations * Display image information in status bar Screenshots ----------- **Image mode:** ![Image](http://muennich.github.com/sxiv/img/image.png "Image mode") **Thumbnail mode:** ![Thumb](http://muennich.github.com/sxiv/img/thumb.png "Thumb mode") Installation ------------ sxiv is built using the commands: $ make # make install Please note, that the latter one requires root privileges. By default, sxiv is installed using the prefix "/usr/local", so the full path of the executable will be "/usr/local/bin/sxiv". You can install sxiv into a directory of your choice by changing the second command to: # make PREFIX="/your/dir" install The build-time specific settings of sxiv 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 ----- sxiv 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. **Command line options:** -b Do not show info bar on bottom of window -c Remove all orphaned cache files from thumbnail cache and exit -d Scale all images to 100%, but fit large images into window -F Use size-hints to make the window fixed/floating -f Start in fullscreen mode -g GEOMETRY Set window position and size (see section GEOMETRY SPECIFICATIONS of X(7)) -i Read file list from stdin -n NUM Start at picture NUM -N NAME Set X window resource name to NAME -o Write file list to stdout when quitting -p Pixelize, i.e. turn off image anti-aliasing -q Be quiet, disable warnings -r Search given directories recursively for images -s Scale all images to fit into window -t Start in thumbnail mode -v Print version information and exit -Z Same as `-z 100' -z ZOOM Scale all images to current zoom level, use ZOOM at startup **Key mappings:** q Quit sxiv Return Switch to thumbnail mode / open selected image 0-9 Prefix the next command with a number (denoted via [count]) g Go to first image G Go to the last image, or image number [count] f Toggle fullscreen mode (requires an EWMH/NetWM compliant window manager) b Toggle visibility of info bar on bottom of window A Toggle visibility of alpha-channel, i.e. transparency r Reload image R Reload all thumbnails D Remove image from file list and go to next image *Thumbnail mode:* h,j,k,l Move selection left/down/up/right [count] times Ctrl-j,k Scroll thumbnail grid one window height down/up *Image mode:* n,Space Go [count] images forward p,Backspace Go [count] images backward [,] Go [count] * 10 images backward/forward Ctrl-n,p Go to the next/previous frame of a multi-frame image Ctrl-Space Play/pause animation of a multi-frame image + Zoom in - Zoom out = Set zoom level to 100%, or [count]% w Fit image into window e Fit image width to window width E Fit image height to window height h,j,k,l Pan image 1/5 of window width/height or [count] pixels left/down/up/right (also with arrow keys) H,J,K,L Pan to left/bottom/top/right image edge Ctrl-h,j,k,l Pan image one window width/height left/down/up/right (also with Ctrl-arrow keys) <,> Rotate image (counter-)clockwise by 90 degrees \,| Flip image horizontally/vertically a Toggle anti-aliasing W Resize window to fit image **Mouse button mappings:** *Image mode:* Button1 Go to the next image Button2 Drag image with mouse while keeping it pressed Button3 Go to the previous image Scroll Pan image up/down Shift+Scroll Pan image left/right Ctrl+Scroll Zoom in/out Download & Changelog -------------------- You can [browse](https://github.com/muennich/sxiv) the source code repository on GitHub or get a copy using git with the following command: git clone https://github.com/muennich/sxiv.git **Stable releases** **[v1.1.1](https://github.com/muennich/sxiv/archive/v1.1.1.tar.gz)** *(June 2, 2013)* * Various bug fixes **[v1.1](https://github.com/muennich/sxiv/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/muennich/sxiv/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/muennich/sxiv/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/muennich/sxiv/archive/v0.8.2.tar.gz)** *(June 29, 2011)* * POSIX-compliant Makefile; compiles under NetBSD **[v0.8.1](https://github.com/muennich/sxiv/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/muennich/sxiv/archive/v0.8.tar.gz)** *(April 18, 2011)* * Support for thumbnail caching, only enabled if directory `~/.sxiv/` exists * Ability to run external commands (e.g. jpegtran, convert) on current image **[v0.7](https://github.com/muennich/sxiv/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/muennich/sxiv/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/muennich/sxiv/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/muennich/sxiv/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/muennich/sxiv/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/muennich/sxiv/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/muennich/sxiv/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/muennich/sxiv/archive/v0.1.tar.gz)** *(January 21, 2011)* * Initial release sxiv-1.1.1/commands.c000066400000000000000000000231001215261427000144440ustar00rootroot00000000000000/* Copyright 2011, 2012 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #define _POSIX_C_SOURCE 200112L #define _IMAGE_CONFIG #include #include #include #include #include "commands.h" #include "image.h" #include "options.h" #include "thumbs.h" #include "util.h" #include "config.h" void cleanup(void); void remove_file(int, bool); void load_image(int); void open_info(void); void redraw(void); void reset_cursor(void); void animate(void); void set_timeout(timeout_f, int, bool); void reset_timeout(timeout_f); extern appmode_t mode; extern img_t img; extern tns_t tns; extern win_t win; extern fileinfo_t *files; extern int filecnt, fileidx; extern int alternate; extern int prefix; const int ss_delays[] = { 1, 2, 3, 5, 10, 15, 20, 30, 60, 120, 180, 300, 600 }; bool it_quit(arg_t a) { unsigned int i; if (options->to_stdout) { for (i = 0; i < filecnt; i++) printf("%s\n", files[i].name); } cleanup(); exit(EXIT_SUCCESS); } bool it_switch_mode(arg_t a) { if (mode == MODE_IMAGE) { if (tns.thumbs == NULL) tns_init(&tns, filecnt, &win); img_close(&img, false); reset_timeout(reset_cursor); tns.sel = fileidx; tns.dirty = true; mode = MODE_THUMB; } else { load_image(tns.sel); mode = MODE_IMAGE; } return true; } bool it_toggle_fullscreen(arg_t a) { 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 it_toggle_bar(arg_t a) { win_toggle_bar(&win); if (mode == MODE_IMAGE) { img.checkpan = img.dirty = true; if (win.bar.h > 0) open_info(); } else { tns.dirty = true; } return true; } bool t_reload_all(arg_t a) { if (mode == MODE_THUMB) { tns_free(&tns); tns_init(&tns, filecnt, &win); return true; } else { return false; } } bool it_reload_image(arg_t a) { if (mode == MODE_IMAGE) { load_image(fileidx); } else { win_set_cursor(&win, CURSOR_WATCH); if (!tns_load(&tns, tns.sel, &files[tns.sel], true, false)) { remove_file(tns.sel, false); tns.dirty = true; if (tns.sel >= tns.cnt) tns.sel = tns.cnt - 1; } } return true; } bool it_remove_image(arg_t a) { if (mode == MODE_IMAGE) { remove_file(fileidx, true); load_image(fileidx >= filecnt ? filecnt - 1 : fileidx); return true; } else if (tns.sel < tns.cnt) { remove_file(tns.sel, true); tns.dirty = true; if (tns.sel >= tns.cnt) tns.sel = tns.cnt - 1; return true; } else { return false; } } bool i_navigate(arg_t a) { long n = (long) a; if (mode == MODE_IMAGE) { if (prefix > 0) n *= prefix; n += fileidx; if (n < 0) n = 0; if (n >= filecnt) n = filecnt - 1; if (n != fileidx) { load_image(n); return true; } } return false; } bool i_alternate(arg_t a) { if (mode == MODE_IMAGE) { load_image(alternate); return true; } else { return false; } } bool it_first(arg_t a) { if (mode == MODE_IMAGE && fileidx != 0) { load_image(0); return true; } else if (mode == MODE_THUMB && tns.sel != 0) { tns.sel = 0; tns.dirty = true; return true; } else { return false; } } bool it_n_or_last(arg_t a) { int n = prefix != 0 && prefix - 1 < filecnt ? prefix - 1 : filecnt - 1; if (mode == MODE_IMAGE && fileidx != n) { load_image(n); return true; } else if (mode == MODE_THUMB && tns.sel != n) { tns.sel = n; tns.dirty = true; return true; } else { return false; } } bool i_navigate_frame(arg_t a) { if (mode == MODE_IMAGE && !img.multi.animate) return img_frame_navigate(&img, (long) a); else return false; } bool i_toggle_animation(arg_t a) { if (mode != MODE_IMAGE) return false; if (img.multi.animate) { reset_timeout(animate); img.multi.animate = false; } else if (img_frame_animate(&img, true)) { set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); } return true; } bool it_scroll_move(arg_t a) { direction_t dir = (direction_t) a; if (mode == MODE_IMAGE) return img_pan(&img, dir, prefix); else return tns_move_selection(&tns, dir, prefix); } bool it_scroll_screen(arg_t a) { direction_t dir = (direction_t) a; if (mode == MODE_IMAGE) return img_pan(&img, dir, -1); else return tns_scroll(&tns, dir, true); } bool i_scroll_to_edge(arg_t a) { direction_t dir = (direction_t) a; if (mode == MODE_IMAGE) return img_pan_edge(&img, dir); else return false; } /* Xlib helper function for i_drag() */ Bool is_motionnotify(Display *d, XEvent *e, XPointer a) { return e != NULL && e->type == MotionNotify; } #define WARP(x,y) \ XWarpPointer(win.env.dpy, None, win.xwin, 0, 0, 0, 0, x, y); \ ox = x, oy = y; \ break bool i_drag(arg_t a) { int dx = 0, dy = 0, i, ox, oy, x, y; unsigned int ui; bool dragging = true, next = false; XEvent e; Window w; if (mode != MODE_IMAGE) return false; if (!XQueryPointer(win.env.dpy, win.xwin, &w, &w, &i, &i, &ox, &oy, &ui)) return false; win_set_cursor(&win, CURSOR_HAND); while (dragging) { if (!next) XMaskEvent(win.env.dpy, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); switch (e.type) { case ButtonPress: case ButtonRelease: dragging = false; break; case MotionNotify: x = e.xmotion.x; y = e.xmotion.y; /* wrap the mouse around */ if (x < 0) { WARP(win.w, y); } else if (x > win.w) { WARP(0, y); } else if (y < 0) { WARP(x, win.h); } else if (y > win.h) { WARP(x, 0); } dx += x - ox; dy += y - oy; ox = x; oy = y; break; } if (dragging) next = XCheckIfEvent(win.env.dpy, &e, is_motionnotify, None); if ((!dragging || !next) && (dx != 0 || dy != 0)) { if (img_move(&img, dx, dy)) { img_render(&img); win_draw(&win); } dx = dy = 0; } } win_set_cursor(&win, CURSOR_ARROW); set_timeout(reset_cursor, TO_CURSOR_HIDE, true); reset_timeout(redraw); return false; } bool i_zoom(arg_t a) { long scale = (long) a; if (mode != MODE_IMAGE) return false; if (scale > 0) return img_zoom_in(&img); else if (scale < 0) return img_zoom_out(&img); else return false; } bool i_set_zoom(arg_t a) { if (mode == MODE_IMAGE) return img_zoom(&img, (prefix ? prefix : (long) a) / 100.0); else return false; } bool i_fit_to_win(arg_t a) { bool ret = false; scalemode_t sm = (scalemode_t) a; if (mode == MODE_IMAGE) { if ((ret = img_fit_win(&img, sm))) img_center(&img); } return ret; } bool i_fit_to_img(arg_t a) { int x, y; unsigned int w, h; bool ret = false; if (mode == MODE_IMAGE) { x = MAX(0, win.x + img.x); y = MAX(0, win.y + img.y); w = img.w * img.zoom; h = img.h * img.zoom; if ((ret = win_moveresize(&win, x, y, w, h))) { img.x = x - win.x; img.y = y - win.y; img.dirty = true; } } return ret; } bool i_rotate(arg_t a) { direction_t dir = (direction_t) a; if (mode == MODE_IMAGE) { if (dir == DIR_LEFT) { img_rotate_left(&img); return true; } else if (dir == DIR_RIGHT) { img_rotate_right(&img); return true; } } return false; } bool i_flip(arg_t a) { flipdir_t dir = (flipdir_t) a; if (mode == MODE_IMAGE) { img_flip(&img, dir); return true; } else { return false; } } bool i_toggle_antialias(arg_t a) { if (mode == MODE_IMAGE) { img_toggle_antialias(&img); return true; } else { return false; } } bool it_toggle_alpha(arg_t a) { img.alpha = tns.alpha = !img.alpha; if (mode == MODE_IMAGE) img.dirty = true; else tns.dirty = true; return true; } bool it_open_with(arg_t a) { const char *prog = (const char*) a; pid_t pid; if (prog == NULL || *prog == '\0') return false; if ((pid = fork()) == 0) { execlp(prog, prog, files[mode == MODE_IMAGE ? fileidx : tns.sel].path, NULL); warn("could not exec: %s", prog); exit(EXIT_FAILURE); } else if (pid < 0) { warn("could not fork. program was: %s", prog); } return false; } bool it_shell_cmd(arg_t a) { int n, status; const char *cmdline = (const char*) a; pid_t pid; if (cmdline == NULL || *cmdline == '\0') return false; n = mode == MODE_IMAGE ? fileidx : tns.sel; if (setenv("SXIV_IMG", files[n].path, 1) < 0) { warn("could not set env.-variable: SXIV_IMG. command line was: %s", cmdline); return false; } if ((pid = fork()) == 0) { execl("/bin/sh", "/bin/sh", "-c", cmdline, NULL); warn("could not exec: /bin/sh. command line was: %s", cmdline); exit(EXIT_FAILURE); } else if (pid < 0) { warn("could not fork. command line was: %s", cmdline); return false; } win_set_cursor(&win, CURSOR_WATCH); waitpid(pid, &status, 0); if (WIFEXITED(status) == 0 || WEXITSTATUS(status) != 0) warn("child exited with non-zero return value: %d. command line was: %s", WEXITSTATUS(status), cmdline); if (mode == MODE_IMAGE) { img_close(&img, true); load_image(fileidx); } if (!tns_load(&tns, n, &files[n], true, mode == MODE_IMAGE) && mode == MODE_THUMB) { remove_file(tns.sel, false); tns.dirty = true; if (tns.sel >= tns.cnt) tns.sel = tns.cnt - 1; } return true; } sxiv-1.1.1/commands.h000066400000000000000000000033371215261427000144630ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #ifndef COMMANDS_H #define COMMANDS_H #include #include "types.h" typedef void* arg_t; typedef bool (*command_f)(arg_t); typedef struct { bool ctrl; KeySym ksym; command_f cmd; arg_t arg; } keymap_t; typedef struct { bool ctrl; bool shift; unsigned int button; command_f cmd; arg_t arg; } button_t; bool it_quit(arg_t); bool it_switch_mode(arg_t); bool it_toggle_fullscreen(arg_t); bool it_toggle_bar(arg_t); bool t_reload_all(arg_t); bool it_reload_image(arg_t); bool it_remove_image(arg_t); bool i_navigate(arg_t); bool i_alternate(arg_t); bool it_first(arg_t); bool it_n_or_last(arg_t); bool i_navigate_frame(arg_t); bool i_toggle_animation(arg_t); bool it_scroll_move(arg_t); bool it_scroll_screen(arg_t); bool i_scroll_to_edge(arg_t); bool i_drag(arg_t); bool i_zoom(arg_t); bool i_set_zoom(arg_t); bool i_fit_to_win(arg_t); bool i_fit_to_img(arg_t); bool i_rotate(arg_t); bool i_flip(arg_t); bool i_toggle_antialias(arg_t); bool it_toggle_alpha(arg_t); bool it_open_with(arg_t); bool it_shell_cmd(arg_t); #endif /* COMMANDS_H */ sxiv-1.1.1/config.def.h000066400000000000000000000152621215261427000146640ustar00rootroot00000000000000#ifdef _WINDOW_CONFIG /* default window dimensions (overwritten via -g option): */ enum { WIN_WIDTH = 800, WIN_HEIGHT = 600 }; /* bar font: * (see X(7) section "FONT NAMES" for valid values) */ static const char * const BAR_FONT = "-*-fixed-medium-r-*-*-13-*-*-*-*-60-*-*"; /* colors: * (see X(7) section "COLOR NAMES" for valid values) */ static const char * const WIN_BG_COLOR = "#777777"; static const char * const WIN_FS_COLOR = "#000000"; static const char * const SEL_COLOR = "#DDDDDD"; static const char * const BAR_BG_COLOR = "#222222"; static const char * const BAR_FG_COLOR = "#EEEEEE"; #endif #ifdef _IMAGE_CONFIG /* how should images be scaled when they are loaded? * (also controllable via -d/-s/-Z/-z options) * SCALE_DOWN: 100%, but fit large images into window, * SCALE_FIT: fit all images into window, * SCALE_ZOOM: use current zoom level, 100% at startup */ static const scalemode_t SCALE_MODE = SCALE_DOWN; /* 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 settings for multi-frame gif images: */ enum { GIF_DELAY = 100, /* delay time (in ms) */ GIF_AUTOPLAY = 1, /* autoplay when loaded [0/1] */ GIF_LOOP = 0 /* endless loop [0/1] */ }; #endif #ifdef _THUMBS_CONFIG /* default dimension of thumbnails (width == height): */ enum { THUMB_SIZE = 60 }; #endif #ifdef _MAPPINGS_CONFIG /* keyboard mappings for image and thumbnail mode: */ static const keymap_t keys[] = { /* ctrl key function argument */ { false, XK_q, it_quit, (arg_t) None }, { false, XK_Return, it_switch_mode, (arg_t) None }, { false, XK_f, it_toggle_fullscreen, (arg_t) None }, { false, XK_b, it_toggle_bar, (arg_t) None }, { false, XK_r, it_reload_image, (arg_t) None }, { false, XK_R, t_reload_all, (arg_t) None }, { false, XK_D, it_remove_image, (arg_t) None }, { false, XK_n, i_navigate, (arg_t) +1 }, { false, XK_space, i_navigate, (arg_t) +1 }, { false, XK_p, i_navigate, (arg_t) -1 }, { false, XK_BackSpace, i_navigate, (arg_t) -1 }, { false, XK_bracketright, i_navigate, (arg_t) +10 }, { false, XK_bracketleft, i_navigate, (arg_t) -10 }, { true, XK_6, i_alternate, (arg_t) None }, { false, XK_g, it_first, (arg_t) None }, { false, XK_G, it_n_or_last, (arg_t) None }, { true, XK_n, i_navigate_frame, (arg_t) +1 }, { true, XK_p, i_navigate_frame, (arg_t) -1 }, { true, XK_space, i_toggle_animation, (arg_t) None }, { false, XK_h, it_scroll_move, (arg_t) DIR_LEFT }, { false, XK_Left, it_scroll_move, (arg_t) DIR_LEFT }, { false, XK_j, it_scroll_move, (arg_t) DIR_DOWN }, { false, XK_Down, it_scroll_move, (arg_t) DIR_DOWN }, { false, XK_k, it_scroll_move, (arg_t) DIR_UP }, { false, XK_Up, it_scroll_move, (arg_t) DIR_UP }, { false, XK_l, it_scroll_move, (arg_t) DIR_RIGHT }, { false, XK_Right, it_scroll_move, (arg_t) DIR_RIGHT }, { true, XK_h, it_scroll_screen, (arg_t) DIR_LEFT }, { true, XK_Left, it_scroll_screen, (arg_t) DIR_LEFT }, { true, XK_j, it_scroll_screen, (arg_t) DIR_DOWN }, { true, XK_Down, it_scroll_screen, (arg_t) DIR_DOWN }, { true, XK_k, it_scroll_screen, (arg_t) DIR_UP }, { true, XK_Up, it_scroll_screen, (arg_t) DIR_UP }, { true, XK_l, it_scroll_screen, (arg_t) DIR_RIGHT }, { true, XK_Right, it_scroll_screen, (arg_t) DIR_RIGHT }, { false, XK_H, i_scroll_to_edge, (arg_t) DIR_LEFT }, { false, XK_J, i_scroll_to_edge, (arg_t) DIR_DOWN }, { false, XK_K, i_scroll_to_edge, (arg_t) DIR_UP }, { false, XK_L, i_scroll_to_edge, (arg_t) DIR_RIGHT }, { false, XK_plus, i_zoom, (arg_t) +1 }, { false, XK_KP_Add, i_zoom, (arg_t) +1 }, { false, XK_minus, i_zoom, (arg_t) -1 }, { false, XK_KP_Subtract, i_zoom, (arg_t) -1 }, { false, XK_equal, i_set_zoom, (arg_t) 100 }, { false, XK_w, i_fit_to_win, (arg_t) SCALE_FIT }, { false, XK_e, i_fit_to_win, (arg_t) SCALE_WIDTH }, { false, XK_E, i_fit_to_win, (arg_t) SCALE_HEIGHT }, { false, XK_W, i_fit_to_img, (arg_t) None }, { false, XK_less, i_rotate, (arg_t) DIR_LEFT }, { false, XK_greater, i_rotate, (arg_t) DIR_RIGHT }, { false, XK_backslash, i_flip, (arg_t) FLIP_HORIZONTAL }, { false, XK_bar, i_flip, (arg_t) FLIP_VERTICAL }, { false, XK_a, i_toggle_antialias, (arg_t) None }, { false, XK_A, it_toggle_alpha, (arg_t) None }, /* open current image with given program: */ { true, XK_g, it_open_with, (arg_t) "gimp" }, /* run shell command line on current file ("$SXIV_IMG"): */ { true, XK_less, it_shell_cmd, (arg_t) \ "mogrify -rotate -90 \"$SXIV_IMG\"" }, { true, XK_greater, it_shell_cmd, (arg_t) \ "mogrify -rotate +90 \"$SXIV_IMG\"" }, { true, XK_comma, it_shell_cmd, (arg_t) \ "jpegtran -rotate 270 -copy all -outfile \"$SXIV_IMG\" \"$SXIV_IMG\"" }, { true, XK_period, it_shell_cmd, (arg_t) \ "jpegtran -rotate 90 -copy all -outfile \"$SXIV_IMG\" \"$SXIV_IMG\"" }, }; /* mouse button mappings for image mode: */ static const button_t buttons[] = { /* ctrl shift button function argument */ { false, false, Button1, i_navigate, (arg_t) +1 }, { false, false, Button3, i_navigate, (arg_t) -1 }, { false, false, Button2, i_drag, (arg_t) None }, { false, false, Button4, it_scroll_move, (arg_t) DIR_UP }, { false, false, Button5, it_scroll_move, (arg_t) DIR_DOWN }, { false, true, Button4, it_scroll_move, (arg_t) DIR_LEFT }, { false, true, Button5, it_scroll_move, (arg_t) DIR_RIGHT }, { true, false, Button4, i_zoom, (arg_t) +1 }, { true, false, Button5, i_zoom, (arg_t) -1 }, }; #endif sxiv-1.1.1/exif.c000066400000000000000000000055241215261427000136100ustar00rootroot00000000000000/* Copyright 2012 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #define _POSIX_C_SOURCE 200112L #include #include #include #include #include "exif.h" #include "util.h" ssize_t s_read(int fd, const char *fn, void *buf, size_t n) { ssize_t ret; ret = read(fd, buf, n); if (ret < n) { warn("unexpected end-of-file: %s", fn); return -1; } else { return ret; } } unsigned short btous(unsigned char *buf, byteorder_t order) { if (buf == NULL) return 0; if (order == BO_BIG_ENDIAN) return buf[0] << 8 | buf[1]; else return buf[1] << 8 | buf[0]; } unsigned int btoui(unsigned char *buf, byteorder_t order) { if (buf == NULL) return 0; if (order == BO_BIG_ENDIAN) return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; else return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]; } int exif_orientation(const fileinfo_t *file) { int fd; unsigned char data[EXIF_MAX_LEN]; byteorder_t order = BO_BIG_ENDIAN; unsigned int cnt, len, idx, val; if (file == NULL || file->path == NULL) return -1; fd = open(file->path, O_RDONLY); if (fd < 0) return -1; if (s_read(fd, file->name, data, 4) < 0) goto abort; if (btous(data, order) != JPEG_MARKER_SOI) goto abort; if (btous(data + 2, order) != JPEG_MARKER_APP1) goto abort; if (s_read(fd, file->name, data, 2) < 0) goto abort; len = btous(data, order); if (len < 8) goto abort; if (s_read(fd, file->name, data, 6) < 0) goto abort; if (btoui(data, order) != EXIF_HEAD) goto abort; len -= 8; if (len < 12 || len > EXIF_MAX_LEN) goto abort; if (s_read(fd, file->name, data, len) < 0) goto abort; switch (btous(data, order)) { case EXIF_BO_BIG_ENDIAN: order = BO_BIG_ENDIAN; break; case EXIF_BO_LITTLE_ENDIAN: order = BO_LITTLE_ENDIAN; break; default: goto abort; break; } if (btous(data + 2, order) != EXIF_TAG_MARK) goto abort; idx = btoui(data + 4, order); if (idx > len - 2) goto abort; val = 0; cnt = btous(data + idx, order); for (idx += 2; cnt > 0 && idx < len - 12; cnt--, idx += 12) { if (btous(data + idx, order) == EXIF_TAG_ORIENTATION) { val = btous(data + idx + 8, order); break; } } close(fd); return val; abort: close(fd); return -1; } sxiv-1.1.1/exif.h000066400000000000000000000021751215261427000136140ustar00rootroot00000000000000/* Copyright 2012 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #ifndef EXIF_H #define EXIF_H #include "types.h" enum { JPEG_MARKER_SOI = 0xFFD8, JPEG_MARKER_APP1 = 0xFFE1 }; enum { EXIF_MAX_LEN = 0x10000, EXIF_HEAD = 0x45786966, EXIF_BO_BIG_ENDIAN = 0x4D4D, EXIF_BO_LITTLE_ENDIAN = 0x4949, EXIF_TAG_MARK = 0x002A, EXIF_TAG_ORIENTATION = 0x0112 }; int exif_orientation(const fileinfo_t*); void exif_auto_orientate(const fileinfo_t*); /* in image.c */ #endif /* EXIF_H */ sxiv-1.1.1/image-info000066400000000000000000000007631215261427000144470ustar00rootroot00000000000000#!/bin/sh # Example for ~/.sxiv/exec/image-info # Called by sxiv(1) whenever an image gets loaded, # with the name of the image file as its first argument. # The output is displayed in sxiv's status bar. s=" | " # field separator filename=$(basename "$1") filesize=$(du -Hh "$1" | cut -f 1) geometry=$(identify -format '%wx%h' "$1[0]") tags=$(exiv2 -q pr -pi "$1" | awk '$1~"Keywords" { printf("%s,", $4); }') tags=${tags%,} echo "${filesize}${s}${geometry}${tags:+$s}${tags}${s}${filename}" sxiv-1.1.1/image.c000066400000000000000000000367371215261427000137510ustar00rootroot00000000000000/* Copyright 2011, 2012 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #define _POSIX_C_SOURCE 200112L #define _IMAGE_CONFIG #include #include #include #include #if HAVE_GIFLIB #include enum { MIN_GIF_DELAY = 25 }; #endif #include "exif.h" #include "image.h" #include "options.h" #include "util.h" #include "config.h" float zoom_min; float zoom_max; int zoomdiff(float z1, float z2) { return (int) (z1 * 1000.0 - z2 * 1000.0); } void img_init(img_t *img, win_t *win) { zoom_min = zoom_levels[0] / 100.0; zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0; if (img == NULL || win == NULL) return; imlib_context_set_display(win->env.dpy); imlib_context_set_visual(win->env.vis); imlib_context_set_colormap(win->env.cmap); img->im = NULL; img->win = win; 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->aa = options->aa; img->alpha = true; img->multi.cap = img->multi.cnt = 0; img->multi.animate = false; } void exif_auto_orientate(const fileinfo_t *file) { switch (exif_orientation(file)) { case 5: imlib_image_orientate(1); case 2: imlib_image_flip_vertical(); break; case 3: imlib_image_orientate(2); break; case 7: imlib_image_orientate(1); case 4: imlib_image_flip_horizontal(); break; case 6: imlib_image_orientate(1); break; case 8: imlib_image_orientate(3); break; } } #if HAVE_GIFLIB bool img_load_gif(img_t *img, const fileinfo_t *file) { GifFileType *gif; GifRowType *rows = NULL; GifRecordType rec; ColorMapObject *cmap; DATA32 bgpixel, *data, *ptr; DATA32 *prev_frame = NULL; Imlib_Image *im; int i, j, bg, r, g, b; int x, y, w, h, sw, sh; int px, py, pw, ph; int intoffset[] = { 0, 4, 2, 1 }; int intjump[] = { 8, 8, 4, 2 }; int transp = -1; unsigned int disposal = 0, prev_disposal = 0; unsigned int delay = 0; bool err = false; if (img->multi.cap == 0) { img->multi.cap = 8; img->multi.frames = (img_frame_t*) s_malloc(sizeof(img_frame_t) * img->multi.cap); } img->multi.cnt = 0; img->multi.sel = 0; #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 gif = DGifOpenFileName(file->path, NULL); #else gif = DGifOpenFileName(file->path); #endif if (gif == NULL) { warn("could not open gif file: %s", file->name); return false; } bg = gif->SBackGroundColor; sw = gif->SWidth; sh = gif->SHeight; px = py = pw = ph = 0; do { if (DGifGetRecordType(gif, &rec) == GIF_ERROR) { err = true; break; } if (rec == EXTENSION_RECORD_TYPE) { int ext_code; GifByteType *ext = NULL; DGifGetExtension(gif, &ext_code, &ext); while (ext) { if (ext_code == 0xf9) { if (ext[1] & 1) transp = (int) ext[4]; else transp = -1; delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]); if (delay) delay = MAX(delay, MIN_GIF_DELAY); disposal = (unsigned int) ext[1] >> 2 & 0x7; } ext = NULL; DGifGetExtensionNext(gif, &ext); } } else if (rec == IMAGE_DESC_RECORD_TYPE) { if (DGifGetImageDesc(gif) == GIF_ERROR) { err = true; break; } x = gif->Image.Left; y = gif->Image.Top; w = gif->Image.Width; h = gif->Image.Height; rows = (GifRowType*) s_malloc(h * sizeof(GifRowType)); for (i = 0; i < h; i++) rows[i] = (GifRowType) s_malloc(w * sizeof(GifPixelType)); if (gif->Image.Interlace) { for (i = 0; i < 4; i++) { for (j = intoffset[i]; j < h; j += intjump[i]) DGifGetLine(gif, rows[j], w); } } else { for (i = 0; i < h; i++) DGifGetLine(gif, rows[i], w); } ptr = data = (DATA32*) s_malloc(sizeof(DATA32) * sw * sh); cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; r = cmap->Colors[bg].Red; g = cmap->Colors[bg].Green; b = cmap->Colors[bg].Blue; bgpixel = 0x00ffffff & (r << 16 | g << 8 | b); for (i = 0; i < sh; i++) { for (j = 0; j < sw; j++) { if (i < y || i >= y + h || j < x || j >= x + w || rows[i-y][j-x] == transp) { if (prev_frame != NULL && (prev_disposal != 2 || i < py || i >= py + ph || j < px || j >= px + pw)) { *ptr = prev_frame[i * sw + j]; } else { *ptr = bgpixel; } } else { r = cmap->Colors[rows[i-y][j-x]].Red; g = cmap->Colors[rows[i-y][j-x]].Green; b = cmap->Colors[rows[i-y][j-x]].Blue; *ptr = 0xff << 24 | r << 16 | g << 8 | b; } ptr++; } } im = imlib_create_image_using_copied_data(sw, sh, data); for (i = 0; i < h; i++) free(rows[i]); free(rows); free(data); if (im == NULL) { err = true; break; } imlib_context_set_image(im); imlib_image_set_format("gif"); if (transp >= 0) imlib_image_set_has_alpha(1); if (disposal != 3) prev_frame = imlib_image_get_data_for_reading_only(); prev_disposal = disposal; px = x, py = y, pw = w, ph = h; if (img->multi.cnt == img->multi.cap) { img->multi.cap *= 2; img->multi.frames = (img_frame_t*) s_realloc(img->multi.frames, img->multi.cap * sizeof(img_frame_t)); } img->multi.frames[img->multi.cnt].im = im; img->multi.frames[img->multi.cnt].delay = delay ? delay : GIF_DELAY; img->multi.cnt++; } } while (rec != TERMINATE_RECORD_TYPE); DGifCloseFile(gif); if (err && !file->loaded) warn("corrupted gif file: %s", file->name); if (img->multi.cnt > 1) { imlib_context_set_image(img->im); imlib_free_image(); img->im = img->multi.frames[0].im; img->multi.animate = GIF_AUTOPLAY; } else if (img->multi.cnt == 1) { imlib_context_set_image(img->multi.frames[0].im); imlib_free_image(); img->multi.cnt = 0; img->multi.animate = false; } imlib_context_set_image(img->im); return !err; } #endif /* HAVE_GIFLIB */ bool img_load(img_t *img, const fileinfo_t *file) { const char *fmt; if (img == NULL || file == NULL || file->name == NULL || file->path == NULL) return false; if (access(file->path, R_OK) < 0 || (img->im = imlib_load_image(file->path)) == NULL) { warn("could not open image: %s", file->name); return false; } imlib_context_set_image(img->im); imlib_image_set_changes_on_disk(); if ((fmt = imlib_image_format()) == NULL) { warn("could not open image: %s", file->name); return false; } if (STREQ(fmt, "jpeg")) exif_auto_orientate(file); #if HAVE_GIFLIB if (STREQ(fmt, "gif")) img_load_gif(img, file); #endif img->w = imlib_image_get_width(); img->h = imlib_image_get_height(); img->scalemode = options->scalemode; img->re = false; img->checkpan = false; img->dirty = true; return true; } void img_close(img_t *img, bool decache) { int i; if (img == NULL) return; if (img->multi.cnt > 0) { for (i = 0; i < img->multi.cnt; i++) { imlib_context_set_image(img->multi.frames[i].im); imlib_free_image(); } img->multi.cnt = 0; img->im = NULL; } else if (img->im != NULL) { imlib_context_set_image(img->im); if (decache) imlib_free_image_and_decache(); else imlib_free_image(); img->im = NULL; } } void img_check_pan(img_t *img, bool moved) { win_t *win; int ox, oy; if (img == NULL || img->im == NULL || img->win == NULL) return; win = img->win; ox = img->x; oy = img->y; if (img->w * img->zoom > win->w) { if (img->x > 0 && img->x + img->w * img->zoom > win->w) img->x = 0; if (img->x < 0 && img->x + img->w * img->zoom < win->w) img->x = win->w - img->w * img->zoom; } else { img->x = (win->w - img->w * img->zoom) / 2; } if (img->h * img->zoom > win->h) { if (img->y > 0 && img->y + img->h * img->zoom > win->h) img->y = 0; if (img->y < 0 && img->y + img->h * img->zoom < win->h) img->y = win->h - img->h * img->zoom; } else { img->y = (win->h - img->h * img->zoom) / 2; } if (!moved && (ox != img->x || oy != img->y)) img->dirty = true; } bool img_fit(img_t *img) { float z, zmax, zw, zh; if (img == NULL || img->im == NULL || img->win == NULL) return false; if (img->scalemode == SCALE_ZOOM) return false; zmax = img->scalemode == SCALE_DOWN ? 1.0 : zoom_max; zw = (float) img->win->w / (float) img->w; zh = (float) img->win->h / (float) img->h; switch (img->scalemode) { case SCALE_WIDTH: z = zw; break; case SCALE_HEIGHT: z = zh; break; default: z = MIN(zw, zh); break; } z = MAX(z, zoom_min); z = MIN(z, zmax); if (zoomdiff(z, img->zoom) != 0) { 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; if (img == NULL || img->im == NULL || img->win == NULL) return; win = img->win; img_fit(img); if (!img->re) { /* rendered for the first time */ img->re = true; if (img->zoom * img->w <= win->w) img->x = (win->w - img->w * img->zoom) / 2; else img->x = 0; if (img->zoom * img->h <= win->h) img->y = (win->h - img->h * img->zoom) / 2; else img->y = 0; } if (img->checkpan) { img_check_pan(img, false); img->checkpan = false; } if (!img->dirty) return; /* calculate source and destination offsets */ if (img->x < 0) { sx = -img->x / img->zoom; sw = win->w / img->zoom; dx = 0; dw = win->w; } else { sx = 0; sw = img->w; dx = img->x; dw = img->w * img->zoom; } if (img->y < 0) { sy = -img->y / img->zoom; sh = win->h / img->zoom; dy = 0; dh = win->h; } else { sy = 0; sh = img->h; dy = img->y; dh = img->h * img->zoom; } win_clear(win); imlib_context_set_image(img->im); imlib_context_set_anti_alias(img->aa); if (!img->alpha && imlib_image_has_alpha()) win_draw_rect(win, win->pm, dx, dy, dw, dh, True, 0, win->white); imlib_context_set_drawable(win->pm); 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) { if (img == NULL || img->im == NULL) return false; img->scalemode = sm; return img_fit(img); } bool img_center(img_t *img) { int ox, oy; if (img == NULL || img->im == NULL || img->win == NULL) return false; ox = img->x; oy = img->y; img->x = (img->win->w - img->w * img->zoom) / 2; img->y = (img->win->h - img->h * img->zoom) / 2; if (ox != img->x || oy != img->y) { img->dirty = true; return true; } else { return false; } } bool img_zoom(img_t *img, float z) { if (img == NULL || img->im == NULL || img->win == NULL) return false; z = MAX(z, zoom_min); z = MIN(z, zoom_max); img->scalemode = SCALE_ZOOM; if (zoomdiff(z, img->zoom) != 0) { img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * z / img->zoom; img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * z / img->zoom; img->zoom = z; img->checkpan = true; img->dirty = true; return true; } else { return false; } } bool img_zoom_in(img_t *img) { int i; float z; if (img == NULL || img->im == NULL) return false; for (i = 1; i < ARRLEN(zoom_levels); i++) { z = zoom_levels[i] / 100.0; if (zoomdiff(z, img->zoom) > 0) return img_zoom(img, z); } return false; } bool img_zoom_out(img_t *img) { int i; float z; if (img == NULL || img->im == NULL) return false; for (i = ARRLEN(zoom_levels) - 2; i >= 0; i--) { z = zoom_levels[i] / 100.0; if (zoomdiff(z, img->zoom) < 0) return img_zoom(img, z); } return false; } bool img_move(img_t *img, float dx, float dy) { float ox, oy; if (img == NULL || img->im == NULL) return false; ox = img->x; oy = img->y; img->x += dx; img->y += dy; img_check_pan(img, true); if (ox != img->x || oy != img->y) { img->dirty = true; return true; } else { return false; } } bool img_pan(img_t *img, direction_t dir, int d) { /* d < 0: screen-wise * d = 0: 1/5 of screen * d > 0: num of pixels */ float x, y; if (img == NULL || img->im == NULL || img->win == NULL) return false; if (d > 0) { x = y = MAX(1, (float) d * img->zoom); } else { x = img->win->w / (d < 0 ? 1 : 5); y = img->win->h / (d < 0 ? 1 : 5); } 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_edge(img_t *img, direction_t dir) { int ox, oy; if (img == NULL || img->im == NULL || img->win == NULL) return false; ox = img->x; oy = img->y; switch (dir) { case DIR_LEFT: img->x = 0; break; case DIR_RIGHT: img->x = img->win->w - img->w * img->zoom; break; case DIR_UP: img->y = 0; break; case DIR_DOWN: img->y = img->win->h - img->h * img->zoom; break; } 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, int d) { win_t *win; int ox, oy, tmp; if (img == NULL || img->im == NULL || img->win == NULL) return; win = img->win; ox = d == 1 ? img->x : win->w - img->x - img->w * img->zoom; oy = d == 3 ? img->y : win->h - img->y - img->h * img->zoom; imlib_context_set_image(img->im); imlib_image_orientate(d); img->x = oy + (win->w - win->h) / 2; img->y = ox + (win->h - win->w) / 2; tmp = img->w; img->w = img->h; img->h = tmp; img->checkpan = true; img->dirty = true; } void img_rotate_left(img_t *img) { img_rotate(img, 3); } void img_rotate_right(img_t *img) { img_rotate(img, 1); } void img_flip(img_t *img, flipdir_t d) { if (img == NULL || img->im == NULL) return; imlib_context_set_image(img->im); switch (d) { case FLIP_HORIZONTAL: imlib_image_flip_horizontal(); break; case FLIP_VERTICAL: imlib_image_flip_vertical(); break; } img->dirty = true; } void img_toggle_antialias(img_t *img) { if (img == NULL || img->im == NULL) return; img->aa = !img->aa; imlib_context_set_image(img->im); imlib_context_set_anti_alias(img->aa); img->dirty = true; } bool img_frame_goto(img_t *img, int n) { if (img == NULL || img->im == NULL) return false; if (n < 0 || n >= img->multi.cnt || 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 == NULL|| img->im == NULL || img->multi.cnt == 0 || d == 0) return false; d += img->multi.sel; if (d < 0) d = 0; else if (d >= img->multi.cnt) d = img->multi.cnt - 1; return img_frame_goto(img, d); } bool img_frame_animate(img_t *img, bool restart) { if (img == NULL || img->im == NULL || img->multi.cnt == 0) return false; if (img->multi.sel + 1 >= img->multi.cnt) { if (restart || GIF_LOOP) { img_frame_goto(img, 0); } else { img->multi.animate = false; return false; } } else if (!restart) { img_frame_goto(img, img->multi.sel + 1); } img->multi.animate = true; img->dirty = true; return true; } sxiv-1.1.1/image.h000066400000000000000000000034551215261427000137450ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #ifndef IMAGE_H #define IMAGE_H #include #include "types.h" #include "window.h" typedef struct { Imlib_Image *im; unsigned int delay; } img_frame_t; typedef struct { img_frame_t *frames; int cap; int cnt; int sel; bool animate; } multi_img_t; typedef struct { Imlib_Image *im; int w; int h; win_t *win; float x; float y; scalemode_t scalemode; float zoom; bool re; bool checkpan; bool dirty; bool aa; bool alpha; multi_img_t multi; } img_t; void img_init(img_t*, win_t*); bool img_load(img_t*, const fileinfo_t*); void img_close(img_t*, bool); void img_render(img_t*); bool img_fit_win(img_t*, scalemode_t); bool img_center(img_t*); bool img_zoom(img_t*, float); bool img_zoom_in(img_t*); bool img_zoom_out(img_t*); bool img_move(img_t*, float, float); bool img_pan(img_t*, direction_t, int); bool img_pan_edge(img_t*, direction_t); void img_rotate(img_t*, int); void img_rotate_left(img_t*); void img_rotate_right(img_t*); void img_flip(img_t*, flipdir_t); void img_toggle_antialias(img_t*); bool img_frame_navigate(img_t*, int); bool img_frame_animate(img_t*, bool); #endif /* IMAGE_H */ sxiv-1.1.1/main.c000066400000000000000000000357121215261427000136030ustar00rootroot00000000000000/* Copyright 2011-2013 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #define _POSIX_C_SOURCE 200112L #define _MAPPINGS_CONFIG #include #include #include #include #include #include #include #include #include #include #include #include #include "types.h" #include "commands.h" #include "image.h" #include "options.h" #include "thumbs.h" #include "util.h" #include "window.h" #include "config.h" enum { FILENAME_CNT = 1024, TITLE_LEN = 256 }; typedef struct { struct timeval when; bool active; timeout_f handler; } timeout_t; /* timeout handler functions: */ void redraw(void); void reset_cursor(void); void animate(void); void clear_resize(void); appmode_t mode; img_t img; tns_t tns; win_t win; fileinfo_t *files; int filecnt, fileidx; int alternate; int prefix; bool resized = false; const char * const INFO_SCRIPT = ".sxiv/exec/image-info"; struct { char *script; int fd; unsigned int i, lastsep; bool open; } info; timeout_t timeouts[] = { { { 0, 0 }, false, redraw }, { { 0, 0 }, false, reset_cursor }, { { 0, 0 }, false, animate }, { { 0, 0 }, false, clear_resize }, }; void cleanup(void) { static bool in = false; if (!in) { in = true; img_close(&img, false); tns_free(&tns); win_close(&win); } } void check_add_file(char *filename) { const char *bn; if (filename == NULL || *filename == '\0') return; if (access(filename, R_OK) < 0) { warn("could not open file: %s", filename); return; } if (fileidx == filecnt) { filecnt *= 2; files = (fileinfo_t*) s_realloc(files, filecnt * sizeof(fileinfo_t)); } if (*filename != '/') { files[fileidx].path = absolute_path(filename); if (files[fileidx].path == NULL) { warn("could not get absolute path of file: %s\n", filename); return; } } files[fileidx].loaded = false; files[fileidx].name = s_strdup(filename); if (*filename == '/') files[fileidx].path = files[fileidx].name; if ((bn = strrchr(files[fileidx].name , '/')) != NULL && bn[1] != '\0') files[fileidx].base = ++bn; else files[fileidx].base = files[fileidx].name; fileidx++; } void remove_file(int n, bool manual) { if (n < 0 || n >= filecnt) return; if (filecnt == 1) { if (!manual) fprintf(stderr, "sxiv: no more files to display, aborting\n"); cleanup(); exit(manual ? EXIT_SUCCESS : EXIT_FAILURE); } if (files[n].path != files[n].name) free((void*) files[n].path); free((void*) files[n].name); if (n + 1 < filecnt) memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(fileinfo_t)); if (n + 1 < tns.cnt) { memmove(tns.thumbs + n, tns.thumbs + n + 1, (tns.cnt - n - 1) * sizeof(thumb_t)); memset(tns.thumbs + tns.cnt - 1, 0, sizeof(thumb_t)); } filecnt--; if (n < tns.cnt) tns.cnt--; } void set_timeout(timeout_f handler, int time, bool overwrite) { 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) { int i; for (i = 0; i < ARRLEN(timeouts); i++) { if (timeouts[i].handler == handler) { timeouts[i].active = false; return; } } } bool check_timeouts(struct timeval *t) { int i = 0, tdiff, tmin = -1; struct timeval now; while (i < ARRLEN(timeouts)) { 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(); i = tmin = -1; } else if (tmin < 0 || tdiff < tmin) { tmin = tdiff; } } i++; } if (tmin > 0 && t != NULL) TV_SET_MSEC(t, tmin); return tmin > 0; } void open_info(void) { static pid_t pid; int pfd[2]; if (info.script == NULL || info.open || win.bar.h == 0) return; if (info.fd != -1) { close(info.fd); kill(pid, SIGTERM); info.fd = -1; } win.bar.l[0] = '\0'; if (pipe(pfd) < 0) return; pid = fork(); if (pid > 0) { close(pfd[1]); fcntl(pfd[0], F_SETFL, O_NONBLOCK); info.fd = pfd[0]; info.i = info.lastsep = 0; info.open = true; } else if (pid == 0) { close(pfd[0]); dup2(pfd[1], 1); execl(info.script, info.script, files[fileidx].name, NULL); warn("could not exec: %s", info.script); exit(EXIT_FAILURE); } } void read_info(void) { ssize_t i, n; char buf[BAR_L_LEN]; while (true) { n = read(info.fd, buf, sizeof(buf)); if (n < 0 && errno == EAGAIN) return; else if (n == 0) goto end; for (i = 0; i < n; i++) { if (buf[i] == '\n') { if (info.lastsep == 0) { win.bar.l[info.i++] = ' '; info.lastsep = 1; } } else { win.bar.l[info.i++] = buf[i]; info.lastsep = 0; } if (info.i + 1 == sizeof(win.bar.l)) goto end; } } end: info.i -= info.lastsep; win.bar.l[info.i] = '\0'; win_update_bar(&win); info.fd = -1; while (waitpid(-1, NULL, WNOHANG) > 0); } void load_image(int new) { if (new < 0 || new >= filecnt) return; win_set_cursor(&win, CURSOR_WATCH); img_close(&img, false); while (!img_load(&img, &files[new])) { remove_file(new, false); if (new >= filecnt) new = filecnt - 1; } files[new].loaded = true; alternate = fileidx; fileidx = new; info.open = false; open_info(); if (img.multi.cnt > 0 && img.multi.animate) set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); else reset_timeout(animate); } void update_info(void) { int sel; unsigned int i, fn, fw, n; unsigned int llen = sizeof(win.bar.l), rlen = sizeof(win.bar.r); char *lt = win.bar.l, *rt = win.bar.r, title[TITLE_LEN]; bool ow_info; for (fw = 0, i = filecnt; i > 0; fw++, i /= 10); sel = mode == MODE_IMAGE ? fileidx : tns.sel; /* update window title */ if (mode == MODE_THUMB) { win_set_title(&win, "sxiv"); } else { snprintf(title, sizeof(title), "sxiv - %s", files[sel].name); win_set_title(&win, title); } /* update bar contents */ if (win.bar.h == 0) return; if (mode == MODE_THUMB) { if (tns.cnt == filecnt) { n = snprintf(rt, rlen, "%0*d/%d", fw, sel + 1, filecnt); ow_info = true; } else { snprintf(lt, llen, "Loading... %0*d/%d", fw, tns.cnt, filecnt); rt[0] = '\0'; ow_info = false; } } else { n = snprintf(rt, rlen, "%3d%% | ", (int) (img.zoom * 100.0)); if (img.multi.cnt > 0) { for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10); n += snprintf(rt + n, rlen - n, "%0*d/%d | ", fn, img.multi.sel + 1, img.multi.cnt); } n += snprintf(rt + n, rlen - n, "%0*d/%d", fw, sel + 1, filecnt); ow_info = info.script == NULL; } if (ow_info) { fn = strlen(files[sel].name); if (fn < llen && win_textwidth(files[sel].name, fn, true) + win_textwidth(rt, n, true) < win.w) { strncpy(lt, files[sel].name, llen); } else { strncpy(lt, files[sel].base, llen); } } } void redraw(void) { if (mode == MODE_IMAGE) img_render(&img); else tns_render(&tns); update_info(); win_draw(&win); reset_timeout(redraw); reset_cursor(); } void reset_cursor(void) { 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) cursor = CURSOR_ARROW; break; } } } else { if (tns.cnt != filecnt) cursor = CURSOR_WATCH; else cursor = CURSOR_ARROW; } win_set_cursor(&win, cursor); } void animate(void) { if (img_frame_animate(&img, false)) { redraw(); set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); } } void clear_resize(void) { resized = false; } bool keymask(const keymap_t *k, unsigned int state) { return (k->ctrl ? ControlMask : 0) == (state & ControlMask); } bool buttonmask(const button_t *b, unsigned int state) { return ((b->ctrl ? ControlMask : 0) | (b->shift ? ShiftMask : 0)) == (state & (ControlMask | ShiftMask)); } void on_keypress(XKeyEvent *kev) { int i; KeySym ksym; char key; if (kev == NULL) return; XLookupString(kev, &key, 1, &ksym, NULL); if ((ksym == XK_Escape || (key >= '0' && key <= '9')) && (kev->state & ControlMask) == 0) { /* number prefix for commands */ prefix = ksym == XK_Escape ? 0 : prefix * 10 + (int) (key - '0'); return; } for (i = 0; i < ARRLEN(keys); i++) { if (keys[i].ksym == ksym && keymask(&keys[i], kev->state)) { if (keys[i].cmd != NULL && keys[i].cmd(keys[i].arg)) redraw(); prefix = 0; return; } } } void on_buttonpress(XButtonEvent *bev) { int i, sel; if (bev == NULL) return; if (mode == MODE_IMAGE) { win_set_cursor(&win, CURSOR_ARROW); set_timeout(reset_cursor, TO_CURSOR_HIDE, true); for (i = 0; i < ARRLEN(buttons); i++) { if (buttons[i].button == bev->button && buttonmask(&buttons[i], bev->state)) { if (buttons[i].cmd != NULL && buttons[i].cmd(buttons[i].arg)) redraw(); return; } } } else { /* thumbnail mode (hard-coded) */ switch (bev->button) { case Button1: if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { if (sel == tns.sel) { mode = MODE_IMAGE; set_timeout(reset_cursor, TO_CURSOR_HIDE, true); load_image(tns.sel); } else { tns_highlight(&tns, tns.sel, false); tns_highlight(&tns, sel, true); tns.sel = sel; } redraw(); break; } break; case Button4: case Button5: if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN, (bev->state & ControlMask) != 0)) redraw(); break; } } } void run(void) { int xfd; fd_set fds; struct timeval timeout; bool discard, to_set; XEvent ev, nextev; redraw(); while (true) { while (mode == MODE_THUMB && tns.cnt < filecnt && XPending(win.env.dpy) == 0) { /* load thumbnails */ set_timeout(redraw, TO_REDRAW_THUMBS, false); if (tns_load(&tns, tns.cnt, &files[tns.cnt], false, false)) { tns.cnt++; } else { remove_file(tns.cnt, false); if (tns.sel >= tns.cnt) tns.sel--; } if (tns.cnt == filecnt) redraw(); else check_timeouts(NULL); } while (XPending(win.env.dpy) == 0 && ((to_set = check_timeouts(&timeout)) || info.fd != -1)) { /* check for timeouts & input */ xfd = ConnectionNumber(win.env.dpy); FD_ZERO(&fds); FD_SET(xfd, &fds); if (info.fd != -1) { FD_SET(info.fd, &fds); xfd = MAX(xfd, info.fd); } select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL); if (info.fd != -1 && FD_ISSET(info.fd, &fds)) read_info(); } 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: 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) { /* handle events */ case ButtonPress: on_buttonpress(&ev.xbutton); break; case ClientMessage: if ((Atom) ev.xclient.data.l[0] == wm_delete_win) return; 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 || win.fullscreen) { redraw(); set_timeout(clear_resize, TO_REDRAW_RESIZE, false); resized = true; } else { set_timeout(redraw, TO_REDRAW_RESIZE, false); } } break; case Expose: win_expose(&win, &ev.xexpose); break; case KeyPress: on_keypress(&ev.xkey); break; case MotionNotify: if (mode == MODE_IMAGE) { win_set_cursor(&win, CURSOR_ARROW); set_timeout(reset_cursor, TO_CURSOR_HIDE, true); } break; } } } int fncmp(const void *a, const void *b) { return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name); } int main(int argc, char **argv) { int i, start; size_t n; ssize_t len; char *filename; const char *homedir; struct stat fstats; r_dir_t dir; parse_options(argc, argv); if (options->clean_cache) { tns_init(&tns, 0, NULL); tns_clean_cache(&tns); exit(EXIT_SUCCESS); } if (options->filecnt == 0 && !options->from_stdin) { print_usage(); exit(EXIT_FAILURE); } if (options->recursive || options->from_stdin) filecnt = FILENAME_CNT; else filecnt = options->filecnt; files = (fileinfo_t*) s_malloc(filecnt * sizeof(fileinfo_t)); fileidx = 0; if (options->from_stdin) { filename = NULL; while ((len = get_line(&filename, &n, stdin)) > 0) { if (filename[len-1] == '\n') filename[len-1] = '\0'; check_add_file(filename); } if (filename != NULL) free(filename); } for (i = 0; i < options->filecnt; i++) { filename = options->filenames[i]; if (stat(filename, &fstats) < 0) { warn("could not stat file: %s", filename); continue; } if (!S_ISDIR(fstats.st_mode)) { check_add_file(filename); } else { if (!options->recursive) { warn("ignoring directory: %s", filename); continue; } if (r_opendir(&dir, filename) < 0) { warn("could not open directory: %s", filename); continue; } start = fileidx; while ((filename = r_readdir(&dir)) != NULL) { check_add_file(filename); free((void*) filename); } r_closedir(&dir); if (fileidx - start > 1) qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp); } } if (fileidx == 0) { fprintf(stderr, "sxiv: no valid image file given, aborting\n"); exit(EXIT_FAILURE); } filecnt = fileidx; fileidx = options->startnum < filecnt ? options->startnum : 0; win_init(&win); img_init(&img, &win); if ((homedir = getenv("HOME")) == NULL) { warn("could not locate home directory"); } else { len = strlen(homedir) + strlen(INFO_SCRIPT) + 2; info.script = (char*) s_malloc(len); snprintf(info.script, len, "%s/%s", homedir, INFO_SCRIPT); if (access(info.script, X_OK) != 0) { free(info.script); info.script = NULL; } } info.fd = -1; if (options->thumb_mode) { mode = MODE_THUMB; tns_init(&tns, filecnt, &win); while (!tns_load(&tns, 0, &files[0], false, false)) remove_file(0, false); tns.cnt = 1; } else { mode = MODE_IMAGE; tns.thumbs = NULL; load_image(fileidx); } win_open(&win); run(); cleanup(); return 0; } sxiv-1.1.1/options.c000066400000000000000000000067071215261427000143540ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #define _POSIX_C_SOURCE 200112L #define _IMAGE_CONFIG #include #include #include #include #include "options.h" #include "util.h" #include "config.h" options_t _options; const options_t *options = (const options_t*) &_options; void print_usage(void) { printf("usage: sxiv [-bcdFfhiopqrstvZ] [-g GEOMETRY] [-n NUM] " "[-N name] [-z ZOOM] FILES...\n"); } void print_version(void) { printf("sxiv %s - Simple X Image Viewer\n", VERSION); } void parse_options(int argc, char **argv) { int opt, t; _options.from_stdin = false; _options.to_stdout = false; _options.recursive = false; _options.startnum = 0; _options.scalemode = SCALE_MODE; _options.zoom = 1.0; _options.aa = true; _options.fixed_win = false; _options.fullscreen = false; _options.hide_bar = false; _options.geometry = NULL; _options.res_name = NULL; _options.quiet = false; _options.thumb_mode = false; _options.clean_cache = false; while ((opt = getopt(argc, argv, "bcdFfg:hin:N:opqrstvZz:")) != -1) { switch (opt) { case '?': print_usage(); exit(EXIT_FAILURE); case 'b': _options.hide_bar = true; break; case 'c': _options.clean_cache = true; break; case 'd': _options.scalemode = SCALE_DOWN; break; case 'F': _options.fixed_win = true; break; case 'f': _options.fullscreen = true; break; case 'g': _options.geometry = optarg; break; case 'h': print_usage(); exit(EXIT_SUCCESS); case 'i': _options.from_stdin = true; break; case 'n': if (sscanf(optarg, "%d", &t) <= 0 || t < 1) { fprintf(stderr, "sxiv: invalid argument for option -n: %s\n", optarg); exit(EXIT_FAILURE); } else { _options.startnum = t - 1; } break; case 'N': _options.res_name = optarg; break; case 'o': _options.to_stdout = true; break; case 'p': _options.aa = false; break; case 'q': _options.quiet = true; break; case 'r': _options.recursive = true; break; case 's': _options.scalemode = SCALE_FIT; break; case 't': _options.thumb_mode = true; break; case 'v': print_version(); exit(EXIT_SUCCESS); case 'Z': _options.scalemode = SCALE_ZOOM; _options.zoom = 1.0; break; case 'z': _options.scalemode = SCALE_ZOOM; if (sscanf(optarg, "%d", &t) <= 0 || t <= 0) { fprintf(stderr, "sxiv: invalid argument for option -z: %s\n", optarg); exit(EXIT_FAILURE); } _options.zoom = (float) t / 100.0; break; } } _options.filenames = argv + optind; _options.filecnt = argc - optind; if (_options.filecnt == 1 && STREQ(_options.filenames[0], "-")) { _options.filenames++; _options.filecnt--; _options.from_stdin = true; } } sxiv-1.1.1/options.h000066400000000000000000000024171215261427000143530ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #ifndef OPTIONS_H #define OPTIONS_H #include "types.h" #include "image.h" typedef struct { /* file list: */ char **filenames; bool from_stdin; bool to_stdout; bool recursive; int filecnt; int startnum; /* image: */ scalemode_t scalemode; float zoom; bool aa; /* window: */ bool fixed_win; bool fullscreen; bool hide_bar; char *geometry; char *res_name; /* misc flags: */ bool quiet; bool thumb_mode; bool clean_cache; } options_t; extern const options_t *options; void print_usage(void); void print_version(void); void parse_options(int, char**); #endif /* OPTIONS_H */ sxiv-1.1.1/sxiv.1000066400000000000000000000154741215261427000135710ustar00rootroot00000000000000.TH SXIV 1 sxiv\-VERSION .SH NAME sxiv \- Simple X Image Viewer .SH SYNOPSIS .B sxiv .RB [ \-bcdFfhiopqrstvZ ] .RB [ \-g .IR GEOMETRY ] .RB [ \-n .IR NUM ] .RB [ \-N .IR NAME ] .RB [ \-z .IR ZOOM ] .IR FILE ... .SH DESCRIPTION sxiv is a simple image viewer for X. It only has the most basic features required for fast image viewing. .P sxiv 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 sxiv can also cache its thumbnails. Please see the section THUMBNAIL CACHING for information on how to enable this feature. .P Please note, that the fullscreen mode requires an EWMH/NetWM compliant window manager. .SH OPTIONS .TP .B \-b Do not show info bar on bottom of window. .TP .B \-c Remove all orphaned cache files from the thumbnail cache directory and exit. .TP .B \-d Scale all images to 100%, but fit large images into window. .TP .B \-F Make the window fixed/floating by setting the minimum and maximum width/height size-hints to the window width/height. .TP .B \-f Start in fullscreen mode. .TP .BI "\-g " GEOMETRY Set window position and size. See section GEOMETRY SPECIFICATIONS of X(7) for more information on .IR GEOMETRY . .TP .BI "\-n " NUM Start at picture number NUM. .TP .BI "\-N " NAME Set the resource name of sxiv's X window to NAME. .TP .B \-h Print brief usage information to standard output and exit. .TP .B \-i Read names of files to open from standard input. .TP .B \-o Write list of opened files to standard output when quitting. If combined with .IR \-i , then sxiv acts as a visual filter/pipe. .TP .B \-p Pixelize images, i.e. turn off anti-aliasing. .TP .B \-q Be quiet, disable warnings to standard error stream. .TP .B \-r Search the given directories recursively for images to view. .TP .B \-s Scale all images to fit into window. .TP .B \-t Start in thumbnail mode. .TP .B \-v Print version information to standard output and exit. .TP .B \-Z The same as `\-z 100'. .TP .BI "\-z " ZOOM Scale all images to the current zoom level, use a zoom level of .I ZOOM at startup. .SH GENERAL KEYBOARD COMMANDS The following keyboard commands are available in both image and thumbnail mode: .TP .B q Quit sxiv. .TP .B Return Switch to thumbnail mode / open selected image in image mode. .TP .BR 0 \- 9 Prefix the next command with a number (denoted via .IR count ). .TP .B g Go to the first image. .TP .B G Go to the last image, or image number .IR count . .TP .B f Toggle fullscreen mode. .TP .B b Toggle visibility of info bar on bottom of window. .TP .B A Toggle visibility of alpha-channel, i.e. image transparency. .TP .B r Reload image. .TP .B R Reload all thumbnails. .TP .B D Remove current image from file list and go to next image. .SH THUMBNAIL KEYBOARD COMMANDS 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 .BR Ctrl-j ", " Ctrl-Down Scroll thumbnail grid one window height down. .TP .BR Ctrl-k ", " Ctrl-Up Scroll thumbnail grid one window height up. .SH IMAGE KEYBOARD COMMANDS The following keyboard commands are only available in image mode: .SS 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. .SS Handle multi-frame images .TP .B Ctrl-n Go to the next frame of a multi-frame image. .TP .B Ctrl-p Go to the previous frame of a multi-frame image. .TP .B Ctrl-Space Play/pause animation of a multi-frame image. .SS Zooming .TP .BR + Zoom in. .TP .B \- Zoom out. .TP .B = Set zoom level to 100%, or .IR count %. .TP .B w Set zoom level to fit image into window. .TP .B e Set zoom level to fit image width to window width. .TP .B E Set zoom level to fit image height to window height. .SS Panning .TP .BR h ", " Left Pan image 1/5 of window width or .I count pixel left. .TP .BR j ", " Down Pan image 1/5 of window height or .I count pixel down. .TP .BR k ", " Up Pan image 1/5 of window height or .I count pixel up. .TP .BR l ", " Right Pan image 1/5 of window width or .I count pixel right. .TP .B H Pan to left image edge. .TP .B J Pan to bottom image edge. .TP .B K Pan to top image edge. .TP .B L Pan to right image edge. .TP .BR Ctrl-h ", " Ctrl-Left Pan image one window width left. .TP .BR Ctrl-j ", " Ctrl-Down Pan image one window height down. .TP .BR Ctrl-k ", " Ctrl-Up Pan image one window height up. .TP .BR Ctrl-l ", " Ctrl-Right Pan image one window width right. .SS Rotation .TP .B < Rotate image counter-clockwise by 90 degrees. .TP .B > Rotate image clockwise by 90 degrees. .SS Flip .TP .B \\\\ Flip image horizontally. .TP .B | Flip image vertically. .SS Miscellaneous .TP .B a Toggle anti-aliasing. .TP .B W Resize window to fit image. .SH MOUSE COMMANDS The following mouse mappings are available in image mode: .SS Navigate image list .TP .B Button1 Go to next image. .TP .B Button3 Go to the previous image. .SS Zooming .TP .B Ctrl+ScrollUp Zoom in. .TP .B Ctrl+ScrollDown Zoom out. .SS Panning/Moving .TP .B Button2 Drag the image with the mouse while keeping this button pressed down. .TP .B ScrollUp Pan image up. .TP .B ScrollDown Pan image down. .TP .B Shift+ScrollUp Pan image left. .TP .B Shift+ScrollDown Pan image right. .SH STATUS BAR The information displayed on the left side of the status bar can be replaced with the output of a user-provided script, which is called by sxiv whenever an image gets loaded. The path of this script is .I ~/.sxiv/exec/image-info and the first argument to this script is the path of the loaded image. .P There is also an example script installed together with sxiv as .IR PREFIX/share/sxiv/exec/image-info . .SH THUMBNAIL CACHING To enable thumbnail caching, please make sure to create the directory .I ~/.sxiv/cache/ with write permissions. sxiv will then store all thumbnails inside this directory, but it will not create this directory by itself. It rather uses the existance of this directory as an affirmation, that the user wants thumbnails to be cached. .P Use the command line option .I \-c to keep the cache directory clean by removing 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 .SH AUTHOR .EX Bert Muennich .EE .SH CONTRIBUTORS .EX Bastien Dejean Dave Reisner Fung SzeTat .EE .SH HOMEPAGE .EX http://muennich.github.com/sxiv https://github.com/muennich/sxiv .EE .SH SEE ALSO .BR feh (1), .BR qiv (1) sxiv-1.1.1/sxiv.desktop000066400000000000000000000002641215261427000150710ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=sxiv GenericName=Image Viewer Exec=sxiv %F MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp; NoDisplay=true sxiv-1.1.1/thumbs.c000066400000000000000000000245631215261427000141630ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #define _POSIX_C_SOURCE 200112L #define _THUMBS_CONFIG #include #include #include #include #include #include #include "exif.h" #include "thumbs.h" #include "util.h" #include "config.h" static const int thumb_dim = THUMB_SIZE + 10; static const char * const CACHE_DIR = ".sxiv/cache"; static char *cache_dir = NULL; bool tns_cache_enabled(void) { struct stat stats; return cache_dir != NULL && stat(cache_dir, &stats) == 0 && S_ISDIR(stats.st_mode) && access(cache_dir, W_OK) == 0; } char* tns_cache_filepath(const char *filepath) { size_t len; char *cfile = NULL; if (cache_dir == NULL || filepath == NULL || *filepath != '/') return NULL; if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) { /* don't cache images inside the cache directory! */ len = strlen(cache_dir) + strlen(filepath) + 6; cfile = (char*) s_malloc(len); snprintf(cfile, len, "%s/%s.png", cache_dir, filepath + 1); } return cfile; } Imlib_Image* tns_cache_load(const char *filepath) { char *cfile; struct stat cstats, fstats; Imlib_Image *im = NULL; if (filepath == NULL) return NULL; if (stat(filepath, &fstats) < 0) return NULL; if ((cfile = tns_cache_filepath(filepath)) != NULL) { if (stat(cfile, &cstats) == 0 && cstats.st_mtime == fstats.st_mtime) im = imlib_load_image(cfile); free(cfile); } return im; } void tns_cache_write(thumb_t *t, bool force) { char *cfile, *dirend; struct stat cstats, fstats; struct utimbuf times; Imlib_Load_Error err = 0; if (t == NULL || t->im == NULL) return; if (t->file == NULL || t->file->name == NULL || t->file->path == NULL) return; if (stat(t->file->path, &fstats) < 0) return; if ((cfile = tns_cache_filepath(t->file->path)) != NULL) { if (force || stat(cfile, &cstats) < 0 || cstats.st_mtime != fstats.st_mtime) { if ((dirend = strrchr(cfile, '/')) != NULL) { *dirend = '\0'; err = r_mkdir(cfile); *dirend = '/'; } if (err == 0) { imlib_context_set_image(t->im); imlib_image_set_format("png"); imlib_save_image_with_error_return(cfile, &err); } if (err == 0) { times.actime = fstats.st_atime; times.modtime = fstats.st_mtime; utime(cfile, ×); } else { warn("could not cache thumbnail: %s", t->file->name); } } free(cfile); } } void tns_clean_cache(tns_t *tns) { int dirlen; bool delete; char *cfile, *filename, *tpos; r_dir_t dir; if (cache_dir == NULL) return; if (r_opendir(&dir, cache_dir) < 0) { warn("could not open thumbnail cache directory: %s", cache_dir); return; } dirlen = strlen(cache_dir); while ((cfile = r_readdir(&dir)) != NULL) { filename = cfile + dirlen; delete = false; if ((tpos = strrchr(filename, '.')) != NULL) { *tpos = '\0'; if (access(filename, F_OK) < 0) delete = true; *tpos = '.'; } if (delete) { if (unlink(cfile) < 0) warn("could not delete cache file: %s", cfile); } free(cfile); } r_closedir(&dir); } void tns_init(tns_t *tns, int cnt, win_t *win) { int len; char *homedir; if (tns == NULL) return; if (cnt > 0) { tns->thumbs = (thumb_t*) s_malloc(cnt * sizeof(thumb_t)); memset(tns->thumbs, 0, cnt * sizeof(thumb_t)); } else { tns->thumbs = NULL; } tns->cap = cnt; tns->cnt = tns->first = tns->sel = 0; tns->win = win; tns->alpha = true; tns->dirty = false; if ((homedir = getenv("HOME")) != NULL) { if (cache_dir != NULL) free(cache_dir); len = strlen(homedir) + strlen(CACHE_DIR) + 2; cache_dir = (char*) s_malloc(len); snprintf(cache_dir, len, "%s/%s", homedir, CACHE_DIR); } else { warn("could not locate thumbnail cache directory"); } } void tns_free(tns_t *tns) { int i; if (tns == NULL) return; if (tns->thumbs != NULL) { for (i = 0; i < tns->cnt; i++) { if (tns->thumbs[i].im != NULL) { imlib_context_set_image(tns->thumbs[i].im); imlib_free_image(); } } free(tns->thumbs); tns->thumbs = NULL; } if (cache_dir != NULL) { free(cache_dir); cache_dir = NULL; } } bool tns_load(tns_t *tns, int n, const fileinfo_t *file, bool force, bool silent) { int w, h; bool use_cache, cache_hit = false; float z, zw, zh; thumb_t *t; Imlib_Image *im; const char *fmt; if (tns == NULL || tns->thumbs == NULL) return false; if (file == NULL || file->name == NULL || file->path == NULL) return false; if (n < 0 || n >= tns->cap) return false; t = &tns->thumbs[n]; t->file = file; if (t->im != NULL) { imlib_context_set_image(t->im); imlib_free_image(); } if ((use_cache = tns_cache_enabled())) { if (!force && (im = tns_cache_load(file->path)) != NULL) cache_hit = true; } if (!cache_hit) { if (access(file->path, R_OK) < 0 || (im = imlib_load_image(file->path)) == NULL) { if (!silent) warn("could not open image: %s", file->name); return false; } } imlib_context_set_image(im); imlib_context_set_anti_alias(1); if ((fmt = imlib_image_format()) == NULL) { if (!silent) warn("could not open image: %s", file->name); imlib_free_image_and_decache(); return false; } if (STREQ(fmt, "jpeg")) exif_auto_orientate(file); w = imlib_image_get_width(); h = imlib_image_get_height(); zw = (float) THUMB_SIZE / (float) w; zh = (float) THUMB_SIZE / (float) h; z = MIN(zw, zh); z = MIN(z, 1.0); t->w = z * w; t->h = z * h; t->im = imlib_create_cropped_scaled_image(0, 0, w, h, t->w, t->h); if (t->im == NULL) die("could not allocate memory"); imlib_free_image_and_decache(); if (use_cache && !cache_hit) tns_cache_write(t, true); tns->dirty = true; return true; } void tns_check_view(tns_t *tns, bool scrolled) { int r; if (tns == NULL) return; 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 == NULL || tns->thumbs == NULL || tns->win == NULL) return; if (!tns->dirty) return; win = tns->win; win_clear(win); imlib_context_set_drawable(win->pm); tns->cols = MAX(1, win->w / thumb_dim); tns->rows = MAX(1, win->h / thumb_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) * thumb_dim) / 2 + 5; tns->y = y = (win->h - (cnt / tns->cols + r) * thumb_dim) / 2 + 5; for (i = 0; i < cnt; i++) { t = &tns->thumbs[tns->first + i]; t->x = x + (THUMB_SIZE - t->w) / 2; t->y = y + (THUMB_SIZE - t->h) / 2; imlib_context_set_image(t->im); if (!tns->alpha && imlib_image_has_alpha()) win_draw_rect(win, win->pm, t->x, t->y, t->w, t->h, true, 0, win->white); imlib_render_image_part_on_drawable_at_size(0, 0, t->w, t->h, t->x, t->y, t->w, t->h); if ((i + 1) % tns->cols == 0) { x = tns->x; y += thumb_dim; } else { x += thumb_dim; } } tns->dirty = false; tns_highlight(tns, tns->sel, true); } void tns_highlight(tns_t *tns, int n, bool hl) { thumb_t *t; win_t *win; int x, y; unsigned long col; if (tns == NULL || tns->thumbs == NULL || tns->win == NULL) return; win = tns->win; if (n >= 0 && n < tns->cnt) { t = &tns->thumbs[n]; if (hl) col = win->selcol; else if (win->fullscreen) col = win->fscol; else col = win->bgcol; x = t->x - (THUMB_SIZE - t->w) / 2; y = t->y - (THUMB_SIZE - t->h) / 2; win_draw_rect(win, win->pm, x - 3, y - 3, THUMB_SIZE + 6, THUMB_SIZE + 6, false, 2, col); } } bool tns_move_selection(tns_t *tns, direction_t dir, int cnt) { int old, max; if (tns == NULL || tns->thumbs == NULL) return false; 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; if (tns == NULL) return false; 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; } int tns_translate(tns_t *tns, int x, int y) { int n; if (tns == NULL || tns->thumbs == NULL) return -1; if (x < tns->x || y < tns->y) return -1; n = tns->first + (y - tns->y) / thumb_dim * tns->cols + (x - tns->x) / thumb_dim; if (n >= tns->cnt) n = -1; return n; } sxiv-1.1.1/thumbs.h000066400000000000000000000026571215261427000141700ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #ifndef THUMBS_H #define THUMBS_H #include #include #include "types.h" #include "window.h" typedef struct { const fileinfo_t *file; Imlib_Image *im; int w; int h; int x; int y; } thumb_t; typedef struct { thumb_t *thumbs; int cap; int cnt; int first; int sel; win_t *win; int x; int y; int cols; int rows; bool alpha; bool dirty; } tns_t; void tns_clean_cache(tns_t*); void tns_init(tns_t*, int, win_t*); void tns_free(tns_t*); bool tns_load(tns_t*, int, const fileinfo_t*, bool, bool); void tns_render(tns_t*); void tns_highlight(tns_t*, int, bool); bool tns_move_selection(tns_t*, direction_t, int); bool tns_scroll(tns_t*, direction_t, bool); int tns_translate(tns_t*, int, int); #endif /* THUMBS_H */ sxiv-1.1.1/types.h000066400000000000000000000027421215261427000140250ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #ifndef TYPES_H #define TYPES_H #include typedef enum { BO_BIG_ENDIAN, BO_LITTLE_ENDIAN } byteorder_t; typedef enum { MODE_IMAGE, MODE_THUMB } appmode_t; typedef enum { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN } direction_t; typedef enum { FLIP_HORIZONTAL, FLIP_VERTICAL } flipdir_t; typedef enum { SCALE_DOWN, SCALE_FIT, SCALE_WIDTH, SCALE_HEIGHT, SCALE_ZOOM } scalemode_t; typedef enum { CURSOR_ARROW, CURSOR_NONE, CURSOR_HAND, CURSOR_WATCH } cursor_t; typedef struct { const char *name; /* as given by user */ const char *path; /* always absolute */ const char *base; bool loaded; } fileinfo_t; /* timeouts in milliseconds: */ enum { TO_REDRAW_RESIZE = 75, TO_REDRAW_THUMBS = 200, TO_CURSOR_HIDE = 1200 }; typedef void (*timeout_f)(void); #endif /* TYPES_H */ sxiv-1.1.1/util.c000066400000000000000000000156141215261427000136330ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include "options.h" #include "util.h" enum { BUF_SIZE = 1024, DNAME_CNT = 512, FNAME_LEN = 1024 }; void cleanup(void); void* s_malloc(size_t size) { void *ptr; ptr = malloc(size); if (ptr == NULL) die("could not allocate memory"); return ptr; } void* s_realloc(void *ptr, size_t size) { ptr = realloc(ptr, size); if (ptr == NULL) die("could not allocate memory"); return ptr; } char* s_strdup(char *s) { char *d = NULL; if (s != NULL) { d = malloc(strlen(s) + 1); if (d == NULL) die("could not allocate memory"); strcpy(d, s); } return d; } void warn(const char* fmt, ...) { va_list args; if (fmt == NULL || options->quiet) return; va_start(args, fmt); fprintf(stderr, "sxiv: warning: "); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); } void die(const char* fmt, ...) { va_list args; if (fmt == NULL) return; va_start(args, fmt); fprintf(stderr, "sxiv: error: "); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); cleanup(); exit(EXIT_FAILURE); } ssize_t get_line(char **buf, size_t *n, FILE *stream) { size_t len; char *s; if (stream == NULL || feof(stream) || ferror(stream)) return -1; if (*buf == NULL || *n == 0) { *n = BUF_SIZE; *buf = (char*) s_malloc(*n); } s = *buf; while (true) { if (fgets(s, *n - (s - *buf), stream) == NULL) return -1; len = strlen(s); if (feof(stream)) break; if (len > 0 && s[len-1] == '\n') break; if (len + 1 == *n - (s - *buf)) { *buf = (char*) s_realloc(*buf, 2 * *n); s = *buf + *n - 1; *n *= 2; } else { s += len; } } return s - *buf + len; } void size_readable(float *size, const char **unit) { const char *units[] = { "", "K", "M", "G" }; int i; for (i = 0; i < ARRLEN(units) && *size > 1024.0; i++) *size /= 1024.0; *unit = units[MIN(i, ARRLEN(units) - 1)]; } char* absolute_path(const char *filename) { size_t len; const char *basename; char *dir, *dirname = NULL, *path = NULL, *s; char *cwd = NULL, *twd = NULL; if (filename == NULL || *filename == '\0' || *filename == '/') return NULL; len = FNAME_LEN; cwd = (char*) s_malloc(len); while ((s = getcwd(cwd, len)) == NULL && errno == ERANGE) { len *= 2; cwd = (char*) s_realloc(cwd, len); } if (s == NULL) goto error; s = strrchr(filename, '/'); if (s != NULL) { len = s - filename; dirname = (char*) s_malloc(len + 1); strncpy(dirname, filename, len); dirname[len] = '\0'; basename = s + 1; if (chdir(cwd) < 0) /* we're not able to come back afterwards */ goto error; if (chdir(dirname) < 0) goto error; len = FNAME_LEN; twd = (char*) s_malloc(len); while ((s = getcwd(twd, len)) == NULL && errno == ERANGE) { len *= 2; twd = (char*) s_realloc(twd, len); } if (chdir(cwd) < 0) die("could not revert to prior working directory"); if (s == NULL) goto error; dir = twd; } else { /* only a single filename given */ basename = filename; dir = cwd; } len = strlen(dir) + strlen(basename) + 2; path = (char*) s_malloc(len); snprintf(path, len, "%s/%s", dir, basename); goto end; error: if (path != NULL) { free(path); path = NULL; } end: if (dirname != NULL) free(dirname); if (cwd != NULL) free(cwd); if (twd != NULL) free(twd); return path; } int r_opendir(r_dir_t *rdir, const char *dirname) { if (rdir == NULL || dirname == NULL || *dirname == '\0') return -1; if ((rdir->dir = opendir(dirname)) == NULL) { rdir->name = NULL; rdir->stack = NULL; return -1; } rdir->stcap = DNAME_CNT; rdir->stack = (char**) s_malloc(rdir->stcap * sizeof(char*)); rdir->stlen = 0; rdir->name = (char*) dirname; rdir->d = 0; return 0; } int r_closedir(r_dir_t *rdir) { int ret = 0; if (rdir == NULL) return -1; 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 && rdir->name != NULL) { free(rdir->name); rdir->name = NULL; } return ret; } char* r_readdir(r_dir_t *rdir) { size_t len; char *filename; struct dirent *dentry; struct stat fstats; if (rdir == NULL || rdir->dir == NULL || rdir->name == NULL) return NULL; while (true) { if (rdir->dir != NULL && (dentry = readdir(rdir->dir)) != NULL) { if (dentry->d_name[0] == '.') continue; len = strlen(rdir->name) + strlen(dentry->d_name) + 2; filename = (char*) s_malloc(len); snprintf(filename, len, "%s%s%s", rdir->name, rdir->name[strlen(rdir->name)-1] == '/' ? "" : "/", dentry->d_name); if (stat(filename, &fstats) < 0) continue; if (S_ISDIR(fstats.st_mode)) { /* put subdirectory on the stack */ if (rdir->stlen == rdir->stcap) { rdir->stcap *= 2; rdir->stack = (char**) s_realloc(rdir->stack, rdir->stcap * sizeof(char*)); } rdir->stack[rdir->stlen++] = filename; continue; } return filename; } if (rdir->stlen > 0) { /* open next subdirectory */ 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) warn("could not open directory: %s", rdir->name); continue; } /* no more entries */ break; } return NULL; } int r_mkdir(const char *path) { char *dir, *d; struct stat stats; int err = 0; if (path == NULL || *path == '\0') return -1; if (stat(path, &stats) == 0) { if (S_ISDIR(stats.st_mode)) { return 0; } else { warn("not a directory: %s", path); return -1; } } d = dir = (char*) s_malloc(strlen(path) + 1); strcpy(dir, path); while (d != NULL && err == 0) { d = strchr(d + 1, '/'); if (d != NULL) *d = '\0'; if (access(dir, F_OK) < 0 && errno == ENOENT) { if (mkdir(dir, 0755) < 0) { warn("could not create directory: %s", dir); err = -1; } } else if (stat(dir, &stats) < 0 || !S_ISDIR(stats.st_mode)) { warn("not a directory: %s", dir); err = -1; } if (d != NULL) *d = '/'; } free(dir); return err; } sxiv-1.1.1/util.h000066400000000000000000000036451215261427000136410ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #ifndef UTIL_H #define UTIL_H #include #include #include #include #include #include "types.h" #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif #define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) #define STREQ(s1,s2) (strcmp((s1), (s2)) == 0) #define TV_DIFF(t1,t2) (((t1)->tv_sec - (t2)->tv_sec ) * 1000 + \ ((t1)->tv_usec - (t2)->tv_usec) / 1000) #define TV_SET_MSEC(tv,t) { \ (tv)->tv_sec = (t) / 1000; \ (tv)->tv_usec = (t) % 1000 * 1000; \ } #define TV_ADD_MSEC(tv,t) { \ (tv)->tv_sec += (t) / 1000; \ (tv)->tv_usec += (t) % 1000 * 1000; \ } typedef struct { DIR *dir; char *name; int d; char **stack; int stcap; int stlen; } r_dir_t; void* s_malloc(size_t); void* s_realloc(void*, size_t); char* s_strdup(char*); void warn(const char*, ...); void die(const char*, ...); ssize_t get_line(char**, size_t*, FILE*); void size_readable(float*, const char**); char* absolute_path(const char*); int r_opendir(r_dir_t*, const char*); int r_closedir(r_dir_t*); char* r_readdir(r_dir_t*); int r_mkdir(const char *); #endif /* UTIL_H */ sxiv-1.1.1/window.c000066400000000000000000000322721215261427000141640ustar00rootroot00000000000000/* Copyright 2011-2013 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #define _POSIX_C_SOURCE 200112L #define _WINDOW_CONFIG #include #include #include #include "options.h" #include "util.h" #include "window.h" #include "config.h" enum { H_TEXT_PAD = 5, V_TEXT_PAD = 1 }; static Cursor carrow; static Cursor cnone; static Cursor chand; static Cursor cwatch; static GC gc; Atom wm_delete_win; static struct { int ascent; int descent; XFontStruct *xfont; XFontSet set; } font; static int fontheight; static int barheight; void win_init_font(Display *dpy, const char *fontstr) { int n; char *def, **missing; font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def); if (missing) XFreeStringList(missing); if (font.set) { XFontStruct **xfonts; char **font_names; font.ascent = font.descent = 0; XExtentsOfFontSet(font.set); n = XFontsOfFontSet(font.set, &xfonts, &font_names); while (n--) { font.ascent = MAX(font.ascent, (*xfonts)->ascent); font.descent = MAX(font.descent,(*xfonts)->descent); xfonts++; } } else { if ((font.xfont = XLoadQueryFont(dpy, fontstr)) == NULL && (font.xfont = XLoadQueryFont(dpy, "fixed")) == NULL) { die("could not load font: %s", fontstr); } font.ascent = font.xfont->ascent; font.descent = font.xfont->descent; } fontheight = font.ascent + font.descent; barheight = fontheight + 2 * V_TEXT_PAD; } unsigned long win_alloc_color(win_t *win, const char *name) { XColor col; if (win == NULL) return 0UL; if (XAllocNamedColor(win->env.dpy, DefaultColormap(win->env.dpy, win->env.scr), name, &col, &col) == 0) { die("could not allocate color: %s", name); } return col.pixel; } void win_init(win_t *win) { win_env_t *e; if (win == NULL) return; memset(win, 0, sizeof(win_t)); e = &win->env; if ((e->dpy = XOpenDisplay(NULL)) == NULL) die("could not open display"); e->scr = DefaultScreen(e->dpy); e->scrw = DisplayWidth(e->dpy, e->scr); e->scrh = DisplayHeight(e->dpy, e->scr); e->vis = DefaultVisual(e->dpy, e->scr); e->cmap = DefaultColormap(e->dpy, e->scr); e->depth = DefaultDepth(e->dpy, e->scr); if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0) warn("no locale support"); win_init_font(e->dpy, BAR_FONT); win->white = WhitePixel(e->dpy, e->scr); win->bgcol = win_alloc_color(win, WIN_BG_COLOR); win->fscol = win_alloc_color(win, WIN_FS_COLOR); win->selcol = win_alloc_color(win, SEL_COLOR); win->bar.bgcol = win_alloc_color(win, BAR_BG_COLOR); win->bar.fgcol = win_alloc_color(win, BAR_FG_COLOR); win->bar.h = options->hide_bar ? 0 : barheight; win->sizehints.flags = PWinGravity; win->sizehints.win_gravity = NorthWestGravity; if (options->fixed_win) /* actual min/max values set in win_update_sizehints() */ win->sizehints.flags |= PMinSize | PMaxSize; wm_delete_win = XInternAtom(e->dpy, "WM_DELETE_WINDOW", False); } void win_update_sizehints(win_t *win) { if (win == NULL || win->xwin == None) return; if ((win->sizehints.flags & USSize) != 0) { win->sizehints.width = win->w; win->sizehints.height = win->h + win->bar.h; } if ((win->sizehints.flags & USPosition) != 0) { win->sizehints.x = win->x; win->sizehints.y = win->y; } if (options->fixed_win) { win->sizehints.min_width = win->sizehints.max_width = win->w; win->sizehints.min_height = win->sizehints.max_height = win->h + win->bar.h; } XSetWMNormalHints(win->env.dpy, win->xwin, &win->sizehints); } void win_open(win_t *win) { win_env_t *e; XClassHint classhint; XSetWindowAttributes attr; unsigned long attr_mask; XColor col; char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; Pixmap none; int gmask; if (win == NULL) return; e = &win->env; /* 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) != 0) win->sizehints.flags |= USSize; else win->w = WIN_WIDTH; if ((gmask & HeightValue) != 0) win->sizehints.flags |= USSize; else win->h = WIN_HEIGHT; if ((gmask & XValue) != 0) { if ((gmask & XNegative) != 0) { win->x += e->scrw - win->w; win->sizehints.win_gravity = NorthEastGravity; } win->sizehints.flags |= USPosition; } else { win->x = (e->scrw - win->w) / 2; } if ((gmask & YValue) != 0) { if ((gmask & YNegative) != 0) { win->y += e->scrh - win->h; if (win->sizehints.win_gravity == NorthEastGravity) win->sizehints.win_gravity = SouthEastGravity; else win->sizehints.win_gravity = SouthWestGravity; } win->sizehints.flags |= USPosition; } else { win->y = (e->scrh - win->h) / 2; } attr.background_pixel = win->bgcol; attr_mask = CWBackPixel; win->xwin = XCreateWindow(e->dpy, RootWindow(e->dpy, e->scr), win->x, win->y, win->w, win->h, 0, e->depth, InputOutput, e->vis, attr_mask, &attr); if (win->xwin == None) die("could not create window"); XSelectInput(e->dpy, win->xwin, ExposureMask | ButtonReleaseMask | ButtonPressMask | KeyPressMask | PointerMotionMask | StructureNotifyMask); carrow = XCreateFontCursor(e->dpy, XC_left_ptr); chand = XCreateFontCursor(e->dpy, XC_fleur); cwatch = XCreateFontCursor(e->dpy, XC_watch); if (XAllocNamedColor(e->dpy, DefaultColormap(e->dpy, e->scr), "black", &col, &col) == 0) { die("could not allocate 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); win_set_title(win, "sxiv"); classhint.res_class = "Sxiv"; classhint.res_name = options->res_name != NULL ? options->res_name : "sxiv"; XSetClassHint(e->dpy, win->xwin, &classhint); XSetWMProtocols(e->dpy, win->xwin, &wm_delete_win, 1); win->h -= win->bar.h; win_update_sizehints(win); XMapWindow(e->dpy, win->xwin); XFlush(e->dpy); if (options->fullscreen) win_toggle_fullscreen(win); } void win_close(win_t *win) { if (win == NULL || win->xwin == None) return; XFreeCursor(win->env.dpy, carrow); XFreeCursor(win->env.dpy, cnone); XFreeCursor(win->env.dpy, chand); XFreeCursor(win->env.dpy, cwatch); XFreeGC(win->env.dpy, gc); XDestroyWindow(win->env.dpy, win->xwin); XCloseDisplay(win->env.dpy); } bool win_configure(win_t *win, XConfigureEvent *c) { bool changed; if (win == NULL || c == NULL) return false; if ((changed = win->w != c->width || win->h + win->bar.h != c->height)) { if (win->pm != None) { XFreePixmap(win->env.dpy, win->pm); win->pm = None; } } 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_expose(win_t *win, XExposeEvent *e) { if (win == NULL || win->xwin == None || win->pm == None || e == NULL) return; XCopyArea(win->env.dpy, win->pm, win->xwin, gc, e->x, e->y, e->width, e->height, e->x, e->y); } bool win_moveresize(win_t *win, int x, int y, unsigned int w, unsigned int h) { if (win == NULL || win->xwin == None) return false; /* caller knows nothing about the bar */ h += win->bar.h; x = MAX(0, x); y = MAX(0, y); w = MIN(w, win->env.scrw - 2 * win->bw); h = MIN(h, win->env.scrh - 2 * win->bw); if (win->x == x && win->y == y && win->w == w && win->h + win->bar.h == h) return false; win->x = x; win->y = y; win->w = w; win->h = h - win->bar.h; win_update_sizehints(win); XMoveResizeWindow(win->env.dpy, win->xwin, x, y, w, h); if (win->pm != None) { XFreePixmap(win->env.dpy, win->pm); win->pm = None; } return true; } void win_toggle_fullscreen(win_t *win) { XEvent ev; XClientMessageEvent *cm; if (win == NULL || win->xwin == None) return; win->fullscreen = !win->fullscreen; memset(&ev, 0, sizeof(ev)); ev.type = ClientMessage; cm = &ev.xclient; cm->window = win->xwin; cm->message_type = XInternAtom(win->env.dpy, "_NET_WM_STATE", False); cm->format = 32; cm->data.l[0] = win->fullscreen; cm->data.l[1] = XInternAtom(win->env.dpy, "_NET_WM_STATE_FULLSCREEN", False); cm->data.l[2] = cm->data.l[3] = 0; XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); } void win_toggle_bar(win_t *win) { if (win == NULL || win->xwin == None) return; 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) { int h; win_env_t *e; if (win == NULL || win->xwin == None) return; h = win->h + win->bar.h; e = &win->env; if (win->pm == None) win->pm = XCreatePixmap(e->dpy, win->xwin, win->w, h, e->depth); XSetForeground(e->dpy, gc, win->fullscreen ? win->fscol : win->bgcol); XFillRectangle(e->dpy, win->pm, gc, 0, 0, win->w, h); } void win_draw_bar(win_t *win) { int len, olen, x, y, w, tw; char rest[3]; const char *dots = "..."; win_env_t *e; if (win == NULL || win->xwin == None || win->pm == None) return; e = &win->env; y = win->h + font.ascent + V_TEXT_PAD; w = win->w; XSetForeground(e->dpy, gc, win->bar.bgcol); XFillRectangle(e->dpy, win->pm, gc, 0, win->h, win->w, win->bar.h); XSetForeground(e->dpy, gc, win->bar.fgcol); XSetBackground(e->dpy, gc, win->bar.bgcol); if ((len = strlen(win->bar.r)) > 0) { if ((tw = win_textwidth(win->bar.r, len, true)) > w) return; x = win->w - tw + H_TEXT_PAD; w -= tw; if (font.set) XmbDrawString(e->dpy, win->pm, font.set, gc, x, y, win->bar.r, len); else XDrawString(e->dpy, win->pm, gc, x, y, win->bar.r, len); } if ((len = strlen(win->bar.l)) > 0) { olen = len; while (len > 0 && (tw = win_textwidth(win->bar.l, len, true)) > w) len--; if (len > 0) { if (len != olen) { w = strlen(dots); if (len <= w) return; memcpy(rest, win->bar.l + len - w, w); memcpy(win->bar.l + len - w, dots, w); } x = H_TEXT_PAD; if (font.set) XmbDrawString(e->dpy, win->pm, font.set, gc, x, y, win->bar.l, len); else XDrawString(e->dpy, win->pm, gc, x, y, win->bar.l, len); if (len != olen) memcpy(win->bar.l + len - w, rest, w); } } } void win_draw(win_t *win) { if (win == NULL || win->xwin == None || win->pm == None) return; if (win->bar.h > 0) win_draw_bar(win); XCopyArea(win->env.dpy, win->pm, win->xwin, gc, 0, 0, win->w, win->h + win->bar.h, 0, 0); } void win_draw_rect(win_t *win, Pixmap pm, int x, int y, int w, int h, bool fill, int lw, unsigned long col) { XGCValues gcval; if (win == NULL || pm == None) return; gcval.line_width = lw; gcval.foreground = col; XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval); if (fill) XFillRectangle(win->env.dpy, pm, gc, x, y, w, h); else XDrawRectangle(win->env.dpy, pm, gc, x, y, w, h); } void win_update_bar(win_t *win) { if (win == NULL || win->xwin == None || win->pm == None) return; if (win->bar.h > 0) { win_draw_bar(win); XCopyArea(win->env.dpy, win->pm, win->xwin, gc, 0, win->h, win->w, win->bar.h, 0, win->h); } } int win_textwidth(const char *text, unsigned int len, bool with_padding) { XRectangle r; int padding = with_padding ? 2 * H_TEXT_PAD : 0; if (font.set) { XmbTextExtents(font.set, text, len, NULL, &r); return r.width + padding; } else { return XTextWidth(font.xfont, text, len) + padding; } } void win_set_title(win_t *win, const char *title) { if (win == NULL || win->xwin == None) return; if (title == NULL) title = "sxiv"; XStoreName(win->env.dpy, win->xwin, title); XSetIconName(win->env.dpy, win->xwin, title); XChangeProperty(win->env.dpy, win->xwin, XInternAtom(win->env.dpy, "_NET_WM_NAME", False), XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, PropModeReplace, (unsigned char *) title, strlen(title)); XChangeProperty(win->env.dpy, win->xwin, XInternAtom(win->env.dpy, "_NET_WM_ICON_NAME", False), XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, PropModeReplace, (unsigned char *) title, strlen(title)); } void win_set_cursor(win_t *win, cursor_t cursor) { if (win == NULL || win->xwin == None) return; switch (cursor) { case CURSOR_NONE: XDefineCursor(win->env.dpy, win->xwin, cnone); break; case CURSOR_HAND: XDefineCursor(win->env.dpy, win->xwin, chand); break; case CURSOR_WATCH: XDefineCursor(win->env.dpy, win->xwin, cwatch); break; case CURSOR_ARROW: default: XDefineCursor(win->env.dpy, win->xwin, carrow); break; } XFlush(win->env.dpy); } sxiv-1.1.1/window.h000066400000000000000000000037661215261427000141770ustar00rootroot00000000000000/* Copyright 2011 Bert Muennich * * This file is part of sxiv. * * sxiv 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. * * sxiv 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 sxiv. If not, see . */ #ifndef WINDOW_H #define WINDOW_H #include #include #include "types.h" enum { BAR_L_LEN = 512, BAR_R_LEN = 64 }; typedef struct { Display *dpy; int scr; int scrw, scrh; Visual *vis; Colormap cmap; int depth; } win_env_t; typedef struct { Window xwin; win_env_t env; unsigned long white; unsigned long bgcol; unsigned long fscol; unsigned long selcol; Pixmap pm; int x; int y; unsigned int w; unsigned int h; /* = win height - bar height */ unsigned int bw; XSizeHints sizehints; bool fullscreen; struct { unsigned int h; char l[BAR_L_LEN]; char r[BAR_R_LEN]; unsigned long bgcol; unsigned long fgcol; } bar; } win_t; extern Atom wm_delete_win; void win_init(win_t*); void win_open(win_t*); void win_close(win_t*); bool win_configure(win_t*, XConfigureEvent*); void win_expose(win_t*, XExposeEvent*); bool win_moveresize(win_t*, int, int, unsigned int, unsigned int); 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*, Pixmap, int, int, int, int, bool, int, unsigned long); void win_update_bar(win_t*); int win_textwidth(const char*, unsigned int, bool); void win_set_title(win_t*, const char*); void win_set_cursor(win_t*, cursor_t); #endif /* WINDOW_H */