├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── TODO ├── autoreload_inotify.c ├── autoreload_nop.c ├── commands.c ├── commands.lst ├── config.def.h ├── exec ├── image-info └── key-handler ├── icon ├── 128x128.png ├── 16x16.png ├── 32x32.png ├── 48x48.png ├── 64x64.png ├── Makefile ├── dat2h.awk └── data.h ├── image.c ├── main.c ├── options.c ├── sxiv.1 ├── sxiv.desktop ├── sxiv.h ├── thumbs.c ├── utf8.h ├── util.c └── window.c /.gitignore: -------------------------------------------------------------------------------- 1 | config.h 2 | version.h 3 | *.d 4 | *.o 5 | sxiv 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | version = 26 2 | 3 | srcdir = . 4 | VPATH = $(srcdir) 5 | 6 | PREFIX = /usr/local 7 | MANPREFIX = $(PREFIX)/share/man 8 | 9 | # autoreload backend: inotify/nop 10 | AUTORELOAD = inotify 11 | 12 | # enable features requiring giflib (-lgif) 13 | HAVE_GIFLIB = 1 14 | 15 | # enable features requiring libexif (-lexif) 16 | HAVE_LIBEXIF = 1 17 | 18 | cflags = -std=c99 -Wall -pedantic $(CFLAGS) 19 | cppflags = -I. $(CPPFLAGS) -D_XOPEN_SOURCE=700 \ 20 | -DHAVE_GIFLIB=$(HAVE_GIFLIB) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \ 21 | -I/usr/include/freetype2 -I$(PREFIX)/include/freetype2 22 | 23 | lib_exif_0 = 24 | lib_exif_1 = -lexif 25 | lib_gif_0 = 26 | lib_gif_1 = -lgif 27 | ldlibs = $(LDLIBS) -lImlib2 -lX11 -lXft -lfontconfig \ 28 | $(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_GIFLIB)) 29 | 30 | objs = autoreload_$(AUTORELOAD).o commands.o image.o main.o options.o \ 31 | thumbs.o util.o window.o 32 | 33 | all: sxiv 34 | 35 | .PHONY: all clean install uninstall 36 | .SUFFIXES: 37 | .SUFFIXES: .c .o 38 | $(V).SILENT: 39 | 40 | sxiv: $(objs) 41 | @echo "LINK $@" 42 | $(CC) $(LDFLAGS) -o $@ $(objs) $(ldlibs) 43 | 44 | $(objs): Makefile sxiv.h commands.lst config.h 45 | options.o: version.h 46 | window.o: icon/data.h 47 | 48 | .c.o: 49 | @echo "CC $@" 50 | $(CC) $(cflags) $(cppflags) -c -o $@ $< 51 | 52 | config.h: 53 | @echo "GEN $@" 54 | cp $(srcdir)/config.def.h $@ 55 | 56 | version.h: Makefile .git/index 57 | @echo "GEN $@" 58 | v="$$(cd $(srcdir); git describe 2>/dev/null)"; \ 59 | echo "#define VERSION \"$${v:-$(version)}\"" >$@ 60 | 61 | .git/index: 62 | 63 | clean: 64 | rm -f *.o sxiv 65 | 66 | install: all 67 | @echo "INSTALL bin/sxiv" 68 | mkdir -p $(DESTDIR)$(PREFIX)/bin 69 | cp sxiv $(DESTDIR)$(PREFIX)/bin/ 70 | chmod 755 $(DESTDIR)$(PREFIX)/bin/sxiv 71 | @echo "INSTALL sxiv.1" 72 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1 73 | sed "s!PREFIX!$(PREFIX)!g; s!VERSION!$(version)!g" sxiv.1 \ 74 | >$(DESTDIR)$(MANPREFIX)/man1/sxiv.1 75 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 76 | @echo "INSTALL share/sxiv/" 77 | mkdir -p $(DESTDIR)$(PREFIX)/share/sxiv/exec 78 | cp exec/* $(DESTDIR)$(PREFIX)/share/sxiv/exec/ 79 | chmod 755 $(DESTDIR)$(PREFIX)/share/sxiv/exec/* 80 | 81 | uninstall: 82 | @echo "REMOVE bin/sxiv" 83 | rm -f $(DESTDIR)$(PREFIX)/bin/sxiv 84 | @echo "REMOVE sxiv.1" 85 | rm -f $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 86 | @echo "REMOVE share/sxiv/" 87 | rm -rf $(DESTDIR)$(PREFIX)/share/sxiv 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![sxiv](http://xyb3rt.github.io/sxiv/img/logo.png "sxiv") 2 | 3 | **Simple X Image Viewer** 4 | 5 | The sole purpose of sxiv is to be the perfect image viewer for me. It is free 6 | software so that you can use it and modify it for your needs. Please file a bug 7 | report if something does not work as documented or expected. Contributions are 8 | welcome but there is no guarantee that they will be incorporated. 9 | 10 | 11 | Features 12 | -------- 13 | 14 | * Basic image operations, e.g. zooming, panning, rotating 15 | * Customizable key and mouse button mappings (in *config.h*) 16 | * Thumbnail mode: grid of selectable previews of all images 17 | * Ability to cache thumbnails for fast re-loading 18 | * Basic support for multi-frame images 19 | * Load all frames from GIF files and play GIF animations 20 | * Display image information in status bar 21 | 22 | 23 | Screenshots 24 | ----------- 25 | 26 | **Image mode:** 27 | 28 | ![Image](http://xyb3rt.github.io/sxiv/img/image.png "Image mode") 29 | 30 | **Thumbnail mode:** 31 | 32 | ![Thumb](http://xyb3rt.github.io/sxiv/img/thumb.png "Thumb mode") 33 | 34 | 35 | Dependencies 36 | ------------ 37 | 38 | sxiv requires the following software to be installed: 39 | 40 | * Imlib2 41 | * X11 42 | * Xft 43 | * freetype2 44 | * fontconfig 45 | * giflib (optional, disabled with `HAVE_GIFLIB=0`) 46 | * libexif (optional, disabled with `HAVE_LIBEXIF=0`) 47 | 48 | Please make sure to install the corresponding development packages in case that 49 | you want to build sxiv on a distribution with separate runtime and development 50 | packages (e.g. *-dev on Debian). 51 | 52 | 53 | Building 54 | -------- 55 | 56 | sxiv is built using the commands: 57 | 58 | $ make 59 | # make install 60 | 61 | Please note, that the latter one requires root privileges. 62 | By default, sxiv is installed using the prefix "/usr/local", so the full path 63 | of the executable will be "/usr/local/bin/sxiv". 64 | 65 | You can install sxiv into a directory of your choice by changing the second 66 | command to: 67 | 68 | # make PREFIX="/your/dir" install 69 | 70 | The build-time specific settings of sxiv can be found in the file *config.h*. 71 | Please check and change them, so that they fit your needs. 72 | If the file *config.h* does not already exist, then you have to create it with 73 | the following command: 74 | 75 | $ make config.h 76 | 77 | 78 | Usage 79 | ----- 80 | 81 | Please see the [man page](http://xyb3rt.github.io/sxiv/sxiv.1.html) for 82 | information on how to use sxiv. 83 | 84 | 85 | Download & Changelog 86 | -------------------- 87 | 88 | You can [browse](https://github.com/xyb3rt/sxiv) the source code repository 89 | on GitHub or get a copy using git with the following command: 90 | 91 | git clone https://github.com/xyb3rt/sxiv.git 92 | 93 | **Stable releases** 94 | 95 | **[v26](https://github.com/xyb3rt/sxiv/archive/v26.tar.gz)** 96 | *(January 16, 2020)* 97 | 98 | * Maintenance release 99 | 100 | **[v25](https://github.com/xyb3rt/sxiv/archive/v25.tar.gz)** 101 | *(January 26, 2019)* 102 | 103 | * Support font fallback for missing glyphs 104 | * Fix busy loop when built without inotify 105 | * Use background/foreground colors from X resource database 106 | 107 | **[v24](https://github.com/xyb3rt/sxiv/archive/v24.tar.gz)** 108 | *(October 27, 2017)* 109 | 110 | * Automatically reload the current image whenever it changes 111 | * Support embedding into other X windows with -e (e.g. tabbed) 112 | * New option -p prevents sxiv from creating cache and temporary files 113 | * Simpler mouse mappings, the most basic features are accessible with the 114 | mouse only (navigate, zoom, pan) 115 | 116 | **[v1.3.2](https://github.com/xyb3rt/sxiv/archive/v1.3.2.tar.gz)** 117 | *(December 20, 2015)* 118 | 119 | * external key handler gets file paths on stdin, not as arguments 120 | * Cache out-of-view thumbnails in the background 121 | * Apply gamma correction to thumbnails 122 | 123 | **[v1.3.1](https://github.com/xyb3rt/sxiv/archive/v1.3.1.tar.gz)** 124 | *(November 16, 2014)* 125 | 126 | * Fixed build error, caused by delayed config.h creation 127 | * Fixed segfault when run with -c 128 | 129 | **[v1.3](https://github.com/xyb3rt/sxiv/archive/v1.3.tar.gz)** 130 | *(October 24, 2014)* 131 | 132 | * Extract thumbnails from EXIF tags (requires libexif) 133 | * Zoomable thumbnails, supported sizes defined in config.h 134 | * Fixed build error with giflib version >= 5.1.0 135 | 136 | **[v1.2](https://github.com/xyb3rt/sxiv/archive/v1.2.tar.gz)** 137 | *(April 24, 2014)* 138 | 139 | * Added external key handler, called on keys prefixed with `Ctrl-x` 140 | * New keybinding `{`/`}` to change gamma (by András Mohari) 141 | * Support for slideshows, enabled with `-S` option & toggled with `s` 142 | * Added application icon (created by 0ion9) 143 | * Checkerboard background for alpha layer 144 | * Option `-o` only prints files marked with `m` key 145 | * Fixed rotation/flipping of multi-frame images (gifs) 146 | 147 | **[v1.1.1](https://github.com/xyb3rt/sxiv/archive/v1.1.1.tar.gz)** 148 | *(June 2, 2013)* 149 | 150 | * Various bug fixes 151 | 152 | **[v1.1](https://github.com/xyb3rt/sxiv/archive/v1.1.tar.gz)** 153 | *(March 30, 2013)* 154 | 155 | * Added status bar on bottom of window with customizable content 156 | * New keyboard shortcuts `\`/`|`: flip image vertically/horizontally 157 | * New keyboard shortcut `Ctrl-6`: go to last/alternate image 158 | * Added own EXIF orientation handling, removed dependency on libexif 159 | * Fixed various bugs 160 | 161 | **[v1.0](https://github.com/xyb3rt/sxiv/archive/v1.0.tar.gz)** 162 | *(October 31, 2011)* 163 | 164 | * Support for multi-frame images & GIF animations 165 | * POSIX compliant (IEEE Std 1003.1-2001) 166 | 167 | **[v0.9](https://github.com/xyb3rt/sxiv/archive/v0.9.tar.gz)** 168 | *(August 17, 2011)* 169 | 170 | * Made key and mouse mappings fully configurable in config.h 171 | * Complete code refactoring 172 | 173 | **[v0.8.2](https://github.com/xyb3rt/sxiv/archive/v0.8.2.tar.gz)** 174 | *(June 29, 2011)* 175 | 176 | * POSIX-compliant Makefile; compiles under NetBSD 177 | 178 | **[v0.8.1](https://github.com/xyb3rt/sxiv/archive/v0.8.1.tar.gz)** 179 | *(May 8, 2011)* 180 | 181 | * Fixed fullscreen under window managers, which are not fully EWMH-compliant 182 | 183 | **[v0.8](https://github.com/xyb3rt/sxiv/archive/v0.8.tar.gz)** 184 | *(April 18, 2011)* 185 | 186 | * Support for thumbnail caching 187 | * Ability to run external commands (e.g. jpegtran, convert) on current image 188 | 189 | **[v0.7](https://github.com/xyb3rt/sxiv/archive/v0.7.tar.gz)** 190 | *(February 26, 2011)* 191 | 192 | * Sort directory entries when using `-r` command line option 193 | * Hide cursor in image mode 194 | * Full functional thumbnail mode, use Return key to switch between image and 195 | thumbnail mode 196 | 197 | **[v0.6](https://github.com/xyb3rt/sxiv/archive/v0.6.tar.gz)** 198 | *(February 16, 2011)* 199 | 200 | * Bug fix: Correctly display filenames with umlauts in window title 201 | * Basic support of thumbnails 202 | 203 | **[v0.5](https://github.com/xyb3rt/sxiv/archive/v0.5.tar.gz)** 204 | *(February 6, 2011)* 205 | 206 | * New command line option: `-r`: open all images in given directories 207 | * New key shortcuts: `w`: resize image to fit into window; `W`: resize window 208 | to fit to image 209 | 210 | **[v0.4](https://github.com/xyb3rt/sxiv/archive/v0.4.tar.gz)** 211 | *(February 1, 2011)* 212 | 213 | * New command line option: `-F`, `-g`: use fixed window dimensions and apply 214 | a given window geometry 215 | * New key shortcut: `r`: reload current image 216 | 217 | **[v0.3.1](https://github.com/xyb3rt/sxiv/archive/v0.3.1.tar.gz)** 218 | *(January 30, 2011)* 219 | 220 | * Bug fix: Do not set setuid bit on executable when using `make install` 221 | * Pan image with mouse while pressing middle mouse button 222 | 223 | **[v0.3](https://github.com/xyb3rt/sxiv/archive/v0.3.tar.gz)** 224 | *(January 29, 2011)* 225 | 226 | * New command line options: `-d`, `-f`, `-p`, `-s`, `-v`, `-w`, `-Z`, `-z` 227 | * More mouse mappings: Go to next/previous image with left/right click, 228 | scroll image with mouse wheel (horizontally if Shift key is pressed), 229 | zoom image with mouse wheel if Ctrl key is pressed 230 | 231 | **[v0.2](https://github.com/xyb3rt/sxiv/archive/v0.2.tar.gz)** 232 | *(January 23, 2011)* 233 | 234 | * Bug fix: Handle window resizes correctly 235 | * New keyboard shortcuts: `g`/`G`: go to first/last image; `[`/`]`: go 10 236 | images back/forward 237 | * Support for mouse wheel zooming (by Dave Reisner) 238 | * Added fullscreen mode 239 | 240 | **[v0.1](https://github.com/xyb3rt/sxiv/archive/v0.1.tar.gz)** 241 | *(January 21, 2011)* 242 | 243 | * Initial release 244 | 245 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - Load all frames from TIFF files. We have to write our own loader for this to 2 | happen--just like we did for GIF images--because Imlib2 does not support 3 | multiple frames. Issue #241. 4 | - Add support for more embedded thumbnail formats. Right now, sxiv seems to use 5 | the smallest one. Issue #238. 6 | -------------------------------------------------------------------------------- /autoreload_inotify.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Max Voit, Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | void arl_init(arl_t *arl) 28 | { 29 | arl->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); 30 | arl->wd_dir = arl->wd_file = -1; 31 | if (arl->fd == -1) 32 | error(0, 0, "Could not initialize inotify, no automatic image reloading"); 33 | } 34 | 35 | CLEANUP void arl_cleanup(arl_t *arl) 36 | { 37 | if (arl->fd != -1) 38 | close(arl->fd); 39 | free(arl->filename); 40 | } 41 | 42 | static void rm_watch(int fd, int *wd) 43 | { 44 | if (*wd != -1) { 45 | inotify_rm_watch(fd, *wd); 46 | *wd = -1; 47 | } 48 | } 49 | 50 | static void add_watch(int fd, int *wd, const char *path, uint32_t mask) 51 | { 52 | *wd = inotify_add_watch(fd, path, mask); 53 | if (*wd == -1) 54 | error(0, errno, "inotify: %s", path); 55 | } 56 | 57 | void arl_setup(arl_t *arl, const char *filepath) 58 | { 59 | char *base = strrchr(filepath, '/'); 60 | 61 | if (arl->fd == -1) 62 | return; 63 | 64 | rm_watch(arl->fd, &arl->wd_dir); 65 | rm_watch(arl->fd, &arl->wd_file); 66 | 67 | add_watch(arl->fd, &arl->wd_file, filepath, IN_CLOSE_WRITE | IN_DELETE_SELF); 68 | 69 | free(arl->filename); 70 | arl->filename = estrdup(filepath); 71 | 72 | if (base != NULL) { 73 | arl->filename[++base - filepath] = '\0'; 74 | add_watch(arl->fd, &arl->wd_dir, arl->filename, IN_CREATE | IN_MOVED_TO); 75 | strcpy(arl->filename, base); 76 | } 77 | } 78 | 79 | union { 80 | char d[4096]; /* aligned buffer */ 81 | struct inotify_event e; 82 | } buf; 83 | 84 | bool arl_handle(arl_t *arl) 85 | { 86 | bool reload = false; 87 | char *ptr; 88 | const struct inotify_event *e; 89 | 90 | for (;;) { 91 | ssize_t len = read(arl->fd, buf.d, sizeof(buf.d)); 92 | 93 | if (len == -1) { 94 | if (errno == EINTR) 95 | continue; 96 | break; 97 | } 98 | for (ptr = buf.d; ptr < buf.d + len; ptr += sizeof(*e) + e->len) { 99 | e = (const struct inotify_event*) ptr; 100 | if (e->wd == arl->wd_file && (e->mask & IN_CLOSE_WRITE)) { 101 | reload = true; 102 | } else if (e->wd == arl->wd_file && (e->mask & IN_DELETE_SELF)) { 103 | rm_watch(arl->fd, &arl->wd_file); 104 | } else if (e->wd == arl->wd_dir && (e->mask & (IN_CREATE | IN_MOVED_TO))) { 105 | if (STREQ(e->name, arl->filename)) 106 | reload = true; 107 | } 108 | } 109 | } 110 | return reload; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /autoreload_nop.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Max Voit 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | 21 | void arl_init(arl_t *arl) 22 | { 23 | arl->fd = -1; 24 | } 25 | 26 | void arl_cleanup(arl_t *arl) 27 | { 28 | (void) arl; 29 | } 30 | 31 | void arl_setup(arl_t *arl, const char *filepath) 32 | { 33 | (void) arl; 34 | (void) filepath; 35 | } 36 | 37 | bool arl_handle(arl_t *arl) 38 | { 39 | (void) arl; 40 | return false; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /commands.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2011, 2012, 2014 Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | #define _IMAGE_CONFIG 21 | #include "config.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | void remove_file(int, bool); 29 | void load_image(int); 30 | bool mark_image(int, bool); 31 | void close_info(void); 32 | void open_info(void); 33 | int ptr_third_x(void); 34 | void redraw(void); 35 | void reset_cursor(void); 36 | void animate(void); 37 | void slideshow(void); 38 | void set_timeout(timeout_f, int, bool); 39 | void reset_timeout(timeout_f); 40 | 41 | extern appmode_t mode; 42 | extern img_t img; 43 | extern tns_t tns; 44 | extern win_t win; 45 | 46 | extern fileinfo_t *files; 47 | extern int filecnt, fileidx; 48 | extern int alternate; 49 | extern int markcnt; 50 | extern int markidx; 51 | 52 | extern int prefix; 53 | extern bool extprefix; 54 | 55 | bool cg_quit(arg_t _) 56 | { 57 | unsigned int i; 58 | 59 | if (options->to_stdout && markcnt > 0) { 60 | for (i = 0; i < filecnt; i++) { 61 | if (files[i].flags & FF_MARK) 62 | printf("%s\n", files[i].name); 63 | } 64 | } 65 | exit(EXIT_SUCCESS); 66 | } 67 | 68 | bool cg_switch_mode(arg_t _) 69 | { 70 | if (mode == MODE_IMAGE) { 71 | if (tns.thumbs == NULL) 72 | tns_init(&tns, files, &filecnt, &fileidx, &win); 73 | img_close(&img, false); 74 | reset_timeout(reset_cursor); 75 | if (img.ss.on) { 76 | img.ss.on = false; 77 | reset_timeout(slideshow); 78 | } 79 | tns.dirty = true; 80 | mode = MODE_THUMB; 81 | } else { 82 | load_image(fileidx); 83 | mode = MODE_IMAGE; 84 | } 85 | return true; 86 | } 87 | 88 | bool cg_toggle_fullscreen(arg_t _) 89 | { 90 | win_toggle_fullscreen(&win); 91 | /* redraw after next ConfigureNotify event */ 92 | set_timeout(redraw, TO_REDRAW_RESIZE, false); 93 | if (mode == MODE_IMAGE) 94 | img.checkpan = img.dirty = true; 95 | else 96 | tns.dirty = true; 97 | return false; 98 | } 99 | 100 | bool cg_toggle_bar(arg_t _) 101 | { 102 | win_toggle_bar(&win); 103 | if (mode == MODE_IMAGE) { 104 | if (win.bar.h > 0) 105 | open_info(); 106 | else 107 | close_info(); 108 | img.checkpan = img.dirty = true; 109 | } else { 110 | tns.dirty = true; 111 | } 112 | return true; 113 | } 114 | 115 | bool cg_prefix_external(arg_t _) 116 | { 117 | extprefix = true; 118 | return false; 119 | } 120 | 121 | bool cg_reload_image(arg_t _) 122 | { 123 | if (mode == MODE_IMAGE) { 124 | load_image(fileidx); 125 | } else { 126 | win_set_cursor(&win, CURSOR_WATCH); 127 | if (!tns_load(&tns, fileidx, true, false)) { 128 | remove_file(fileidx, false); 129 | tns.dirty = true; 130 | } 131 | } 132 | return true; 133 | } 134 | 135 | bool cg_remove_image(arg_t _) 136 | { 137 | remove_file(fileidx, true); 138 | if (mode == MODE_IMAGE) 139 | load_image(fileidx); 140 | else 141 | tns.dirty = true; 142 | return true; 143 | } 144 | 145 | bool cg_first(arg_t _) 146 | { 147 | if (mode == MODE_IMAGE && fileidx != 0) { 148 | load_image(0); 149 | return true; 150 | } else if (mode == MODE_THUMB && fileidx != 0) { 151 | fileidx = 0; 152 | tns.dirty = true; 153 | return true; 154 | } else { 155 | return false; 156 | } 157 | } 158 | 159 | bool cg_n_or_last(arg_t _) 160 | { 161 | int n = prefix != 0 && prefix - 1 < filecnt ? prefix - 1 : filecnt - 1; 162 | 163 | if (mode == MODE_IMAGE && fileidx != n) { 164 | load_image(n); 165 | return true; 166 | } else if (mode == MODE_THUMB && fileidx != n) { 167 | fileidx = n; 168 | tns.dirty = true; 169 | return true; 170 | } else { 171 | return false; 172 | } 173 | } 174 | 175 | bool cg_scroll_screen(arg_t dir) 176 | { 177 | if (mode == MODE_IMAGE) 178 | return img_pan(&img, dir, -1); 179 | else 180 | return tns_scroll(&tns, dir, true); 181 | } 182 | 183 | bool cg_zoom(arg_t d) 184 | { 185 | if (mode == MODE_THUMB) 186 | return tns_zoom(&tns, d); 187 | else if (d > 0) 188 | return img_zoom_in(&img); 189 | else if (d < 0) 190 | return img_zoom_out(&img); 191 | else 192 | return false; 193 | } 194 | 195 | bool cg_toggle_image_mark(arg_t _) 196 | { 197 | return mark_image(fileidx, !(files[fileidx].flags & FF_MARK)); 198 | } 199 | 200 | bool cg_reverse_marks(arg_t _) 201 | { 202 | int i; 203 | 204 | for (i = 0; i < filecnt; i++) { 205 | files[i].flags ^= FF_MARK; 206 | markcnt += files[i].flags & FF_MARK ? 1 : -1; 207 | } 208 | if (mode == MODE_THUMB) 209 | tns.dirty = true; 210 | return true; 211 | } 212 | 213 | bool cg_mark_range(arg_t _) 214 | { 215 | int d = markidx < fileidx ? 1 : -1, end, i; 216 | bool dirty = false, on = !!(files[markidx].flags & FF_MARK); 217 | 218 | for (i = markidx + d, end = fileidx + d; i != end; i += d) 219 | dirty |= mark_image(i, on); 220 | return dirty; 221 | } 222 | 223 | bool cg_unmark_all(arg_t _) 224 | { 225 | int i; 226 | 227 | for (i = 0; i < filecnt; i++) 228 | files[i].flags &= ~FF_MARK; 229 | markcnt = 0; 230 | if (mode == MODE_THUMB) 231 | tns.dirty = true; 232 | return true; 233 | } 234 | 235 | bool cg_navigate_marked(arg_t n) 236 | { 237 | int d, i; 238 | int new = fileidx; 239 | 240 | if (prefix > 0) 241 | n *= prefix; 242 | d = n > 0 ? 1 : -1; 243 | for (i = fileidx + d; n != 0 && i >= 0 && i < filecnt; i += d) { 244 | if (files[i].flags & FF_MARK) { 245 | n -= d; 246 | new = i; 247 | } 248 | } 249 | if (new != fileidx) { 250 | if (mode == MODE_IMAGE) { 251 | load_image(new); 252 | } else { 253 | fileidx = new; 254 | tns.dirty = true; 255 | } 256 | return true; 257 | } else { 258 | return false; 259 | } 260 | } 261 | 262 | bool cg_change_gamma(arg_t d) 263 | { 264 | if (img_change_gamma(&img, d * (prefix > 0 ? prefix : 1))) { 265 | if (mode == MODE_THUMB) 266 | tns.dirty = true; 267 | return true; 268 | } else { 269 | return false; 270 | } 271 | } 272 | 273 | bool ci_navigate(arg_t n) 274 | { 275 | if (prefix > 0) 276 | n *= prefix; 277 | n += fileidx; 278 | if (n < 0) 279 | n = 0; 280 | if (n >= filecnt) 281 | n = filecnt - 1; 282 | 283 | if (n != fileidx) { 284 | load_image(n); 285 | return true; 286 | } else { 287 | return false; 288 | } 289 | } 290 | 291 | bool ci_cursor_navigate(arg_t _) 292 | { 293 | return ci_navigate(ptr_third_x() - 1); 294 | } 295 | 296 | bool ci_alternate(arg_t _) 297 | { 298 | load_image(alternate); 299 | return true; 300 | } 301 | 302 | bool ci_navigate_frame(arg_t d) 303 | { 304 | if (prefix > 0) 305 | d *= prefix; 306 | return !img.multi.animate && img_frame_navigate(&img, d); 307 | } 308 | 309 | bool ci_toggle_animation(arg_t _) 310 | { 311 | bool dirty = false; 312 | 313 | if (img.multi.cnt > 0) { 314 | img.multi.animate = !img.multi.animate; 315 | if (img.multi.animate) { 316 | dirty = img_frame_animate(&img); 317 | set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); 318 | } else { 319 | reset_timeout(animate); 320 | } 321 | } 322 | return dirty; 323 | } 324 | 325 | bool ci_scroll(arg_t dir) 326 | { 327 | return img_pan(&img, dir, prefix); 328 | } 329 | 330 | bool ci_scroll_to_edge(arg_t dir) 331 | { 332 | return img_pan_edge(&img, dir); 333 | } 334 | 335 | bool ci_drag(arg_t mode) 336 | { 337 | int x, y, ox, oy; 338 | float px, py; 339 | XEvent e; 340 | 341 | if ((int)(img.w * img.zoom) <= win.w && (int)(img.h * img.zoom) <= win.h) 342 | return false; 343 | 344 | win_set_cursor(&win, CURSOR_DRAG); 345 | 346 | win_cursor_pos(&win, &x, &y); 347 | ox = x; 348 | oy = y; 349 | 350 | for (;;) { 351 | if (mode == DRAG_ABSOLUTE) { 352 | px = MIN(MAX(0.0, x - win.w*0.1), win.w*0.8) / (win.w*0.8) 353 | * (win.w - img.w * img.zoom); 354 | py = MIN(MAX(0.0, y - win.h*0.1), win.h*0.8) / (win.h*0.8) 355 | * (win.h - img.h * img.zoom); 356 | } else { 357 | px = img.x + x - ox; 358 | py = img.y + y - oy; 359 | } 360 | 361 | if (img_pos(&img, px, py)) { 362 | img_render(&img); 363 | win_draw(&win); 364 | } 365 | XMaskEvent(win.env.dpy, 366 | ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); 367 | if (e.type == ButtonPress || e.type == ButtonRelease) 368 | break; 369 | while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)); 370 | ox = x; 371 | oy = y; 372 | x = e.xmotion.x; 373 | y = e.xmotion.y; 374 | } 375 | set_timeout(reset_cursor, TO_CURSOR_HIDE, true); 376 | reset_cursor(); 377 | 378 | return true; 379 | } 380 | 381 | bool ci_set_zoom(arg_t zl) 382 | { 383 | return img_zoom(&img, (prefix ? prefix : zl) / 100.0); 384 | } 385 | 386 | bool ci_fit_to_win(arg_t sm) 387 | { 388 | return img_fit_win(&img, sm); 389 | } 390 | 391 | bool ci_rotate(arg_t degree) 392 | { 393 | img_rotate(&img, degree); 394 | return true; 395 | } 396 | 397 | bool ci_flip(arg_t dir) 398 | { 399 | img_flip(&img, dir); 400 | return true; 401 | } 402 | 403 | bool ci_toggle_antialias(arg_t _) 404 | { 405 | img_toggle_antialias(&img); 406 | return true; 407 | } 408 | 409 | bool ci_toggle_alpha(arg_t _) 410 | { 411 | img.alpha = !img.alpha; 412 | img.dirty = true; 413 | return true; 414 | } 415 | 416 | bool ci_slideshow(arg_t _) 417 | { 418 | if (prefix > 0) { 419 | img.ss.on = true; 420 | img.ss.delay = prefix * 10; 421 | set_timeout(slideshow, img.ss.delay * 100, true); 422 | } else if (img.ss.on) { 423 | img.ss.on = false; 424 | reset_timeout(slideshow); 425 | } else { 426 | img.ss.on = true; 427 | } 428 | return true; 429 | } 430 | 431 | bool ct_move_sel(arg_t dir) 432 | { 433 | return tns_move_selection(&tns, dir, prefix); 434 | } 435 | 436 | bool ct_reload_all(arg_t _) 437 | { 438 | tns_free(&tns); 439 | tns_init(&tns, files, &filecnt, &fileidx, &win); 440 | tns.dirty = true; 441 | return true; 442 | } 443 | 444 | 445 | #undef G_CMD 446 | #define G_CMD(c) { -1, cg_##c }, 447 | #undef I_CMD 448 | #define I_CMD(c) { MODE_IMAGE, ci_##c }, 449 | #undef T_CMD 450 | #define T_CMD(c) { MODE_THUMB, ct_##c }, 451 | 452 | const cmd_t cmds[CMD_COUNT] = { 453 | #include "commands.lst" 454 | }; 455 | 456 | -------------------------------------------------------------------------------- /commands.lst: -------------------------------------------------------------------------------- 1 | G_CMD(quit) 2 | G_CMD(switch_mode) 3 | G_CMD(toggle_fullscreen) 4 | G_CMD(toggle_bar) 5 | G_CMD(prefix_external) 6 | G_CMD(reload_image) 7 | G_CMD(remove_image) 8 | G_CMD(first) 9 | G_CMD(n_or_last) 10 | G_CMD(scroll_screen) 11 | G_CMD(zoom) 12 | G_CMD(toggle_image_mark) 13 | G_CMD(reverse_marks) 14 | G_CMD(mark_range) 15 | G_CMD(unmark_all) 16 | G_CMD(navigate_marked) 17 | G_CMD(change_gamma) 18 | 19 | I_CMD(navigate) 20 | I_CMD(cursor_navigate) 21 | I_CMD(alternate) 22 | I_CMD(navigate_frame) 23 | I_CMD(toggle_animation) 24 | I_CMD(scroll) 25 | I_CMD(scroll_to_edge) 26 | I_CMD(drag) 27 | I_CMD(set_zoom) 28 | I_CMD(fit_to_win) 29 | I_CMD(rotate) 30 | I_CMD(flip) 31 | I_CMD(toggle_antialias) 32 | I_CMD(toggle_alpha) 33 | I_CMD(slideshow) 34 | 35 | T_CMD(move_sel) 36 | T_CMD(reload_all) 37 | 38 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | #ifdef _WINDOW_CONFIG 2 | 3 | /* default window dimensions (overwritten via -g option): */ 4 | enum { 5 | WIN_WIDTH = 800, 6 | WIN_HEIGHT = 600 7 | }; 8 | 9 | /* colors and font are configured with 'background', 'foreground' and 10 | * 'font' X resource properties. 11 | * See X(7) section Resources and xrdb(1) for more information. 12 | */ 13 | 14 | #endif 15 | #ifdef _IMAGE_CONFIG 16 | 17 | /* levels (in percent) to use when zooming via '-' and '+': 18 | * (first/last value is used as min/max zoom level) 19 | */ 20 | static const float zoom_levels[] = { 21 | 12.5, 25.0, 50.0, 75.0, 22 | 100.0, 150.0, 200.0, 400.0, 800.0 23 | }; 24 | 25 | /* default slideshow delay (in sec, overwritten via -S option): */ 26 | enum { SLIDESHOW_DELAY = 5 }; 27 | 28 | /* gamma correction: the user-visible ranges [-GAMMA_RANGE, 0] and 29 | * (0, GAMMA_RANGE] are mapped to the ranges [0, 1], and (1, GAMMA_MAX]. 30 | * */ 31 | static const double GAMMA_MAX = 10.0; 32 | static const int GAMMA_RANGE = 32; 33 | 34 | /* command i_scroll pans image 1/PAN_FRACTION of screen width/height */ 35 | static const int PAN_FRACTION = 5; 36 | 37 | /* if false, pixelate images at zoom level != 100%, 38 | * toggled with 'a' key binding 39 | */ 40 | static const bool ANTI_ALIAS = true; 41 | 42 | /* if true, use a checkerboard background for alpha layer, 43 | * toggled with 'A' key binding 44 | */ 45 | static const bool ALPHA_LAYER = false; 46 | 47 | #endif 48 | #ifdef _THUMBS_CONFIG 49 | 50 | /* thumbnail sizes in pixels (width == height): */ 51 | static const int thumb_sizes[] = { 32, 64, 96, 128, 160 }; 52 | 53 | /* thumbnail size at startup, index into thumb_sizes[]: */ 54 | static const int THUMB_SIZE = 3; 55 | 56 | #endif 57 | #ifdef _MAPPINGS_CONFIG 58 | 59 | /* keyboard mappings for image and thumbnail mode: */ 60 | static const keymap_t keys[] = { 61 | /* modifiers key function argument */ 62 | { 0, XK_q, g_quit, None }, 63 | { 0, XK_Return, g_switch_mode, None }, 64 | { 0, XK_f, g_toggle_fullscreen, None }, 65 | { 0, XK_b, g_toggle_bar, None }, 66 | { ControlMask, XK_x, g_prefix_external, None }, 67 | { 0, XK_g, g_first, None }, 68 | { 0, XK_G, g_n_or_last, None }, 69 | { 0, XK_r, g_reload_image, None }, 70 | { 0, XK_D, g_remove_image, None }, 71 | { ControlMask, XK_h, g_scroll_screen, DIR_LEFT }, 72 | { ControlMask, XK_Left, g_scroll_screen, DIR_LEFT }, 73 | { ControlMask, XK_j, g_scroll_screen, DIR_DOWN }, 74 | { ControlMask, XK_Down, g_scroll_screen, DIR_DOWN }, 75 | { ControlMask, XK_k, g_scroll_screen, DIR_UP }, 76 | { ControlMask, XK_Up, g_scroll_screen, DIR_UP }, 77 | { ControlMask, XK_l, g_scroll_screen, DIR_RIGHT }, 78 | { ControlMask, XK_Right, g_scroll_screen, DIR_RIGHT }, 79 | { 0, XK_plus, g_zoom, +1 }, 80 | { 0, XK_KP_Add, g_zoom, +1 }, 81 | { 0, XK_minus, g_zoom, -1 }, 82 | { 0, XK_KP_Subtract, g_zoom, -1 }, 83 | { 0, XK_m, g_toggle_image_mark, None }, 84 | { 0, XK_M, g_mark_range, None }, 85 | { ControlMask, XK_m, g_reverse_marks, None }, 86 | { ControlMask, XK_u, g_unmark_all, None }, 87 | { 0, XK_N, g_navigate_marked, +1 }, 88 | { 0, XK_P, g_navigate_marked, -1 }, 89 | { 0, XK_braceleft, g_change_gamma, -1 }, 90 | { 0, XK_braceright, g_change_gamma, +1 }, 91 | { ControlMask, XK_g, g_change_gamma, 0 }, 92 | 93 | { 0, XK_h, t_move_sel, DIR_LEFT }, 94 | { 0, XK_Left, t_move_sel, DIR_LEFT }, 95 | { 0, XK_j, t_move_sel, DIR_DOWN }, 96 | { 0, XK_Down, t_move_sel, DIR_DOWN }, 97 | { 0, XK_k, t_move_sel, DIR_UP }, 98 | { 0, XK_Up, t_move_sel, DIR_UP }, 99 | { 0, XK_l, t_move_sel, DIR_RIGHT }, 100 | { 0, XK_Right, t_move_sel, DIR_RIGHT }, 101 | { 0, XK_R, t_reload_all, None }, 102 | 103 | { 0, XK_n, i_navigate, +1 }, 104 | { 0, XK_n, i_scroll_to_edge, DIR_LEFT | DIR_UP }, 105 | { 0, XK_space, i_navigate, +1 }, 106 | { 0, XK_p, i_navigate, -1 }, 107 | { 0, XK_p, i_scroll_to_edge, DIR_LEFT | DIR_UP }, 108 | { 0, XK_BackSpace, i_navigate, -1 }, 109 | { 0, XK_bracketright, i_navigate, +10 }, 110 | { 0, XK_bracketleft, i_navigate, -10 }, 111 | { ControlMask, XK_6, i_alternate, None }, 112 | { ControlMask, XK_n, i_navigate_frame, +1 }, 113 | { ControlMask, XK_p, i_navigate_frame, -1 }, 114 | { ControlMask, XK_space, i_toggle_animation, None }, 115 | { 0, XK_h, i_scroll, DIR_LEFT }, 116 | { 0, XK_Left, i_scroll, DIR_LEFT }, 117 | { 0, XK_j, i_scroll, DIR_DOWN }, 118 | { 0, XK_Down, i_scroll, DIR_DOWN }, 119 | { 0, XK_k, i_scroll, DIR_UP }, 120 | { 0, XK_Up, i_scroll, DIR_UP }, 121 | { 0, XK_l, i_scroll, DIR_RIGHT }, 122 | { 0, XK_Right, i_scroll, DIR_RIGHT }, 123 | { 0, XK_H, i_scroll_to_edge, DIR_LEFT }, 124 | { 0, XK_J, i_scroll_to_edge, DIR_DOWN }, 125 | { 0, XK_K, i_scroll_to_edge, DIR_UP }, 126 | { 0, XK_L, i_scroll_to_edge, DIR_RIGHT }, 127 | { 0, XK_equal, i_set_zoom, 100 }, 128 | { 0, XK_w, i_fit_to_win, SCALE_DOWN }, 129 | { 0, XK_W, i_fit_to_win, SCALE_FIT }, 130 | { 0, XK_e, i_fit_to_win, SCALE_WIDTH }, 131 | { 0, XK_E, i_fit_to_win, SCALE_HEIGHT }, 132 | { 0, XK_less, i_rotate, DEGREE_270 }, 133 | { 0, XK_greater, i_rotate, DEGREE_90 }, 134 | { 0, XK_question, i_rotate, DEGREE_180 }, 135 | { 0, XK_bar, i_flip, FLIP_HORIZONTAL }, 136 | { 0, XK_underscore, i_flip, FLIP_VERTICAL }, 137 | { 0, XK_a, i_toggle_antialias, None }, 138 | { 0, XK_A, i_toggle_alpha, None }, 139 | { 0, XK_s, i_slideshow, None }, 140 | }; 141 | 142 | /* mouse button mappings for image mode: */ 143 | static const button_t buttons[] = { 144 | /* modifiers button function argument */ 145 | { 0, 1, i_cursor_navigate, None }, 146 | { 0, 2, i_drag, DRAG_ABSOLUTE }, 147 | { 0, 3, g_switch_mode, None }, 148 | { 0, 4, g_zoom, +1 }, 149 | { 0, 5, g_zoom, -1 }, 150 | }; 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /exec/image-info: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Example for $XDG_CONFIG_HOME/sxiv/exec/image-info 4 | # Called by sxiv(1) whenever an image gets loaded. 5 | # The output is displayed in sxiv's status bar. 6 | # Arguments: 7 | # $1: path to image file 8 | # $2: image width 9 | # $3: image height 10 | 11 | s=" " # field separator 12 | 13 | exec 2>/dev/null 14 | 15 | filename=$(basename -- "$1") 16 | filesize=$(du -Hh -- "$1" | cut -f 1) 17 | geometry="${2}x${3}" 18 | 19 | echo "${filesize}${s}${geometry}${s}${filename}" 20 | 21 | -------------------------------------------------------------------------------- /exec/key-handler: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Example for $XDG_CONFIG_HOME/sxiv/exec/key-handler 4 | # Called by sxiv(1) after the external prefix key (C-x by default) is pressed. 5 | # The next key combo is passed as its first argument. Passed via stdin are the 6 | # images to act upon, one path per line: all marked images, if in thumbnail 7 | # mode and at least one image has been marked, otherwise the current image. 8 | # sxiv(1) blocks until this script terminates. It then checks which images 9 | # have been modified and reloads them. 10 | 11 | # The key combo argument has the following form: "[C-][M-][S-]KEY", 12 | # where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X 13 | # keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. 14 | 15 | rotate() { 16 | degree="$1" 17 | tr '\n' '\0' | xargs -0 realpath | sort | uniq | while read file; do 18 | case "$(file -b -i "$file")" in 19 | image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "$file" "$file" ;; 20 | *) mogrify -rotate "$degree" "$file" ;; 21 | esac 22 | done 23 | } 24 | 25 | case "$1" in 26 | "C-x") xclip -in -filter | tr '\n' ' ' | xclip -in -selection clipboard ;; 27 | "C-c") while read file; do xclip -selection clipboard -target image/png "$file"; done ;; 28 | "C-e") while read file; do urxvt -bg "#444" -fg "#eee" -sl 0 -title "$file" -e sh -c "exiv2 pr -q -pa '$file' | less" & done ;; 29 | "C-g") tr '\n' '\0' | xargs -0 gimp & ;; 30 | "C-r") while read file; do rawtherapee "$file" & done ;; 31 | "C-comma") rotate 270 ;; 32 | "C-period") rotate 90 ;; 33 | "C-slash") rotate 180 ;; 34 | esac 35 | 36 | -------------------------------------------------------------------------------- /icon/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyb3rt/sxiv/b1e742e36572621655f65c7f90533fa3f357fdda/icon/128x128.png -------------------------------------------------------------------------------- /icon/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyb3rt/sxiv/b1e742e36572621655f65c7f90533fa3f357fdda/icon/16x16.png -------------------------------------------------------------------------------- /icon/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyb3rt/sxiv/b1e742e36572621655f65c7f90533fa3f357fdda/icon/32x32.png -------------------------------------------------------------------------------- /icon/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyb3rt/sxiv/b1e742e36572621655f65c7f90533fa3f357fdda/icon/48x48.png -------------------------------------------------------------------------------- /icon/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyb3rt/sxiv/b1e742e36572621655f65c7f90533fa3f357fdda/icon/64x64.png -------------------------------------------------------------------------------- /icon/Makefile: -------------------------------------------------------------------------------- 1 | PREFIX = /usr/local 2 | ICONS = 16x16.png 32x32.png 48x48.png 64x64.png 128x128.png 3 | 4 | all: 5 | 6 | install: 7 | for f in $(ICONS); do \ 8 | dir="$(DESTDIR)$(PREFIX)/share/icons/hicolor/$${f%.png}/apps"; \ 9 | mkdir -p "$$dir"; \ 10 | cp "$$f" "$$dir/sxiv.png"; \ 11 | chmod 644 "$$dir/sxiv.png"; \ 12 | done 13 | -------------------------------------------------------------------------------- /icon/dat2h.awk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/awk -f 2 | 3 | function printchars() { 4 | while (n > 0) { 5 | x = n / 16 >= 1 ? 16 : n; 6 | printf("0x%x%x,%s", x - 1, ref[c] - 1, ++i % 12 == 0 ? "\n" : " "); 7 | n -= x; 8 | } 9 | } 10 | 11 | /^$/ { 12 | printchars(); 13 | printf("\n\n"); 14 | c = ""; 15 | i = 0; 16 | } 17 | 18 | /./ { 19 | if (!ref[$0]) { 20 | col[cnt++] = $0; 21 | ref[$0] = cnt; 22 | } 23 | if ($0 != c) { 24 | if (c != "") 25 | printchars(); 26 | c = $0; 27 | n = 0; 28 | } 29 | n++; 30 | } 31 | 32 | END { 33 | for (i = 0; i < cnt; i++) 34 | printf("%s,%s", col[i], ++j % 4 == 0 || i + 1 == cnt ? "\n" : " "); 35 | } 36 | -------------------------------------------------------------------------------- /icon/data.h: -------------------------------------------------------------------------------- 1 | #ifndef ICON_DATA_H 2 | #define ICON_DATA_H 3 | 4 | typedef struct { 5 | unsigned int size; 6 | unsigned int cnt; 7 | const unsigned char *data; 8 | } icon_data_t; 9 | 10 | static const unsigned int icon_colors[] = { 11 | 0xff222034, 0xffffffff, 0xff306082, 0xff76428a, 12 | 0xfffbf236, 0xff99e550, 0xffd95763, 0xff37946e, 13 | 0xff6abe30, 0xffac3232 14 | }; 15 | 16 | static const unsigned char icon_data_16[] = { 17 | 0xf0, 0x80, 0x01, 0xf0, 0x80, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 18 | 0x00, 0x01, 0x40, 0x01, 0x10, 0x01, 0x10, 0x01, 0x00, 0x01, 0x00, 0x01, 19 | 0x30, 0x11, 0x00, 0x01, 0x00, 0x51, 0xf0, 0x22, 0xa0, 0x42, 0x80, 0x62, 20 | 0x03, 0x50, 0x02, 0x34, 0x05, 0x22, 0x13, 0x06, 0x10, 0x37, 0x08, 0x35, 21 | 0x12, 0x13, 0x09, 0x06, 0x22, 0x47, 0x25, 0x02, 0x13, 0x09, 0x06, 0x32, 22 | 0x47, 0x08, 0x05, 0x08, 0x03, 0x19, 0x16, 0x32, 0x47, 0x18, 0x29, 0x16, 23 | 0x42, 0x37, 0x18, 0x19, 0x26, 0x42, 0x47, 0x08 24 | }; 25 | 26 | static const unsigned char icon_data_32[] = { 27 | 0xf0, 0x10, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xf0, 0xf0, 28 | 0xf0, 0xf0, 0xf0, 0xf0, 0x10, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 29 | 0x10, 0x11, 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 30 | 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x90, 0x11, 31 | 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x00, 0x22, 0x50, 0x11, 32 | 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x50, 0x11, 0x30, 33 | 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x30, 0x31, 0x10, 0x11, 34 | 0x10, 0xb1, 0x52, 0x30, 0x31, 0x10, 0x11, 0x10, 0xb1, 0x52, 0x30, 0x31, 35 | 0x10, 0x11, 0x10, 0xb1, 0x52, 0xf0, 0x10, 0xd2, 0xf0, 0x00, 0xe2, 0xf0, 36 | 0x54, 0x92, 0x13, 0xc0, 0x84, 0x05, 0x62, 0x33, 0x80, 0x74, 0x55, 0x42, 37 | 0x33, 0x09, 0x02, 0x30, 0x77, 0x08, 0x85, 0x32, 0x33, 0x19, 0x12, 0xc7, 38 | 0x08, 0x65, 0x08, 0x12, 0x33, 0x19, 0x06, 0x52, 0x97, 0x08, 0x45, 0x18, 39 | 0x02, 0x33, 0x19, 0x16, 0x62, 0x97, 0x08, 0x35, 0x18, 0x23, 0x29, 0x16, 40 | 0x72, 0x97, 0x18, 0x15, 0x18, 0x23, 0x29, 0x26, 0x72, 0x97, 0x48, 0x13, 41 | 0x39, 0x26, 0x72, 0x97, 0x48, 0x03, 0x39, 0x36, 0x82, 0x97, 0x38, 0x49, 42 | 0x36, 0x82, 0x97, 0x38, 0x39, 0x46, 0x82, 0x97, 0x38, 0x29, 0x56, 0x92, 43 | 0x97, 0x28, 0x29, 0x56, 0x92, 0x97, 0x28 44 | }; 45 | 46 | static const unsigned char icon_data_48[] = { 47 | 0xf0, 0xa0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 48 | 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 49 | 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 50 | 0x20, 0x21, 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 51 | 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x81, 52 | 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 53 | 0x50, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, 54 | 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, 55 | 0x20, 0x21, 0x10, 0x32, 0x80, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, 56 | 0x20, 0x21, 0x52, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x00, 0x72, 57 | 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, 58 | 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, 59 | 0xf0, 0xf0, 0x00, 0xe2, 0xf0, 0xe0, 0xf2, 0x02, 0xf0, 0xc0, 0xf2, 0x22, 60 | 0xf0, 0xb0, 0xf2, 0x32, 0xf0, 0xa0, 0xf2, 0x42, 0xf0, 0x90, 0xf2, 0x52, 61 | 0xf0, 0x80, 0x74, 0x15, 0xc2, 0x03, 0xf0, 0x50, 0xc4, 0x15, 0x92, 0x23, 62 | 0xf0, 0x10, 0xe4, 0x35, 0x72, 0x33, 0x19, 0xc0, 0xc4, 0x85, 0x62, 0x43, 63 | 0x19, 0x12, 0x70, 0x57, 0x18, 0xf5, 0x05, 0x52, 0x53, 0x19, 0x12, 0x40, 64 | 0xb7, 0x18, 0xe5, 0x32, 0x53, 0x29, 0x22, 0xf7, 0x37, 0xb5, 0x08, 0x22, 65 | 0x53, 0x29, 0x06, 0x72, 0xf7, 0xa5, 0x08, 0x12, 0x53, 0x29, 0x16, 0x92, 66 | 0xe7, 0x85, 0x18, 0x02, 0x43, 0x39, 0x26, 0x92, 0xe7, 0x08, 0x65, 0x28, 67 | 0x43, 0x39, 0x26, 0xa2, 0xe7, 0x18, 0x45, 0x28, 0x43, 0x39, 0x36, 0xa2, 68 | 0xe7, 0x28, 0x25, 0x28, 0x33, 0x49, 0x36, 0xb2, 0xe7, 0x78, 0x33, 0x49, 69 | 0x46, 0xb2, 0xe7, 0x68, 0x23, 0x59, 0x46, 0xb2, 0xe7, 0x68, 0x13, 0x59, 70 | 0x56, 0xc2, 0xe7, 0x58, 0x79, 0x56, 0xc2, 0xe7, 0x58, 0x69, 0x66, 0xd2, 71 | 0xd7, 0x58, 0x59, 0x76, 0xd2, 0xd7, 0x58, 0x49, 0x86, 0xe2, 0xe7, 0x38, 72 | 0x39, 0x86, 0xf2, 0xe7, 0x38, 0x29, 0x86, 0xf2, 0x02, 0xe7, 0x38 73 | }; 74 | 75 | static const unsigned char icon_data_64[] = { 76 | 0xf0, 0xf0, 0x30, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 77 | 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 78 | 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 79 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 80 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 81 | 0x31, 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 82 | 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 83 | 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 84 | 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 85 | 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 86 | 0xf0, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x20, 87 | 0x42, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x00, 88 | 0x62, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 89 | 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, 90 | 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, 0x31, 91 | 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0x70, 0x71, 0x30, 92 | 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 93 | 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 94 | 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 95 | 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0xf0, 0xf0, 0x50, 96 | 0xf2, 0x92, 0xf0, 0xf0, 0x30, 0xf2, 0xb2, 0xf0, 0xf0, 0x20, 0xf2, 0xc2, 97 | 0xf0, 0xf0, 0x10, 0xf2, 0xd2, 0xf0, 0xf0, 0x00, 0x94, 0xf2, 0x42, 0xf0, 98 | 0xe0, 0xe4, 0xf2, 0x12, 0x23, 0xf0, 0xa0, 0xf4, 0x14, 0x05, 0xe2, 0x43, 99 | 0xf0, 0x70, 0xf4, 0x14, 0x35, 0xc2, 0x43, 0x19, 0xf0, 0x30, 0xf4, 0x95, 100 | 0xa2, 0x53, 0x29, 0xe0, 0xf4, 0x04, 0xd5, 0x82, 0x63, 0x29, 0x02, 0x90, 101 | 0xc7, 0xf5, 0x65, 0x62, 0x73, 0x29, 0x12, 0x50, 0xf7, 0x27, 0xf5, 0x35, 102 | 0x52, 0x73, 0x29, 0x06, 0x32, 0xf7, 0x87, 0xf5, 0x05, 0x08, 0x42, 0x73, 103 | 0x39, 0x06, 0x32, 0xf7, 0xa7, 0xd5, 0x28, 0x22, 0x73, 0x39, 0x16, 0xa2, 104 | 0xf7, 0x47, 0xb5, 0x38, 0x12, 0x73, 0x39, 0x26, 0xb2, 0xf7, 0x47, 0xa5, 105 | 0x38, 0x02, 0x73, 0x39, 0x26, 0xd2, 0xf7, 0x47, 0x08, 0x75, 0x48, 0x63, 106 | 0x49, 0x36, 0xd2, 0xf7, 0x47, 0x18, 0x65, 0x38, 0x63, 0x49, 0x36, 0xe2, 107 | 0xf7, 0x47, 0x28, 0x45, 0x38, 0x53, 0x59, 0x46, 0xe2, 0xf7, 0x47, 0x38, 108 | 0x15, 0x48, 0x53, 0x59, 0x46, 0xf2, 0xf7, 0x37, 0xa8, 0x43, 0x69, 0x56, 109 | 0xf2, 0xf7, 0x37, 0x98, 0x33, 0x79, 0x56, 0xf2, 0xf7, 0x37, 0x98, 0x23, 110 | 0x79, 0x66, 0xf2, 0x02, 0xf7, 0x37, 0x88, 0x13, 0x89, 0x66, 0xf2, 0x02, 111 | 0xf7, 0x37, 0x88, 0x03, 0x89, 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x99, 112 | 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x89, 0x86, 0xf2, 0x12, 0xf7, 0x37, 113 | 0x78, 0x79, 0x96, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, 114 | 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x59, 0xb6, 115 | 0xf2, 0x32, 0xf7, 0x37, 0x58, 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58, 116 | 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58 117 | }; 118 | 119 | static const unsigned char icon_data_128[] = { 120 | 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 121 | 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 122 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 123 | 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 124 | 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 125 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 126 | 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 127 | 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 128 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 129 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 130 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 131 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 132 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 133 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 134 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 135 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 136 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 137 | 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 138 | 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 139 | 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 140 | 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 141 | 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 142 | 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 143 | 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 144 | 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 145 | 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 146 | 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 147 | 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 148 | 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 149 | 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xa0, 150 | 0x42, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, 0x71, 151 | 0x60, 0x82, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, 152 | 0x71, 0x40, 0xa2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 153 | 0x70, 0x71, 0x20, 0xc2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 154 | 0x71, 0x70, 0x71, 0x00, 0xe2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 155 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 156 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 157 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 158 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 159 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 160 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 161 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 162 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 163 | 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 164 | 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 165 | 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 166 | 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 167 | 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 168 | 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 169 | 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 170 | 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 171 | 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 172 | 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0xf2, 0xf2, 173 | 0xf2, 0x42, 0xf0, 0xf0, 0xf0, 0xf0, 0x80, 0xf2, 0xf2, 0xf2, 0x62, 0xf0, 174 | 0xf0, 0xf0, 0xf0, 0x70, 0xf2, 0xf2, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, 175 | 0x60, 0xf2, 0xf2, 0xf2, 0x82, 0xf0, 0xf0, 0xf0, 0xf0, 0x50, 0xf2, 0xf2, 176 | 0xf2, 0x92, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf2, 0xf2, 0xf2, 0xa2, 0xf0, 177 | 0xf0, 0xf0, 0xf0, 0x30, 0xf2, 0xf2, 0xf2, 0xb2, 0xf0, 0xf0, 0xf0, 0xf0, 178 | 0x30, 0x12, 0xa4, 0xf2, 0xf2, 0xe2, 0xf0, 0xf0, 0xf0, 0xf0, 0x20, 0xf4, 179 | 0x14, 0xf2, 0xf2, 0xa2, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0xf4, 0x64, 0xf2, 180 | 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xe0, 0xf4, 0xb4, 0xf2, 0xf2, 0x42, 0xf0, 181 | 0xf0, 0xf0, 0xd0, 0xf4, 0xd4, 0x15, 0xf2, 0xf2, 0x12, 0x43, 0xf0, 0xf0, 182 | 0xf0, 0x60, 0xf4, 0xf4, 0x04, 0x35, 0xf2, 0xe2, 0x63, 0xf0, 0xf0, 0xf0, 183 | 0x30, 0xf4, 0xf4, 0x24, 0x45, 0xf2, 0xc2, 0x83, 0xf0, 0xf0, 0xf0, 0x00, 184 | 0xf4, 0xf4, 0x34, 0x65, 0xf2, 0xa2, 0x93, 0xf0, 0xf0, 0xe0, 0xf4, 0xf4, 185 | 0x34, 0x95, 0xf2, 0x82, 0xa3, 0x19, 0xf0, 0xf0, 0x90, 0xf4, 0xf4, 0x44, 186 | 0xc5, 0xf2, 0x62, 0xb3, 0x29, 0xf0, 0xf0, 0x40, 0xf4, 0xf4, 0x64, 0xf5, 187 | 0xf2, 0x42, 0xc3, 0x39, 0xf0, 0xf0, 0xf4, 0xf4, 0x74, 0xf5, 0x35, 0xf2, 188 | 0x22, 0xd3, 0x49, 0xf0, 0xa0, 0xf4, 0xf4, 0x84, 0xf5, 0x75, 0xf2, 0x02, 189 | 0xd3, 0x59, 0x02, 0xf0, 0x50, 0xf7, 0x07, 0xf4, 0x74, 0xf5, 0xb5, 0x08, 190 | 0xe2, 0xe3, 0x59, 0x12, 0xf0, 0x10, 0xf7, 0xd7, 0xf5, 0xf5, 0x95, 0x18, 191 | 0xc2, 0xe3, 0x59, 0x06, 0x22, 0xd0, 0xf7, 0xf7, 0x37, 0xf5, 0xf5, 0x65, 192 | 0x18, 0xb2, 0xf3, 0x59, 0x06, 0x32, 0x80, 0xf7, 0xf7, 0x97, 0xf5, 0xf5, 193 | 0x45, 0x18, 0xa2, 0xf3, 0x59, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x07, 0xf5, 194 | 0xf5, 0x25, 0x28, 0x82, 0xf3, 0x69, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x27, 195 | 0xf5, 0xf5, 0x05, 0x38, 0x62, 0xf3, 0x69, 0x26, 0x82, 0xf7, 0xf7, 0xf7, 196 | 0x37, 0xf5, 0xd5, 0x48, 0x52, 0xf3, 0x79, 0x26, 0xc2, 0xf7, 0xf7, 0xf7, 197 | 0x07, 0xf5, 0xb5, 0x58, 0x42, 0xf3, 0x79, 0x36, 0xf2, 0x22, 0xf7, 0xf7, 198 | 0xb7, 0xf5, 0xa5, 0x58, 0x32, 0xf3, 0x79, 0x46, 0xf2, 0x52, 0xf7, 0xf7, 199 | 0x97, 0xf5, 0x85, 0x68, 0x22, 0xf3, 0x89, 0x36, 0xf2, 0x72, 0xf7, 0xf7, 200 | 0x97, 0xf5, 0x65, 0x78, 0x12, 0xf3, 0x89, 0x46, 0xf2, 0x82, 0xf7, 0xf7, 201 | 0x97, 0xf5, 0x55, 0x78, 0x02, 0xf3, 0x89, 0x46, 0xf2, 0xa2, 0xf7, 0xf7, 202 | 0x87, 0x08, 0xf5, 0x35, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xb2, 0xf7, 0xf7, 203 | 0x77, 0x08, 0xf5, 0x25, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xc2, 0xf7, 0xf7, 204 | 0x77, 0x18, 0xf5, 0x05, 0x88, 0xd3, 0xa9, 0x66, 0xf2, 0xc2, 0xf7, 0xf7, 205 | 0x77, 0x28, 0xf5, 0x78, 0xd3, 0xa9, 0x66, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, 206 | 0x38, 0xd5, 0x78, 0xc3, 0xb9, 0x76, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, 0x48, 207 | 0xa5, 0x88, 0xc3, 0xb9, 0x76, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x58, 0x85, 208 | 0x88, 0xb3, 0xc9, 0x86, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x68, 0x55, 0x98, 209 | 0xb3, 0xc9, 0x86, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0x78, 0x25, 0xa8, 0xa3, 210 | 0xc9, 0xa6, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x93, 0xd9, 0xa6, 211 | 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x83, 0xe9, 0xb6, 0xf2, 0xf2, 212 | 0xf7, 0xf7, 0x77, 0xf8, 0x38, 0x73, 0xf9, 0xb6, 0xf2, 0xf2, 0xf7, 0xf7, 213 | 0x77, 0xf8, 0x38, 0x63, 0xf9, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, 214 | 0xf8, 0x28, 0x53, 0xf9, 0x09, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, 215 | 0xf8, 0x28, 0x43, 0xf9, 0x09, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, 216 | 0xf8, 0x18, 0x33, 0xf9, 0x19, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, 217 | 0xf8, 0x18, 0x23, 0xf9, 0x19, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, 218 | 0xf8, 0x08, 0x13, 0xf9, 0x29, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, 219 | 0xf8, 0x08, 0x03, 0xf9, 0x29, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 220 | 0x77, 0xf8, 0xf9, 0x39, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 221 | 0xf8, 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 0xf8, 222 | 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 223 | 0x19, 0xf6, 0x26, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0x09, 224 | 0xf6, 0x36, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0xf6, 0x56, 225 | 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xe9, 0xf6, 0x66, 0xf2, 0xf2, 226 | 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x32, 0xf7, 227 | 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 228 | 0xc8, 0xc9, 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xc9, 229 | 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xb9, 0xf6, 0x96, 230 | 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 231 | 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 232 | 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 233 | 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8 234 | }; 235 | 236 | #define ICON_(s) { s, ARRLEN(icon_data_##s), icon_data_##s } 237 | 238 | static const icon_data_t icons[] = { 239 | ICON_(16), 240 | ICON_(32), 241 | ICON_(48), 242 | ICON_(64), 243 | ICON_(128) 244 | }; 245 | 246 | #endif /* ICON_DATA_H */ 247 | 248 | -------------------------------------------------------------------------------- /image.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2011, 2012 Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | #define _IMAGE_CONFIG 21 | #include "config.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #if HAVE_LIBEXIF 31 | #include 32 | #endif 33 | 34 | #if HAVE_GIFLIB 35 | #include 36 | enum { DEF_GIF_DELAY = 75 }; 37 | #endif 38 | 39 | float zoom_min; 40 | float zoom_max; 41 | 42 | static int zoomdiff(img_t *img, float z) 43 | { 44 | return (int) ((img->w * z - img->w * img->zoom) + (img->h * z - img->h * img->zoom)); 45 | } 46 | 47 | void img_init(img_t *img, win_t *win) 48 | { 49 | zoom_min = zoom_levels[0] / 100.0; 50 | zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0; 51 | 52 | imlib_context_set_display(win->env.dpy); 53 | imlib_context_set_visual(win->env.vis); 54 | imlib_context_set_colormap(win->env.cmap); 55 | 56 | img->im = NULL; 57 | img->win = win; 58 | img->scalemode = options->scalemode; 59 | img->zoom = options->zoom; 60 | img->zoom = MAX(img->zoom, zoom_min); 61 | img->zoom = MIN(img->zoom, zoom_max); 62 | img->checkpan = false; 63 | img->dirty = false; 64 | img->aa = ANTI_ALIAS; 65 | img->alpha = ALPHA_LAYER; 66 | img->multi.cap = img->multi.cnt = 0; 67 | img->multi.animate = options->animate; 68 | img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0; 69 | img->multi.length = 0; 70 | 71 | img->cmod = imlib_create_color_modifier(); 72 | imlib_context_set_color_modifier(img->cmod); 73 | img->gamma = MIN(MAX(options->gamma, -GAMMA_RANGE), GAMMA_RANGE); 74 | 75 | img->ss.on = options->slideshow > 0; 76 | img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10; 77 | } 78 | 79 | #if HAVE_LIBEXIF 80 | void exif_auto_orientate(const fileinfo_t *file) 81 | { 82 | ExifData *ed; 83 | ExifEntry *entry; 84 | int byte_order, orientation = 0; 85 | 86 | if ((ed = exif_data_new_from_file(file->path)) == NULL) 87 | return; 88 | byte_order = exif_data_get_byte_order(ed); 89 | entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); 90 | if (entry != NULL) 91 | orientation = exif_get_short(entry->data, byte_order); 92 | exif_data_unref(ed); 93 | 94 | switch (orientation) { 95 | case 5: 96 | imlib_image_orientate(1); 97 | case 2: 98 | imlib_image_flip_vertical(); 99 | break; 100 | case 3: 101 | imlib_image_orientate(2); 102 | break; 103 | case 7: 104 | imlib_image_orientate(1); 105 | case 4: 106 | imlib_image_flip_horizontal(); 107 | break; 108 | case 6: 109 | imlib_image_orientate(1); 110 | break; 111 | case 8: 112 | imlib_image_orientate(3); 113 | break; 114 | } 115 | } 116 | #endif 117 | 118 | #if HAVE_GIFLIB 119 | bool img_load_gif(img_t *img, const fileinfo_t *file) 120 | { 121 | GifFileType *gif; 122 | GifRowType *rows = NULL; 123 | GifRecordType rec; 124 | ColorMapObject *cmap; 125 | DATA32 bgpixel, *data, *ptr; 126 | DATA32 *prev_frame = NULL; 127 | Imlib_Image im; 128 | int i, j, bg, r, g, b; 129 | int x, y, w, h, sw, sh; 130 | int px, py, pw, ph; 131 | int intoffset[] = { 0, 4, 2, 1 }; 132 | int intjump[] = { 8, 8, 4, 2 }; 133 | int transp = -1; 134 | unsigned int disposal = 0, prev_disposal = 0; 135 | unsigned int delay = 0; 136 | bool err = false; 137 | 138 | if (img->multi.cap == 0) { 139 | img->multi.cap = 8; 140 | img->multi.frames = (img_frame_t*) 141 | emalloc(sizeof(img_frame_t) * img->multi.cap); 142 | } 143 | img->multi.cnt = img->multi.sel = 0; 144 | img->multi.length = 0; 145 | 146 | #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 147 | gif = DGifOpenFileName(file->path, NULL); 148 | #else 149 | gif = DGifOpenFileName(file->path); 150 | #endif 151 | if (gif == NULL) { 152 | error(0, 0, "%s: Error opening gif image", file->name); 153 | return false; 154 | } 155 | bg = gif->SBackGroundColor; 156 | sw = gif->SWidth; 157 | sh = gif->SHeight; 158 | px = py = pw = ph = 0; 159 | 160 | do { 161 | if (DGifGetRecordType(gif, &rec) == GIF_ERROR) { 162 | err = true; 163 | break; 164 | } 165 | if (rec == EXTENSION_RECORD_TYPE) { 166 | int ext_code; 167 | GifByteType *ext = NULL; 168 | 169 | DGifGetExtension(gif, &ext_code, &ext); 170 | while (ext) { 171 | if (ext_code == GRAPHICS_EXT_FUNC_CODE) { 172 | if (ext[1] & 1) 173 | transp = (int) ext[4]; 174 | else 175 | transp = -1; 176 | 177 | delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]); 178 | disposal = (unsigned int) ext[1] >> 2 & 0x7; 179 | } 180 | ext = NULL; 181 | DGifGetExtensionNext(gif, &ext); 182 | } 183 | } else if (rec == IMAGE_DESC_RECORD_TYPE) { 184 | if (DGifGetImageDesc(gif) == GIF_ERROR) { 185 | err = true; 186 | break; 187 | } 188 | x = gif->Image.Left; 189 | y = gif->Image.Top; 190 | w = gif->Image.Width; 191 | h = gif->Image.Height; 192 | 193 | rows = (GifRowType*) emalloc(h * sizeof(GifRowType)); 194 | for (i = 0; i < h; i++) 195 | rows[i] = (GifRowType) emalloc(w * sizeof(GifPixelType)); 196 | if (gif->Image.Interlace) { 197 | for (i = 0; i < 4; i++) { 198 | for (j = intoffset[i]; j < h; j += intjump[i]) 199 | DGifGetLine(gif, rows[j], w); 200 | } 201 | } else { 202 | for (i = 0; i < h; i++) 203 | DGifGetLine(gif, rows[i], w); 204 | } 205 | 206 | ptr = data = (DATA32*) emalloc(sizeof(DATA32) * sw * sh); 207 | cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; 208 | r = cmap->Colors[bg].Red; 209 | g = cmap->Colors[bg].Green; 210 | b = cmap->Colors[bg].Blue; 211 | bgpixel = 0x00ffffff & (r << 16 | g << 8 | b); 212 | 213 | for (i = 0; i < sh; i++) { 214 | for (j = 0; j < sw; j++) { 215 | if (i < y || i >= y + h || j < x || j >= x + w || 216 | rows[i-y][j-x] == transp) 217 | { 218 | if (prev_frame != NULL && (prev_disposal != 2 || 219 | i < py || i >= py + ph || j < px || j >= px + pw)) 220 | { 221 | *ptr = prev_frame[i * sw + j]; 222 | } else { 223 | *ptr = bgpixel; 224 | } 225 | } else { 226 | r = cmap->Colors[rows[i-y][j-x]].Red; 227 | g = cmap->Colors[rows[i-y][j-x]].Green; 228 | b = cmap->Colors[rows[i-y][j-x]].Blue; 229 | *ptr = 0xffu << 24 | r << 16 | g << 8 | b; 230 | } 231 | ptr++; 232 | } 233 | } 234 | 235 | im = imlib_create_image_using_copied_data(sw, sh, data); 236 | 237 | for (i = 0; i < h; i++) 238 | free(rows[i]); 239 | free(rows); 240 | free(data); 241 | 242 | if (im == NULL) { 243 | err = true; 244 | break; 245 | } 246 | 247 | imlib_context_set_image(im); 248 | imlib_image_set_format("gif"); 249 | if (transp >= 0) 250 | imlib_image_set_has_alpha(1); 251 | 252 | if (disposal != 3) 253 | prev_frame = imlib_image_get_data_for_reading_only(); 254 | prev_disposal = disposal; 255 | px = x, py = y, pw = w, ph = h; 256 | 257 | if (img->multi.cnt == img->multi.cap) { 258 | img->multi.cap *= 2; 259 | img->multi.frames = (img_frame_t*) 260 | erealloc(img->multi.frames, 261 | img->multi.cap * sizeof(img_frame_t)); 262 | } 263 | img->multi.frames[img->multi.cnt].im = im; 264 | delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay; 265 | img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY; 266 | img->multi.length += img->multi.frames[img->multi.cnt].delay; 267 | img->multi.cnt++; 268 | } 269 | } while (rec != TERMINATE_RECORD_TYPE); 270 | 271 | #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1 272 | DGifCloseFile(gif, NULL); 273 | #else 274 | DGifCloseFile(gif); 275 | #endif 276 | 277 | if (err && (file->flags & FF_WARN)) 278 | error(0, 0, "%s: Corrupted gif file", file->name); 279 | 280 | if (img->multi.cnt > 1) { 281 | imlib_context_set_image(img->im); 282 | imlib_free_image(); 283 | img->im = img->multi.frames[0].im; 284 | } else if (img->multi.cnt == 1) { 285 | imlib_context_set_image(img->multi.frames[0].im); 286 | imlib_free_image(); 287 | img->multi.cnt = 0; 288 | } 289 | 290 | imlib_context_set_image(img->im); 291 | 292 | return !err; 293 | } 294 | #endif /* HAVE_GIFLIB */ 295 | 296 | Imlib_Image img_open(const fileinfo_t *file) 297 | { 298 | struct stat st; 299 | Imlib_Image im = NULL; 300 | 301 | if (access(file->path, R_OK) == 0 && 302 | stat(file->path, &st) == 0 && S_ISREG(st.st_mode)) 303 | { 304 | im = imlib_load_image(file->path); 305 | if (im != NULL) { 306 | imlib_context_set_image(im); 307 | if (imlib_image_get_data_for_reading_only() == NULL) { 308 | imlib_free_image(); 309 | im = NULL; 310 | } 311 | } 312 | } 313 | if (im == NULL && (file->flags & FF_WARN)) 314 | error(0, 0, "%s: Error opening image", file->name); 315 | return im; 316 | } 317 | 318 | bool img_load(img_t *img, const fileinfo_t *file) 319 | { 320 | const char *fmt; 321 | 322 | if ((img->im = img_open(file)) == NULL) 323 | return false; 324 | 325 | imlib_image_set_changes_on_disk(); 326 | 327 | #if HAVE_LIBEXIF 328 | exif_auto_orientate(file); 329 | #endif 330 | 331 | if ((fmt = imlib_image_format()) != NULL) { 332 | #if HAVE_GIFLIB 333 | if (STREQ(fmt, "gif")) 334 | img_load_gif(img, file); 335 | #endif 336 | } 337 | img->w = imlib_image_get_width(); 338 | img->h = imlib_image_get_height(); 339 | img->checkpan = true; 340 | img->dirty = true; 341 | 342 | return true; 343 | } 344 | 345 | CLEANUP void img_close(img_t *img, bool decache) 346 | { 347 | int i; 348 | 349 | if (img->multi.cnt > 0) { 350 | for (i = 0; i < img->multi.cnt; i++) { 351 | imlib_context_set_image(img->multi.frames[i].im); 352 | imlib_free_image(); 353 | } 354 | img->multi.cnt = 0; 355 | img->im = NULL; 356 | } else if (img->im != NULL) { 357 | imlib_context_set_image(img->im); 358 | if (decache) 359 | imlib_free_image_and_decache(); 360 | else 361 | imlib_free_image(); 362 | img->im = NULL; 363 | } 364 | } 365 | 366 | void img_check_pan(img_t *img, bool moved) 367 | { 368 | win_t *win; 369 | float w, h, ox, oy; 370 | 371 | win = img->win; 372 | w = img->w * img->zoom; 373 | h = img->h * img->zoom; 374 | ox = img->x; 375 | oy = img->y; 376 | 377 | if (w < win->w) 378 | img->x = (win->w - w) / 2; 379 | else if (img->x > 0) 380 | img->x = 0; 381 | else if (img->x + w < win->w) 382 | img->x = win->w - w; 383 | if (h < win->h) 384 | img->y = (win->h - h) / 2; 385 | else if (img->y > 0) 386 | img->y = 0; 387 | else if (img->y + h < win->h) 388 | img->y = win->h - h; 389 | 390 | if (!moved && (ox != img->x || oy != img->y)) 391 | img->dirty = true; 392 | } 393 | 394 | bool img_fit(img_t *img) 395 | { 396 | float z, zw, zh; 397 | 398 | if (img->scalemode == SCALE_ZOOM) 399 | return false; 400 | 401 | zw = (float) img->win->w / (float) img->w; 402 | zh = (float) img->win->h / (float) img->h; 403 | 404 | switch (img->scalemode) { 405 | case SCALE_WIDTH: 406 | z = zw; 407 | break; 408 | case SCALE_HEIGHT: 409 | z = zh; 410 | break; 411 | default: 412 | z = MIN(zw, zh); 413 | break; 414 | } 415 | z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : zoom_max); 416 | 417 | if (zoomdiff(img, z) != 0) { 418 | img->zoom = z; 419 | img->dirty = true; 420 | return true; 421 | } else { 422 | return false; 423 | } 424 | } 425 | 426 | void img_render(img_t *img) 427 | { 428 | win_t *win; 429 | int sx, sy, sw, sh; 430 | int dx, dy, dw, dh; 431 | Imlib_Image bg; 432 | unsigned long c; 433 | 434 | win = img->win; 435 | img_fit(img); 436 | 437 | if (img->checkpan) { 438 | img_check_pan(img, false); 439 | img->checkpan = false; 440 | } 441 | 442 | if (!img->dirty) 443 | return; 444 | 445 | /* calculate source and destination offsets: 446 | * - part of image drawn on full window, or 447 | * - full image drawn on part of window 448 | */ 449 | if (img->x <= 0) { 450 | sx = -img->x / img->zoom + 0.5; 451 | sw = win->w / img->zoom; 452 | dx = 0; 453 | dw = win->w; 454 | } else { 455 | sx = 0; 456 | sw = img->w; 457 | dx = img->x; 458 | dw = img->w * img->zoom; 459 | } 460 | if (img->y <= 0) { 461 | sy = -img->y / img->zoom + 0.5; 462 | sh = win->h / img->zoom; 463 | dy = 0; 464 | dh = win->h; 465 | } else { 466 | sy = 0; 467 | sh = img->h; 468 | dy = img->y; 469 | dh = img->h * img->zoom; 470 | } 471 | 472 | win_clear(win); 473 | 474 | imlib_context_set_image(img->im); 475 | imlib_context_set_anti_alias(img->aa); 476 | imlib_context_set_drawable(win->buf.pm); 477 | 478 | if (imlib_image_has_alpha()) { 479 | if ((bg = imlib_create_image(dw, dh)) == NULL) 480 | error(EXIT_FAILURE, ENOMEM, NULL); 481 | imlib_context_set_image(bg); 482 | imlib_image_set_has_alpha(0); 483 | 484 | if (img->alpha) { 485 | int i, c, r; 486 | DATA32 col[2] = { 0xFF666666, 0xFF999999 }; 487 | DATA32 * data = imlib_image_get_data(); 488 | 489 | for (r = 0; r < dh; r++) { 490 | i = r * dw; 491 | if (r == 0 || r == 8) { 492 | for (c = 0; c < dw; c++) 493 | data[i++] = col[!(c & 8) ^ !r]; 494 | } else { 495 | memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0])); 496 | } 497 | } 498 | imlib_image_put_back_data(data); 499 | } else { 500 | c = win->bg.pixel; 501 | imlib_context_set_color(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF, 0xFF); 502 | imlib_image_fill_rectangle(0, 0, dw, dh); 503 | } 504 | imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh); 505 | imlib_context_set_color_modifier(NULL); 506 | imlib_render_image_on_drawable(dx, dy); 507 | imlib_free_image(); 508 | imlib_context_set_color_modifier(img->cmod); 509 | } else { 510 | imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh); 511 | } 512 | img->dirty = false; 513 | } 514 | 515 | bool img_fit_win(img_t *img, scalemode_t sm) 516 | { 517 | float oz; 518 | 519 | oz = img->zoom; 520 | img->scalemode = sm; 521 | 522 | if (img_fit(img)) { 523 | img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz; 524 | img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz; 525 | img->checkpan = true; 526 | return true; 527 | } else { 528 | return false; 529 | } 530 | } 531 | 532 | bool img_zoom(img_t *img, float z) 533 | { 534 | z = MAX(z, zoom_min); 535 | z = MIN(z, zoom_max); 536 | 537 | img->scalemode = SCALE_ZOOM; 538 | 539 | if (zoomdiff(img, z) != 0) { 540 | int x, y; 541 | 542 | win_cursor_pos(img->win, &x, &y); 543 | if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) { 544 | x = img->win->w / 2; 545 | y = img->win->h / 2; 546 | } 547 | img->x = x - (x - img->x) * z / img->zoom; 548 | img->y = y - (y - img->y) * z / img->zoom; 549 | img->zoom = z; 550 | img->checkpan = true; 551 | img->dirty = true; 552 | return true; 553 | } else { 554 | return false; 555 | } 556 | } 557 | 558 | bool img_zoom_in(img_t *img) 559 | { 560 | int i; 561 | float z; 562 | 563 | for (i = 0; i < ARRLEN(zoom_levels); i++) { 564 | z = zoom_levels[i] / 100.0; 565 | if (zoomdiff(img, z) > 0) 566 | return img_zoom(img, z); 567 | } 568 | return false; 569 | } 570 | 571 | bool img_zoom_out(img_t *img) 572 | { 573 | int i; 574 | float z; 575 | 576 | for (i = ARRLEN(zoom_levels) - 1; i >= 0; i--) { 577 | z = zoom_levels[i] / 100.0; 578 | if (zoomdiff(img, z) < 0) 579 | return img_zoom(img, z); 580 | } 581 | return false; 582 | } 583 | 584 | bool img_pos(img_t *img, float x, float y) 585 | { 586 | float ox, oy; 587 | 588 | ox = img->x; 589 | oy = img->y; 590 | 591 | img->x = x; 592 | img->y = y; 593 | 594 | img_check_pan(img, true); 595 | 596 | if (ox != img->x || oy != img->y) { 597 | img->dirty = true; 598 | return true; 599 | } else { 600 | return false; 601 | } 602 | } 603 | 604 | bool img_move(img_t *img, float dx, float dy) 605 | { 606 | return img_pos(img, img->x + dx, img->y + dy); 607 | } 608 | 609 | bool img_pan(img_t *img, direction_t dir, int d) 610 | { 611 | /* d < 0: screen-wise 612 | * d = 0: 1/PAN_FRACTION of screen 613 | * d > 0: num of pixels 614 | */ 615 | float x, y; 616 | 617 | if (d > 0) { 618 | x = y = MAX(1, (float) d * img->zoom); 619 | } else { 620 | x = img->win->w / (d < 0 ? 1 : PAN_FRACTION); 621 | y = img->win->h / (d < 0 ? 1 : PAN_FRACTION); 622 | } 623 | 624 | switch (dir) { 625 | case DIR_LEFT: 626 | return img_move(img, x, 0.0); 627 | case DIR_RIGHT: 628 | return img_move(img, -x, 0.0); 629 | case DIR_UP: 630 | return img_move(img, 0.0, y); 631 | case DIR_DOWN: 632 | return img_move(img, 0.0, -y); 633 | } 634 | return false; 635 | } 636 | 637 | bool img_pan_edge(img_t *img, direction_t dir) 638 | { 639 | float ox, oy; 640 | 641 | ox = img->x; 642 | oy = img->y; 643 | 644 | if (dir & DIR_LEFT) 645 | img->x = 0; 646 | if (dir & DIR_RIGHT) 647 | img->x = img->win->w - img->w * img->zoom; 648 | if (dir & DIR_UP) 649 | img->y = 0; 650 | if (dir & DIR_DOWN) 651 | img->y = img->win->h - img->h * img->zoom; 652 | 653 | img_check_pan(img, true); 654 | 655 | if (ox != img->x || oy != img->y) { 656 | img->dirty = true; 657 | return true; 658 | } else { 659 | return false; 660 | } 661 | } 662 | 663 | void img_rotate(img_t *img, degree_t d) 664 | { 665 | int i, tmp; 666 | float ox, oy; 667 | 668 | imlib_context_set_image(img->im); 669 | imlib_image_orientate(d); 670 | 671 | for (i = 0; i < img->multi.cnt; i++) { 672 | if (i != img->multi.sel) { 673 | imlib_context_set_image(img->multi.frames[i].im); 674 | imlib_image_orientate(d); 675 | } 676 | } 677 | if (d == DEGREE_90 || d == DEGREE_270) { 678 | ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom; 679 | oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom; 680 | 681 | img->x = oy + (img->win->w - img->win->h) / 2; 682 | img->y = ox + (img->win->h - img->win->w) / 2; 683 | 684 | tmp = img->w; 685 | img->w = img->h; 686 | img->h = tmp; 687 | img->checkpan = true; 688 | } 689 | img->dirty = true; 690 | } 691 | 692 | void img_flip(img_t *img, flipdir_t d) 693 | { 694 | int i; 695 | void (*imlib_flip_op[3])(void) = { 696 | imlib_image_flip_horizontal, 697 | imlib_image_flip_vertical, 698 | imlib_image_flip_diagonal 699 | }; 700 | 701 | d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1; 702 | 703 | if (d < 0 || d >= ARRLEN(imlib_flip_op)) 704 | return; 705 | 706 | imlib_context_set_image(img->im); 707 | imlib_flip_op[d](); 708 | 709 | for (i = 0; i < img->multi.cnt; i++) { 710 | if (i != img->multi.sel) { 711 | imlib_context_set_image(img->multi.frames[i].im); 712 | imlib_flip_op[d](); 713 | } 714 | } 715 | img->dirty = true; 716 | } 717 | 718 | void img_toggle_antialias(img_t *img) 719 | { 720 | img->aa = !img->aa; 721 | imlib_context_set_image(img->im); 722 | imlib_context_set_anti_alias(img->aa); 723 | img->dirty = true; 724 | } 725 | 726 | bool img_change_gamma(img_t *img, int d) 727 | { 728 | /* d < 0: decrease gamma 729 | * d = 0: reset gamma 730 | * d > 0: increase gamma 731 | */ 732 | int gamma; 733 | double range; 734 | 735 | if (d == 0) 736 | gamma = 0; 737 | else 738 | gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE); 739 | 740 | if (img->gamma != gamma) { 741 | imlib_reset_color_modifier(); 742 | if (gamma != 0) { 743 | range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0; 744 | imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE)); 745 | } 746 | img->gamma = gamma; 747 | img->dirty = true; 748 | return true; 749 | } else { 750 | return false; 751 | } 752 | } 753 | 754 | bool img_frame_goto(img_t *img, int n) 755 | { 756 | if (n < 0 || n >= img->multi.cnt || n == img->multi.sel) 757 | return false; 758 | 759 | img->multi.sel = n; 760 | img->im = img->multi.frames[n].im; 761 | 762 | imlib_context_set_image(img->im); 763 | img->w = imlib_image_get_width(); 764 | img->h = imlib_image_get_height(); 765 | img->checkpan = true; 766 | img->dirty = true; 767 | 768 | return true; 769 | } 770 | 771 | bool img_frame_navigate(img_t *img, int d) 772 | { 773 | if (img->multi.cnt == 0 || d == 0) 774 | return false; 775 | 776 | d += img->multi.sel; 777 | if (d < 0) 778 | d = 0; 779 | else if (d >= img->multi.cnt) 780 | d = img->multi.cnt - 1; 781 | 782 | return img_frame_goto(img, d); 783 | } 784 | 785 | bool img_frame_animate(img_t *img) 786 | { 787 | if (img->multi.cnt == 0) 788 | return false; 789 | 790 | if (img->multi.sel + 1 >= img->multi.cnt) 791 | img_frame_goto(img, 0); 792 | else 793 | img_frame_goto(img, img->multi.sel + 1); 794 | img->dirty = true; 795 | return true; 796 | } 797 | 798 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2011-2013 Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | #define _MAPPINGS_CONFIG 21 | #include "config.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | typedef struct { 38 | struct timeval when; 39 | bool active; 40 | timeout_f handler; 41 | } timeout_t; 42 | 43 | /* timeout handler functions: */ 44 | void redraw(void); 45 | void reset_cursor(void); 46 | void animate(void); 47 | void slideshow(void); 48 | void clear_resize(void); 49 | 50 | appmode_t mode; 51 | arl_t arl; 52 | img_t img; 53 | tns_t tns; 54 | win_t win; 55 | 56 | fileinfo_t *files; 57 | int filecnt, fileidx; 58 | int alternate; 59 | int markcnt; 60 | int markidx; 61 | 62 | int prefix; 63 | bool extprefix; 64 | 65 | bool resized = false; 66 | 67 | typedef struct { 68 | int err; 69 | char *cmd; 70 | } extcmd_t; 71 | 72 | struct { 73 | extcmd_t f; 74 | int fd; 75 | unsigned int i, lastsep; 76 | pid_t pid; 77 | } info; 78 | 79 | struct { 80 | extcmd_t f; 81 | bool warned; 82 | } keyhandler; 83 | 84 | timeout_t timeouts[] = { 85 | { { 0, 0 }, false, redraw }, 86 | { { 0, 0 }, false, reset_cursor }, 87 | { { 0, 0 }, false, animate }, 88 | { { 0, 0 }, false, slideshow }, 89 | { { 0, 0 }, false, clear_resize }, 90 | }; 91 | 92 | cursor_t imgcursor[3] = { 93 | CURSOR_ARROW, CURSOR_ARROW, CURSOR_ARROW 94 | }; 95 | 96 | void cleanup(void) 97 | { 98 | img_close(&img, false); 99 | arl_cleanup(&arl); 100 | tns_free(&tns); 101 | win_close(&win); 102 | } 103 | 104 | void check_add_file(char *filename, bool given) 105 | { 106 | char *path; 107 | 108 | if (*filename == '\0') 109 | return; 110 | 111 | if (access(filename, R_OK) < 0 || 112 | (path = realpath(filename, NULL)) == NULL) 113 | { 114 | if (given) 115 | error(0, errno, "%s", filename); 116 | return; 117 | } 118 | 119 | if (fileidx == filecnt) { 120 | filecnt *= 2; 121 | files = erealloc(files, filecnt * sizeof(*files)); 122 | memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files)); 123 | } 124 | 125 | files[fileidx].name = estrdup(filename); 126 | files[fileidx].path = path; 127 | if (given) 128 | files[fileidx].flags |= FF_WARN; 129 | fileidx++; 130 | } 131 | 132 | void remove_file(int n, bool manual) 133 | { 134 | if (n < 0 || n >= filecnt) 135 | return; 136 | 137 | if (filecnt == 1) { 138 | if (!manual) 139 | fprintf(stderr, "sxiv: no more files to display, aborting\n"); 140 | exit(manual ? EXIT_SUCCESS : EXIT_FAILURE); 141 | } 142 | if (files[n].flags & FF_MARK) 143 | markcnt--; 144 | 145 | if (files[n].path != files[n].name) 146 | free((void*) files[n].path); 147 | free((void*) files[n].name); 148 | 149 | if (n + 1 < filecnt) { 150 | if (tns.thumbs != NULL) { 151 | memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) * 152 | sizeof(*tns.thumbs)); 153 | memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs)); 154 | } 155 | memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files)); 156 | } 157 | filecnt--; 158 | if (fileidx > n || fileidx == filecnt) 159 | fileidx--; 160 | if (alternate > n || alternate == filecnt) 161 | alternate--; 162 | if (markidx > n || markidx == filecnt) 163 | markidx--; 164 | } 165 | 166 | void set_timeout(timeout_f handler, int time, bool overwrite) 167 | { 168 | int i; 169 | 170 | for (i = 0; i < ARRLEN(timeouts); i++) { 171 | if (timeouts[i].handler == handler) { 172 | if (!timeouts[i].active || overwrite) { 173 | gettimeofday(&timeouts[i].when, 0); 174 | TV_ADD_MSEC(&timeouts[i].when, time); 175 | timeouts[i].active = true; 176 | } 177 | return; 178 | } 179 | } 180 | } 181 | 182 | void reset_timeout(timeout_f handler) 183 | { 184 | int i; 185 | 186 | for (i = 0; i < ARRLEN(timeouts); i++) { 187 | if (timeouts[i].handler == handler) { 188 | timeouts[i].active = false; 189 | return; 190 | } 191 | } 192 | } 193 | 194 | bool check_timeouts(struct timeval *t) 195 | { 196 | int i = 0, tdiff, tmin = -1; 197 | struct timeval now; 198 | 199 | while (i < ARRLEN(timeouts)) { 200 | if (timeouts[i].active) { 201 | gettimeofday(&now, 0); 202 | tdiff = TV_DIFF(&timeouts[i].when, &now); 203 | if (tdiff <= 0) { 204 | timeouts[i].active = false; 205 | if (timeouts[i].handler != NULL) 206 | timeouts[i].handler(); 207 | i = tmin = -1; 208 | } else if (tmin < 0 || tdiff < tmin) { 209 | tmin = tdiff; 210 | } 211 | } 212 | i++; 213 | } 214 | if (tmin > 0 && t != NULL) 215 | TV_SET_MSEC(t, tmin); 216 | return tmin > 0; 217 | } 218 | 219 | void close_info(void) 220 | { 221 | if (info.fd != -1) { 222 | kill(info.pid, SIGTERM); 223 | close(info.fd); 224 | info.fd = -1; 225 | } 226 | } 227 | 228 | void open_info(void) 229 | { 230 | int pfd[2]; 231 | char w[12], h[12]; 232 | 233 | if (info.f.err != 0 || info.fd >= 0 || win.bar.h == 0) 234 | return; 235 | win.bar.l.buf[0] = '\0'; 236 | if (pipe(pfd) < 0) 237 | return; 238 | if ((info.pid = fork()) == 0) { 239 | close(pfd[0]); 240 | dup2(pfd[1], 1); 241 | snprintf(w, sizeof(w), "%d", img.w); 242 | snprintf(h, sizeof(h), "%d", img.h); 243 | execl(info.f.cmd, info.f.cmd, files[fileidx].name, w, h, NULL); 244 | error(EXIT_FAILURE, errno, "exec: %s", info.f.cmd); 245 | } 246 | close(pfd[1]); 247 | if (info.pid < 0) { 248 | close(pfd[0]); 249 | } else { 250 | fcntl(pfd[0], F_SETFL, O_NONBLOCK); 251 | info.fd = pfd[0]; 252 | info.i = info.lastsep = 0; 253 | } 254 | } 255 | 256 | void read_info(void) 257 | { 258 | ssize_t i, n; 259 | char buf[BAR_L_LEN]; 260 | 261 | while (true) { 262 | n = read(info.fd, buf, sizeof(buf)); 263 | if (n < 0 && errno == EAGAIN) 264 | return; 265 | else if (n == 0) 266 | goto end; 267 | for (i = 0; i < n; i++) { 268 | if (buf[i] == '\n') { 269 | if (info.lastsep == 0) { 270 | win.bar.l.buf[info.i++] = ' '; 271 | info.lastsep = 1; 272 | } 273 | } else { 274 | win.bar.l.buf[info.i++] = buf[i]; 275 | info.lastsep = 0; 276 | } 277 | if (info.i + 1 == win.bar.l.size) 278 | goto end; 279 | } 280 | } 281 | end: 282 | info.i -= info.lastsep; 283 | win.bar.l.buf[info.i] = '\0'; 284 | win_draw(&win); 285 | close_info(); 286 | } 287 | 288 | void load_image(int new) 289 | { 290 | bool prev = new < fileidx; 291 | static int current; 292 | 293 | if (new < 0 || new >= filecnt) 294 | return; 295 | 296 | if (win.xwin != None) 297 | win_set_cursor(&win, CURSOR_WATCH); 298 | reset_timeout(slideshow); 299 | 300 | if (new != current) 301 | alternate = current; 302 | 303 | img_close(&img, false); 304 | while (!img_load(&img, &files[new])) { 305 | remove_file(new, false); 306 | if (new >= filecnt) 307 | new = filecnt - 1; 308 | else if (new > 0 && prev) 309 | new--; 310 | } 311 | files[new].flags &= ~FF_WARN; 312 | fileidx = current = new; 313 | 314 | close_info(); 315 | open_info(); 316 | arl_setup(&arl, files[fileidx].path); 317 | 318 | if (img.multi.cnt > 0 && img.multi.animate) 319 | set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); 320 | else 321 | reset_timeout(animate); 322 | } 323 | 324 | bool mark_image(int n, bool on) 325 | { 326 | markidx = n; 327 | if (!!(files[n].flags & FF_MARK) != on) { 328 | files[n].flags ^= FF_MARK; 329 | markcnt += on ? 1 : -1; 330 | if (mode == MODE_THUMB) 331 | tns_mark(&tns, n, on); 332 | return true; 333 | } 334 | return false; 335 | } 336 | 337 | void bar_put(win_bar_t *bar, const char *fmt, ...) 338 | { 339 | size_t len = bar->size - (bar->p - bar->buf), n; 340 | va_list ap; 341 | 342 | va_start(ap, fmt); 343 | n = vsnprintf(bar->p, len, fmt, ap); 344 | bar->p += MIN(len, n); 345 | va_end(ap); 346 | } 347 | 348 | #define BAR_SEP " " 349 | 350 | void update_info(void) 351 | { 352 | unsigned int i, fn, fw; 353 | const char * mark; 354 | win_bar_t *l = &win.bar.l, *r = &win.bar.r; 355 | 356 | /* update bar contents */ 357 | if (win.bar.h == 0) 358 | return; 359 | for (fw = 0, i = filecnt; i > 0; fw++, i /= 10); 360 | mark = files[fileidx].flags & FF_MARK ? "* " : ""; 361 | l->p = l->buf; 362 | r->p = r->buf; 363 | if (mode == MODE_THUMB) { 364 | if (tns.loadnext < tns.end) 365 | bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1); 366 | else if (tns.initnext < filecnt) 367 | bar_put(l, "Caching... %0*d", fw, tns.initnext + 1); 368 | else 369 | strncpy(l->buf, files[fileidx].name, l->size); 370 | bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt); 371 | } else { 372 | bar_put(r, "%s", mark); 373 | if (img.ss.on) { 374 | if (img.ss.delay % 10 != 0) 375 | bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10); 376 | else 377 | bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10); 378 | } 379 | if (img.gamma != 0) 380 | bar_put(r, "G%+d" BAR_SEP, img.gamma); 381 | bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0)); 382 | if (img.multi.cnt > 0) { 383 | for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10); 384 | bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt); 385 | } 386 | bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt); 387 | if (info.f.err) 388 | strncpy(l->buf, files[fileidx].name, l->size); 389 | } 390 | } 391 | 392 | int ptr_third_x(void) 393 | { 394 | int x, y; 395 | 396 | win_cursor_pos(&win, &x, &y); 397 | return MAX(0, MIN(2, (x / (win.w * 0.33)))); 398 | } 399 | 400 | void redraw(void) 401 | { 402 | int t; 403 | 404 | if (mode == MODE_IMAGE) { 405 | img_render(&img); 406 | if (img.ss.on) { 407 | t = img.ss.delay * 100; 408 | if (img.multi.cnt > 0 && img.multi.animate) 409 | t = MAX(t, img.multi.length); 410 | set_timeout(slideshow, t, false); 411 | } 412 | } else { 413 | tns_render(&tns); 414 | } 415 | update_info(); 416 | win_draw(&win); 417 | reset_timeout(redraw); 418 | reset_cursor(); 419 | } 420 | 421 | void reset_cursor(void) 422 | { 423 | int c, i; 424 | cursor_t cursor = CURSOR_NONE; 425 | 426 | if (mode == MODE_IMAGE) { 427 | for (i = 0; i < ARRLEN(timeouts); i++) { 428 | if (timeouts[i].handler == reset_cursor) { 429 | if (timeouts[i].active) { 430 | c = ptr_third_x(); 431 | c = MAX(fileidx > 0 ? 0 : 1, c); 432 | c = MIN(fileidx + 1 < filecnt ? 2 : 1, c); 433 | cursor = imgcursor[c]; 434 | } 435 | break; 436 | } 437 | } 438 | } else { 439 | if (tns.loadnext < tns.end || tns.initnext < filecnt) 440 | cursor = CURSOR_WATCH; 441 | else 442 | cursor = CURSOR_ARROW; 443 | } 444 | win_set_cursor(&win, cursor); 445 | } 446 | 447 | void animate(void) 448 | { 449 | if (img_frame_animate(&img)) { 450 | redraw(); 451 | set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); 452 | } 453 | } 454 | 455 | void slideshow(void) 456 | { 457 | load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0); 458 | redraw(); 459 | } 460 | 461 | void clear_resize(void) 462 | { 463 | resized = false; 464 | } 465 | 466 | Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg) 467 | { 468 | return ev->type == ButtonPress || ev->type == KeyPress; 469 | } 470 | 471 | void run_key_handler(const char *key, unsigned int mask) 472 | { 473 | pid_t pid; 474 | FILE *pfs; 475 | bool marked = mode == MODE_THUMB && markcnt > 0; 476 | bool changed = false; 477 | int f, i, pfd[2]; 478 | int fcnt = marked ? markcnt : 1; 479 | char kstr[32]; 480 | struct stat *oldst, st; 481 | XEvent dump; 482 | 483 | if (keyhandler.f.err != 0) { 484 | if (!keyhandler.warned) { 485 | error(0, keyhandler.f.err, "%s", keyhandler.f.cmd); 486 | keyhandler.warned = true; 487 | } 488 | return; 489 | } 490 | if (key == NULL) 491 | return; 492 | 493 | if (pipe(pfd) < 0) { 494 | error(0, errno, "pipe"); 495 | return; 496 | } 497 | if ((pfs = fdopen(pfd[1], "w")) == NULL) { 498 | error(0, errno, "open pipe"); 499 | close(pfd[0]), close(pfd[1]); 500 | return; 501 | } 502 | oldst = emalloc(fcnt * sizeof(*oldst)); 503 | 504 | close_info(); 505 | strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size); 506 | win_draw(&win); 507 | win_set_cursor(&win, CURSOR_WATCH); 508 | 509 | snprintf(kstr, sizeof(kstr), "%s%s%s%s", 510 | mask & ControlMask ? "C-" : "", 511 | mask & Mod1Mask ? "M-" : "", 512 | mask & ShiftMask ? "S-" : "", key); 513 | 514 | if ((pid = fork()) == 0) { 515 | close(pfd[1]); 516 | dup2(pfd[0], 0); 517 | execl(keyhandler.f.cmd, keyhandler.f.cmd, kstr, NULL); 518 | error(EXIT_FAILURE, errno, "exec: %s", keyhandler.f.cmd); 519 | } 520 | close(pfd[0]); 521 | if (pid < 0) { 522 | error(0, errno, "fork"); 523 | fclose(pfs); 524 | goto end; 525 | } 526 | 527 | for (f = i = 0; f < fcnt; i++) { 528 | if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { 529 | stat(files[i].path, &oldst[f]); 530 | fprintf(pfs, "%s\n", files[i].name); 531 | f++; 532 | } 533 | } 534 | fclose(pfs); 535 | while (waitpid(pid, NULL, 0) == -1 && errno == EINTR); 536 | 537 | for (f = i = 0; f < fcnt; i++) { 538 | if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { 539 | if (stat(files[i].path, &st) != 0 || 540 | memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0) 541 | { 542 | if (tns.thumbs != NULL) { 543 | tns_unload(&tns, i); 544 | tns.loadnext = MIN(tns.loadnext, i); 545 | } 546 | changed = true; 547 | } 548 | f++; 549 | } 550 | } 551 | /* drop user input events that occurred while running the key handler */ 552 | while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL)); 553 | 554 | end: 555 | if (mode == MODE_IMAGE) { 556 | if (changed) { 557 | img_close(&img, true); 558 | load_image(fileidx); 559 | } else { 560 | open_info(); 561 | } 562 | } 563 | free(oldst); 564 | reset_cursor(); 565 | redraw(); 566 | } 567 | 568 | #define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask)) 569 | 570 | void on_keypress(XKeyEvent *kev) 571 | { 572 | int i; 573 | unsigned int sh = 0; 574 | KeySym ksym, shksym; 575 | char dummy, key; 576 | bool dirty = false; 577 | 578 | XLookupString(kev, &key, 1, &ksym, NULL); 579 | 580 | if (kev->state & ShiftMask) { 581 | kev->state &= ~ShiftMask; 582 | XLookupString(kev, &dummy, 1, &shksym, NULL); 583 | kev->state |= ShiftMask; 584 | if (ksym != shksym) 585 | sh = ShiftMask; 586 | } 587 | if (IsModifierKey(ksym)) 588 | return; 589 | if (ksym == XK_Escape && MODMASK(kev->state) == 0) { 590 | extprefix = False; 591 | } else if (extprefix) { 592 | run_key_handler(XKeysymToString(ksym), kev->state & ~sh); 593 | extprefix = False; 594 | } else if (key >= '0' && key <= '9') { 595 | /* number prefix for commands */ 596 | prefix = prefix * 10 + (int) (key - '0'); 597 | return; 598 | } else for (i = 0; i < ARRLEN(keys); i++) { 599 | if (keys[i].ksym == ksym && 600 | MODMASK(keys[i].mask | sh) == MODMASK(kev->state) && 601 | keys[i].cmd >= 0 && keys[i].cmd < CMD_COUNT && 602 | (cmds[keys[i].cmd].mode < 0 || cmds[keys[i].cmd].mode == mode)) 603 | { 604 | if (cmds[keys[i].cmd].func(keys[i].arg)) 605 | dirty = true; 606 | } 607 | } 608 | if (dirty) 609 | redraw(); 610 | prefix = 0; 611 | } 612 | 613 | void on_buttonpress(XButtonEvent *bev) 614 | { 615 | int i, sel; 616 | bool dirty = false; 617 | static Time firstclick; 618 | 619 | if (mode == MODE_IMAGE) { 620 | set_timeout(reset_cursor, TO_CURSOR_HIDE, true); 621 | reset_cursor(); 622 | 623 | for (i = 0; i < ARRLEN(buttons); i++) { 624 | if (buttons[i].button == bev->button && 625 | MODMASK(buttons[i].mask) == MODMASK(bev->state) && 626 | buttons[i].cmd >= 0 && buttons[i].cmd < CMD_COUNT && 627 | (cmds[buttons[i].cmd].mode < 0 || cmds[buttons[i].cmd].mode == mode)) 628 | { 629 | if (cmds[buttons[i].cmd].func(buttons[i].arg)) 630 | dirty = true; 631 | } 632 | } 633 | if (dirty) 634 | redraw(); 635 | } else { 636 | /* thumbnail mode (hard-coded) */ 637 | switch (bev->button) { 638 | case Button1: 639 | if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { 640 | if (sel != fileidx) { 641 | tns_highlight(&tns, fileidx, false); 642 | tns_highlight(&tns, sel, true); 643 | fileidx = sel; 644 | firstclick = bev->time; 645 | redraw(); 646 | } else if (bev->time - firstclick <= TO_DOUBLE_CLICK) { 647 | mode = MODE_IMAGE; 648 | set_timeout(reset_cursor, TO_CURSOR_HIDE, true); 649 | load_image(fileidx); 650 | redraw(); 651 | } else { 652 | firstclick = bev->time; 653 | } 654 | } 655 | break; 656 | case Button3: 657 | if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { 658 | bool on = !(files[sel].flags & FF_MARK); 659 | XEvent e; 660 | 661 | for (;;) { 662 | if (sel >= 0 && mark_image(sel, on)) 663 | redraw(); 664 | XMaskEvent(win.env.dpy, 665 | ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); 666 | if (e.type == ButtonPress || e.type == ButtonRelease) 667 | break; 668 | while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)); 669 | sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y); 670 | } 671 | } 672 | break; 673 | case Button4: 674 | case Button5: 675 | if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN, 676 | (bev->state & ControlMask) != 0)) 677 | redraw(); 678 | break; 679 | } 680 | } 681 | prefix = 0; 682 | } 683 | 684 | const struct timespec ten_ms = {0, 10000000}; 685 | 686 | void run(void) 687 | { 688 | int xfd; 689 | fd_set fds; 690 | struct timeval timeout; 691 | bool discard, init_thumb, load_thumb, to_set; 692 | XEvent ev, nextev; 693 | 694 | while (true) { 695 | to_set = check_timeouts(&timeout); 696 | init_thumb = mode == MODE_THUMB && tns.initnext < filecnt; 697 | load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end; 698 | 699 | if ((init_thumb || load_thumb || to_set || info.fd != -1 || 700 | arl.fd != -1) && XPending(win.env.dpy) == 0) 701 | { 702 | if (load_thumb) { 703 | set_timeout(redraw, TO_REDRAW_THUMBS, false); 704 | if (!tns_load(&tns, tns.loadnext, false, false)) { 705 | remove_file(tns.loadnext, false); 706 | tns.dirty = true; 707 | } 708 | if (tns.loadnext >= tns.end) 709 | redraw(); 710 | } else if (init_thumb) { 711 | set_timeout(redraw, TO_REDRAW_THUMBS, false); 712 | if (!tns_load(&tns, tns.initnext, false, true)) 713 | remove_file(tns.initnext, false); 714 | } else { 715 | xfd = ConnectionNumber(win.env.dpy); 716 | FD_ZERO(&fds); 717 | FD_SET(xfd, &fds); 718 | if (info.fd != -1) { 719 | FD_SET(info.fd, &fds); 720 | xfd = MAX(xfd, info.fd); 721 | } 722 | if (arl.fd != -1) { 723 | FD_SET(arl.fd, &fds); 724 | xfd = MAX(xfd, arl.fd); 725 | } 726 | select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL); 727 | if (info.fd != -1 && FD_ISSET(info.fd, &fds)) 728 | read_info(); 729 | if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) { 730 | if (arl_handle(&arl)) { 731 | /* when too fast, imlib2 can't load the image */ 732 | nanosleep(&ten_ms, NULL); 733 | img_close(&img, true); 734 | load_image(fileidx); 735 | redraw(); 736 | } 737 | } 738 | } 739 | continue; 740 | } 741 | 742 | do { 743 | XNextEvent(win.env.dpy, &ev); 744 | discard = false; 745 | if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) { 746 | XPeekEvent(win.env.dpy, &nextev); 747 | switch (ev.type) { 748 | case ConfigureNotify: 749 | case MotionNotify: 750 | discard = ev.type == nextev.type; 751 | break; 752 | case KeyPress: 753 | discard = (nextev.type == KeyPress || nextev.type == KeyRelease) 754 | && ev.xkey.keycode == nextev.xkey.keycode; 755 | break; 756 | } 757 | } 758 | } while (discard); 759 | 760 | switch (ev.type) { 761 | /* handle events */ 762 | case ButtonPress: 763 | on_buttonpress(&ev.xbutton); 764 | break; 765 | case ClientMessage: 766 | if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW]) 767 | cmds[g_quit].func(0); 768 | break; 769 | case ConfigureNotify: 770 | if (win_configure(&win, &ev.xconfigure)) { 771 | if (mode == MODE_IMAGE) { 772 | img.dirty = true; 773 | img.checkpan = true; 774 | } else { 775 | tns.dirty = true; 776 | } 777 | if (!resized) { 778 | redraw(); 779 | set_timeout(clear_resize, TO_REDRAW_RESIZE, false); 780 | resized = true; 781 | } else { 782 | set_timeout(redraw, TO_REDRAW_RESIZE, false); 783 | } 784 | } 785 | break; 786 | case KeyPress: 787 | on_keypress(&ev.xkey); 788 | break; 789 | case MotionNotify: 790 | if (mode == MODE_IMAGE) { 791 | set_timeout(reset_cursor, TO_CURSOR_HIDE, true); 792 | reset_cursor(); 793 | } 794 | break; 795 | } 796 | } 797 | } 798 | 799 | int fncmp(const void *a, const void *b) 800 | { 801 | return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name); 802 | } 803 | 804 | void sigchld(int sig) 805 | { 806 | while (waitpid(-1, NULL, WNOHANG) > 0); 807 | } 808 | 809 | void setup_signal(int sig, void (*handler)(int sig)) 810 | { 811 | struct sigaction sa; 812 | 813 | sa.sa_handler = handler; 814 | sigemptyset(&sa.sa_mask); 815 | sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; 816 | if (sigaction(sig, &sa, 0) == -1) 817 | error(EXIT_FAILURE, errno, "signal %d", sig); 818 | } 819 | 820 | int main(int argc, char **argv) 821 | { 822 | int i, start; 823 | size_t n; 824 | ssize_t len; 825 | char *filename; 826 | const char *homedir, *dsuffix = ""; 827 | struct stat fstats; 828 | r_dir_t dir; 829 | 830 | setup_signal(SIGCHLD, sigchld); 831 | setup_signal(SIGPIPE, SIG_IGN); 832 | 833 | setlocale(LC_COLLATE, ""); 834 | 835 | parse_options(argc, argv); 836 | 837 | if (options->clean_cache) { 838 | tns_init(&tns, NULL, NULL, NULL, NULL); 839 | tns_clean_cache(&tns); 840 | exit(EXIT_SUCCESS); 841 | } 842 | 843 | if (options->filecnt == 0 && !options->from_stdin) { 844 | print_usage(); 845 | exit(EXIT_FAILURE); 846 | } 847 | 848 | if (options->recursive || options->from_stdin) 849 | filecnt = 1024; 850 | else 851 | filecnt = options->filecnt; 852 | 853 | files = emalloc(filecnt * sizeof(*files)); 854 | memset(files, 0, filecnt * sizeof(*files)); 855 | fileidx = 0; 856 | 857 | if (options->from_stdin) { 858 | n = 0; 859 | filename = NULL; 860 | while ((len = getline(&filename, &n, stdin)) > 0) { 861 | if (filename[len-1] == '\n') 862 | filename[len-1] = '\0'; 863 | check_add_file(filename, true); 864 | } 865 | free(filename); 866 | } 867 | 868 | for (i = 0; i < options->filecnt; i++) { 869 | filename = options->filenames[i]; 870 | 871 | if (stat(filename, &fstats) < 0) { 872 | error(0, errno, "%s", filename); 873 | continue; 874 | } 875 | if (!S_ISDIR(fstats.st_mode)) { 876 | check_add_file(filename, true); 877 | } else { 878 | if (r_opendir(&dir, filename, options->recursive) < 0) { 879 | error(0, errno, "%s", filename); 880 | continue; 881 | } 882 | start = fileidx; 883 | while ((filename = r_readdir(&dir, true)) != NULL) { 884 | check_add_file(filename, false); 885 | free((void*) filename); 886 | } 887 | r_closedir(&dir); 888 | if (fileidx - start > 1) 889 | qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp); 890 | } 891 | } 892 | 893 | if (fileidx == 0) 894 | error(EXIT_FAILURE, 0, "No valid image file given, aborting"); 895 | 896 | filecnt = fileidx; 897 | fileidx = options->startnum < filecnt ? options->startnum : 0; 898 | 899 | for (i = 0; i < ARRLEN(buttons); i++) { 900 | if (buttons[i].cmd == i_cursor_navigate) { 901 | imgcursor[0] = CURSOR_LEFT; 902 | imgcursor[2] = CURSOR_RIGHT; 903 | break; 904 | } 905 | } 906 | 907 | win_init(&win); 908 | img_init(&img, &win); 909 | arl_init(&arl); 910 | 911 | if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') { 912 | homedir = getenv("HOME"); 913 | dsuffix = "/.config"; 914 | } 915 | if (homedir != NULL) { 916 | extcmd_t *cmd[] = { &info.f, &keyhandler.f }; 917 | const char *name[] = { "image-info", "key-handler" }; 918 | 919 | for (i = 0; i < ARRLEN(cmd); i++) { 920 | n = strlen(homedir) + strlen(dsuffix) + strlen(name[i]) + 12; 921 | cmd[i]->cmd = (char*) emalloc(n); 922 | snprintf(cmd[i]->cmd, n, "%s%s/sxiv/exec/%s", homedir, dsuffix, name[i]); 923 | if (access(cmd[i]->cmd, X_OK) != 0) 924 | cmd[i]->err = errno; 925 | } 926 | } else { 927 | error(0, 0, "Exec directory not found"); 928 | } 929 | info.fd = -1; 930 | 931 | if (options->thumb_mode) { 932 | mode = MODE_THUMB; 933 | tns_init(&tns, files, &filecnt, &fileidx, &win); 934 | while (!tns_load(&tns, fileidx, false, false)) 935 | remove_file(fileidx, false); 936 | } else { 937 | mode = MODE_IMAGE; 938 | tns.thumbs = NULL; 939 | load_image(fileidx); 940 | } 941 | win_open(&win); 942 | win_set_cursor(&win, CURSOR_WATCH); 943 | 944 | atexit(cleanup); 945 | 946 | set_timeout(redraw, 25, false); 947 | 948 | run(); 949 | 950 | return 0; 951 | } 952 | -------------------------------------------------------------------------------- /options.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | #define _IMAGE_CONFIG 21 | #include "config.h" 22 | #include "version.h" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | opt_t _options; 29 | const opt_t *options = (const opt_t*) &_options; 30 | 31 | void print_usage(void) 32 | { 33 | printf("usage: sxiv [-abcfhiopqrtvZ] [-A FRAMERATE] [-e WID] [-G GAMMA] " 34 | "[-g GEOMETRY] [-N NAME] [-n NUM] [-S DELAY] [-s MODE] [-z ZOOM] " 35 | "FILES...\n"); 36 | } 37 | 38 | void print_version(void) 39 | { 40 | puts("sxiv " VERSION); 41 | } 42 | 43 | void parse_options(int argc, char **argv) 44 | { 45 | int n, opt; 46 | char *end, *s; 47 | const char *scalemodes = "dfwh"; 48 | 49 | progname = strrchr(argv[0], '/'); 50 | progname = progname ? progname + 1 : argv[0]; 51 | 52 | _options.from_stdin = false; 53 | _options.to_stdout = false; 54 | _options.recursive = false; 55 | _options.startnum = 0; 56 | 57 | _options.scalemode = SCALE_DOWN; 58 | _options.zoom = 1.0; 59 | _options.animate = false; 60 | _options.gamma = 0; 61 | _options.slideshow = 0; 62 | _options.framerate = 0; 63 | 64 | _options.fullscreen = false; 65 | _options.embed = 0; 66 | _options.hide_bar = false; 67 | _options.geometry = NULL; 68 | _options.res_name = NULL; 69 | 70 | _options.quiet = false; 71 | _options.thumb_mode = false; 72 | _options.clean_cache = false; 73 | _options.private_mode = false; 74 | 75 | while ((opt = getopt(argc, argv, "A:abce:fG:g:hin:N:opqrS:s:tvZz:")) != -1) { 76 | switch (opt) { 77 | case '?': 78 | print_usage(); 79 | exit(EXIT_FAILURE); 80 | case 'A': 81 | n = strtol(optarg, &end, 0); 82 | if (*end != '\0' || n <= 0) 83 | error(EXIT_FAILURE, 0, "Invalid argument for option -A: %s", optarg); 84 | _options.framerate = n; 85 | /* fall through */ 86 | case 'a': 87 | _options.animate = true; 88 | break; 89 | case 'b': 90 | _options.hide_bar = true; 91 | break; 92 | case 'c': 93 | _options.clean_cache = true; 94 | break; 95 | case 'e': 96 | n = strtol(optarg, &end, 0); 97 | if (*end != '\0') 98 | error(EXIT_FAILURE, 0, "Invalid argument for option -e: %s", optarg); 99 | _options.embed = n; 100 | break; 101 | case 'f': 102 | _options.fullscreen = true; 103 | break; 104 | case 'G': 105 | n = strtol(optarg, &end, 0); 106 | if (*end != '\0') 107 | error(EXIT_FAILURE, 0, "Invalid argument for option -G: %s", optarg); 108 | _options.gamma = n; 109 | break; 110 | case 'g': 111 | _options.geometry = optarg; 112 | break; 113 | case 'h': 114 | print_usage(); 115 | exit(EXIT_SUCCESS); 116 | case 'i': 117 | _options.from_stdin = true; 118 | break; 119 | case 'n': 120 | n = strtol(optarg, &end, 0); 121 | if (*end != '\0' || n <= 0) 122 | error(EXIT_FAILURE, 0, "Invalid argument for option -n: %s", optarg); 123 | _options.startnum = n - 1; 124 | break; 125 | case 'N': 126 | _options.res_name = optarg; 127 | break; 128 | case 'o': 129 | _options.to_stdout = true; 130 | break; 131 | case 'p': 132 | _options.private_mode = true; 133 | break; 134 | case 'q': 135 | _options.quiet = true; 136 | break; 137 | case 'r': 138 | _options.recursive = true; 139 | break; 140 | case 'S': 141 | n = strtof(optarg, &end) * 10; 142 | if (*end != '\0' || n <= 0) 143 | error(EXIT_FAILURE, 0, "Invalid argument for option -S: %s", optarg); 144 | _options.slideshow = n; 145 | break; 146 | case 's': 147 | s = strchr(scalemodes, optarg[0]); 148 | if (s == NULL || *s == '\0' || strlen(optarg) != 1) 149 | error(EXIT_FAILURE, 0, "Invalid argument for option -s: %s", optarg); 150 | _options.scalemode = s - scalemodes; 151 | break; 152 | case 't': 153 | _options.thumb_mode = true; 154 | break; 155 | case 'v': 156 | print_version(); 157 | exit(EXIT_SUCCESS); 158 | case 'Z': 159 | _options.scalemode = SCALE_ZOOM; 160 | _options.zoom = 1.0; 161 | break; 162 | case 'z': 163 | n = strtol(optarg, &end, 0); 164 | if (*end != '\0' || n <= 0) 165 | error(EXIT_FAILURE, 0, "Invalid argument for option -z: %s", optarg); 166 | _options.scalemode = SCALE_ZOOM; 167 | _options.zoom = (float) n / 100.0; 168 | break; 169 | } 170 | } 171 | 172 | _options.filenames = argv + optind; 173 | _options.filecnt = argc - optind; 174 | 175 | if (_options.filecnt == 1 && STREQ(_options.filenames[0], "-")) { 176 | _options.filenames++; 177 | _options.filecnt--; 178 | _options.from_stdin = true; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /sxiv.1: -------------------------------------------------------------------------------- 1 | .TH SXIV 1 sxiv\-VERSION 2 | .SH NAME 3 | sxiv \- Simple X Image Viewer 4 | .SH SYNOPSIS 5 | .B sxiv 6 | .RB [ \-abcfhiopqrtvZ ] 7 | .RB [ \-A 8 | .IR FRAMERATE ] 9 | .RB [ \-e 10 | .IR WID ] 11 | .RB [ \-G 12 | .IR GAMMA ] 13 | .RB [ \-g 14 | .IR GEOMETRY ] 15 | .RB [ \-N 16 | .IR NAME ] 17 | .RB [ \-n 18 | .IR NUM ] 19 | .RB [ \-S 20 | .IR DELAY ] 21 | .RB [ \-s 22 | .IR MODE ] 23 | .RB [ \-z 24 | .IR ZOOM ] 25 | .IR FILE ... 26 | .SH DESCRIPTION 27 | sxiv is a simple image viewer for X. 28 | .P 29 | It has two modes of operation: image and thumbnail mode. The default is image 30 | mode, in which only the current image is shown. In thumbnail mode a grid of 31 | small previews is displayed, making it easy to choose an image to open. 32 | .P 33 | Please note, that the fullscreen mode requires an EWMH/NetWM compliant window 34 | manager. 35 | .SH OPTIONS 36 | .TP 37 | .BI "\-A " FRAMERATE 38 | Play animations with a constant frame rate set to 39 | .IR FRAMERATE . 40 | .TP 41 | .B \-a 42 | Play animations of multi-frame images. 43 | .TP 44 | .B \-b 45 | Do not show info bar on bottom of window. 46 | .TP 47 | .B \-c 48 | Remove all orphaned cache files from the thumbnail cache directory and exit. 49 | .TP 50 | .BI "\-e " WID 51 | Embed sxiv's window into window whose ID is 52 | .IR WID . 53 | .TP 54 | .B \-f 55 | Start in fullscreen mode. 56 | .TP 57 | .BI "\-G " GAMMA 58 | Set image gamma to GAMMA (-32..32). 59 | .TP 60 | .BI "\-g " GEOMETRY 61 | Set window position and size. See section GEOMETRY SPECIFICATIONS of X(7) for 62 | more information on GEOMETRY argument. 63 | .TP 64 | .BI "\-N " NAME 65 | Set the resource name of sxiv's X window to NAME. 66 | .TP 67 | .BI "\-n " NUM 68 | Start at picture number NUM. 69 | .TP 70 | .B \-h 71 | Print brief usage information to standard output and exit. 72 | .TP 73 | .B \-i 74 | Read names of files to open from standard input. Also done if FILE is `-'. 75 | .TP 76 | .B \-o 77 | Write list of all marked files to standard output when quitting. In combination 78 | with 79 | .B \-i 80 | sxiv can be used as a visual filter/pipe. 81 | .TP 82 | .B \-p 83 | Enable private mode, in which sxiv does not write any cache or temporary files. 84 | .TP 85 | .B \-q 86 | Be quiet, disable warnings to standard error stream. 87 | .TP 88 | .B \-r 89 | Search the given directories recursively for images to view. 90 | .TP 91 | .BI "\-S " DELAY 92 | Start in slideshow mode. Set the delay between images to 93 | .I DELAY 94 | seconds. 95 | .I DELAY 96 | may be a floating point number. 97 | .TP 98 | .BI "\-s " MODE 99 | Set scale mode according to MODE character. Supported modes are: [d]own, 100 | [f]it, [w]idth, [h]eight. 101 | .TP 102 | .B \-t 103 | Start in thumbnail mode. 104 | .TP 105 | .B \-v 106 | Print version information to standard output and exit. 107 | .TP 108 | .B \-Z 109 | The same as `\-z 100'. 110 | .TP 111 | .BI "\-z " ZOOM 112 | Set zoom level to ZOOM percent. 113 | .SH KEYBOARD COMMANDS 114 | .SS General 115 | The following keyboard commands are available in both image and thumbnail mode: 116 | .TP 117 | .BR 0 \- 9 118 | Prefix the next command with a number (denoted via 119 | .IR count ). 120 | .TP 121 | .B q 122 | Quit sxiv. 123 | .TP 124 | .B Return 125 | Switch to thumbnail mode / open selected image in image mode. 126 | .TP 127 | .B f 128 | Toggle fullscreen mode. 129 | .TP 130 | .B b 131 | Toggle visibility of info bar on bottom of window. 132 | .TP 133 | .B Ctrl-x 134 | Send the next key to the external key-handler. See section EXTERNAL KEY HANDLER 135 | for more information. 136 | .TP 137 | .B g 138 | Go to the first image. 139 | .TP 140 | .B G 141 | Go to the last image, or image number 142 | .IR count . 143 | .TP 144 | .B r 145 | Reload image. 146 | .TP 147 | .B D 148 | Remove current image from file list and go to next image. 149 | .TP 150 | .BR Ctrl-h ", " Ctrl-Left 151 | Scroll left one screen width. 152 | .TP 153 | .BR Ctrl-j ", " Ctrl-Down 154 | Scroll down one screen height. 155 | .TP 156 | .BR Ctrl-k ", " Ctrl-Up 157 | Scroll up one screen height. 158 | .TP 159 | .BR Ctrl-l ", " Ctrl-Right 160 | Scroll right one screen width. 161 | .TP 162 | .BR + 163 | Zoom in. 164 | .TP 165 | .B \- 166 | Zoom out. 167 | .TP 168 | .B m 169 | Mark/unmark the current image. 170 | .TP 171 | .B M 172 | Reverse all image marks. 173 | .TP 174 | .B Ctrl-M 175 | Repeat last mark action on all images from the last marked/unmarked up to the 176 | current one. 177 | .TP 178 | .B Ctrl-m 179 | Remove all image marks. 180 | .TP 181 | .B N 182 | Go 183 | .I count 184 | marked images forward. 185 | .TP 186 | .B P 187 | Go 188 | .I count 189 | marked images backward. 190 | .TP 191 | .B { 192 | Decrease gamma correction by 193 | .I count 194 | steps. 195 | .TP 196 | .B } 197 | Increase gamma correction by 198 | .I count 199 | steps. 200 | .TP 201 | .B Ctrl-g 202 | Reset gamma correction. 203 | .SS Thumbnail mode 204 | The following keyboard commands are only available in thumbnail mode: 205 | .TP 206 | .BR h ", " Left 207 | Move selection left 208 | .I count 209 | times. 210 | .TP 211 | .BR j ", " Down 212 | Move selection down 213 | .I count 214 | times. 215 | .TP 216 | .BR k ", " Up 217 | Move selection up 218 | .I count 219 | times. 220 | .TP 221 | .BR l ", " Right 222 | Move selection right 223 | .I count 224 | times. 225 | .TP 226 | .B R 227 | Reload all thumbnails. 228 | .SS Image mode 229 | The following keyboard commands are only available in image mode: 230 | .TP 231 | Navigate image list: 232 | .TP 233 | .BR n ", " Space 234 | Go 235 | .I count 236 | images forward. 237 | .TP 238 | .BR p ", " Backspace 239 | Go 240 | .I count 241 | images backward. 242 | .TP 243 | .B [ 244 | Go 245 | .I count 246 | * 10 images backward. 247 | .TP 248 | .B ] 249 | Go 250 | .I count 251 | * 10 images forward. 252 | .TP 253 | Handle multi-frame images: 254 | .TP 255 | .B Ctrl-n 256 | Go 257 | .I count 258 | frames of a multi-frame image forward. 259 | .TP 260 | .B Ctrl-p 261 | Go 262 | .I count 263 | frames of a multi-frame image backward. 264 | .TP 265 | .B Ctrl-Space 266 | Play/stop animations of multi-frame images. 267 | .TP 268 | Panning: 269 | .TP 270 | .BR h ", " Left 271 | Scroll image 1/5 of window width or 272 | .I count 273 | pixel left. 274 | .TP 275 | .BR j ", " Down 276 | Scroll image 1/5 of window height or 277 | .I count 278 | pixel down. 279 | .TP 280 | .BR k ", " Up 281 | Scroll image 1/5 of window height or 282 | .I count 283 | pixel up. 284 | .TP 285 | .BR l ", " Right 286 | Scroll image 1/5 of window width or 287 | .I count 288 | pixel right. 289 | .TP 290 | .B H 291 | Scroll to left image edge. 292 | .TP 293 | .B J 294 | Scroll to bottom image edge. 295 | .TP 296 | .B K 297 | Scroll to top image edge. 298 | .TP 299 | .B L 300 | Scroll to right image edge. 301 | .TP 302 | Zooming: 303 | .TP 304 | .B = 305 | Set zoom level to 100%, or 306 | .IR count %. 307 | .TP 308 | .B w 309 | Set zoom level to 100%, but fit large images into window. 310 | .TP 311 | .B W 312 | Fit image to window. 313 | .TP 314 | .B e 315 | Fit image to window width. 316 | .TP 317 | .B E 318 | Fit image to window height. 319 | .TP 320 | Rotation: 321 | .TP 322 | .B < 323 | Rotate image counter-clockwise by 90 degrees. 324 | .TP 325 | .B > 326 | Rotate image clockwise by 90 degrees. 327 | .TP 328 | .B ? 329 | Rotate image by 180 degrees. 330 | .TP 331 | Flipping: 332 | .TP 333 | .B | 334 | Flip image horizontally. 335 | .TP 336 | .B _ 337 | Flip image vertically. 338 | .TP 339 | Miscellaneous: 340 | .TP 341 | .B a 342 | Toggle anti-aliasing. 343 | .TP 344 | .B A 345 | Toggle visibility of alpha-channel, i.e. image transparency. 346 | .TP 347 | .B s 348 | Toggle slideshow mode and/or set the delay between images to 349 | .I count 350 | seconds. 351 | .SH MOUSE COMMANDS 352 | The following mouse mappings are available in image mode: 353 | .TP 354 | General: 355 | .TP 356 | .B Button3 357 | Switch to thumbnail mode. 358 | .TP 359 | Navigate image list: 360 | .TP 361 | .B Button1 362 | Go to the next image if the mouse cursor is in the right part of the window or 363 | to the previous image if it is in the left part. 364 | .TP 365 | Panning: 366 | .TP 367 | .B Button2 368 | Pan the image according to the mouse cursor position in the window while 369 | keeping this button pressed down. 370 | .TP 371 | Zooming: 372 | .TP 373 | .B ScrollUp 374 | Zoom in. 375 | .TP 376 | .B ScrollDown 377 | Zoom out. 378 | .SH CONFIGURATION 379 | The following X resources are supported: 380 | .TP 381 | .B background 382 | Color of the window background and bar foreground 383 | .TP 384 | .B foreground 385 | Color of the window foreground and bar background 386 | .TP 387 | .B font 388 | Name of Xft bar font 389 | .TP 390 | Please see xrdb(1) on how to change them. 391 | .SH STATUS BAR 392 | The information displayed on the left side of the status bar can be replaced 393 | with the output of a user-provided script, which is called by sxiv whenever an 394 | image gets loaded. The path of this script is 395 | .I $XDG_CONFIG_HOME/sxiv/exec/image-info 396 | and the arguments given to it are: 1) path to image file, 2) image width, 397 | 3) image height. 398 | .P 399 | There is also an example script installed together with sxiv as 400 | .IR PREFIX/share/sxiv/exec/image-info . 401 | .SH EXTERNAL KEY HANDLER 402 | Additional external keyboard commands can be defined using a handler program 403 | located in 404 | .IR $XDG_CONFIG_HOME/sxiv/exec/key-handler . 405 | The handler is invoked by pressing 406 | .BR Ctrl-x . 407 | The next key combo is passed as its first argument. Passed via stdin are the 408 | images to act upon, one path per line: all marked images, if in thumbnail mode 409 | and at least one image has been marked, otherwise the current image. 410 | sxiv(1) will block until the handler terminates. It then checks which images 411 | have been modified and reloads them. 412 | 413 | The key combo argument has the following form: "[C-][M-][S-]KEY", 414 | where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X 415 | keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. 416 | 417 | There is also an example script installed together with sxiv as 418 | .IR PREFIX/share/sxiv/exec/key-handler . 419 | .SH THUMBNAIL CACHING 420 | sxiv stores all thumbnails under 421 | .IR $XDG_CACHE_HOME/sxiv/ . 422 | .P 423 | Use the command line option 424 | .I \-c 425 | to remove all orphaned cache files. Additionally, run the following command 426 | afterwards inside the cache directory to remove empty subdirectories: 427 | .P 428 | .RS 429 | find . \-depth \-type d \-empty ! \-name '.' \-exec rmdir {} \\; 430 | .RE 431 | .SH AUTHOR 432 | .EX 433 | Bert Muennich 434 | .EE 435 | .SH CONTRIBUTORS 436 | .EX 437 | Bastien Dejean 438 | Dave Reisner 439 | Fung SzeTat 440 | Max Voit 441 | .EE 442 | .SH HOMEPAGE 443 | .EX 444 | https://github.com/xyb3rt/sxiv 445 | .EE 446 | .SH SEE ALSO 447 | .BR X (7), 448 | .BR xrdb (1) 449 | -------------------------------------------------------------------------------- /sxiv.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=sxiv 4 | GenericName=Image Viewer 5 | Exec=sxiv %F 6 | MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-tga;image/x-xpixmap; 7 | NoDisplay=true 8 | Icon=sxiv 9 | -------------------------------------------------------------------------------- /sxiv.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #ifndef SXIV_H 20 | #define SXIV_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | /* 31 | * Annotation for functions called in cleanup(). 32 | * These functions are not allowed to call error(!0, ...) or exit(). 33 | */ 34 | #define CLEANUP 35 | 36 | #ifndef MIN 37 | #define MIN(a,b) ((a) < (b) ? (a) : (b)) 38 | #endif 39 | #ifndef MAX 40 | #define MAX(a,b) ((a) > (b) ? (a) : (b)) 41 | #endif 42 | 43 | #define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) 44 | 45 | #define STREQ(s1,s2) (strcmp((s1), (s2)) == 0) 46 | 47 | #define TV_DIFF(t1,t2) (((t1)->tv_sec - (t2)->tv_sec ) * 1000 + \ 48 | ((t1)->tv_usec - (t2)->tv_usec) / 1000) 49 | 50 | #define TV_SET_MSEC(tv,t) { \ 51 | (tv)->tv_sec = (t) / 1000; \ 52 | (tv)->tv_usec = (t) % 1000 * 1000; \ 53 | } 54 | 55 | #define TV_ADD_MSEC(tv,t) { \ 56 | (tv)->tv_sec += (t) / 1000; \ 57 | (tv)->tv_usec += (t) % 1000 * 1000; \ 58 | } 59 | 60 | typedef enum { 61 | BO_BIG_ENDIAN, 62 | BO_LITTLE_ENDIAN 63 | } byteorder_t; 64 | 65 | typedef enum { 66 | MODE_IMAGE, 67 | MODE_THUMB 68 | } appmode_t; 69 | 70 | typedef enum { 71 | DIR_LEFT = 1, 72 | DIR_RIGHT = 2, 73 | DIR_UP = 4, 74 | DIR_DOWN = 8 75 | } direction_t; 76 | 77 | typedef enum { 78 | DEGREE_90 = 1, 79 | DEGREE_180 = 2, 80 | DEGREE_270 = 3 81 | } degree_t; 82 | 83 | typedef enum { 84 | FLIP_HORIZONTAL = 1, 85 | FLIP_VERTICAL = 2 86 | } flipdir_t; 87 | 88 | typedef enum { 89 | SCALE_DOWN, 90 | SCALE_FIT, 91 | SCALE_WIDTH, 92 | SCALE_HEIGHT, 93 | SCALE_ZOOM 94 | } scalemode_t; 95 | 96 | typedef enum { 97 | DRAG_RELATIVE, 98 | DRAG_ABSOLUTE 99 | } dragmode_t; 100 | 101 | typedef enum { 102 | CURSOR_ARROW, 103 | CURSOR_DRAG, 104 | CURSOR_WATCH, 105 | CURSOR_LEFT, 106 | CURSOR_RIGHT, 107 | CURSOR_NONE, 108 | 109 | CURSOR_COUNT 110 | } cursor_t; 111 | 112 | typedef enum { 113 | FF_WARN = 1, 114 | FF_MARK = 2, 115 | FF_TN_INIT = 4 116 | } fileflags_t; 117 | 118 | typedef struct { 119 | const char *name; /* as given by user */ 120 | const char *path; /* always absolute */ 121 | fileflags_t flags; 122 | } fileinfo_t; 123 | 124 | /* timeouts in milliseconds: */ 125 | enum { 126 | TO_REDRAW_RESIZE = 75, 127 | TO_REDRAW_THUMBS = 200, 128 | TO_CURSOR_HIDE = 1200, 129 | TO_DOUBLE_CLICK = 300 130 | }; 131 | 132 | typedef void (*timeout_f)(void); 133 | 134 | typedef struct arl arl_t; 135 | typedef struct img img_t; 136 | typedef struct opt opt_t; 137 | typedef struct tns tns_t; 138 | typedef struct win win_t; 139 | 140 | 141 | /* autoreload.c */ 142 | 143 | struct arl { 144 | int fd; 145 | int wd_dir; 146 | int wd_file; 147 | char *filename; 148 | }; 149 | 150 | void arl_init(arl_t*); 151 | void arl_cleanup(arl_t*); 152 | void arl_setup(arl_t*, const char* /* result of realpath(3) */); 153 | bool arl_handle(arl_t*); 154 | 155 | 156 | /* commands.c */ 157 | 158 | typedef int arg_t; 159 | typedef bool (*cmd_f)(arg_t); 160 | 161 | #define G_CMD(c) g_##c, 162 | #define I_CMD(c) i_##c, 163 | #define T_CMD(c) t_##c, 164 | 165 | typedef enum { 166 | #include "commands.lst" 167 | CMD_COUNT 168 | } cmd_id_t; 169 | 170 | typedef struct { 171 | int mode; 172 | cmd_f func; 173 | } cmd_t; 174 | 175 | typedef struct { 176 | unsigned int mask; 177 | KeySym ksym; 178 | cmd_id_t cmd; 179 | arg_t arg; 180 | } keymap_t; 181 | 182 | typedef struct { 183 | unsigned int mask; 184 | unsigned int button; 185 | cmd_id_t cmd; 186 | arg_t arg; 187 | } button_t; 188 | 189 | extern const cmd_t cmds[CMD_COUNT]; 190 | 191 | 192 | /* image.c */ 193 | 194 | typedef struct { 195 | Imlib_Image im; 196 | unsigned int delay; 197 | } img_frame_t; 198 | 199 | typedef struct { 200 | img_frame_t *frames; 201 | int cap; 202 | int cnt; 203 | int sel; 204 | bool animate; 205 | int framedelay; 206 | int length; 207 | } multi_img_t; 208 | 209 | struct img { 210 | Imlib_Image im; 211 | int w; 212 | int h; 213 | 214 | win_t *win; 215 | float x; 216 | float y; 217 | 218 | scalemode_t scalemode; 219 | float zoom; 220 | 221 | bool checkpan; 222 | bool dirty; 223 | bool aa; 224 | bool alpha; 225 | 226 | Imlib_Color_Modifier cmod; 227 | int gamma; 228 | 229 | struct { 230 | bool on; 231 | int delay; 232 | } ss; 233 | 234 | multi_img_t multi; 235 | }; 236 | 237 | void img_init(img_t*, win_t*); 238 | bool img_load(img_t*, const fileinfo_t*); 239 | CLEANUP void img_close(img_t*, bool); 240 | void img_render(img_t*); 241 | bool img_fit_win(img_t*, scalemode_t); 242 | bool img_zoom(img_t*, float); 243 | bool img_zoom_in(img_t*); 244 | bool img_zoom_out(img_t*); 245 | bool img_pos(img_t*, float, float); 246 | bool img_move(img_t*, float, float); 247 | bool img_pan(img_t*, direction_t, int); 248 | bool img_pan_edge(img_t*, direction_t); 249 | void img_rotate(img_t*, degree_t); 250 | void img_flip(img_t*, flipdir_t); 251 | void img_toggle_antialias(img_t*); 252 | bool img_change_gamma(img_t*, int); 253 | bool img_frame_navigate(img_t*, int); 254 | bool img_frame_animate(img_t*); 255 | 256 | 257 | /* options.c */ 258 | 259 | struct opt { 260 | /* file list: */ 261 | char **filenames; 262 | bool from_stdin; 263 | bool to_stdout; 264 | bool recursive; 265 | int filecnt; 266 | int startnum; 267 | 268 | /* image: */ 269 | scalemode_t scalemode; 270 | float zoom; 271 | bool animate; 272 | int gamma; 273 | int slideshow; 274 | int framerate; 275 | 276 | /* window: */ 277 | bool fullscreen; 278 | bool hide_bar; 279 | long embed; 280 | char *geometry; 281 | char *res_name; 282 | 283 | /* misc flags: */ 284 | bool quiet; 285 | bool thumb_mode; 286 | bool clean_cache; 287 | bool private_mode; 288 | }; 289 | 290 | extern const opt_t *options; 291 | 292 | void print_usage(void); 293 | void print_version(void); 294 | void parse_options(int, char**); 295 | 296 | 297 | /* thumbs.c */ 298 | 299 | typedef struct { 300 | Imlib_Image im; 301 | int w; 302 | int h; 303 | int x; 304 | int y; 305 | } thumb_t; 306 | 307 | struct tns { 308 | fileinfo_t *files; 309 | thumb_t *thumbs; 310 | const int *cnt; 311 | int *sel; 312 | int initnext; 313 | int loadnext; 314 | int first, end; 315 | int r_first, r_end; 316 | 317 | win_t *win; 318 | int x; 319 | int y; 320 | int cols; 321 | int rows; 322 | int zl; 323 | int bw; 324 | int dim; 325 | 326 | bool dirty; 327 | }; 328 | 329 | void tns_clean_cache(tns_t*); 330 | void tns_init(tns_t*, fileinfo_t*, const int*, int*, win_t*); 331 | CLEANUP void tns_free(tns_t*); 332 | bool tns_load(tns_t*, int, bool, bool); 333 | void tns_unload(tns_t*, int); 334 | void tns_render(tns_t*); 335 | void tns_mark(tns_t*, int, bool); 336 | void tns_highlight(tns_t*, int, bool); 337 | bool tns_move_selection(tns_t*, direction_t, int); 338 | bool tns_scroll(tns_t*, direction_t, bool); 339 | bool tns_zoom(tns_t*, int); 340 | int tns_translate(tns_t*, int, int); 341 | 342 | 343 | /* util.c */ 344 | 345 | #include 346 | 347 | typedef struct { 348 | DIR *dir; 349 | char *name; 350 | int d; 351 | bool recursive; 352 | 353 | char **stack; 354 | int stcap; 355 | int stlen; 356 | } r_dir_t; 357 | 358 | extern const char *progname; 359 | 360 | void* emalloc(size_t); 361 | void* erealloc(void*, size_t); 362 | char* estrdup(const char*); 363 | void error(int, int, const char*, ...); 364 | void size_readable(float*, const char**); 365 | int r_opendir(r_dir_t*, const char*, bool); 366 | int r_closedir(r_dir_t*); 367 | char* r_readdir(r_dir_t*, bool); 368 | int r_mkdir(char*); 369 | 370 | 371 | /* window.c */ 372 | 373 | #include 374 | #include 375 | 376 | enum { 377 | BAR_L_LEN = 512, 378 | BAR_R_LEN = 64 379 | }; 380 | 381 | enum { 382 | ATOM_WM_DELETE_WINDOW, 383 | ATOM__NET_WM_NAME, 384 | ATOM__NET_WM_ICON_NAME, 385 | ATOM__NET_WM_ICON, 386 | ATOM__NET_WM_STATE, 387 | ATOM__NET_WM_STATE_FULLSCREEN, 388 | ATOM_COUNT 389 | }; 390 | 391 | typedef struct { 392 | Display *dpy; 393 | int scr; 394 | int scrw, scrh; 395 | Visual *vis; 396 | Colormap cmap; 397 | int depth; 398 | } win_env_t; 399 | 400 | typedef struct { 401 | size_t size; 402 | char *p; 403 | char *buf; 404 | } win_bar_t; 405 | 406 | struct win { 407 | Window xwin; 408 | win_env_t env; 409 | 410 | XftColor bg; 411 | XftColor fg; 412 | 413 | int x; 414 | int y; 415 | unsigned int w; 416 | unsigned int h; /* = win height - bar height */ 417 | unsigned int bw; 418 | 419 | struct { 420 | int w; 421 | int h; 422 | Pixmap pm; 423 | } buf; 424 | 425 | struct { 426 | unsigned int h; 427 | win_bar_t l; 428 | win_bar_t r; 429 | } bar; 430 | }; 431 | 432 | extern Atom atoms[ATOM_COUNT]; 433 | 434 | void win_init(win_t*); 435 | void win_open(win_t*); 436 | CLEANUP void win_close(win_t*); 437 | bool win_configure(win_t*, XConfigureEvent*); 438 | void win_toggle_fullscreen(win_t*); 439 | void win_toggle_bar(win_t*); 440 | void win_clear(win_t*); 441 | void win_draw(win_t*); 442 | void win_draw_rect(win_t*, int, int, int, int, bool, int, unsigned long); 443 | void win_set_title(win_t*, const char*); 444 | void win_set_cursor(win_t*, cursor_t); 445 | void win_cursor_pos(win_t*, int*, int*); 446 | 447 | #endif /* SXIV_H */ 448 | 449 | -------------------------------------------------------------------------------- /thumbs.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | #define _THUMBS_CONFIG 21 | #include "config.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #if HAVE_LIBEXIF 32 | #include 33 | void exif_auto_orientate(const fileinfo_t*); 34 | #endif 35 | Imlib_Image img_open(const fileinfo_t*); 36 | 37 | static char *cache_dir; 38 | 39 | char* tns_cache_filepath(const char *filepath) 40 | { 41 | size_t len; 42 | char *cfile = NULL; 43 | 44 | if (*filepath != '/') 45 | return NULL; 46 | 47 | if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) { 48 | /* don't cache images inside the cache directory! */ 49 | len = strlen(cache_dir) + strlen(filepath) + 2; 50 | cfile = (char*) emalloc(len); 51 | snprintf(cfile, len, "%s/%s", cache_dir, filepath + 1); 52 | } 53 | return cfile; 54 | } 55 | 56 | Imlib_Image tns_cache_load(const char *filepath, bool *outdated) 57 | { 58 | char *cfile; 59 | struct stat cstats, fstats; 60 | Imlib_Image im = NULL; 61 | 62 | if (stat(filepath, &fstats) < 0) 63 | return NULL; 64 | 65 | if ((cfile = tns_cache_filepath(filepath)) != NULL) { 66 | if (stat(cfile, &cstats) == 0) { 67 | if (cstats.st_mtime == fstats.st_mtime) 68 | im = imlib_load_image(cfile); 69 | else 70 | *outdated = true; 71 | } 72 | free(cfile); 73 | } 74 | return im; 75 | } 76 | 77 | void tns_cache_write(Imlib_Image im, const char *filepath, bool force) 78 | { 79 | char *cfile, *dirend; 80 | struct stat cstats, fstats; 81 | struct utimbuf times; 82 | Imlib_Load_Error err; 83 | 84 | if (options->private_mode) 85 | return; 86 | 87 | if (stat(filepath, &fstats) < 0) 88 | return; 89 | 90 | if ((cfile = tns_cache_filepath(filepath)) != NULL) { 91 | if (force || stat(cfile, &cstats) < 0 || 92 | cstats.st_mtime != fstats.st_mtime) 93 | { 94 | if ((dirend = strrchr(cfile, '/')) != NULL) { 95 | *dirend = '\0'; 96 | if (r_mkdir(cfile) == -1) { 97 | error(0, errno, "%s", cfile); 98 | goto end; 99 | } 100 | *dirend = '/'; 101 | } 102 | imlib_context_set_image(im); 103 | if (imlib_image_has_alpha()) { 104 | imlib_image_set_format("png"); 105 | } else { 106 | imlib_image_set_format("jpg"); 107 | imlib_image_attach_data_value("quality", NULL, 90, NULL); 108 | } 109 | imlib_save_image_with_error_return(cfile, &err); 110 | if (err) 111 | goto end; 112 | times.actime = fstats.st_atime; 113 | times.modtime = fstats.st_mtime; 114 | utime(cfile, ×); 115 | } 116 | end: 117 | free(cfile); 118 | } 119 | } 120 | 121 | void tns_clean_cache(tns_t *tns) 122 | { 123 | int dirlen; 124 | char *cfile, *filename; 125 | r_dir_t dir; 126 | 127 | if (r_opendir(&dir, cache_dir, true) < 0) { 128 | error(0, errno, "%s", cache_dir); 129 | return; 130 | } 131 | 132 | dirlen = strlen(cache_dir); 133 | 134 | while ((cfile = r_readdir(&dir, false)) != NULL) { 135 | filename = cfile + dirlen; 136 | if (access(filename, F_OK) < 0) { 137 | if (unlink(cfile) < 0) 138 | error(0, errno, "%s", cfile); 139 | } 140 | free(cfile); 141 | } 142 | r_closedir(&dir); 143 | } 144 | 145 | 146 | void tns_init(tns_t *tns, fileinfo_t *files, const int *cnt, int *sel, 147 | win_t *win) 148 | { 149 | int len; 150 | const char *homedir, *dsuffix = ""; 151 | 152 | if (cnt != NULL && *cnt > 0) { 153 | tns->thumbs = (thumb_t*) emalloc(*cnt * sizeof(thumb_t)); 154 | memset(tns->thumbs, 0, *cnt * sizeof(thumb_t)); 155 | } else { 156 | tns->thumbs = NULL; 157 | } 158 | tns->files = files; 159 | tns->cnt = cnt; 160 | tns->initnext = tns->loadnext = 0; 161 | tns->first = tns->end = tns->r_first = tns->r_end = 0; 162 | tns->sel = sel; 163 | tns->win = win; 164 | tns->dirty = false; 165 | 166 | tns->zl = THUMB_SIZE; 167 | tns_zoom(tns, 0); 168 | 169 | if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') { 170 | homedir = getenv("HOME"); 171 | dsuffix = "/.cache"; 172 | } 173 | if (homedir != NULL) { 174 | free(cache_dir); 175 | len = strlen(homedir) + strlen(dsuffix) + 6; 176 | cache_dir = (char*) emalloc(len); 177 | snprintf(cache_dir, len, "%s%s/sxiv", homedir, dsuffix); 178 | } else { 179 | error(0, 0, "Cache directory not found"); 180 | } 181 | } 182 | 183 | CLEANUP void tns_free(tns_t *tns) 184 | { 185 | int i; 186 | 187 | if (tns->thumbs != NULL) { 188 | for (i = 0; i < *tns->cnt; i++) { 189 | if (tns->thumbs[i].im != NULL) { 190 | imlib_context_set_image(tns->thumbs[i].im); 191 | imlib_free_image(); 192 | } 193 | } 194 | free(tns->thumbs); 195 | tns->thumbs = NULL; 196 | } 197 | 198 | free(cache_dir); 199 | cache_dir = NULL; 200 | } 201 | 202 | Imlib_Image tns_scale_down(Imlib_Image im, int dim) 203 | { 204 | int w, h; 205 | float z, zw, zh; 206 | 207 | imlib_context_set_image(im); 208 | w = imlib_image_get_width(); 209 | h = imlib_image_get_height(); 210 | zw = (float) dim / (float) w; 211 | zh = (float) dim / (float) h; 212 | z = MIN(zw, zh); 213 | z = MIN(z, 1.0); 214 | 215 | if (z < 1.0) { 216 | imlib_context_set_anti_alias(1); 217 | im = imlib_create_cropped_scaled_image(0, 0, w, h, 218 | MAX(z * w, 1), MAX(z * h, 1)); 219 | if (im == NULL) 220 | error(EXIT_FAILURE, ENOMEM, NULL); 221 | imlib_free_image_and_decache(); 222 | } 223 | return im; 224 | } 225 | 226 | bool tns_load(tns_t *tns, int n, bool force, bool cache_only) 227 | { 228 | int maxwh = thumb_sizes[ARRLEN(thumb_sizes)-1]; 229 | bool cache_hit = false; 230 | char *cfile; 231 | thumb_t *t; 232 | fileinfo_t *file; 233 | Imlib_Image im = NULL; 234 | 235 | if (n < 0 || n >= *tns->cnt) 236 | return false; 237 | file = &tns->files[n]; 238 | if (file->name == NULL || file->path == NULL) 239 | return false; 240 | 241 | t = &tns->thumbs[n]; 242 | 243 | if (t->im != NULL) { 244 | imlib_context_set_image(t->im); 245 | imlib_free_image(); 246 | t->im = NULL; 247 | } 248 | 249 | if (!force) { 250 | if ((im = tns_cache_load(file->path, &force)) != NULL) { 251 | imlib_context_set_image(im); 252 | if (imlib_image_get_width() < maxwh && 253 | imlib_image_get_height() < maxwh) 254 | { 255 | if ((cfile = tns_cache_filepath(file->path)) != NULL) { 256 | unlink(cfile); 257 | free(cfile); 258 | } 259 | imlib_free_image_and_decache(); 260 | im = NULL; 261 | } else { 262 | cache_hit = true; 263 | } 264 | #if HAVE_LIBEXIF 265 | } else if (!force && !options->private_mode) { 266 | int pw = 0, ph = 0, w, h, x = 0, y = 0; 267 | bool err; 268 | float zw, zh; 269 | ExifData *ed; 270 | ExifEntry *entry; 271 | ExifContent *ifd; 272 | ExifByteOrder byte_order; 273 | int tmpfd; 274 | char tmppath[] = "/tmp/sxiv-XXXXXX"; 275 | Imlib_Image tmpim; 276 | 277 | if ((ed = exif_data_new_from_file(file->path)) != NULL) { 278 | if (ed->data != NULL && ed->size > 0 && 279 | (tmpfd = mkstemp(tmppath)) >= 0) 280 | { 281 | err = write(tmpfd, ed->data, ed->size) != ed->size; 282 | close(tmpfd); 283 | 284 | if (!err && (tmpim = imlib_load_image(tmppath)) != NULL) { 285 | byte_order = exif_data_get_byte_order(ed); 286 | ifd = ed->ifd[EXIF_IFD_EXIF]; 287 | entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_X_DIMENSION); 288 | if (entry != NULL) 289 | pw = exif_get_long(entry->data, byte_order); 290 | entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_Y_DIMENSION); 291 | if (entry != NULL) 292 | ph = exif_get_long(entry->data, byte_order); 293 | 294 | imlib_context_set_image(tmpim); 295 | w = imlib_image_get_width(); 296 | h = imlib_image_get_height(); 297 | 298 | if (pw > w && ph > h && (pw - ph >= 0) == (w - h >= 0)) { 299 | zw = (float) pw / (float) w; 300 | zh = (float) ph / (float) h; 301 | if (zw < zh) { 302 | pw /= zh; 303 | x = (w - pw) / 2; 304 | w = pw; 305 | } else if (zw > zh) { 306 | ph /= zw; 307 | y = (h - ph) / 2; 308 | h = ph; 309 | } 310 | } 311 | if (w >= maxwh || h >= maxwh) { 312 | if ((im = imlib_create_cropped_image(x, y, w, h)) == NULL) 313 | error(EXIT_FAILURE, ENOMEM, NULL); 314 | } 315 | imlib_free_image_and_decache(); 316 | } 317 | unlink(tmppath); 318 | } 319 | exif_data_unref(ed); 320 | } 321 | #endif 322 | } 323 | } 324 | 325 | if (im == NULL) { 326 | if ((im = img_open(file)) == NULL) 327 | return false; 328 | } 329 | imlib_context_set_image(im); 330 | 331 | if (!cache_hit) { 332 | #if HAVE_LIBEXIF 333 | exif_auto_orientate(file); 334 | #endif 335 | im = tns_scale_down(im, maxwh); 336 | imlib_context_set_image(im); 337 | if (imlib_image_get_width() == maxwh || imlib_image_get_height() == maxwh) 338 | tns_cache_write(im, file->path, true); 339 | } 340 | 341 | if (cache_only) { 342 | imlib_free_image_and_decache(); 343 | } else { 344 | t->im = tns_scale_down(im, thumb_sizes[tns->zl]); 345 | imlib_context_set_image(t->im); 346 | t->w = imlib_image_get_width(); 347 | t->h = imlib_image_get_height(); 348 | tns->dirty = true; 349 | } 350 | file->flags |= FF_TN_INIT; 351 | 352 | if (n == tns->initnext) 353 | while (++tns->initnext < *tns->cnt && ((++file)->flags & FF_TN_INIT)); 354 | if (n == tns->loadnext && !cache_only) 355 | while (++tns->loadnext < tns->end && (++t)->im != NULL); 356 | 357 | return true; 358 | } 359 | 360 | void tns_unload(tns_t *tns, int n) 361 | { 362 | thumb_t *t; 363 | 364 | if (n < 0 || n >= *tns->cnt) 365 | return; 366 | 367 | t = &tns->thumbs[n]; 368 | 369 | if (t->im != NULL) { 370 | imlib_context_set_image(t->im); 371 | imlib_free_image(); 372 | t->im = NULL; 373 | } 374 | } 375 | 376 | void tns_check_view(tns_t *tns, bool scrolled) 377 | { 378 | int r; 379 | 380 | if (tns == NULL) 381 | return; 382 | 383 | tns->first -= tns->first % tns->cols; 384 | r = *tns->sel % tns->cols; 385 | 386 | if (scrolled) { 387 | /* move selection into visible area */ 388 | if (*tns->sel >= tns->first + tns->cols * tns->rows) 389 | *tns->sel = tns->first + r + tns->cols * (tns->rows - 1); 390 | else if (*tns->sel < tns->first) 391 | *tns->sel = tns->first + r; 392 | } else { 393 | /* scroll to selection */ 394 | if (tns->first + tns->cols * tns->rows <= *tns->sel) { 395 | tns->first = *tns->sel - r - tns->cols * (tns->rows - 1); 396 | tns->dirty = true; 397 | } else if (tns->first > *tns->sel) { 398 | tns->first = *tns->sel - r; 399 | tns->dirty = true; 400 | } 401 | } 402 | } 403 | 404 | void tns_render(tns_t *tns) 405 | { 406 | thumb_t *t; 407 | win_t *win; 408 | int i, cnt, r, x, y; 409 | 410 | if (!tns->dirty) 411 | return; 412 | 413 | win = tns->win; 414 | win_clear(win); 415 | imlib_context_set_drawable(win->buf.pm); 416 | 417 | tns->cols = MAX(1, win->w / tns->dim); 418 | tns->rows = MAX(1, win->h / tns->dim); 419 | 420 | if (*tns->cnt < tns->cols * tns->rows) { 421 | tns->first = 0; 422 | cnt = *tns->cnt; 423 | } else { 424 | tns_check_view(tns, false); 425 | cnt = tns->cols * tns->rows; 426 | if ((r = tns->first + cnt - *tns->cnt) >= tns->cols) 427 | tns->first -= r - r % tns->cols; 428 | if (r > 0) 429 | cnt -= r % tns->cols; 430 | } 431 | r = cnt % tns->cols ? 1 : 0; 432 | tns->x = x = (win->w - MIN(cnt, tns->cols) * tns->dim) / 2 + tns->bw + 3; 433 | tns->y = y = (win->h - (cnt / tns->cols + r) * tns->dim) / 2 + tns->bw + 3; 434 | tns->loadnext = *tns->cnt; 435 | tns->end = tns->first + cnt; 436 | 437 | for (i = tns->r_first; i < tns->r_end; i++) { 438 | if ((i < tns->first || i >= tns->end) && tns->thumbs[i].im != NULL) 439 | tns_unload(tns, i); 440 | } 441 | tns->r_first = tns->first; 442 | tns->r_end = tns->end; 443 | 444 | for (i = tns->first; i < tns->end; i++) { 445 | t = &tns->thumbs[i]; 446 | if (t->im != NULL) { 447 | t->x = x + (thumb_sizes[tns->zl] - t->w) / 2; 448 | t->y = y + (thumb_sizes[tns->zl] - t->h) / 2; 449 | imlib_context_set_image(t->im); 450 | imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h); 451 | if (tns->files[i].flags & FF_MARK) 452 | tns_mark(tns, i, true); 453 | } else { 454 | tns->loadnext = MIN(tns->loadnext, i); 455 | } 456 | if ((i + 1) % tns->cols == 0) { 457 | x = tns->x; 458 | y += tns->dim; 459 | } else { 460 | x += tns->dim; 461 | } 462 | } 463 | tns->dirty = false; 464 | tns_highlight(tns, *tns->sel, true); 465 | } 466 | 467 | void tns_mark(tns_t *tns, int n, bool mark) 468 | { 469 | if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { 470 | win_t *win = tns->win; 471 | thumb_t *t = &tns->thumbs[n]; 472 | unsigned long col = win->bg.pixel; 473 | int x = t->x + t->w, y = t->y + t->h; 474 | 475 | win_draw_rect(win, x - 1, y + 1, 1, tns->bw, true, 1, col); 476 | win_draw_rect(win, x + 1, y - 1, tns->bw, 1, true, 1, col); 477 | 478 | if (mark) 479 | col = win->fg.pixel; 480 | 481 | win_draw_rect(win, x, y, tns->bw + 2, tns->bw + 2, true, 1, col); 482 | 483 | if (!mark && n == *tns->sel) 484 | tns_highlight(tns, n, true); 485 | } 486 | } 487 | 488 | void tns_highlight(tns_t *tns, int n, bool hl) 489 | { 490 | if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { 491 | win_t *win = tns->win; 492 | thumb_t *t = &tns->thumbs[n]; 493 | unsigned long col = hl ? win->fg.pixel : win->bg.pixel; 494 | int oxy = (tns->bw + 1) / 2 + 1, owh = tns->bw + 2; 495 | 496 | win_draw_rect(win, t->x - oxy, t->y - oxy, t->w + owh, t->h + owh, 497 | false, tns->bw, col); 498 | 499 | if (tns->files[n].flags & FF_MARK) 500 | tns_mark(tns, n, true); 501 | } 502 | } 503 | 504 | bool tns_move_selection(tns_t *tns, direction_t dir, int cnt) 505 | { 506 | int old, max; 507 | 508 | old = *tns->sel; 509 | cnt = cnt > 1 ? cnt : 1; 510 | 511 | switch (dir) { 512 | case DIR_UP: 513 | *tns->sel = MAX(*tns->sel - cnt * tns->cols, *tns->sel % tns->cols); 514 | break; 515 | case DIR_DOWN: 516 | max = tns->cols * ((*tns->cnt - 1) / tns->cols) + 517 | MIN((*tns->cnt - 1) % tns->cols, *tns->sel % tns->cols); 518 | *tns->sel = MIN(*tns->sel + cnt * tns->cols, max); 519 | break; 520 | case DIR_LEFT: 521 | *tns->sel = MAX(*tns->sel - cnt, 0); 522 | break; 523 | case DIR_RIGHT: 524 | *tns->sel = MIN(*tns->sel + cnt, *tns->cnt - 1); 525 | break; 526 | } 527 | 528 | if (*tns->sel != old) { 529 | tns_highlight(tns, old, false); 530 | tns_check_view(tns, false); 531 | if (!tns->dirty) 532 | tns_highlight(tns, *tns->sel, true); 533 | } 534 | return *tns->sel != old; 535 | } 536 | 537 | bool tns_scroll(tns_t *tns, direction_t dir, bool screen) 538 | { 539 | int d, max, old; 540 | 541 | old = tns->first; 542 | d = tns->cols * (screen ? tns->rows : 1); 543 | 544 | if (dir == DIR_DOWN) { 545 | max = *tns->cnt - tns->cols * tns->rows; 546 | if (*tns->cnt % tns->cols != 0) 547 | max += tns->cols - *tns->cnt % tns->cols; 548 | tns->first = MIN(tns->first + d, max); 549 | } else if (dir == DIR_UP) { 550 | tns->first = MAX(tns->first - d, 0); 551 | } 552 | 553 | if (tns->first != old) { 554 | tns_check_view(tns, true); 555 | tns->dirty = true; 556 | } 557 | return tns->first != old; 558 | } 559 | 560 | bool tns_zoom(tns_t *tns, int d) 561 | { 562 | int i, oldzl; 563 | 564 | oldzl = tns->zl; 565 | tns->zl += -(d < 0) + (d > 0); 566 | tns->zl = MAX(tns->zl, 0); 567 | tns->zl = MIN(tns->zl, ARRLEN(thumb_sizes)-1); 568 | 569 | tns->bw = ((thumb_sizes[tns->zl] - 1) >> 5) + 1; 570 | tns->bw = MIN(tns->bw, 4); 571 | tns->dim = thumb_sizes[tns->zl] + 2 * tns->bw + 6; 572 | 573 | if (tns->zl != oldzl) { 574 | for (i = 0; i < *tns->cnt; i++) 575 | tns_unload(tns, i); 576 | tns->dirty = true; 577 | } 578 | return tns->zl != oldzl; 579 | } 580 | 581 | int tns_translate(tns_t *tns, int x, int y) 582 | { 583 | int n; 584 | 585 | if (x < tns->x || y < tns->y) 586 | return -1; 587 | 588 | n = tns->first + (y - tns->y) / tns->dim * tns->cols + 589 | (x - tns->x) / tns->dim; 590 | if (n >= *tns->cnt) 591 | n = -1; 592 | 593 | return n; 594 | } 595 | -------------------------------------------------------------------------------- /utf8.h: -------------------------------------------------------------------------------- 1 | /* Branchless UTF-8 decoder 2 | * 3 | * This is free and unencumbered software released into the public domain. 4 | */ 5 | #ifndef UTF8_H 6 | #define UTF8_H 7 | 8 | #include 9 | 10 | /* Decode the next character, C, from BUF, reporting errors in E. 11 | * 12 | * Since this is a branchless decoder, four bytes will be read from the 13 | * buffer regardless of the actual length of the next character. This 14 | * means the buffer _must_ have at least three bytes of zero padding 15 | * following the end of the data stream. 16 | * 17 | * Errors are reported in E, which will be non-zero if the parsed 18 | * character was somehow invalid: invalid byte sequence, non-canonical 19 | * encoding, or a surrogate half. 20 | * 21 | * The function returns a pointer to the next character. When an error 22 | * occurs, this pointer will be a guess that depends on the particular 23 | * error, but it will always advance at least one byte. 24 | */ 25 | static void * 26 | utf8_decode(void *buf, uint32_t *c, int *e) 27 | { 28 | static const char lengths[] = { 29 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30 | 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 31 | }; 32 | static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; 33 | static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; 34 | static const int shiftc[] = {0, 18, 12, 6, 0}; 35 | static const int shifte[] = {0, 6, 4, 2, 0}; 36 | 37 | unsigned char *s = buf; 38 | int len = lengths[s[0] >> 3]; 39 | 40 | /* Compute the pointer to the next character early so that the next 41 | * iteration can start working on the next character. Neither Clang 42 | * nor GCC figure out this reordering on their own. 43 | */ 44 | unsigned char *next = s + len + !len; 45 | 46 | /* Assume a four-byte character and load four bytes. Unused bits are 47 | * shifted out. 48 | */ 49 | *c = (uint32_t)(s[0] & masks[len]) << 18; 50 | *c |= (uint32_t)(s[1] & 0x3f) << 12; 51 | *c |= (uint32_t)(s[2] & 0x3f) << 6; 52 | *c |= (uint32_t)(s[3] & 0x3f) << 0; 53 | *c >>= shiftc[len]; 54 | 55 | /* Accumulate the various error conditions. */ 56 | *e = (*c < mins[len]) << 6; // non-canonical encoding 57 | *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? 58 | *e |= (*c > 0x10FFFF) << 8; // out of range? 59 | *e |= (s[1] & 0xc0) >> 2; 60 | *e |= (s[2] & 0xc0) >> 4; 61 | *e |= (s[3] ) >> 6; 62 | *e ^= 0x2a; // top two bits of each tail byte correct? 63 | *e >>= shifte[len]; 64 | 65 | return next; 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | const char *progname; 29 | 30 | void* emalloc(size_t size) 31 | { 32 | void *ptr; 33 | 34 | ptr = malloc(size); 35 | if (ptr == NULL) 36 | error(EXIT_FAILURE, errno, NULL); 37 | return ptr; 38 | } 39 | 40 | void* erealloc(void *ptr, size_t size) 41 | { 42 | ptr = realloc(ptr, size); 43 | if (ptr == NULL) 44 | error(EXIT_FAILURE, errno, NULL); 45 | return ptr; 46 | } 47 | 48 | char* estrdup(const char *s) 49 | { 50 | char *d; 51 | size_t n = strlen(s) + 1; 52 | 53 | d = malloc(n); 54 | if (d == NULL) 55 | error(EXIT_FAILURE, errno, NULL); 56 | memcpy(d, s, n); 57 | return d; 58 | } 59 | 60 | void error(int eval, int err, const char* fmt, ...) 61 | { 62 | va_list ap; 63 | 64 | if (eval == 0 && options->quiet) 65 | return; 66 | 67 | fflush(stdout); 68 | fprintf(stderr, "%s: ", progname); 69 | va_start(ap, fmt); 70 | if (fmt != NULL) 71 | vfprintf(stderr, fmt, ap); 72 | va_end(ap); 73 | if (err != 0) 74 | fprintf(stderr, "%s%s", fmt != NULL ? ": " : "", strerror(err)); 75 | fputc('\n', stderr); 76 | 77 | if (eval != 0) 78 | exit(eval); 79 | } 80 | 81 | void size_readable(float *size, const char **unit) 82 | { 83 | const char *units[] = { "", "K", "M", "G" }; 84 | int i; 85 | 86 | for (i = 0; i < ARRLEN(units) && *size > 1024.0; i++) 87 | *size /= 1024.0; 88 | *unit = units[MIN(i, ARRLEN(units) - 1)]; 89 | } 90 | 91 | int r_opendir(r_dir_t *rdir, const char *dirname, bool recursive) 92 | { 93 | if (*dirname == '\0') 94 | return -1; 95 | 96 | if ((rdir->dir = opendir(dirname)) == NULL) { 97 | rdir->name = NULL; 98 | rdir->stack = NULL; 99 | return -1; 100 | } 101 | 102 | rdir->stcap = 512; 103 | rdir->stack = (char**) emalloc(rdir->stcap * sizeof(char*)); 104 | rdir->stlen = 0; 105 | 106 | rdir->name = (char*) dirname; 107 | rdir->d = 0; 108 | rdir->recursive = recursive; 109 | 110 | return 0; 111 | } 112 | 113 | int r_closedir(r_dir_t *rdir) 114 | { 115 | int ret = 0; 116 | 117 | if (rdir->stack != NULL) { 118 | while (rdir->stlen > 0) 119 | free(rdir->stack[--rdir->stlen]); 120 | free(rdir->stack); 121 | rdir->stack = NULL; 122 | } 123 | 124 | if (rdir->dir != NULL) { 125 | if ((ret = closedir(rdir->dir)) == 0) 126 | rdir->dir = NULL; 127 | } 128 | 129 | if (rdir->d != 0) { 130 | free(rdir->name); 131 | rdir->name = NULL; 132 | } 133 | 134 | return ret; 135 | } 136 | 137 | char* r_readdir(r_dir_t *rdir, bool skip_dotfiles) 138 | { 139 | size_t len; 140 | char *filename; 141 | struct dirent *dentry; 142 | struct stat fstats; 143 | 144 | while (true) { 145 | if (rdir->dir != NULL && (dentry = readdir(rdir->dir)) != NULL) { 146 | if (dentry->d_name[0] == '.') { 147 | if (skip_dotfiles) 148 | continue; 149 | if (dentry->d_name[1] == '\0') 150 | continue; 151 | if (dentry->d_name[1] == '.' && dentry->d_name[2] == '\0') 152 | continue; 153 | } 154 | 155 | len = strlen(rdir->name) + strlen(dentry->d_name) + 2; 156 | filename = (char*) emalloc(len); 157 | snprintf(filename, len, "%s%s%s", rdir->name, 158 | rdir->name[strlen(rdir->name)-1] == '/' ? "" : "/", 159 | dentry->d_name); 160 | 161 | if (stat(filename, &fstats) < 0) 162 | continue; 163 | if (S_ISDIR(fstats.st_mode)) { 164 | /* put subdirectory on the stack */ 165 | if (rdir->stlen == rdir->stcap) { 166 | rdir->stcap *= 2; 167 | rdir->stack = (char**) erealloc(rdir->stack, 168 | rdir->stcap * sizeof(char*)); 169 | } 170 | rdir->stack[rdir->stlen++] = filename; 171 | continue; 172 | } 173 | return filename; 174 | } 175 | 176 | if (rdir->recursive && rdir->stlen > 0) { 177 | /* open next subdirectory */ 178 | closedir(rdir->dir); 179 | if (rdir->d != 0) 180 | free(rdir->name); 181 | rdir->name = rdir->stack[--rdir->stlen]; 182 | rdir->d = 1; 183 | if ((rdir->dir = opendir(rdir->name)) == NULL) 184 | error(0, errno, "%s", rdir->name); 185 | continue; 186 | } 187 | /* no more entries */ 188 | break; 189 | } 190 | return NULL; 191 | } 192 | 193 | int r_mkdir(char *path) 194 | { 195 | char c, *s = path; 196 | struct stat st; 197 | 198 | while (*s != '\0') { 199 | if (*s == '/') { 200 | s++; 201 | continue; 202 | } 203 | for (; *s != '\0' && *s != '/'; s++); 204 | c = *s; 205 | *s = '\0'; 206 | if (mkdir(path, 0755) == -1) 207 | if (errno != EEXIST || stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) 208 | return -1; 209 | *s = c; 210 | } 211 | return 0; 212 | } 213 | 214 | -------------------------------------------------------------------------------- /window.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2011-2013 Bert Muennich 2 | * 3 | * This file is part of sxiv. 4 | * 5 | * sxiv is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation; either version 2 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * sxiv is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with sxiv. If not, see . 17 | */ 18 | 19 | #include "sxiv.h" 20 | #define _WINDOW_CONFIG 21 | #include "config.h" 22 | #include "icon/data.h" 23 | #include "utf8.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define RES_CLASS "Sxiv" 33 | 34 | enum { 35 | H_TEXT_PAD = 5, 36 | V_TEXT_PAD = 1 37 | }; 38 | 39 | static struct { 40 | int name; 41 | Cursor icon; 42 | } cursors[CURSOR_COUNT] = { 43 | { XC_left_ptr }, { XC_dotbox }, { XC_watch }, 44 | { XC_sb_left_arrow }, { XC_sb_right_arrow } 45 | }; 46 | 47 | static GC gc; 48 | 49 | static XftFont *font; 50 | static int fontheight; 51 | static double fontsize; 52 | static int barheight; 53 | 54 | Atom atoms[ATOM_COUNT]; 55 | 56 | void win_init_font(const win_env_t *e, const char *fontstr) 57 | { 58 | if ((font = XftFontOpenName(e->dpy, e->scr, fontstr)) == NULL) 59 | error(EXIT_FAILURE, 0, "Error loading font '%s'", fontstr); 60 | fontheight = font->ascent + font->descent; 61 | FcPatternGetDouble(font->pattern, FC_SIZE, 0, &fontsize); 62 | barheight = fontheight + 2 * V_TEXT_PAD; 63 | } 64 | 65 | void win_alloc_color(const win_env_t *e, const char *name, XftColor *col) 66 | { 67 | if (!XftColorAllocName(e->dpy, DefaultVisual(e->dpy, e->scr), 68 | DefaultColormap(e->dpy, e->scr), name, col)) 69 | { 70 | error(EXIT_FAILURE, 0, "Error allocating color '%s'", name); 71 | } 72 | } 73 | 74 | const char* win_res(XrmDatabase db, const char *name, const char *def) 75 | { 76 | char *type; 77 | XrmValue ret; 78 | 79 | if (db != None && 80 | XrmGetResource(db, name, name, &type, &ret) && 81 | STREQ(type, "String")) 82 | { 83 | return ret.addr; 84 | } else { 85 | return def; 86 | } 87 | } 88 | 89 | #define INIT_ATOM_(atom) \ 90 | atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False); 91 | 92 | void win_init(win_t *win) 93 | { 94 | win_env_t *e; 95 | const char *bg, *fg, *f; 96 | char *res_man; 97 | XrmDatabase db; 98 | 99 | memset(win, 0, sizeof(win_t)); 100 | 101 | e = &win->env; 102 | if ((e->dpy = XOpenDisplay(NULL)) == NULL) 103 | error(EXIT_FAILURE, 0, "Error opening X display"); 104 | 105 | e->scr = DefaultScreen(e->dpy); 106 | e->scrw = DisplayWidth(e->dpy, e->scr); 107 | e->scrh = DisplayHeight(e->dpy, e->scr); 108 | e->vis = DefaultVisual(e->dpy, e->scr); 109 | e->cmap = DefaultColormap(e->dpy, e->scr); 110 | e->depth = DefaultDepth(e->dpy, e->scr); 111 | 112 | if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0) 113 | error(0, 0, "No locale support"); 114 | 115 | XrmInitialize(); 116 | res_man = XResourceManagerString(e->dpy); 117 | db = res_man != NULL ? XrmGetStringDatabase(res_man) : None; 118 | 119 | f = win_res(db, RES_CLASS ".font", "monospace-8"); 120 | win_init_font(e, f); 121 | 122 | bg = win_res(db, RES_CLASS ".background", "white"); 123 | fg = win_res(db, RES_CLASS ".foreground", "black"); 124 | win_alloc_color(e, bg, &win->bg); 125 | win_alloc_color(e, fg, &win->fg); 126 | 127 | win->bar.l.size = BAR_L_LEN; 128 | win->bar.r.size = BAR_R_LEN; 129 | /* 3 padding bytes needed by utf8_decode */ 130 | win->bar.l.buf = emalloc(win->bar.l.size + 3); 131 | win->bar.l.buf[0] = '\0'; 132 | win->bar.r.buf = emalloc(win->bar.r.size + 3); 133 | win->bar.r.buf[0] = '\0'; 134 | win->bar.h = options->hide_bar ? 0 : barheight; 135 | 136 | INIT_ATOM_(WM_DELETE_WINDOW); 137 | INIT_ATOM_(_NET_WM_NAME); 138 | INIT_ATOM_(_NET_WM_ICON_NAME); 139 | INIT_ATOM_(_NET_WM_ICON); 140 | INIT_ATOM_(_NET_WM_STATE); 141 | INIT_ATOM_(_NET_WM_STATE_FULLSCREEN); 142 | } 143 | 144 | void win_open(win_t *win) 145 | { 146 | int c, i, j, n; 147 | long parent; 148 | win_env_t *e; 149 | XClassHint classhint; 150 | unsigned long *icon_data; 151 | XColor col; 152 | Cursor *cnone = &cursors[CURSOR_NONE].icon; 153 | char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; 154 | Pixmap none; 155 | int gmask; 156 | XSizeHints sizehints; 157 | 158 | e = &win->env; 159 | parent = options->embed != 0 ? options->embed : RootWindow(e->dpy, e->scr); 160 | 161 | sizehints.flags = PWinGravity; 162 | sizehints.win_gravity = NorthWestGravity; 163 | 164 | /* determine window offsets, width & height */ 165 | if (options->geometry == NULL) 166 | gmask = 0; 167 | else 168 | gmask = XParseGeometry(options->geometry, &win->x, &win->y, 169 | &win->w, &win->h); 170 | if ((gmask & WidthValue) != 0) 171 | sizehints.flags |= USSize; 172 | else 173 | win->w = WIN_WIDTH; 174 | if ((gmask & HeightValue) != 0) 175 | sizehints.flags |= USSize; 176 | else 177 | win->h = WIN_HEIGHT; 178 | if ((gmask & XValue) != 0) { 179 | if ((gmask & XNegative) != 0) { 180 | win->x += e->scrw - win->w; 181 | sizehints.win_gravity = NorthEastGravity; 182 | } 183 | sizehints.flags |= USPosition; 184 | } else { 185 | win->x = 0; 186 | } 187 | if ((gmask & YValue) != 0) { 188 | if ((gmask & YNegative) != 0) { 189 | win->y += e->scrh - win->h; 190 | sizehints.win_gravity = sizehints.win_gravity == NorthEastGravity 191 | ? SouthEastGravity : SouthWestGravity; 192 | } 193 | sizehints.flags |= USPosition; 194 | } else { 195 | win->y = 0; 196 | } 197 | 198 | win->xwin = XCreateWindow(e->dpy, parent, 199 | win->x, win->y, win->w, win->h, 0, 200 | e->depth, InputOutput, e->vis, 0, NULL); 201 | if (win->xwin == None) 202 | error(EXIT_FAILURE, 0, "Error creating X window"); 203 | 204 | XSelectInput(e->dpy, win->xwin, 205 | ButtonReleaseMask | ButtonPressMask | KeyPressMask | 206 | PointerMotionMask | StructureNotifyMask); 207 | 208 | for (i = 0; i < ARRLEN(cursors); i++) { 209 | if (i != CURSOR_NONE) 210 | cursors[i].icon = XCreateFontCursor(e->dpy, cursors[i].name); 211 | } 212 | if (XAllocNamedColor(e->dpy, DefaultColormap(e->dpy, e->scr), "black", 213 | &col, &col) == 0) 214 | { 215 | error(EXIT_FAILURE, 0, "Error allocating color 'black'"); 216 | } 217 | none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8); 218 | *cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0); 219 | 220 | gc = XCreateGC(e->dpy, win->xwin, 0, None); 221 | 222 | n = icons[ARRLEN(icons)-1].size; 223 | icon_data = emalloc((n * n + 2) * sizeof(*icon_data)); 224 | 225 | for (i = 0; i < ARRLEN(icons); i++) { 226 | n = 0; 227 | icon_data[n++] = icons[i].size; 228 | icon_data[n++] = icons[i].size; 229 | 230 | for (j = 0; j < icons[i].cnt; j++) { 231 | for (c = icons[i].data[j] >> 4; c >= 0; c--) 232 | icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F]; 233 | } 234 | XChangeProperty(e->dpy, win->xwin, 235 | atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32, 236 | i == 0 ? PropModeReplace : PropModeAppend, 237 | (unsigned char *) icon_data, n); 238 | } 239 | free(icon_data); 240 | 241 | win_set_title(win, "sxiv"); 242 | 243 | classhint.res_class = RES_CLASS; 244 | classhint.res_name = options->res_name != NULL ? options->res_name : "sxiv"; 245 | XSetClassHint(e->dpy, win->xwin, &classhint); 246 | 247 | XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1); 248 | 249 | sizehints.width = win->w; 250 | sizehints.height = win->h; 251 | sizehints.x = win->x; 252 | sizehints.y = win->y; 253 | XSetWMNormalHints(win->env.dpy, win->xwin, &sizehints); 254 | 255 | win->h -= win->bar.h; 256 | 257 | win->buf.w = e->scrw; 258 | win->buf.h = e->scrh; 259 | win->buf.pm = XCreatePixmap(e->dpy, win->xwin, 260 | win->buf.w, win->buf.h, e->depth); 261 | XSetForeground(e->dpy, gc, win->bg.pixel); 262 | XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); 263 | XSetWindowBackgroundPixmap(e->dpy, win->xwin, win->buf.pm); 264 | 265 | XMapWindow(e->dpy, win->xwin); 266 | XFlush(e->dpy); 267 | 268 | if (options->fullscreen) 269 | win_toggle_fullscreen(win); 270 | } 271 | 272 | CLEANUP void win_close(win_t *win) 273 | { 274 | int i; 275 | 276 | for (i = 0; i < ARRLEN(cursors); i++) 277 | XFreeCursor(win->env.dpy, cursors[i].icon); 278 | 279 | XFreeGC(win->env.dpy, gc); 280 | 281 | XDestroyWindow(win->env.dpy, win->xwin); 282 | XCloseDisplay(win->env.dpy); 283 | } 284 | 285 | bool win_configure(win_t *win, XConfigureEvent *c) 286 | { 287 | bool changed; 288 | 289 | changed = win->w != c->width || win->h + win->bar.h != c->height; 290 | 291 | win->x = c->x; 292 | win->y = c->y; 293 | win->w = c->width; 294 | win->h = c->height - win->bar.h; 295 | win->bw = c->border_width; 296 | 297 | return changed; 298 | } 299 | 300 | void win_toggle_fullscreen(win_t *win) 301 | { 302 | XEvent ev; 303 | XClientMessageEvent *cm; 304 | 305 | memset(&ev, 0, sizeof(ev)); 306 | ev.type = ClientMessage; 307 | 308 | cm = &ev.xclient; 309 | cm->window = win->xwin; 310 | cm->message_type = atoms[ATOM__NET_WM_STATE]; 311 | cm->format = 32; 312 | cm->data.l[0] = 2; // toggle 313 | cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN]; 314 | 315 | XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False, 316 | SubstructureNotifyMask | SubstructureRedirectMask, &ev); 317 | } 318 | 319 | void win_toggle_bar(win_t *win) 320 | { 321 | if (win->bar.h != 0) { 322 | win->h += win->bar.h; 323 | win->bar.h = 0; 324 | } else { 325 | win->bar.h = barheight; 326 | win->h -= win->bar.h; 327 | } 328 | } 329 | 330 | void win_clear(win_t *win) 331 | { 332 | win_env_t *e; 333 | 334 | e = &win->env; 335 | 336 | if (win->w > win->buf.w || win->h + win->bar.h > win->buf.h) { 337 | XFreePixmap(e->dpy, win->buf.pm); 338 | win->buf.w = MAX(win->buf.w, win->w); 339 | win->buf.h = MAX(win->buf.h, win->h + win->bar.h); 340 | win->buf.pm = XCreatePixmap(e->dpy, win->xwin, 341 | win->buf.w, win->buf.h, e->depth); 342 | } 343 | XSetForeground(e->dpy, gc, win->bg.pixel); 344 | XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); 345 | } 346 | 347 | #define TEXTWIDTH(win, text, len) \ 348 | win_draw_text(win, NULL, NULL, 0, 0, text, len, 0) 349 | 350 | int win_draw_text(win_t *win, XftDraw *d, const XftColor *color, int x, int y, 351 | char *text, int len, int w) 352 | { 353 | int err, tw = 0; 354 | char *t, *next; 355 | uint32_t rune; 356 | XftFont *f; 357 | FcCharSet *fccharset; 358 | XGlyphInfo ext; 359 | 360 | for (t = text; t - text < len; t = next) { 361 | next = utf8_decode(t, &rune, &err); 362 | if (XftCharExists(win->env.dpy, font, rune)) { 363 | f = font; 364 | } else { /* fallback font */ 365 | fccharset = FcCharSetCreate(); 366 | FcCharSetAddChar(fccharset, rune); 367 | f = XftFontOpen(win->env.dpy, win->env.scr, FC_CHARSET, FcTypeCharSet, 368 | fccharset, FC_SCALABLE, FcTypeBool, FcTrue, 369 | FC_SIZE, FcTypeDouble, fontsize, NULL); 370 | FcCharSetDestroy(fccharset); 371 | } 372 | XftTextExtentsUtf8(win->env.dpy, f, (XftChar8*)t, next - t, &ext); 373 | tw += ext.xOff; 374 | if (tw <= w) { 375 | XftDrawStringUtf8(d, color, f, x, y, (XftChar8*)t, next - t); 376 | x += ext.xOff; 377 | } 378 | if (f != font) 379 | XftFontClose(win->env.dpy, f); 380 | } 381 | return tw; 382 | } 383 | 384 | void win_draw_bar(win_t *win) 385 | { 386 | int len, x, y, w, tw; 387 | win_env_t *e; 388 | win_bar_t *l, *r; 389 | XftDraw *d; 390 | 391 | if ((l = &win->bar.l)->buf == NULL || (r = &win->bar.r)->buf == NULL) 392 | return; 393 | 394 | e = &win->env; 395 | y = win->h + font->ascent + V_TEXT_PAD; 396 | w = win->w - 2*H_TEXT_PAD; 397 | d = XftDrawCreate(e->dpy, win->buf.pm, DefaultVisual(e->dpy, e->scr), 398 | DefaultColormap(e->dpy, e->scr)); 399 | 400 | XSetForeground(e->dpy, gc, win->fg.pixel); 401 | XFillRectangle(e->dpy, win->buf.pm, gc, 0, win->h, win->w, win->bar.h); 402 | 403 | XSetForeground(e->dpy, gc, win->bg.pixel); 404 | XSetBackground(e->dpy, gc, win->fg.pixel); 405 | 406 | if ((len = strlen(r->buf)) > 0) { 407 | if ((tw = TEXTWIDTH(win, r->buf, len)) > w) 408 | return; 409 | x = win->w - tw - H_TEXT_PAD; 410 | w -= tw; 411 | win_draw_text(win, d, &win->bg, x, y, r->buf, len, tw); 412 | } 413 | if ((len = strlen(l->buf)) > 0) { 414 | x = H_TEXT_PAD; 415 | w -= 2 * H_TEXT_PAD; /* gap between left and right parts */ 416 | win_draw_text(win, d, &win->bg, x, y, l->buf, len, w); 417 | } 418 | XftDrawDestroy(d); 419 | } 420 | 421 | void win_draw(win_t *win) 422 | { 423 | if (win->bar.h > 0) 424 | win_draw_bar(win); 425 | 426 | XSetWindowBackgroundPixmap(win->env.dpy, win->xwin, win->buf.pm); 427 | XClearWindow(win->env.dpy, win->xwin); 428 | XFlush(win->env.dpy); 429 | } 430 | 431 | void win_draw_rect(win_t *win, int x, int y, int w, int h, bool fill, int lw, 432 | unsigned long col) 433 | { 434 | XGCValues gcval; 435 | 436 | gcval.line_width = lw; 437 | gcval.foreground = col; 438 | XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval); 439 | 440 | if (fill) 441 | XFillRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); 442 | else 443 | XDrawRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); 444 | } 445 | 446 | void win_set_title(win_t *win, const char *title) 447 | { 448 | XStoreName(win->env.dpy, win->xwin, title); 449 | XSetIconName(win->env.dpy, win->xwin, title); 450 | 451 | XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_NAME], 452 | XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, 453 | PropModeReplace, (unsigned char *) title, strlen(title)); 454 | XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_ICON_NAME], 455 | XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, 456 | PropModeReplace, (unsigned char *) title, strlen(title)); 457 | } 458 | 459 | void win_set_cursor(win_t *win, cursor_t cursor) 460 | { 461 | if (cursor >= 0 && cursor < ARRLEN(cursors)) { 462 | XDefineCursor(win->env.dpy, win->xwin, cursors[cursor].icon); 463 | XFlush(win->env.dpy); 464 | } 465 | } 466 | 467 | void win_cursor_pos(win_t *win, int *x, int *y) 468 | { 469 | int i; 470 | unsigned int ui; 471 | Window w; 472 | 473 | if (!XQueryPointer(win->env.dpy, win->xwin, &w, &w, &i, &i, x, y, &ui)) 474 | *x = *y = 0; 475 | } 476 | 477 | --------------------------------------------------------------------------------