@ -1,3 +1,4 @@ | |||
#!/bin/bash | |||
date > $HOME/last | |||
~/.local/bin/firefox-sync |
@ -1,18 +0,0 @@ | |||
# vim:ft=bash | |||
# Environment variables | |||
export GOPATH=$HOME/go | |||
export _JAVA_AWT_WM_NONREPARENTING=1 | |||
export AWT_TOOLKIT=MToolkit | |||
export ANDROID_HOME=~/Android/Sdk | |||
export FLUTTER_HOME=~/flutter | |||
export TMUX_PLUGIN_MANAGER_PATH=~/.tmux/plugins | |||
export BORG_KEYS_DIR=~/.keys/borg | |||
export BROWSER=brave | |||
export DEFAULT_RECIPIENT="yigitcolakoglu@hotmail.com" | |||
export EDITOR=vim | |||
# Setup PATH | |||
export PATH=$PATH:$ANDROID_HOME/tools | |||
export PATH=$PATH:$ANDROID_HOME/platform-tools | |||
export PATH=$PATH:$FLUTTER_HOME/bin | |||
export PATH="$PATH:/home/yigit/.local/bin:/home/yigit/.gem/ruby/2.7.0/bin:$GOPATH/bin:$GOPATH/binexport:/home/yigit/.local/bin" |
@ -0,0 +1,57 @@ | |||
# Prerequisites | |||
*.d | |||
# Object files | |||
*.o | |||
*.ko | |||
*.obj | |||
*.elf | |||
# Linker output | |||
*.ilk | |||
*.map | |||
*.exp | |||
# Precompiled Headers | |||
*.gch | |||
*.pch | |||
# Libraries | |||
*.lib | |||
*.a | |||
*.la | |||
*.lo | |||
# Shared objects (inc. Windows DLLs) | |||
*.dll | |||
*.so | |||
*.so.* | |||
*.dylib | |||
# Executables | |||
*.exe | |||
*.out | |||
*.app | |||
*.i*86 | |||
*.x86_64 | |||
*.hex | |||
# Debug files | |||
*.dSYM/ | |||
*.su | |||
*.idb | |||
*.pdb | |||
# Kernel Module Compile Results | |||
*.mod* | |||
*.cmd | |||
.tmp_versions/ | |||
modules.order | |||
Module.symvers | |||
Mkfile.old | |||
dkms.conf | |||
slock | |||
*.orig | |||
*.rej |
@ -0,0 +1,339 @@ | |||
GNU GENERAL PUBLIC LICENSE | |||
Version 2, June 1991 | |||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., | |||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
Everyone is permitted to copy and distribute verbatim copies | |||
of this license document, but changing it is not allowed. | |||
Preamble | |||
The licenses for most software are designed to take away your | |||
freedom to share and change it. By contrast, the GNU General Public | |||
License is intended to guarantee your freedom to share and change free | |||
software--to make sure the software is free for all its users. This | |||
General Public License applies to most of the Free Software | |||
Foundation's software and to any other program whose authors commit to | |||
using it. (Some other Free Software Foundation software is covered by | |||
the GNU Lesser General Public License instead.) You can apply it to | |||
your programs, too. | |||
When we speak of free software, we are referring to freedom, not | |||
price. Our General Public Licenses are designed to make sure that you | |||
have the freedom to distribute copies of free software (and charge for | |||
this service if you wish), that you receive source code or can get it | |||
if you want it, that you can change the software or use pieces of it | |||
in new free programs; and that you know you can do these things. | |||
To protect your rights, we need to make restrictions that forbid | |||
anyone to deny you these rights or to ask you to surrender the rights. | |||
These restrictions translate to certain responsibilities for you if you | |||
distribute copies of the software, or if you modify it. | |||
For example, if you distribute copies of such a program, whether | |||
gratis or for a fee, you must give the recipients all the rights that | |||
you have. You must make sure that they, too, receive or can get the | |||
source code. And you must show them these terms so they know their | |||
rights. | |||
We protect your rights with two steps: (1) copyright the software, and | |||
(2) offer you this license which gives you legal permission to copy, | |||
distribute and/or modify the software. | |||
Also, for each author's protection and ours, we want to make certain | |||
that everyone understands that there is no warranty for this free | |||
software. If the software is modified by someone else and passed on, we | |||
want its recipients to know that what they have is not the original, so | |||
that any problems introduced by others will not reflect on the original | |||
authors' reputations. | |||
Finally, any free program is threatened constantly by software | |||
patents. We wish to avoid the danger that redistributors of a free | |||
program will individually obtain patent licenses, in effect making the | |||
program proprietary. To prevent this, we have made it clear that any | |||
patent must be licensed for everyone's free use or not licensed at all. | |||
The precise terms and conditions for copying, distribution and | |||
modification follow. | |||
GNU GENERAL PUBLIC LICENSE | |||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |||
0. This License applies to any program or other work which contains | |||
a notice placed by the copyright holder saying it may be distributed | |||
under the terms of this General Public License. The "Program", below, | |||
refers to any such program or work, and a "work based on the Program" | |||
means either the Program or any derivative work under copyright law: | |||
that is to say, a work containing the Program or a portion of it, | |||
either verbatim or with modifications and/or translated into another | |||
language. (Hereinafter, translation is included without limitation in | |||
the term "modification".) Each licensee is addressed as "you". | |||
Activities other than copying, distribution and modification are not | |||
covered by this License; they are outside its scope. The act of | |||
running the Program is not restricted, and the output from the Program | |||
is covered only if its contents constitute a work based on the | |||
Program (independent of having been made by running the Program). | |||
Whether that is true depends on what the Program does. | |||
1. You may copy and distribute verbatim copies of the Program's | |||
source code as you receive it, in any medium, provided that you | |||
conspicuously and appropriately publish on each copy an appropriate | |||
copyright notice and disclaimer of warranty; keep intact all the | |||
notices that refer to this License and to the absence of any warranty; | |||
and give any other recipients of the Program a copy of this License | |||
along with the Program. | |||
You may charge a fee for the physical act of transferring a copy, and | |||
you may at your option offer warranty protection in exchange for a fee. | |||
2. You may modify your copy or copies of the Program or any portion | |||
of it, thus forming a work based on the Program, and copy and | |||
distribute such modifications or work under the terms of Section 1 | |||
above, provided that you also meet all of these conditions: | |||
a) You must cause the modified files to carry prominent notices | |||
stating that you changed the files and the date of any change. | |||
b) You must cause any work that you distribute or publish, that in | |||
whole or in part contains or is derived from the Program or any | |||
part thereof, to be licensed as a whole at no charge to all third | |||
parties under the terms of this License. | |||
c) If the modified program normally reads commands interactively | |||
when run, you must cause it, when started running for such | |||
interactive use in the most ordinary way, to print or display an | |||
announcement including an appropriate copyright notice and a | |||
notice that there is no warranty (or else, saying that you provide | |||
a warranty) and that users may redistribute the program under | |||
these conditions, and telling the user how to view a copy of this | |||
License. (Exception: if the Program itself is interactive but | |||
does not normally print such an announcement, your work based on | |||
the Program is not required to print an announcement.) | |||
These requirements apply to the modified work as a whole. If | |||
identifiable sections of that work are not derived from the Program, | |||
and can be reasonably considered independent and separate works in | |||
themselves, then this License, and its terms, do not apply to those | |||
sections when you distribute them as separate works. But when you | |||
distribute the same sections as part of a whole which is a work based | |||
on the Program, the distribution of the whole must be on the terms of | |||
this License, whose permissions for other licensees extend to the | |||
entire whole, and thus to each and every part regardless of who wrote it. | |||
Thus, it is not the intent of this section to claim rights or contest | |||
your rights to work written entirely by you; rather, the intent is to | |||
exercise the right to control the distribution of derivative or | |||
collective works based on the Program. | |||
In addition, mere aggregation of another work not based on the Program | |||
with the Program (or with a work based on the Program) on a volume of | |||
a storage or distribution medium does not bring the other work under | |||
the scope of this License. | |||
3. You may copy and distribute the Program (or a work based on it, | |||
under Section 2) in object code or executable form under the terms of | |||
Sections 1 and 2 above provided that you also do one of the following: | |||
a) Accompany it with the complete corresponding machine-readable | |||
source code, which must be distributed under the terms of Sections | |||
1 and 2 above on a medium customarily used for software interchange; or, | |||
b) Accompany it with a written offer, valid for at least three | |||
years, to give any third party, for a charge no more than your | |||
cost of physically performing source distribution, a complete | |||
machine-readable copy of the corresponding source code, to be | |||
distributed under the terms of Sections 1 and 2 above on a medium | |||
customarily used for software interchange; or, | |||
c) Accompany it with the information you received as to the offer | |||
to distribute corresponding source code. (This alternative is | |||
allowed only for noncommercial distribution and only if you | |||
received the program in object code or executable form with such | |||
an offer, in accord with Subsection b above.) | |||
The source code for a work means the preferred form of the work for | |||
making modifications to it. For an executable work, complete source | |||
code means all the source code for all modules it contains, plus any | |||
associated interface definition files, plus the scripts used to | |||
control compilation and installation of the executable. However, as a | |||
special exception, the source code distributed need not include | |||
anything that is normally distributed (in either source or binary | |||
form) with the major components (compiler, kernel, and so on) of the | |||
operating system on which the executable runs, unless that component | |||
itself accompanies the executable. | |||
If distribution of executable or object code is made by offering | |||
access to copy from a designated place, then offering equivalent | |||
access to copy the source code from the same place counts as | |||
distribution of the source code, even though third parties are not | |||
compelled to copy the source along with the object code. | |||
4. You may not copy, modify, sublicense, or distribute the Program | |||
except as expressly provided under this License. Any attempt | |||
otherwise to copy, modify, sublicense or distribute the Program is | |||
void, and will automatically terminate your rights under this License. | |||
However, parties who have received copies, or rights, from you under | |||
this License will not have their licenses terminated so long as such | |||
parties remain in full compliance. | |||
5. You are not required to accept this License, since you have not | |||
signed it. However, nothing else grants you permission to modify or | |||
distribute the Program or its derivative works. These actions are | |||
prohibited by law if you do not accept this License. Therefore, by | |||
modifying or distributing the Program (or any work based on the | |||
Program), you indicate your acceptance of this License to do so, and | |||
all its terms and conditions for copying, distributing or modifying | |||
the Program or works based on it. | |||
6. Each time you redistribute the Program (or any work based on the | |||
Program), the recipient automatically receives a license from the | |||
original licensor to copy, distribute or modify the Program subject to | |||
these terms and conditions. You may not impose any further | |||
restrictions on the recipients' exercise of the rights granted herein. | |||
You are not responsible for enforcing compliance by third parties to | |||
this License. | |||
7. If, as a consequence of a court judgment or allegation of patent | |||
infringement or for any other reason (not limited to patent issues), | |||
conditions are imposed on you (whether by court order, agreement or | |||
otherwise) that contradict the conditions of this License, they do not | |||
excuse you from the conditions of this License. If you cannot | |||
distribute so as to satisfy simultaneously your obligations under this | |||
License and any other pertinent obligations, then as a consequence you | |||
may not distribute the Program at all. For example, if a patent | |||
license would not permit royalty-free redistribution of the Program by | |||
all those who receive copies directly or indirectly through you, then | |||
the only way you could satisfy both it and this License would be to | |||
refrain entirely from distribution of the Program. | |||
If any portion of this section is held invalid or unenforceable under | |||
any particular circumstance, the balance of the section is intended to | |||
apply and the section as a whole is intended to apply in other | |||
circumstances. | |||
It is not the purpose of this section to induce you to infringe any | |||
patents or other property right claims or to contest validity of any | |||
such claims; this section has the sole purpose of protecting the | |||
integrity of the free software distribution system, which is | |||
implemented by public license practices. Many people have made | |||
generous contributions to the wide range of software distributed | |||
through that system in reliance on consistent application of that | |||
system; it is up to the author/donor to decide if he or she is willing | |||
to distribute software through any other system and a licensee cannot | |||
impose that choice. | |||
This section is intended to make thoroughly clear what is believed to | |||
be a consequence of the rest of this License. | |||
8. If the distribution and/or use of the Program is restricted in | |||
certain countries either by patents or by copyrighted interfaces, the | |||
original copyright holder who places the Program under this License | |||
may add an explicit geographical distribution limitation excluding | |||
those countries, so that distribution is permitted only in or among | |||
countries not thus excluded. In such case, this License incorporates | |||
the limitation as if written in the body of this License. | |||
9. The Free Software Foundation may publish revised and/or new versions | |||
of the General Public License from time to time. Such new versions will | |||
be similar in spirit to the present version, but may differ in detail to | |||
address new problems or concerns. | |||
Each version is given a distinguishing version number. If the Program | |||
specifies a version number of this License which applies to it and "any | |||
later version", you have the option of following the terms and conditions | |||
either of that version or of any later version published by the Free | |||
Software Foundation. If the Program does not specify a version number of | |||
this License, you may choose any version ever published by the Free Software | |||
Foundation. | |||
10. If you wish to incorporate parts of the Program into other free | |||
programs whose distribution conditions are different, write to the author | |||
to ask for permission. For software which is copyrighted by the Free | |||
Software Foundation, write to the Free Software Foundation; we sometimes | |||
make exceptions for this. Our decision will be guided by the two goals | |||
of preserving the free status of all derivatives of our free software and | |||
of promoting the sharing and reuse of software generally. | |||
NO WARRANTY | |||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | |||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN | |||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | |||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | |||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS | |||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE | |||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | |||
REPAIR OR CORRECTION. | |||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | |||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | |||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | |||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | |||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | |||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | |||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGES. | |||
END OF TERMS AND CONDITIONS | |||
How to Apply These Terms to Your New Programs | |||
If you develop a new program, and you want it to be of the greatest | |||
possible use to the public, the best way to achieve this is to make it | |||
free software which everyone can redistribute and change under these terms. | |||
To do so, attach the following notices to the program. It is safest | |||
to attach them to the start of each source file to most effectively | |||
convey the exclusion of warranty; and each file should have at least | |||
the "copyright" line and a pointer to where the full notice is found. | |||
<one line to give the program's name and a brief idea of what it does.> | |||
Copyright (C) <year> <name of author> | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation; either version 2 of the License, or | |||
(at your option) any later version. | |||
This program is distributed in the hope that it will be useful, | |||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
GNU General Public License for more details. | |||
You should have received a copy of the GNU General Public License along | |||
with this program; if not, write to the Free Software Foundation, Inc., | |||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |||
Also add information on how to contact you by electronic and paper mail. | |||
If the program is interactive, make it output a short notice like this | |||
when it starts in an interactive mode: | |||
Gnomovision version 69, Copyright (C) year name of author | |||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |||
This is free software, and you are welcome to redistribute it | |||
under certain conditions; type `show c' for details. | |||
The hypothetical commands `show w' and `show c' should show the appropriate | |||
parts of the General Public License. Of course, the commands you use may | |||
be called something other than `show w' and `show c'; they could even be | |||
mouse-clicks or menu items--whatever suits your program. | |||
You should also get your employer (if you work as a programmer) or your | |||
school, if any, to sign a "copyright disclaimer" for the program, if | |||
necessary. Here is a sample; alter the names: | |||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program | |||
`Gnomovision' (which makes passes at compilers) written by James Hacker. | |||
<signature of Ty Coon>, 1 April 1989 | |||
Ty Coon, President of Vice | |||
This General Public License does not permit incorporating your program into | |||
proprietary programs. If your program is a subroutine library, you may | |||
consider it more useful to permit linking proprietary applications with the | |||
library. If this is what you want to do, use the GNU Lesser General | |||
Public License instead of this License. |
@ -0,0 +1,88 @@ | |||
version = 26 | |||
srcdir = . | |||
VPATH = $(srcdir) | |||
PREFIX = /usr/local | |||
MANPREFIX = $(PREFIX)/share/man | |||
# autoreload backend: inotify/nop | |||
AUTORELOAD = inotify | |||
# enable features requiring giflib (-lgif) | |||
HAVE_GIFLIB = 1 | |||
# enable features requiring libexif (-lexif) | |||
HAVE_LIBEXIF = 1 | |||
cflags = -std=c99 -Wall -pedantic $(CFLAGS) | |||
cppflags = -I. $(CPPFLAGS) -D_XOPEN_SOURCE=700 \ | |||
-DHAVE_GIFLIB=$(HAVE_GIFLIB) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \ | |||
-I/usr/include/freetype2 -I$(PREFIX)/include/freetype2 | |||
lib_exif_0 = | |||
lib_exif_1 = -lexif | |||
lib_gif_0 = | |||
lib_gif_1 = -lgif | |||
ldlibs = $(LDLIBS) -lImlib2 -lX11 -lXft -lfontconfig \ | |||
$(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_GIFLIB)) | |||
objs = autoreload_$(AUTORELOAD).o commands.o image.o main.o options.o \ | |||
thumbs.o util.o window.o | |||
all: sxiv | |||
.PHONY: all clean install uninstall | |||
.SUFFIXES: | |||
.SUFFIXES: .c .o | |||
$(V).SILENT: | |||
sxiv: $(objs) | |||
@echo "LINK $@" | |||
$(CC) $(LDFLAGS) -o $@ $(objs) $(ldlibs) | |||
$(objs): Makefile sxiv.h commands.lst config.h | |||
options.o: version.h | |||
window.o: icon/data.h | |||
.c.o: | |||
@echo "CC $@" | |||
$(CC) $(cflags) $(cppflags) -c -o $@ $< | |||
config.h: | |||
@echo "GEN $@" | |||
cp $(srcdir)/config.def.h $@ | |||
version.h: Makefile .git/index | |||
@echo "GEN $@" | |||
v="$$(cd $(srcdir); git describe 2>/dev/null)"; \ | |||
echo "#define VERSION \"$${v:-$(version)}\"" >$@ | |||
.git/index: | |||
clean: | |||
rm -f *.o sxiv | |||
install: all | |||
@echo "INSTALL bin/sxiv" | |||
mkdir -p $(DESTDIR)$(PREFIX)/bin | |||
cp sxiv $(DESTDIR)$(PREFIX)/bin/ | |||
chmod 755 $(DESTDIR)$(PREFIX)/bin/sxiv | |||
@echo "INSTALL sxiv.1" | |||
mkdir -p $(DESTDIR)$(MANPREFIX)/man1 | |||
sed "s!PREFIX!$(PREFIX)!g; s!VERSION!$(version)!g" sxiv.1 \ | |||
>$(DESTDIR)$(MANPREFIX)/man1/sxiv.1 | |||
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 | |||
@echo "INSTALL share/sxiv/" | |||
mkdir -p $(DESTDIR)$(PREFIX)/share/sxiv/exec | |||
cp exec/* $(DESTDIR)$(PREFIX)/share/sxiv/exec/ | |||
chmod 755 $(DESTDIR)$(PREFIX)/share/sxiv/exec/* | |||
uninstall: | |||
@echo "REMOVE bin/sxiv" | |||
rm -f $(DESTDIR)$(PREFIX)/bin/sxiv | |||
@echo "REMOVE sxiv.1" | |||
rm -f $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 | |||
@echo "REMOVE share/sxiv/" | |||
rm -rf $(DESTDIR)$(PREFIX)/share/sxiv | |||
@ -0,0 +1,5 @@ | |||
- Load all frames from TIFF files. We have to write our own loader for this to | |||
happen--just like we did for GIF images--because Imlib2 does not support | |||
multiple frames. Issue #241. | |||
- Add support for more embedded thumbnail formats. Right now, sxiv seems to use | |||
the smallest one. Issue #238. |
@ -0,0 +1,112 @@ | |||
/* Copyright 2017 Max Voit, Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
#include <errno.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <sys/inotify.h> | |||
void arl_init(arl_t *arl) | |||
{ | |||
arl->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); | |||
arl->wd_dir = arl->wd_file = -1; | |||
if (arl->fd == -1) | |||
error(0, 0, "Could not initialize inotify, no automatic image reloading"); | |||
} | |||
CLEANUP void arl_cleanup(arl_t *arl) | |||
{ | |||
if (arl->fd != -1) | |||
close(arl->fd); | |||
free(arl->filename); | |||
} | |||
static void rm_watch(int fd, int *wd) | |||
{ | |||
if (*wd != -1) { | |||
inotify_rm_watch(fd, *wd); | |||
*wd = -1; | |||
} | |||
} | |||
static void add_watch(int fd, int *wd, const char *path, uint32_t mask) | |||
{ | |||
*wd = inotify_add_watch(fd, path, mask); | |||
if (*wd == -1) | |||
error(0, errno, "inotify: %s", path); | |||
} | |||
void arl_setup(arl_t *arl, const char *filepath) | |||
{ | |||
char *base = strrchr(filepath, '/'); | |||
if (arl->fd == -1) | |||
return; | |||
rm_watch(arl->fd, &arl->wd_dir); | |||
rm_watch(arl->fd, &arl->wd_file); | |||
add_watch(arl->fd, &arl->wd_file, filepath, IN_CLOSE_WRITE | IN_DELETE_SELF); | |||
free(arl->filename); | |||
arl->filename = estrdup(filepath); | |||
if (base != NULL) { | |||
arl->filename[++base - filepath] = '\0'; | |||
add_watch(arl->fd, &arl->wd_dir, arl->filename, IN_CREATE | IN_MOVED_TO); | |||
strcpy(arl->filename, base); | |||
} | |||
} | |||
union { | |||
char d[4096]; /* aligned buffer */ | |||
struct inotify_event e; | |||
} buf; | |||
bool arl_handle(arl_t *arl) | |||
{ | |||
bool reload = false; | |||
char *ptr; | |||
const struct inotify_event *e; | |||
for (;;) { | |||
ssize_t len = read(arl->fd, buf.d, sizeof(buf.d)); | |||
if (len == -1) { | |||
if (errno == EINTR) | |||
continue; | |||
break; | |||
} | |||
for (ptr = buf.d; ptr < buf.d + len; ptr += sizeof(*e) + e->len) { | |||
e = (const struct inotify_event*) ptr; | |||
if (e->wd == arl->wd_file && (e->mask & IN_CLOSE_WRITE)) { | |||
reload = true; | |||
} else if (e->wd == arl->wd_file && (e->mask & IN_DELETE_SELF)) { | |||
rm_watch(arl->fd, &arl->wd_file); | |||
} else if (e->wd == arl->wd_dir && (e->mask & (IN_CREATE | IN_MOVED_TO))) { | |||
if (STREQ(e->name, arl->filename)) | |||
reload = true; | |||
} | |||
} | |||
} | |||
return reload; | |||
} | |||
@ -0,0 +1,42 @@ | |||
/* Copyright 2017 Max Voit | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
void arl_init(arl_t *arl) | |||
{ | |||
arl->fd = -1; | |||
} | |||
void arl_cleanup(arl_t *arl) | |||
{ | |||
(void) arl; | |||
} | |||
void arl_setup(arl_t *arl, const char *filepath) | |||
{ | |||
(void) arl; | |||
(void) filepath; | |||
} | |||
bool arl_handle(arl_t *arl) | |||
{ | |||
(void) arl; | |||
return false; | |||
} | |||
@ -0,0 +1,455 @@ | |||
/* Copyright 2011, 2012, 2014 Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
#define _IMAGE_CONFIG | |||
#include "config.h" | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <sys/wait.h> | |||
void remove_file(int, bool); | |||
void load_image(int); | |||
bool mark_image(int, bool); | |||
void close_info(void); | |||
void open_info(void); | |||
int ptr_third_x(void); | |||
void redraw(void); | |||
void reset_cursor(void); | |||
void animate(void); | |||
void slideshow(void); | |||
void set_timeout(timeout_f, int, bool); | |||
void reset_timeout(timeout_f); | |||
extern appmode_t mode; | |||
extern img_t img; | |||
extern tns_t tns; | |||
extern win_t win; | |||
extern fileinfo_t *files; | |||
extern int filecnt, fileidx; | |||
extern int alternate; | |||
extern int markcnt; | |||
extern int markidx; | |||
extern int prefix; | |||
extern bool extprefix; | |||
bool cg_quit(arg_t _) | |||
{ | |||
unsigned int i; | |||
if (options->to_stdout && markcnt > 0) { | |||
for (i = 0; i < filecnt; i++) { | |||
if (files[i].flags & FF_MARK) | |||
printf("%s\n", files[i].name); | |||
} | |||
} | |||
exit(EXIT_SUCCESS); | |||
} | |||
bool cg_switch_mode(arg_t _) | |||
{ | |||
if (mode == MODE_IMAGE) { | |||
if (tns.thumbs == NULL) | |||
tns_init(&tns, files, &filecnt, &fileidx, &win); | |||
img_close(&img, false); | |||
reset_timeout(reset_cursor); | |||
if (img.ss.on) { | |||
img.ss.on = false; | |||
reset_timeout(slideshow); | |||
} | |||
tns.dirty = true; | |||
mode = MODE_THUMB; | |||
} else { | |||
load_image(fileidx); | |||
mode = MODE_IMAGE; | |||
} | |||
return true; | |||
} | |||
bool cg_toggle_fullscreen(arg_t _) | |||
{ | |||
win_toggle_fullscreen(&win); | |||
/* redraw after next ConfigureNotify event */ | |||
set_timeout(redraw, TO_REDRAW_RESIZE, false); | |||
if (mode == MODE_IMAGE) | |||
img.checkpan = img.dirty = true; | |||
else | |||
tns.dirty = true; | |||
return false; | |||
} | |||
bool cg_toggle_bar(arg_t _) | |||
{ | |||
win_toggle_bar(&win); | |||
if (mode == MODE_IMAGE) { | |||
if (win.bar.h > 0) | |||
open_info(); | |||
else | |||
close_info(); | |||
img.checkpan = img.dirty = true; | |||
} else { | |||
tns.dirty = true; | |||
} | |||
return true; | |||
} | |||
bool cg_prefix_external(arg_t _) | |||
{ | |||
extprefix = true; | |||
return false; | |||
} | |||
bool cg_reload_image(arg_t _) | |||
{ | |||
if (mode == MODE_IMAGE) { | |||
load_image(fileidx); | |||
} else { | |||
win_set_cursor(&win, CURSOR_WATCH); | |||
if (!tns_load(&tns, fileidx, true, false)) { | |||
remove_file(fileidx, false); | |||
tns.dirty = true; | |||
} | |||
} | |||
return true; | |||
} | |||
bool cg_remove_image(arg_t _) | |||
{ | |||
remove_file(fileidx, true); | |||
if (mode == MODE_IMAGE) | |||
load_image(fileidx); | |||
else | |||
tns.dirty = true; | |||
return true; | |||
} | |||
bool cg_first(arg_t _) | |||
{ | |||
if (mode == MODE_IMAGE && fileidx != 0) { | |||
load_image(0); | |||
return true; | |||
} else if (mode == MODE_THUMB && fileidx != 0) { | |||
fileidx = 0; | |||
tns.dirty = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool cg_n_or_last(arg_t _) | |||
{ | |||
int n = prefix != 0 && prefix - 1 < filecnt ? prefix - 1 : filecnt - 1; | |||
if (mode == MODE_IMAGE && fileidx != n) { | |||
load_image(n); | |||
return true; | |||
} else if (mode == MODE_THUMB && fileidx != n) { | |||
fileidx = n; | |||
tns.dirty = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool cg_scroll_screen(arg_t dir) | |||
{ | |||
if (mode == MODE_IMAGE) | |||
return img_pan(&img, dir, -1); | |||
else | |||
return tns_scroll(&tns, dir, true); | |||
} | |||
bool cg_zoom(arg_t d) | |||
{ | |||
if (mode == MODE_THUMB) | |||
return tns_zoom(&tns, d); | |||
else if (d > 0) | |||
return img_zoom_in(&img); | |||
else if (d < 0) | |||
return img_zoom_out(&img); | |||
else | |||
return false; | |||
} | |||
bool cg_toggle_image_mark(arg_t _) | |||
{ | |||
return mark_image(fileidx, !(files[fileidx].flags & FF_MARK)); | |||
} | |||
bool cg_reverse_marks(arg_t _) | |||
{ | |||
int i; | |||
for (i = 0; i < filecnt; i++) { | |||
files[i].flags ^= FF_MARK; | |||
markcnt += files[i].flags & FF_MARK ? 1 : -1; | |||
} | |||
if (mode == MODE_THUMB) | |||
tns.dirty = true; | |||
return true; | |||
} | |||
bool cg_mark_range(arg_t _) | |||
{ | |||
int d = markidx < fileidx ? 1 : -1, end, i; | |||
bool dirty = false, on = !!(files[markidx].flags & FF_MARK); | |||
for (i = markidx + d, end = fileidx + d; i != end; i += d) | |||
dirty |= mark_image(i, on); | |||
return dirty; | |||
} | |||
bool cg_unmark_all(arg_t _) | |||
{ | |||
int i; | |||
for (i = 0; i < filecnt; i++) | |||
files[i].flags &= ~FF_MARK; | |||
markcnt = 0; | |||
if (mode == MODE_THUMB) | |||
tns.dirty = true; | |||
return true; | |||
} | |||
bool cg_navigate_marked(arg_t n) | |||
{ | |||
int d, i; | |||
int new = fileidx; | |||
if (prefix > 0) | |||
n *= prefix; | |||
d = n > 0 ? 1 : -1; | |||
for (i = fileidx + d; n != 0 && i >= 0 && i < filecnt; i += d) { | |||
if (files[i].flags & FF_MARK) { | |||
n -= d; | |||
new = i; | |||
} | |||
} | |||
if (new != fileidx) { | |||
if (mode == MODE_IMAGE) { | |||
load_image(new); | |||
} else { | |||
fileidx = new; | |||
tns.dirty = true; | |||
} | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool cg_change_gamma(arg_t d) | |||
{ | |||
if (img_change_gamma(&img, d * (prefix > 0 ? prefix : 1))) { | |||
if (mode == MODE_THUMB) | |||
tns.dirty = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool ci_navigate(arg_t n) | |||
{ | |||
if (prefix > 0) | |||
n *= prefix; | |||
n += fileidx; | |||
if (n < 0) | |||
n = 0; | |||
if (n >= filecnt) | |||
n = filecnt - 1; | |||
if (n != fileidx) { | |||
load_image(n); | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool ci_cursor_navigate(arg_t _) | |||
{ | |||
return ci_navigate(ptr_third_x() - 1); | |||
} | |||
bool ci_alternate(arg_t _) | |||
{ | |||
load_image(alternate); | |||
return true; | |||
} | |||
bool ci_navigate_frame(arg_t d) | |||
{ | |||
if (prefix > 0) | |||
d *= prefix; | |||
return !img.multi.animate && img_frame_navigate(&img, d); | |||
} | |||
bool ci_toggle_animation(arg_t _) | |||
{ | |||
bool dirty = false; | |||
if (img.multi.cnt > 0) { | |||
img.multi.animate = !img.multi.animate; | |||
if (img.multi.animate) { | |||
dirty = img_frame_animate(&img); | |||
set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); | |||
} else { | |||
reset_timeout(animate); | |||
} | |||
} | |||
return dirty; | |||
} | |||
bool ci_scroll(arg_t dir) | |||
{ | |||
return img_pan(&img, dir, prefix); | |||
} | |||
bool ci_scroll_to_edge(arg_t dir) | |||
{ | |||
return img_pan_edge(&img, dir); | |||
} | |||
bool ci_drag(arg_t mode) | |||
{ | |||
int x, y, ox, oy; | |||
float px, py; | |||
XEvent e; | |||
if ((int)(img.w * img.zoom) <= win.w && (int)(img.h * img.zoom) <= win.h) | |||
return false; | |||
win_set_cursor(&win, CURSOR_DRAG); | |||
win_cursor_pos(&win, &x, &y); | |||
ox = x; | |||
oy = y; | |||
for (;;) { | |||
if (mode == DRAG_ABSOLUTE) { | |||
px = MIN(MAX(0.0, x - win.w*0.1), win.w*0.8) / (win.w*0.8) | |||
* (win.w - img.w * img.zoom); | |||
py = MIN(MAX(0.0, y - win.h*0.1), win.h*0.8) / (win.h*0.8) | |||
* (win.h - img.h * img.zoom); | |||
} else { | |||
px = img.x + x - ox; | |||
py = img.y + y - oy; | |||
} | |||
if (img_pos(&img, px, py)) { | |||
img_render(&img); | |||
win_draw(&win); | |||
} | |||
XMaskEvent(win.env.dpy, | |||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); | |||
if (e.type == ButtonPress || e.type == ButtonRelease) | |||
break; | |||
while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)); | |||
ox = x; | |||
oy = y; | |||
x = e.xmotion.x; | |||
y = e.xmotion.y; | |||
} | |||
set_timeout(reset_cursor, TO_CURSOR_HIDE, true); | |||
reset_cursor(); | |||
return true; | |||
} | |||
bool ci_set_zoom(arg_t zl) | |||
{ | |||
return img_zoom(&img, (prefix ? prefix : zl) / 100.0); | |||
} | |||
bool ci_fit_to_win(arg_t sm) | |||
{ | |||
return img_fit_win(&img, sm); | |||
} | |||
bool ci_rotate(arg_t degree) | |||
{ | |||
img_rotate(&img, degree); | |||
return true; | |||
} | |||
bool ci_flip(arg_t dir) | |||
{ | |||
img_flip(&img, dir); | |||
return true; | |||
} | |||
bool ci_toggle_antialias(arg_t _) | |||
{ | |||
img_toggle_antialias(&img); | |||
return true; | |||
} | |||
bool ci_toggle_alpha(arg_t _) | |||
{ | |||
img.alpha = !img.alpha; | |||
img.dirty = true; | |||
return true; | |||
} | |||
bool ci_slideshow(arg_t _) | |||
{ | |||
if (prefix > 0) { | |||
img.ss.on = true; | |||
img.ss.delay = prefix * 10; | |||
set_timeout(slideshow, img.ss.delay * 100, true); | |||
} else if (img.ss.on) { | |||
img.ss.on = false; | |||
reset_timeout(slideshow); | |||
} else { | |||
img.ss.on = true; | |||
} | |||
return true; | |||
} | |||
bool ct_move_sel(arg_t dir) | |||
{ | |||
return tns_move_selection(&tns, dir, prefix); | |||
} | |||
bool ct_reload_all(arg_t _) | |||
{ | |||
tns_free(&tns); | |||
tns_init(&tns, files, &filecnt, &fileidx, &win); | |||
tns.dirty = true; | |||
return true; | |||
} | |||
#undef G_CMD | |||
#define G_CMD(c) { -1, cg_##c }, | |||
#undef I_CMD | |||
#define I_CMD(c) { MODE_IMAGE, ci_##c }, | |||
#undef T_CMD | |||
#define T_CMD(c) { MODE_THUMB, ct_##c }, | |||
const cmd_t cmds[CMD_COUNT] = { | |||
#include "commands.lst" | |||
}; | |||
@ -0,0 +1,37 @@ | |||
G_CMD(quit) | |||
G_CMD(switch_mode) | |||
G_CMD(toggle_fullscreen) | |||
G_CMD(toggle_bar) | |||
G_CMD(prefix_external) | |||
G_CMD(reload_image) | |||
G_CMD(remove_image) | |||
G_CMD(first) | |||
G_CMD(n_or_last) | |||
G_CMD(scroll_screen) | |||
G_CMD(zoom) | |||
G_CMD(toggle_image_mark) | |||
G_CMD(reverse_marks) | |||
G_CMD(mark_range) | |||
G_CMD(unmark_all) | |||
G_CMD(navigate_marked) | |||
G_CMD(change_gamma) | |||
I_CMD(navigate) | |||
I_CMD(cursor_navigate) | |||
I_CMD(alternate) | |||
I_CMD(navigate_frame) | |||
I_CMD(toggle_animation) | |||
I_CMD(scroll) | |||
I_CMD(scroll_to_edge) | |||
I_CMD(drag) | |||
I_CMD(set_zoom) | |||
I_CMD(fit_to_win) | |||
I_CMD(rotate) | |||
I_CMD(flip) | |||
I_CMD(toggle_antialias) | |||
I_CMD(toggle_alpha) | |||
I_CMD(slideshow) | |||
T_CMD(move_sel) | |||
T_CMD(reload_all) | |||
@ -0,0 +1,152 @@ | |||
#ifdef _WINDOW_CONFIG | |||
/* default window dimensions (overwritten via -g option): */ | |||
enum { | |||
WIN_WIDTH = 800, | |||
WIN_HEIGHT = 600 | |||
}; | |||
/* colors and font are configured with 'background', 'foreground' and | |||
* 'font' X resource properties. | |||
* See X(7) section Resources and xrdb(1) for more information. | |||
*/ | |||
#endif | |||
#ifdef _IMAGE_CONFIG | |||
/* levels (in percent) to use when zooming via '-' and '+': | |||
* (first/last value is used as min/max zoom level) | |||
*/ | |||
static const float zoom_levels[] = { | |||
12.5, 25.0, 50.0, 75.0, | |||
100.0, 150.0, 200.0, 400.0, 800.0 | |||
}; | |||
/* default slideshow delay (in sec, overwritten via -S option): */ | |||
enum { SLIDESHOW_DELAY = 5 }; | |||
/* gamma correction: the user-visible ranges [-GAMMA_RANGE, 0] and | |||
* (0, GAMMA_RANGE] are mapped to the ranges [0, 1], and (1, GAMMA_MAX]. | |||
* */ | |||
static const double GAMMA_MAX = 10.0; | |||
static const int GAMMA_RANGE = 32; | |||
/* command i_scroll pans image 1/PAN_FRACTION of screen width/height */ | |||
static const int PAN_FRACTION = 5; | |||
/* if false, pixelate images at zoom level != 100%, | |||
* toggled with 'a' key binding | |||
*/ | |||
static const bool ANTI_ALIAS = true; | |||
/* if true, use a checkerboard background for alpha layer, | |||
* toggled with 'A' key binding | |||
*/ | |||
static const bool ALPHA_LAYER = false; | |||
#endif | |||
#ifdef _THUMBS_CONFIG | |||
/* thumbnail sizes in pixels (width == height): */ | |||
static const int thumb_sizes[] = { 32, 64, 96, 128, 160 }; | |||
/* thumbnail size at startup, index into thumb_sizes[]: */ | |||
static const int THUMB_SIZE = 3; | |||
#endif | |||
#ifdef _MAPPINGS_CONFIG | |||
/* keyboard mappings for image and thumbnail mode: */ | |||
static const keymap_t keys[] = { | |||
/* modifiers key function argument */ | |||
{ 0, XK_q, g_quit, None }, | |||
{ 0, XK_Return, g_switch_mode, None }, | |||
{ 0, XK_f, g_toggle_fullscreen, None }, | |||
{ 0, XK_b, g_toggle_bar, None }, | |||
{ ControlMask, XK_x, g_prefix_external, None }, | |||
{ 0, XK_g, g_first, None }, | |||
{ 0, XK_G, g_n_or_last, None }, | |||
{ 0, XK_r, g_reload_image, None }, | |||
{ 0, XK_D, g_remove_image, None }, | |||
{ ControlMask, XK_h, g_scroll_screen, DIR_LEFT }, | |||
{ ControlMask, XK_Left, g_scroll_screen, DIR_LEFT }, | |||
{ ControlMask, XK_j, g_scroll_screen, DIR_DOWN }, | |||
{ ControlMask, XK_Down, g_scroll_screen, DIR_DOWN }, | |||
{ ControlMask, XK_k, g_scroll_screen, DIR_UP }, | |||
{ ControlMask, XK_Up, g_scroll_screen, DIR_UP }, | |||
{ ControlMask, XK_l, g_scroll_screen, DIR_RIGHT }, | |||
{ ControlMask, XK_Right, g_scroll_screen, DIR_RIGHT }, | |||
{ 0, XK_plus, g_zoom, +1 }, | |||
{ 0, XK_KP_Add, g_zoom, +1 }, | |||
{ 0, XK_minus, g_zoom, -1 }, | |||
{ 0, XK_KP_Subtract, g_zoom, -1 }, | |||
{ 0, XK_m, g_toggle_image_mark, None }, | |||
{ 0, XK_M, g_mark_range, None }, | |||
{ ControlMask, XK_m, g_reverse_marks, None }, | |||
{ ControlMask, XK_u, g_unmark_all, None }, | |||
{ 0, XK_N, g_navigate_marked, +1 }, | |||
{ 0, XK_P, g_navigate_marked, -1 }, | |||
{ 0, XK_braceleft, g_change_gamma, -1 }, | |||
{ 0, XK_braceright, g_change_gamma, +1 }, | |||
{ ControlMask, XK_g, g_change_gamma, 0 }, | |||
{ 0, XK_h, t_move_sel, DIR_LEFT }, | |||
{ 0, XK_Left, t_move_sel, DIR_LEFT }, | |||
{ 0, XK_j, t_move_sel, DIR_DOWN }, | |||
{ 0, XK_Down, t_move_sel, DIR_DOWN }, | |||
{ 0, XK_k, t_move_sel, DIR_UP }, | |||
{ 0, XK_Up, t_move_sel, DIR_UP }, | |||
{ 0, XK_l, t_move_sel, DIR_RIGHT }, | |||
{ 0, XK_Right, t_move_sel, DIR_RIGHT }, | |||
{ 0, XK_R, t_reload_all, None }, | |||
{ 0, XK_n, i_navigate, +1 }, | |||
{ 0, XK_n, i_scroll_to_edge, DIR_LEFT | DIR_UP }, | |||
{ 0, XK_space, i_navigate, +1 }, | |||
{ 0, XK_p, i_navigate, -1 }, | |||
{ 0, XK_p, i_scroll_to_edge, DIR_LEFT | DIR_UP }, | |||
{ 0, XK_BackSpace, i_navigate, -1 }, | |||
{ 0, XK_bracketright, i_navigate, +10 }, | |||
{ 0, XK_bracketleft, i_navigate, -10 }, | |||
{ ControlMask, XK_6, i_alternate, None }, | |||
{ ControlMask, XK_n, i_navigate_frame, +1 }, | |||
{ ControlMask, XK_p, i_navigate_frame, -1 }, | |||
{ ControlMask, XK_space, i_toggle_animation, None }, | |||
{ 0, XK_h, i_scroll, DIR_LEFT }, | |||
{ 0, XK_Left, i_scroll, DIR_LEFT }, | |||
{ 0, XK_j, i_scroll, DIR_DOWN }, | |||
{ 0, XK_Down, i_scroll, DIR_DOWN }, | |||
{ 0, XK_k, i_scroll, DIR_UP }, | |||
{ 0, XK_Up, i_scroll, DIR_UP }, | |||
{ 0, XK_l, i_scroll, DIR_RIGHT }, | |||
{ 0, XK_Right, i_scroll, DIR_RIGHT }, | |||
{ 0, XK_H, i_scroll_to_edge, DIR_LEFT }, | |||
{ 0, XK_J, i_scroll_to_edge, DIR_DOWN }, | |||
{ 0, XK_K, i_scroll_to_edge, DIR_UP }, | |||
{ 0, XK_L, i_scroll_to_edge, DIR_RIGHT }, | |||
{ 0, XK_equal, i_set_zoom, 100 }, | |||
{ 0, XK_w, i_fit_to_win, SCALE_DOWN }, | |||
{ 0, XK_W, i_fit_to_win, SCALE_FIT }, | |||
{ 0, XK_e, i_fit_to_win, SCALE_WIDTH }, | |||
{ 0, XK_E, i_fit_to_win, SCALE_HEIGHT }, | |||
{ 0, XK_less, i_rotate, DEGREE_270 }, | |||
{ 0, XK_greater, i_rotate, DEGREE_90 }, | |||
{ 0, XK_question, i_rotate, DEGREE_180 }, | |||
{ 0, XK_bar, i_flip, FLIP_HORIZONTAL }, | |||
{ 0, XK_underscore, i_flip, FLIP_VERTICAL }, | |||
{ 0, XK_a, i_toggle_antialias, None }, | |||
{ 0, XK_A, i_toggle_alpha, None }, | |||
{ 0, XK_s, i_slideshow, None }, | |||
}; | |||
/* mouse button mappings for image mode: */ | |||
static const button_t buttons[] = { | |||
/* modifiers button function argument */ | |||
{ 0, 1, i_cursor_navigate, None }, | |||
{ 0, 2, i_drag, DRAG_ABSOLUTE }, | |||
{ 0, 3, g_switch_mode, None }, | |||
{ 0, 4, g_zoom, +1 }, | |||
{ 0, 5, g_zoom, -1 }, | |||
}; | |||
#endif |
@ -0,0 +1,152 @@ | |||
#ifdef _WINDOW_CONFIG | |||
/* default window dimensions (overwritten via -g option): */ | |||
enum { | |||
WIN_WIDTH = 800, | |||
WIN_HEIGHT = 600 | |||
}; | |||
/* colors and font are configured with 'background', 'foreground' and | |||
* 'font' X resource properties. | |||
* See X(7) section Resources and xrdb(1) for more information. | |||
*/ | |||
#endif | |||
#ifdef _IMAGE_CONFIG | |||
/* levels (in percent) to use when zooming via '-' and '+': | |||
* (first/last value is used as min/max zoom level) | |||
*/ | |||
static const float zoom_levels[] = { | |||
12.5, 25.0, 50.0, 75.0, | |||
100.0, 150.0, 200.0, 400.0, 800.0 | |||
}; | |||
/* default slideshow delay (in sec, overwritten via -S option): */ | |||
enum { SLIDESHOW_DELAY = 5 }; | |||
/* gamma correction: the user-visible ranges [-GAMMA_RANGE, 0] and | |||
* (0, GAMMA_RANGE] are mapped to the ranges [0, 1], and (1, GAMMA_MAX]. | |||
* */ | |||
static const double GAMMA_MAX = 10.0; | |||
static const int GAMMA_RANGE = 32; | |||
/* command i_scroll pans image 1/PAN_FRACTION of screen width/height */ | |||
static const int PAN_FRACTION = 5; | |||
/* if false, pixelate images at zoom level != 100%, | |||
* toggled with 'a' key binding | |||
*/ | |||
static const bool ANTI_ALIAS = true; | |||
/* if true, use a checkerboard background for alpha layer, | |||
* toggled with 'A' key binding | |||
*/ | |||
static const bool ALPHA_LAYER = false; | |||
#endif | |||
#ifdef _THUMBS_CONFIG | |||
/* thumbnail sizes in pixels (width == height): */ | |||
static const int thumb_sizes[] = { 32, 64, 96, 128, 160 }; | |||
/* thumbnail size at startup, index into thumb_sizes[]: */ | |||
static const int THUMB_SIZE = 3; | |||
#endif | |||
#ifdef _MAPPINGS_CONFIG | |||
/* keyboard mappings for image and thumbnail mode: */ | |||
static const keymap_t keys[] = { | |||
/* modifiers key function argument */ | |||
{ 0, XK_q, g_quit, None }, | |||
{ 0, XK_Return, g_switch_mode, None }, | |||
{ 0, XK_f, g_toggle_fullscreen, None }, | |||
{ 0, XK_b, g_toggle_bar, None }, | |||
{ ControlMask, XK_x, g_prefix_external, None }, | |||
{ 0, XK_g, g_first, None }, | |||
{ 0, XK_G, g_n_or_last, None }, | |||
{ 0, XK_r, g_reload_image, None }, | |||
{ 0, XK_D, g_remove_image, None }, | |||
{ ControlMask, XK_h, g_scroll_screen, DIR_LEFT }, | |||
{ ControlMask, XK_Left, g_scroll_screen, DIR_LEFT }, | |||
{ ControlMask, XK_j, g_scroll_screen, DIR_DOWN }, | |||
{ ControlMask, XK_Down, g_scroll_screen, DIR_DOWN }, | |||
{ ControlMask, XK_k, g_scroll_screen, DIR_UP }, | |||
{ ControlMask, XK_Up, g_scroll_screen, DIR_UP }, | |||
{ ControlMask, XK_l, g_scroll_screen, DIR_RIGHT }, | |||
{ ControlMask, XK_Right, g_scroll_screen, DIR_RIGHT }, | |||
{ 0, XK_plus, g_zoom, +1 }, | |||
{ 0, XK_KP_Add, g_zoom, +1 }, | |||
{ 0, XK_minus, g_zoom, -1 }, | |||
{ 0, XK_KP_Subtract, g_zoom, -1 }, | |||
{ 0, XK_m, g_toggle_image_mark, None }, | |||
{ 0, XK_M, g_mark_range, None }, | |||
{ ControlMask, XK_m, g_reverse_marks, None }, | |||
{ ControlMask, XK_u, g_unmark_all, None }, | |||
{ 0, XK_N, g_navigate_marked, +1 }, | |||
{ 0, XK_P, g_navigate_marked, -1 }, | |||
{ 0, XK_braceleft, g_change_gamma, -1 }, | |||
{ 0, XK_braceright, g_change_gamma, +1 }, | |||
{ ControlMask, XK_g, g_change_gamma, 0 }, | |||
{ 0, XK_h, t_move_sel, DIR_LEFT }, | |||
{ 0, XK_Left, t_move_sel, DIR_LEFT }, | |||
{ 0, XK_j, t_move_sel, DIR_DOWN }, | |||
{ 0, XK_Down, t_move_sel, DIR_DOWN }, | |||
{ 0, XK_k, t_move_sel, DIR_UP }, | |||
{ 0, XK_Up, t_move_sel, DIR_UP }, | |||
{ 0, XK_l, t_move_sel, DIR_RIGHT }, | |||
{ 0, XK_Right, t_move_sel, DIR_RIGHT }, | |||
{ 0, XK_R, t_reload_all, None }, | |||
{ 0, XK_n, i_navigate, +1 }, | |||
{ 0, XK_n, i_scroll_to_edge, DIR_LEFT | DIR_UP }, | |||
{ 0, XK_space, i_navigate, +1 }, | |||
{ 0, XK_p, i_navigate, -1 }, | |||
{ 0, XK_p, i_scroll_to_edge, DIR_LEFT | DIR_UP }, | |||
{ 0, XK_BackSpace, i_navigate, -1 }, | |||
{ 0, XK_bracketright, i_navigate, +10 }, | |||
{ 0, XK_bracketleft, i_navigate, -10 }, | |||
{ ControlMask, XK_6, i_alternate, None }, | |||
{ ControlMask, XK_n, i_navigate_frame, +1 }, | |||
{ ControlMask, XK_p, i_navigate_frame, -1 }, | |||
{ ControlMask, XK_space, i_toggle_animation, None }, | |||
{ 0, XK_h, i_scroll, DIR_LEFT }, | |||
{ 0, XK_Left, i_scroll, DIR_LEFT }, | |||
{ 0, XK_j, i_scroll, DIR_DOWN }, | |||
{ 0, XK_Down, i_scroll, DIR_DOWN }, | |||
{ 0, XK_k, i_scroll, DIR_UP }, | |||
{ 0, XK_Up, i_scroll, DIR_UP }, | |||
{ 0, XK_l, i_scroll, DIR_RIGHT }, | |||
{ 0, XK_Right, i_scroll, DIR_RIGHT }, | |||
{ 0, XK_H, i_scroll_to_edge, DIR_LEFT }, | |||
{ 0, XK_J, i_scroll_to_edge, DIR_DOWN }, | |||
{ 0, XK_K, i_scroll_to_edge, DIR_UP }, | |||
{ 0, XK_L, i_scroll_to_edge, DIR_RIGHT }, | |||
{ 0, XK_equal, i_set_zoom, 100 }, | |||
{ 0, XK_w, i_fit_to_win, SCALE_DOWN }, | |||
{ 0, XK_W, i_fit_to_win, SCALE_FIT }, | |||
{ 0, XK_e, i_fit_to_win, SCALE_WIDTH }, | |||
{ 0, XK_E, i_fit_to_win, SCALE_HEIGHT }, | |||
{ 0, XK_less, i_rotate, DEGREE_270 }, | |||
{ 0, XK_greater, i_rotate, DEGREE_90 }, | |||
{ 0, XK_question, i_rotate, DEGREE_180 }, | |||
{ 0, XK_bar, i_flip, FLIP_HORIZONTAL }, | |||
{ 0, XK_underscore, i_flip, FLIP_VERTICAL }, | |||
{ 0, XK_a, i_toggle_antialias, None }, | |||
{ 0, XK_A, i_toggle_alpha, None }, | |||
{ 0, XK_s, i_slideshow, None }, | |||
}; | |||
/* mouse button mappings for image mode: */ | |||
static const button_t buttons[] = { | |||
/* modifiers button function argument */ | |||
{ 0, 1, i_cursor_navigate, None }, | |||
{ 0, 2, i_drag, DRAG_ABSOLUTE }, | |||
{ 0, 3, g_switch_mode, None }, | |||
{ 0, 4, g_zoom, +1 }, | |||
{ 0, 5, g_zoom, -1 }, | |||
}; | |||
#endif |
@ -0,0 +1,20 @@ | |||
#!/bin/sh | |||
# Example for $XDG_CONFIG_HOME/sxiv/exec/image-info | |||
# Called by sxiv(1) whenever an image gets loaded. | |||
# The output is displayed in sxiv's status bar. | |||
# Arguments: | |||
# $1: path to image file | |||
# $2: image width | |||
# $3: image height | |||
s=" " # field separator | |||
exec 2>/dev/null | |||
filename=$(basename -- "$1") | |||
filesize=$(du -Hh -- "$1" | cut -f 1) | |||
geometry="${2}x${3}" | |||
echo "${filesize}${s}${geometry}${s}${filename}" | |||
@ -0,0 +1,35 @@ | |||
#!/bin/sh | |||
# Example for $XDG_CONFIG_HOME/sxiv/exec/key-handler | |||
# Called by sxiv(1) after the external prefix key (C-x by default) is pressed. | |||
# The next key combo is passed as its first argument. Passed via stdin are the | |||
# images to act upon, one path per line: all marked images, if in thumbnail | |||
# mode and at least one image has been marked, otherwise the current image. | |||
# sxiv(1) blocks until this script terminates. It then checks which images | |||
# have been modified and reloads them. | |||
# The key combo argument has the following form: "[C-][M-][S-]KEY", | |||
# where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X | |||
# keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. | |||
rotate() { | |||
degree="$1" | |||
tr '\n' '\0' | xargs -0 realpath | sort | uniq | while read file; do | |||
case "$(file -b -i "$file")" in | |||
image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "$file" "$file" ;; | |||
*) mogrify -rotate "$degree" "$file" ;; | |||
esac | |||
done | |||
} | |||
case "$1" in | |||
"C-x") xclip -in -filter | tr '\n' ' ' | xclip -in -selection clipboard ;; | |||
"C-c") while read file; do xclip -selection clipboard -target image/png "$file"; done ;; | |||
"C-e") while read file; do urxvt -bg "#444" -fg "#eee" -sl 0 -title "$file" -e sh -c "exiv2 pr -q -pa '$file' | less" & done ;; | |||
"C-g") tr '\n' '\0' | xargs -0 gimp & ;; | |||
"C-r") while read file; do rawtherapee "$file" & done ;; | |||
"C-comma") rotate 270 ;; | |||
"C-period") rotate 90 ;; | |||
"C-slash") rotate 180 ;; | |||
esac | |||
@ -0,0 +1,12 @@ | |||
PREFIX = /usr/local | |||
ICONS = 16x16.png 32x32.png 48x48.png 64x64.png 128x128.png | |||
all: | |||
install: | |||
for f in $(ICONS); do \ | |||
dir="$(DESTDIR)$(PREFIX)/share/icons/hicolor/$${f%.png}/apps"; \ | |||
mkdir -p "$$dir"; \ | |||
cp "$$f" "$$dir/sxiv.png"; \ | |||
chmod 644 "$$dir/sxiv.png"; \ | |||
done |
@ -0,0 +1,35 @@ | |||
#!/usr/bin/awk -f | |||
function printchars() { | |||
while (n > 0) { | |||
x = n / 16 >= 1 ? 16 : n; | |||
printf("0x%x%x,%s", x - 1, ref[c] - 1, ++i % 12 == 0 ? "\n" : " "); | |||
n -= x; | |||
} | |||
} | |||
/^$/ { | |||
printchars(); | |||
printf("\n\n"); | |||
c = ""; | |||
i = 0; | |||
} | |||
/./ { | |||
if (!ref[$0]) { | |||
col[cnt++] = $0; | |||
ref[$0] = cnt; | |||
} | |||
if ($0 != c) { | |||
if (c != "") | |||
printchars(); | |||
c = $0; | |||
n = 0; | |||
} | |||
n++; | |||
} | |||
END { | |||
for (i = 0; i < cnt; i++) | |||
printf("%s,%s", col[i], ++j % 4 == 0 || i + 1 == cnt ? "\n" : " "); | |||
} |
@ -0,0 +1,247 @@ | |||
#ifndef ICON_DATA_H | |||
#define ICON_DATA_H | |||
typedef struct { | |||
unsigned int size; | |||
unsigned int cnt; | |||
const unsigned char *data; | |||
} icon_data_t; | |||
static const unsigned int icon_colors[] = { | |||
0xff222034, 0xffffffff, 0xff306082, 0xff76428a, | |||
0xfffbf236, 0xff99e550, 0xffd95763, 0xff37946e, | |||
0xff6abe30, 0xffac3232 | |||
}; | |||
static const unsigned char icon_data_16[] = { | |||
0xf0, 0x80, 0x01, 0xf0, 0x80, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, | |||
0x00, 0x01, 0x40, 0x01, 0x10, 0x01, 0x10, 0x01, 0x00, 0x01, 0x00, 0x01, | |||
0x30, 0x11, 0x00, 0x01, 0x00, 0x51, 0xf0, 0x22, 0xa0, 0x42, 0x80, 0x62, | |||
0x03, 0x50, 0x02, 0x34, 0x05, 0x22, 0x13, 0x06, 0x10, 0x37, 0x08, 0x35, | |||
0x12, 0x13, 0x09, 0x06, 0x22, 0x47, 0x25, 0x02, 0x13, 0x09, 0x06, 0x32, | |||
0x47, 0x08, 0x05, 0x08, 0x03, 0x19, 0x16, 0x32, 0x47, 0x18, 0x29, 0x16, | |||
0x42, 0x37, 0x18, 0x19, 0x26, 0x42, 0x47, 0x08 | |||
}; | |||
static const unsigned char icon_data_32[] = { | |||
0xf0, 0x10, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0x10, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, | |||
0x10, 0x11, 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, | |||
0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x90, 0x11, | |||
0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x00, 0x22, 0x50, 0x11, | |||
0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x50, 0x11, 0x30, | |||
0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x30, 0x31, 0x10, 0x11, | |||
0x10, 0xb1, 0x52, 0x30, 0x31, 0x10, 0x11, 0x10, 0xb1, 0x52, 0x30, 0x31, | |||
0x10, 0x11, 0x10, 0xb1, 0x52, 0xf0, 0x10, 0xd2, 0xf0, 0x00, 0xe2, 0xf0, | |||
0x54, 0x92, 0x13, 0xc0, 0x84, 0x05, 0x62, 0x33, 0x80, 0x74, 0x55, 0x42, | |||
0x33, 0x09, 0x02, 0x30, 0x77, 0x08, 0x85, 0x32, 0x33, 0x19, 0x12, 0xc7, | |||
0x08, 0x65, 0x08, 0x12, 0x33, 0x19, 0x06, 0x52, 0x97, 0x08, 0x45, 0x18, | |||
0x02, 0x33, 0x19, 0x16, 0x62, 0x97, 0x08, 0x35, 0x18, 0x23, 0x29, 0x16, | |||
0x72, 0x97, 0x18, 0x15, 0x18, 0x23, 0x29, 0x26, 0x72, 0x97, 0x48, 0x13, | |||
0x39, 0x26, 0x72, 0x97, 0x48, 0x03, 0x39, 0x36, 0x82, 0x97, 0x38, 0x49, | |||
0x36, 0x82, 0x97, 0x38, 0x39, 0x46, 0x82, 0x97, 0x38, 0x29, 0x56, 0x92, | |||
0x97, 0x28, 0x29, 0x56, 0x92, 0x97, 0x28 | |||
}; | |||
static const unsigned char icon_data_48[] = { | |||
0xf0, 0xa0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, | |||
0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, | |||
0x20, 0x21, 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, | |||
0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x81, | |||
0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, | |||
0x50, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, | |||
0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, | |||
0x20, 0x21, 0x10, 0x32, 0x80, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, | |||
0x20, 0x21, 0x52, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x00, 0x72, | |||
0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, | |||
0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, | |||
0xf0, 0xf0, 0x00, 0xe2, 0xf0, 0xe0, 0xf2, 0x02, 0xf0, 0xc0, 0xf2, 0x22, | |||
0xf0, 0xb0, 0xf2, 0x32, 0xf0, 0xa0, 0xf2, 0x42, 0xf0, 0x90, 0xf2, 0x52, | |||
0xf0, 0x80, 0x74, 0x15, 0xc2, 0x03, 0xf0, 0x50, 0xc4, 0x15, 0x92, 0x23, | |||
0xf0, 0x10, 0xe4, 0x35, 0x72, 0x33, 0x19, 0xc0, 0xc4, 0x85, 0x62, 0x43, | |||
0x19, 0x12, 0x70, 0x57, 0x18, 0xf5, 0x05, 0x52, 0x53, 0x19, 0x12, 0x40, | |||
0xb7, 0x18, 0xe5, 0x32, 0x53, 0x29, 0x22, 0xf7, 0x37, 0xb5, 0x08, 0x22, | |||
0x53, 0x29, 0x06, 0x72, 0xf7, 0xa5, 0x08, 0x12, 0x53, 0x29, 0x16, 0x92, | |||
0xe7, 0x85, 0x18, 0x02, 0x43, 0x39, 0x26, 0x92, 0xe7, 0x08, 0x65, 0x28, | |||
0x43, 0x39, 0x26, 0xa2, 0xe7, 0x18, 0x45, 0x28, 0x43, 0x39, 0x36, 0xa2, | |||
0xe7, 0x28, 0x25, 0x28, 0x33, 0x49, 0x36, 0xb2, 0xe7, 0x78, 0x33, 0x49, | |||
0x46, 0xb2, 0xe7, 0x68, 0x23, 0x59, 0x46, 0xb2, 0xe7, 0x68, 0x13, 0x59, | |||
0x56, 0xc2, 0xe7, 0x58, 0x79, 0x56, 0xc2, 0xe7, 0x58, 0x69, 0x66, 0xd2, | |||
0xd7, 0x58, 0x59, 0x76, 0xd2, 0xd7, 0x58, 0x49, 0x86, 0xe2, 0xe7, 0x38, | |||
0x39, 0x86, 0xf2, 0xe7, 0x38, 0x29, 0x86, 0xf2, 0x02, 0xe7, 0x38 | |||
}; | |||
static const unsigned char icon_data_64[] = { | |||
0xf0, 0xf0, 0x30, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, | |||
0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, | |||
0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, | |||
0x31, 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, | |||
0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, | |||
0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, | |||
0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, | |||
0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, | |||
0xf0, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x20, | |||
0x42, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x00, | |||
0x62, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, | |||
0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, | |||
0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, 0x31, | |||
0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0x70, 0x71, 0x30, | |||
0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, | |||
0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, | |||
0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, | |||
0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0xf0, 0xf0, 0x50, | |||
0xf2, 0x92, 0xf0, 0xf0, 0x30, 0xf2, 0xb2, 0xf0, 0xf0, 0x20, 0xf2, 0xc2, | |||
0xf0, 0xf0, 0x10, 0xf2, 0xd2, 0xf0, 0xf0, 0x00, 0x94, 0xf2, 0x42, 0xf0, | |||
0xe0, 0xe4, 0xf2, 0x12, 0x23, 0xf0, 0xa0, 0xf4, 0x14, 0x05, 0xe2, 0x43, | |||
0xf0, 0x70, 0xf4, 0x14, 0x35, 0xc2, 0x43, 0x19, 0xf0, 0x30, 0xf4, 0x95, | |||
0xa2, 0x53, 0x29, 0xe0, 0xf4, 0x04, 0xd5, 0x82, 0x63, 0x29, 0x02, 0x90, | |||
0xc7, 0xf5, 0x65, 0x62, 0x73, 0x29, 0x12, 0x50, 0xf7, 0x27, 0xf5, 0x35, | |||
0x52, 0x73, 0x29, 0x06, 0x32, 0xf7, 0x87, 0xf5, 0x05, 0x08, 0x42, 0x73, | |||
0x39, 0x06, 0x32, 0xf7, 0xa7, 0xd5, 0x28, 0x22, 0x73, 0x39, 0x16, 0xa2, | |||
0xf7, 0x47, 0xb5, 0x38, 0x12, 0x73, 0x39, 0x26, 0xb2, 0xf7, 0x47, 0xa5, | |||
0x38, 0x02, 0x73, 0x39, 0x26, 0xd2, 0xf7, 0x47, 0x08, 0x75, 0x48, 0x63, | |||
0x49, 0x36, 0xd2, 0xf7, 0x47, 0x18, 0x65, 0x38, 0x63, 0x49, 0x36, 0xe2, | |||
0xf7, 0x47, 0x28, 0x45, 0x38, 0x53, 0x59, 0x46, 0xe2, 0xf7, 0x47, 0x38, | |||
0x15, 0x48, 0x53, 0x59, 0x46, 0xf2, 0xf7, 0x37, 0xa8, 0x43, 0x69, 0x56, | |||
0xf2, 0xf7, 0x37, 0x98, 0x33, 0x79, 0x56, 0xf2, 0xf7, 0x37, 0x98, 0x23, | |||
0x79, 0x66, 0xf2, 0x02, 0xf7, 0x37, 0x88, 0x13, 0x89, 0x66, 0xf2, 0x02, | |||
0xf7, 0x37, 0x88, 0x03, 0x89, 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x99, | |||
0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x89, 0x86, 0xf2, 0x12, 0xf7, 0x37, | |||
0x78, 0x79, 0x96, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, | |||
0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x59, 0xb6, | |||
0xf2, 0x32, 0xf7, 0x37, 0x58, 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58, | |||
0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58 | |||
}; | |||
static const unsigned char icon_data_128[] = { | |||
0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, | |||
0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, | |||
0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, | |||
0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, | |||
0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, | |||
0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, | |||
0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, | |||
0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, | |||
0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, | |||
0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xa0, | |||
0x42, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, 0x71, | |||
0x60, 0x82, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, | |||
0x71, 0x40, 0xa2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, | |||
0x70, 0x71, 0x20, 0xc2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, | |||
0x71, 0x70, 0x71, 0x00, 0xe2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, | |||
0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, | |||
0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, | |||
0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, | |||
0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, | |||
0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, | |||
0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, | |||
0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, | |||
0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, | |||
0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, | |||
0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, | |||
0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0xf2, 0xf2, | |||
0xf2, 0x42, 0xf0, 0xf0, 0xf0, 0xf0, 0x80, 0xf2, 0xf2, 0xf2, 0x62, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0x70, 0xf2, 0xf2, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0x60, 0xf2, 0xf2, 0xf2, 0x82, 0xf0, 0xf0, 0xf0, 0xf0, 0x50, 0xf2, 0xf2, | |||
0xf2, 0x92, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf2, 0xf2, 0xf2, 0xa2, 0xf0, | |||
0xf0, 0xf0, 0xf0, 0x30, 0xf2, 0xf2, 0xf2, 0xb2, 0xf0, 0xf0, 0xf0, 0xf0, | |||
0x30, 0x12, 0xa4, 0xf2, 0xf2, 0xe2, 0xf0, 0xf0, 0xf0, 0xf0, 0x20, 0xf4, | |||
0x14, 0xf2, 0xf2, 0xa2, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0xf4, 0x64, 0xf2, | |||
0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xe0, 0xf4, 0xb4, 0xf2, 0xf2, 0x42, 0xf0, | |||
0xf0, 0xf0, 0xd0, 0xf4, 0xd4, 0x15, 0xf2, 0xf2, 0x12, 0x43, 0xf0, 0xf0, | |||
0xf0, 0x60, 0xf4, 0xf4, 0x04, 0x35, 0xf2, 0xe2, 0x63, 0xf0, 0xf0, 0xf0, | |||
0x30, 0xf4, 0xf4, 0x24, 0x45, 0xf2, 0xc2, 0x83, 0xf0, 0xf0, 0xf0, 0x00, | |||
0xf4, 0xf4, 0x34, 0x65, 0xf2, 0xa2, 0x93, 0xf0, 0xf0, 0xe0, 0xf4, 0xf4, | |||
0x34, 0x95, 0xf2, 0x82, 0xa3, 0x19, 0xf0, 0xf0, 0x90, 0xf4, 0xf4, 0x44, | |||
0xc5, 0xf2, 0x62, 0xb3, 0x29, 0xf0, 0xf0, 0x40, 0xf4, 0xf4, 0x64, 0xf5, | |||
0xf2, 0x42, 0xc3, 0x39, 0xf0, 0xf0, 0xf4, 0xf4, 0x74, 0xf5, 0x35, 0xf2, | |||
0x22, 0xd3, 0x49, 0xf0, 0xa0, 0xf4, 0xf4, 0x84, 0xf5, 0x75, 0xf2, 0x02, | |||
0xd3, 0x59, 0x02, 0xf0, 0x50, 0xf7, 0x07, 0xf4, 0x74, 0xf5, 0xb5, 0x08, | |||
0xe2, 0xe3, 0x59, 0x12, 0xf0, 0x10, 0xf7, 0xd7, 0xf5, 0xf5, 0x95, 0x18, | |||
0xc2, 0xe3, 0x59, 0x06, 0x22, 0xd0, 0xf7, 0xf7, 0x37, 0xf5, 0xf5, 0x65, | |||
0x18, 0xb2, 0xf3, 0x59, 0x06, 0x32, 0x80, 0xf7, 0xf7, 0x97, 0xf5, 0xf5, | |||
0x45, 0x18, 0xa2, 0xf3, 0x59, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x07, 0xf5, | |||
0xf5, 0x25, 0x28, 0x82, 0xf3, 0x69, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x27, | |||
0xf5, 0xf5, 0x05, 0x38, 0x62, 0xf3, 0x69, 0x26, 0x82, 0xf7, 0xf7, 0xf7, | |||
0x37, 0xf5, 0xd5, 0x48, 0x52, 0xf3, 0x79, 0x26, 0xc2, 0xf7, 0xf7, 0xf7, | |||
0x07, 0xf5, 0xb5, 0x58, 0x42, 0xf3, 0x79, 0x36, 0xf2, 0x22, 0xf7, 0xf7, | |||
0xb7, 0xf5, 0xa5, 0x58, 0x32, 0xf3, 0x79, 0x46, 0xf2, 0x52, 0xf7, 0xf7, | |||
0x97, 0xf5, 0x85, 0x68, 0x22, 0xf3, 0x89, 0x36, 0xf2, 0x72, 0xf7, 0xf7, | |||
0x97, 0xf5, 0x65, 0x78, 0x12, 0xf3, 0x89, 0x46, 0xf2, 0x82, 0xf7, 0xf7, | |||
0x97, 0xf5, 0x55, 0x78, 0x02, 0xf3, 0x89, 0x46, 0xf2, 0xa2, 0xf7, 0xf7, | |||
0x87, 0x08, 0xf5, 0x35, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xb2, 0xf7, 0xf7, | |||
0x77, 0x08, 0xf5, 0x25, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xc2, 0xf7, 0xf7, | |||
0x77, 0x18, 0xf5, 0x05, 0x88, 0xd3, 0xa9, 0x66, 0xf2, 0xc2, 0xf7, 0xf7, | |||
0x77, 0x28, 0xf5, 0x78, 0xd3, 0xa9, 0x66, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, | |||
0x38, 0xd5, 0x78, 0xc3, 0xb9, 0x76, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, 0x48, | |||
0xa5, 0x88, 0xc3, 0xb9, 0x76, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x58, 0x85, | |||
0x88, 0xb3, 0xc9, 0x86, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x68, 0x55, 0x98, | |||
0xb3, 0xc9, 0x86, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0x78, 0x25, 0xa8, 0xa3, | |||
0xc9, 0xa6, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x93, 0xd9, 0xa6, | |||
0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x83, 0xe9, 0xb6, 0xf2, 0xf2, | |||
0xf7, 0xf7, 0x77, 0xf8, 0x38, 0x73, 0xf9, 0xb6, 0xf2, 0xf2, 0xf7, 0xf7, | |||
0x77, 0xf8, 0x38, 0x63, 0xf9, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, | |||
0xf8, 0x28, 0x53, 0xf9, 0x09, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, | |||
0xf8, 0x28, 0x43, 0xf9, 0x09, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, | |||
0xf8, 0x18, 0x33, 0xf9, 0x19, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, | |||
0xf8, 0x18, 0x23, 0xf9, 0x19, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, | |||
0xf8, 0x08, 0x13, 0xf9, 0x29, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, | |||
0xf8, 0x08, 0x03, 0xf9, 0x29, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, | |||
0x77, 0xf8, 0xf9, 0x39, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, | |||
0xf8, 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 0xf8, | |||
0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, | |||
0x19, 0xf6, 0x26, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0x09, | |||
0xf6, 0x36, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0xf6, 0x56, | |||
0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xe9, 0xf6, 0x66, 0xf2, 0xf2, | |||
0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x32, 0xf7, | |||
0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, | |||
0xc8, 0xc9, 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xc9, | |||
0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xb9, 0xf6, 0x96, | |||
0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, | |||
0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, | |||
0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, | |||
0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8 | |||
}; | |||
#define ICON_(s) { s, ARRLEN(icon_data_##s), icon_data_##s } | |||
static const icon_data_t icons[] = { | |||
ICON_(16), | |||
ICON_(32), | |||
ICON_(48), | |||
ICON_(64), | |||
ICON_(128) | |||
}; | |||
#endif /* ICON_DATA_H */ | |||
@ -0,0 +1,797 @@ | |||
/* Copyright 2011, 2012 Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
#define _IMAGE_CONFIG | |||
#include "config.h" | |||
#include <errno.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <sys/stat.h> | |||
#include <sys/types.h> | |||
#include <unistd.h> | |||
#if HAVE_LIBEXIF | |||
#include <libexif/exif-data.h> | |||
#endif | |||
#if HAVE_GIFLIB | |||
#include <gif_lib.h> | |||
enum { DEF_GIF_DELAY = 75 }; | |||
#endif | |||
float zoom_min; | |||
float zoom_max; | |||
static int zoomdiff(img_t *img, float z) | |||
{ | |||
return (int) ((img->w * z - img->w * img->zoom) + (img->h * z - img->h * img->zoom)); | |||
} | |||
void img_init(img_t *img, win_t *win) | |||
{ | |||
zoom_min = zoom_levels[0] / 100.0; | |||
zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0; | |||
imlib_context_set_display(win->env.dpy); | |||
imlib_context_set_visual(win->env.vis); | |||
imlib_context_set_colormap(win->env.cmap); | |||
img->im = NULL; | |||
img->win = win; | |||
img->scalemode = options->scalemode; | |||
img->zoom = options->zoom; | |||
img->zoom = MAX(img->zoom, zoom_min); | |||
img->zoom = MIN(img->zoom, zoom_max); | |||
img->checkpan = false; | |||
img->dirty = false; | |||
img->aa = ANTI_ALIAS; | |||
img->alpha = ALPHA_LAYER; | |||
img->multi.cap = img->multi.cnt = 0; | |||
img->multi.animate = options->animate; | |||
img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0; | |||
img->multi.length = 0; | |||
img->cmod = imlib_create_color_modifier(); | |||
imlib_context_set_color_modifier(img->cmod); | |||
img->gamma = MIN(MAX(options->gamma, -GAMMA_RANGE), GAMMA_RANGE); | |||
img->ss.on = options->slideshow > 0; | |||
img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10; | |||
} | |||
#if HAVE_LIBEXIF | |||
void exif_auto_orientate(const fileinfo_t *file) | |||
{ | |||
ExifData *ed; | |||
ExifEntry *entry; | |||
int byte_order, orientation = 0; | |||
if ((ed = exif_data_new_from_file(file->path)) == NULL) | |||
return; | |||
byte_order = exif_data_get_byte_order(ed); | |||
entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); | |||
if (entry != NULL) | |||
orientation = exif_get_short(entry->data, byte_order); | |||
exif_data_unref(ed); | |||
switch (orientation) { | |||
case 5: | |||
imlib_image_orientate(1); | |||
case 2: | |||
imlib_image_flip_vertical(); | |||
break; | |||
case 3: | |||
imlib_image_orientate(2); | |||
break; | |||
case 7: | |||
imlib_image_orientate(1); | |||
case 4: | |||
imlib_image_flip_horizontal(); | |||
break; | |||
case 6: | |||
imlib_image_orientate(1); | |||
break; | |||
case 8: | |||
imlib_image_orientate(3); | |||
break; | |||
} | |||
} | |||
#endif | |||
#if HAVE_GIFLIB | |||
bool img_load_gif(img_t *img, const fileinfo_t *file) | |||
{ | |||
GifFileType *gif; | |||
GifRowType *rows = NULL; | |||
GifRecordType rec; | |||
ColorMapObject *cmap; | |||
DATA32 bgpixel, *data, *ptr; | |||
DATA32 *prev_frame = NULL; | |||
Imlib_Image im; | |||
int i, j, bg, r, g, b; | |||
int x, y, w, h, sw, sh; | |||
int px, py, pw, ph; | |||
int intoffset[] = { 0, 4, 2, 1 }; | |||
int intjump[] = { 8, 8, 4, 2 }; | |||
int transp = -1; | |||
unsigned int disposal = 0, prev_disposal = 0; | |||
unsigned int delay = 0; | |||
bool err = false; | |||
if (img->multi.cap == 0) { | |||
img->multi.cap = 8; | |||
img->multi.frames = (img_frame_t*) | |||
emalloc(sizeof(img_frame_t) * img->multi.cap); | |||
} | |||
img->multi.cnt = img->multi.sel = 0; | |||
img->multi.length = 0; | |||
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 | |||
gif = DGifOpenFileName(file->path, NULL); | |||
#else | |||
gif = DGifOpenFileName(file->path); | |||
#endif | |||
if (gif == NULL) { | |||
error(0, 0, "%s: Error opening gif image", file->name); | |||
return false; | |||
} | |||
bg = gif->SBackGroundColor; | |||
sw = gif->SWidth; | |||
sh = gif->SHeight; | |||
px = py = pw = ph = 0; | |||
do { | |||
if (DGifGetRecordType(gif, &rec) == GIF_ERROR) { | |||
err = true; | |||
break; | |||
} | |||
if (rec == EXTENSION_RECORD_TYPE) { | |||
int ext_code; | |||
GifByteType *ext = NULL; | |||
DGifGetExtension(gif, &ext_code, &ext); | |||
while (ext) { | |||
if (ext_code == GRAPHICS_EXT_FUNC_CODE) { | |||
if (ext[1] & 1) | |||
transp = (int) ext[4]; | |||
else | |||
transp = -1; | |||
delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]); | |||
disposal = (unsigned int) ext[1] >> 2 & 0x7; | |||
} | |||
ext = NULL; | |||
DGifGetExtensionNext(gif, &ext); | |||
} | |||
} else if (rec == IMAGE_DESC_RECORD_TYPE) { | |||
if (DGifGetImageDesc(gif) == GIF_ERROR) { | |||
err = true; | |||
break; | |||
} | |||
x = gif->Image.Left; | |||
y = gif->Image.Top; | |||
w = gif->Image.Width; | |||
h = gif->Image.Height; | |||
rows = (GifRowType*) emalloc(h * sizeof(GifRowType)); | |||
for (i = 0; i < h; i++) | |||
rows[i] = (GifRowType) emalloc(w * sizeof(GifPixelType)); | |||
if (gif->Image.Interlace) { | |||
for (i = 0; i < 4; i++) { | |||
for (j = intoffset[i]; j < h; j += intjump[i]) | |||
DGifGetLine(gif, rows[j], w); | |||
} | |||
} else { | |||
for (i = 0; i < h; i++) | |||
DGifGetLine(gif, rows[i], w); | |||
} | |||
ptr = data = (DATA32*) emalloc(sizeof(DATA32) * sw * sh); | |||
cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; | |||
r = cmap->Colors[bg].Red; | |||
g = cmap->Colors[bg].Green; | |||
b = cmap->Colors[bg].Blue; | |||
bgpixel = 0x00ffffff & (r << 16 | g << 8 | b); | |||
for (i = 0; i < sh; i++) { | |||
for (j = 0; j < sw; j++) { | |||
if (i < y || i >= y + h || j < x || j >= x + w || | |||
rows[i-y][j-x] == transp) | |||
{ | |||
if (prev_frame != NULL && (prev_disposal != 2 || | |||
i < py || i >= py + ph || j < px || j >= px + pw)) | |||
{ | |||
*ptr = prev_frame[i * sw + j]; | |||
} else { | |||
*ptr = bgpixel; | |||
} | |||
} else { | |||
r = cmap->Colors[rows[i-y][j-x]].Red; | |||
g = cmap->Colors[rows[i-y][j-x]].Green; | |||
b = cmap->Colors[rows[i-y][j-x]].Blue; | |||
*ptr = 0xffu << 24 | r << 16 | g << 8 | b; | |||
} | |||
ptr++; | |||
} | |||
} | |||
im = imlib_create_image_using_copied_data(sw, sh, data); | |||
for (i = 0; i < h; i++) | |||
free(rows[i]); | |||
free(rows); | |||
free(data); | |||
if (im == NULL) { | |||
err = true; | |||
break; | |||
} | |||
imlib_context_set_image(im); | |||
imlib_image_set_format("gif"); | |||
if (transp >= 0) | |||
imlib_image_set_has_alpha(1); | |||
if (disposal != 3) | |||
prev_frame = imlib_image_get_data_for_reading_only(); | |||
prev_disposal = disposal; | |||
px = x, py = y, pw = w, ph = h; | |||
if (img->multi.cnt == img->multi.cap) { | |||
img->multi.cap *= 2; | |||
img->multi.frames = (img_frame_t*) | |||
erealloc(img->multi.frames, | |||
img->multi.cap * sizeof(img_frame_t)); | |||
} | |||
img->multi.frames[img->multi.cnt].im = im; | |||
delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay; | |||
img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY; | |||
img->multi.length += img->multi.frames[img->multi.cnt].delay; | |||
img->multi.cnt++; | |||
} | |||
} while (rec != TERMINATE_RECORD_TYPE); | |||
#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1 | |||
DGifCloseFile(gif, NULL); | |||
#else | |||
DGifCloseFile(gif); | |||
#endif | |||
if (err && (file->flags & FF_WARN)) | |||
error(0, 0, "%s: Corrupted gif file", file->name); | |||
if (img->multi.cnt > 1) { | |||
imlib_context_set_image(img->im); | |||
imlib_free_image(); | |||
img->im = img->multi.frames[0].im; | |||
} else if (img->multi.cnt == 1) { | |||
imlib_context_set_image(img->multi.frames[0].im); | |||
imlib_free_image(); | |||
img->multi.cnt = 0; | |||
} | |||
imlib_context_set_image(img->im); | |||
return !err; | |||
} | |||
#endif /* HAVE_GIFLIB */ | |||
Imlib_Image img_open(const fileinfo_t *file) | |||
{ | |||
struct stat st; | |||
Imlib_Image im = NULL; | |||
if (access(file->path, R_OK) == 0 && | |||
stat(file->path, &st) == 0 && S_ISREG(st.st_mode)) | |||
{ | |||
im = imlib_load_image(file->path); | |||
if (im != NULL) { | |||
imlib_context_set_image(im); | |||
if (imlib_image_get_data_for_reading_only() == NULL) { | |||
imlib_free_image(); | |||
im = NULL; | |||
} | |||
} | |||
} | |||
if (im == NULL && (file->flags & FF_WARN)) | |||
error(0, 0, "%s: Error opening image", file->name); | |||
return im; | |||
} | |||
bool img_load(img_t *img, const fileinfo_t *file) | |||
{ | |||
const char *fmt; | |||
if ((img->im = img_open(file)) == NULL) | |||
return false; | |||
imlib_image_set_changes_on_disk(); | |||
#if HAVE_LIBEXIF | |||
exif_auto_orientate(file); | |||
#endif | |||
if ((fmt = imlib_image_format()) != NULL) { | |||
#if HAVE_GIFLIB | |||
if (STREQ(fmt, "gif")) | |||
img_load_gif(img, file); | |||
#endif | |||
} | |||
img->w = imlib_image_get_width(); | |||
img->h = imlib_image_get_height(); | |||
img->checkpan = true; | |||
img->dirty = true; | |||
return true; | |||
} | |||
CLEANUP void img_close(img_t *img, bool decache) | |||
{ | |||
int i; | |||
if (img->multi.cnt > 0) { | |||
for (i = 0; i < img->multi.cnt; i++) { | |||
imlib_context_set_image(img->multi.frames[i].im); | |||
imlib_free_image(); | |||
} | |||
img->multi.cnt = 0; | |||
img->im = NULL; | |||
} else if (img->im != NULL) { | |||
imlib_context_set_image(img->im); | |||
if (decache) | |||
imlib_free_image_and_decache(); | |||
else | |||
imlib_free_image(); | |||
img->im = NULL; | |||
} | |||
} | |||
void img_check_pan(img_t *img, bool moved) | |||
{ | |||
win_t *win; | |||
float w, h, ox, oy; | |||
win = img->win; | |||
w = img->w * img->zoom; | |||
h = img->h * img->zoom; | |||
ox = img->x; | |||
oy = img->y; | |||
if (w < win->w) | |||
img->x = (win->w - w) / 2; | |||
else if (img->x > 0) | |||
img->x = 0; | |||
else if (img->x + w < win->w) | |||
img->x = win->w - w; | |||
if (h < win->h) | |||
img->y = (win->h - h) / 2; | |||
else if (img->y > 0) | |||
img->y = 0; | |||
else if (img->y + h < win->h) | |||
img->y = win->h - h; | |||
if (!moved && (ox != img->x || oy != img->y)) | |||
img->dirty = true; | |||
} | |||
bool img_fit(img_t *img) | |||
{ | |||
float z, zw, zh; | |||
if (img->scalemode == SCALE_ZOOM) | |||
return false; | |||
zw = (float) img->win->w / (float) img->w; | |||
zh = (float) img->win->h / (float) img->h; | |||
switch (img->scalemode) { | |||
case SCALE_WIDTH: | |||
z = zw; | |||
break; | |||
case SCALE_HEIGHT: | |||
z = zh; | |||
break; | |||
default: | |||
z = MIN(zw, zh); | |||
break; | |||
} | |||
z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : zoom_max); | |||
if (zoomdiff(img, z) != 0) { | |||
img->zoom = z; | |||
img->dirty = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
void img_render(img_t *img) | |||
{ | |||
win_t *win; | |||
int sx, sy, sw, sh; | |||
int dx, dy, dw, dh; | |||
Imlib_Image bg; | |||
unsigned long c; | |||
win = img->win; | |||
img_fit(img); | |||
if (img->checkpan) { | |||
img_check_pan(img, false); | |||
img->checkpan = false; | |||
} | |||
if (!img->dirty) | |||
return; | |||
/* calculate source and destination offsets: | |||
* - part of image drawn on full window, or | |||
* - full image drawn on part of window | |||
*/ | |||
if (img->x <= 0) { | |||
sx = -img->x / img->zoom + 0.5; | |||
sw = win->w / img->zoom; | |||
dx = 0; | |||
dw = win->w; | |||
} else { | |||
sx = 0; | |||
sw = img->w; | |||
dx = img->x; | |||
dw = img->w * img->zoom; | |||
} | |||
if (img->y <= 0) { | |||
sy = -img->y / img->zoom + 0.5; | |||
sh = win->h / img->zoom; | |||
dy = 0; | |||
dh = win->h; | |||
} else { | |||
sy = 0; | |||
sh = img->h; | |||
dy = img->y; | |||
dh = img->h * img->zoom; | |||
} | |||
win_clear(win); | |||
imlib_context_set_image(img->im); | |||
imlib_context_set_anti_alias(img->aa); | |||
imlib_context_set_drawable(win->buf.pm); | |||
if (imlib_image_has_alpha()) { | |||
if ((bg = imlib_create_image(dw, dh)) == NULL) | |||
error(EXIT_FAILURE, ENOMEM, NULL); | |||
imlib_context_set_image(bg); | |||
imlib_image_set_has_alpha(0); | |||
if (img->alpha) { | |||
int i, c, r; | |||
DATA32 col[2] = { 0xFF666666, 0xFF999999 }; | |||
DATA32 * data = imlib_image_get_data(); | |||
for (r = 0; r < dh; r++) { | |||
i = r * dw; | |||
if (r == 0 || r == 8) { | |||
for (c = 0; c < dw; c++) | |||
data[i++] = col[!(c & 8) ^ !r]; | |||
} else { | |||
memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0])); | |||
} | |||
} | |||
imlib_image_put_back_data(data); | |||
} else { | |||
c = win->bg.pixel; | |||
imlib_context_set_color(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF, 0xFF); | |||
imlib_image_fill_rectangle(0, 0, dw, dh); | |||
} | |||
imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh); | |||
imlib_context_set_color_modifier(NULL); | |||
imlib_render_image_on_drawable(dx, dy); | |||
imlib_free_image(); | |||
imlib_context_set_color_modifier(img->cmod); | |||
} else { | |||
imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh); | |||
} | |||
img->dirty = false; | |||
} | |||
bool img_fit_win(img_t *img, scalemode_t sm) | |||
{ | |||
float oz; | |||
oz = img->zoom; | |||
img->scalemode = sm; | |||
if (img_fit(img)) { | |||
img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz; | |||
img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz; | |||
img->checkpan = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool img_zoom(img_t *img, float z) | |||
{ | |||
z = MAX(z, zoom_min); | |||
z = MIN(z, zoom_max); | |||
img->scalemode = SCALE_ZOOM; | |||
if (zoomdiff(img, z) != 0) { | |||
int x, y; | |||
win_cursor_pos(img->win, &x, &y); | |||
if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) { | |||
x = img->win->w / 2; | |||
y = img->win->h / 2; | |||
} | |||
img->x = x - (x - img->x) * z / img->zoom; | |||
img->y = y - (y - img->y) * z / img->zoom; | |||
img->zoom = z; | |||
img->checkpan = true; | |||
img->dirty = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool img_zoom_in(img_t *img) | |||
{ | |||
int i; | |||
float z; | |||
for (i = 0; i < ARRLEN(zoom_levels); i++) { | |||
z = zoom_levels[i] / 100.0; | |||
if (zoomdiff(img, z) > 0) | |||
return img_zoom(img, z); | |||
} | |||
return false; | |||
} | |||
bool img_zoom_out(img_t *img) | |||
{ | |||
int i; | |||
float z; | |||
for (i = ARRLEN(zoom_levels) - 1; i >= 0; i--) { | |||
z = zoom_levels[i] / 100.0; | |||
if (zoomdiff(img, z) < 0) | |||
return img_zoom(img, z); | |||
} | |||
return false; | |||
} | |||
bool img_pos(img_t *img, float x, float y) | |||
{ | |||
float ox, oy; | |||
ox = img->x; | |||
oy = img->y; | |||
img->x = x; | |||
img->y = y; | |||
img_check_pan(img, true); | |||
if (ox != img->x || oy != img->y) { | |||
img->dirty = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool img_move(img_t *img, float dx, float dy) | |||
{ | |||
return img_pos(img, img->x + dx, img->y + dy); | |||
} | |||
bool img_pan(img_t *img, direction_t dir, int d) | |||
{ | |||
/* d < 0: screen-wise | |||
* d = 0: 1/PAN_FRACTION of screen | |||
* d > 0: num of pixels | |||
*/ | |||
float x, y; | |||
if (d > 0) { | |||
x = y = MAX(1, (float) d * img->zoom); | |||
} else { | |||
x = img->win->w / (d < 0 ? 1 : PAN_FRACTION); | |||
y = img->win->h / (d < 0 ? 1 : PAN_FRACTION); | |||
} | |||
switch (dir) { | |||
case DIR_LEFT: | |||
return img_move(img, x, 0.0); | |||
case DIR_RIGHT: | |||
return img_move(img, -x, 0.0); | |||
case DIR_UP: | |||
return img_move(img, 0.0, y); | |||
case DIR_DOWN: | |||
return img_move(img, 0.0, -y); | |||
} | |||
return false; | |||
} | |||
bool img_pan_edge(img_t *img, direction_t dir) | |||
{ | |||
float ox, oy; | |||
ox = img->x; | |||
oy = img->y; | |||
if (dir & DIR_LEFT) | |||
img->x = 0; | |||
if (dir & DIR_RIGHT) | |||
img->x = img->win->w - img->w * img->zoom; | |||
if (dir & DIR_UP) | |||
img->y = 0; | |||
if (dir & DIR_DOWN) | |||
img->y = img->win->h - img->h * img->zoom; | |||
img_check_pan(img, true); | |||
if (ox != img->x || oy != img->y) { | |||
img->dirty = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
void img_rotate(img_t *img, degree_t d) | |||
{ | |||
int i, tmp; | |||
float ox, oy; | |||
imlib_context_set_image(img->im); | |||
imlib_image_orientate(d); | |||
for (i = 0; i < img->multi.cnt; i++) { | |||
if (i != img->multi.sel) { | |||
imlib_context_set_image(img->multi.frames[i].im); | |||
imlib_image_orientate(d); | |||
} | |||
} | |||
if (d == DEGREE_90 || d == DEGREE_270) { | |||
ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom; | |||
oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom; | |||
img->x = oy + (img->win->w - img->win->h) / 2; | |||
img->y = ox + (img->win->h - img->win->w) / 2; | |||
tmp = img->w; | |||
img->w = img->h; | |||
img->h = tmp; | |||
img->checkpan = true; | |||
} | |||
img->dirty = true; | |||
} | |||
void img_flip(img_t *img, flipdir_t d) | |||
{ | |||
int i; | |||
void (*imlib_flip_op[3])(void) = { | |||
imlib_image_flip_horizontal, | |||
imlib_image_flip_vertical, | |||
imlib_image_flip_diagonal | |||
}; | |||
d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1; | |||
if (d < 0 || d >= ARRLEN(imlib_flip_op)) | |||
return; | |||
imlib_context_set_image(img->im); | |||
imlib_flip_op[d](); | |||
for (i = 0; i < img->multi.cnt; i++) { | |||
if (i != img->multi.sel) { | |||
imlib_context_set_image(img->multi.frames[i].im); | |||
imlib_flip_op[d](); | |||
} | |||
} | |||
img->dirty = true; | |||
} | |||
void img_toggle_antialias(img_t *img) | |||
{ | |||
img->aa = !img->aa; | |||
imlib_context_set_image(img->im); | |||
imlib_context_set_anti_alias(img->aa); | |||
img->dirty = true; | |||
} | |||
bool img_change_gamma(img_t *img, int d) | |||
{ | |||
/* d < 0: decrease gamma | |||
* d = 0: reset gamma | |||
* d > 0: increase gamma | |||
*/ | |||
int gamma; | |||
double range; | |||
if (d == 0) | |||
gamma = 0; | |||
else | |||
gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE); | |||
if (img->gamma != gamma) { | |||
imlib_reset_color_modifier(); | |||
if (gamma != 0) { | |||
range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0; | |||
imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE)); | |||
} | |||
img->gamma = gamma; | |||
img->dirty = true; | |||
return true; | |||
} else { | |||
return false; | |||
} | |||
} | |||
bool img_frame_goto(img_t *img, int n) | |||
{ | |||
if (n < 0 || n >= img->multi.cnt || n == img->multi.sel) | |||
return false; | |||
img->multi.sel = n; | |||
img->im = img->multi.frames[n].im; | |||
imlib_context_set_image(img->im); | |||
img->w = imlib_image_get_width(); | |||
img->h = imlib_image_get_height(); | |||
img->checkpan = true; | |||
img->dirty = true; | |||
return true; | |||
} | |||
bool img_frame_navigate(img_t *img, int d) | |||
{ | |||
if (img->multi.cnt == 0 || d == 0) | |||
return false; | |||
d += img->multi.sel; | |||
if (d < 0) | |||
d = 0; | |||
else if (d >= img->multi.cnt) | |||
d = img->multi.cnt - 1; | |||
return img_frame_goto(img, d); | |||
} | |||
bool img_frame_animate(img_t *img) | |||
{ | |||
if (img->multi.cnt == 0) | |||
return false; | |||
if (img->multi.sel + 1 >= img->multi.cnt) | |||
img_frame_goto(img, 0); | |||
else | |||
img_frame_goto(img, img->multi.sel + 1); | |||
img->dirty = true; | |||
return true; | |||
} | |||
@ -0,0 +1,951 @@ | |||
/* Copyright 2011-2013 Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
#define _MAPPINGS_CONFIG | |||
#include "config.h" | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <fcntl.h> | |||
#include <unistd.h> | |||
#include <errno.h> | |||
#include <locale.h> | |||
#include <signal.h> | |||
#include <sys/select.h> | |||
#include <sys/stat.h> | |||
#include <sys/wait.h> | |||
#include <time.h> | |||
#include <X11/keysym.h> | |||
#include <X11/XF86keysym.h> | |||
typedef struct { | |||
struct timeval when; | |||
bool active; | |||
timeout_f handler; | |||
} timeout_t; | |||
/* timeout handler functions: */ | |||
void redraw(void); | |||
void reset_cursor(void); | |||
void animate(void); | |||
void slideshow(void); | |||
void clear_resize(void); | |||
appmode_t mode; | |||
arl_t arl; | |||
img_t img; | |||
tns_t tns; | |||
win_t win; | |||
fileinfo_t *files; | |||
int filecnt, fileidx; | |||
int alternate; | |||
int markcnt; | |||
int markidx; | |||
int prefix; | |||
bool extprefix; | |||
bool resized = false; | |||
typedef struct { | |||
int err; | |||
char *cmd; | |||
} extcmd_t; | |||
struct { | |||
extcmd_t f; | |||
int fd; | |||
unsigned int i, lastsep; | |||
pid_t pid; | |||
} info; | |||
struct { | |||
extcmd_t f; | |||
bool warned; | |||
} keyhandler; | |||
timeout_t timeouts[] = { | |||
{ { 0, 0 }, false, redraw }, | |||
{ { 0, 0 }, false, reset_cursor }, | |||
{ { 0, 0 }, false, animate }, | |||
{ { 0, 0 }, false, slideshow }, | |||
{ { 0, 0 }, false, clear_resize }, | |||
}; | |||
cursor_t imgcursor[3] = { | |||
CURSOR_ARROW, CURSOR_ARROW, CURSOR_ARROW | |||
}; | |||
void cleanup(void) | |||
{ | |||
img_close(&img, false); | |||
arl_cleanup(&arl); | |||
tns_free(&tns); | |||
win_close(&win); | |||
} | |||
void check_add_file(char *filename, bool given) | |||
{ | |||
char *path; | |||
if (*filename == '\0') | |||
return; | |||
if (access(filename, R_OK) < 0 || | |||
(path = realpath(filename, NULL)) == NULL) | |||
{ | |||
if (given) | |||
error(0, errno, "%s", filename); | |||
return; | |||
} | |||
if (fileidx == filecnt) { | |||
filecnt *= 2; | |||
files = erealloc(files, filecnt * sizeof(*files)); | |||
memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files)); | |||
} | |||
files[fileidx].name = estrdup(filename); | |||
files[fileidx].path = path; | |||
if (given) | |||
files[fileidx].flags |= FF_WARN; | |||
fileidx++; | |||
} | |||
void remove_file(int n, bool manual) | |||
{ | |||
if (n < 0 || n >= filecnt) | |||
return; | |||
if (filecnt == 1) { | |||
if (!manual) | |||
fprintf(stderr, "sxiv: no more files to display, aborting\n"); | |||
exit(manual ? EXIT_SUCCESS : EXIT_FAILURE); | |||
} | |||
if (files[n].flags & FF_MARK) | |||
markcnt--; | |||
if (files[n].path != files[n].name) | |||
free((void*) files[n].path); | |||
free((void*) files[n].name); | |||
if (n + 1 < filecnt) { | |||
if (tns.thumbs != NULL) { | |||
memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) * | |||
sizeof(*tns.thumbs)); | |||
memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs)); | |||
} | |||
memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files)); | |||
} | |||
filecnt--; | |||
if (fileidx > n || fileidx == filecnt) | |||
fileidx--; | |||
if (alternate > n || alternate == filecnt) | |||
alternate--; | |||
if (markidx > n || markidx == filecnt) | |||
markidx--; | |||
} | |||
void set_timeout(timeout_f handler, int time, bool overwrite) | |||
{ | |||
int i; | |||
for (i = 0; i < ARRLEN(timeouts); i++) { | |||
if (timeouts[i].handler == handler) { | |||
if (!timeouts[i].active || overwrite) { | |||
gettimeofday(&timeouts[i].when, 0); | |||
TV_ADD_MSEC(&timeouts[i].when, time); | |||
timeouts[i].active = true; | |||
} | |||
return; | |||
} | |||
} | |||
} | |||
void reset_timeout(timeout_f handler) | |||
{ | |||
int i; | |||
for (i = 0; i < ARRLEN(timeouts); i++) { | |||
if (timeouts[i].handler == handler) { | |||
timeouts[i].active = false; | |||
return; | |||
} | |||
} | |||
} | |||
bool check_timeouts(struct timeval *t) | |||
{ | |||
int i = 0, tdiff, tmin = -1; | |||
struct timeval now; | |||
while (i < ARRLEN(timeouts)) { | |||
if (timeouts[i].active) { | |||
gettimeofday(&now, 0); | |||
tdiff = TV_DIFF(&timeouts[i].when, &now); | |||
if (tdiff <= 0) { | |||
timeouts[i].active = false; | |||
if (timeouts[i].handler != NULL) | |||
timeouts[i].handler(); | |||
i = tmin = -1; | |||
} else if (tmin < 0 || tdiff < tmin) { | |||
tmin = tdiff; | |||
} | |||
} | |||
i++; | |||
} | |||
if (tmin > 0 && t != NULL) | |||
TV_SET_MSEC(t, tmin); | |||
return tmin > 0; | |||
} | |||
void close_info(void) | |||
{ | |||
if (info.fd != -1) { | |||
kill(info.pid, SIGTERM); | |||
close(info.fd); | |||
info.fd = -1; | |||
} | |||
} | |||
void open_info(void) | |||
{ | |||
int pfd[2]; | |||
char w[12], h[12]; | |||
if (info.f.err != 0 || info.fd >= 0 || win.bar.h == 0) | |||
return; | |||
win.bar.l.buf[0] = '\0'; | |||
if (pipe(pfd) < 0) | |||
return; | |||
if ((info.pid = fork()) == 0) { | |||
close(pfd[0]); | |||
dup2(pfd[1], 1); | |||
snprintf(w, sizeof(w), "%d", img.w); | |||
snprintf(h, sizeof(h), "%d", img.h); | |||
execl(info.f.cmd, info.f.cmd, files[fileidx].name, w, h, NULL); | |||
error(EXIT_FAILURE, errno, "exec: %s", info.f.cmd); | |||
} | |||
close(pfd[1]); | |||
if (info.pid < 0) { | |||
close(pfd[0]); | |||
} else { | |||
fcntl(pfd[0], F_SETFL, O_NONBLOCK); | |||
info.fd = pfd[0]; | |||
info.i = info.lastsep = 0; | |||
} | |||
} | |||
void read_info(void) | |||
{ | |||
ssize_t i, n; | |||
char buf[BAR_L_LEN]; | |||
while (true) { | |||
n = read(info.fd, buf, sizeof(buf)); | |||
if (n < 0 && errno == EAGAIN) | |||
return; | |||
else if (n == 0) | |||
goto end; | |||
for (i = 0; i < n; i++) { | |||
if (buf[i] == '\n') { | |||
if (info.lastsep == 0) { | |||
win.bar.l.buf[info.i++] = ' '; | |||
info.lastsep = 1; | |||
} | |||
} else { | |||
win.bar.l.buf[info.i++] = buf[i]; | |||
info.lastsep = 0; | |||
} | |||
if (info.i + 1 == win.bar.l.size) | |||
goto end; | |||
} | |||
} | |||
end: | |||
info.i -= info.lastsep; | |||
win.bar.l.buf[info.i] = '\0'; | |||
win_draw(&win); | |||
close_info(); | |||
} | |||
void load_image(int new) | |||
{ | |||
bool prev = new < fileidx; | |||
static int current; | |||
if (new < 0 || new >= filecnt) | |||
return; | |||
if (win.xwin != None) | |||
win_set_cursor(&win, CURSOR_WATCH); | |||
reset_timeout(slideshow); | |||
if (new != current) | |||
alternate = current; | |||
img_close(&img, false); | |||
while (!img_load(&img, &files[new])) { | |||
remove_file(new, false); | |||
if (new >= filecnt) | |||
new = filecnt - 1; | |||
else if (new > 0 && prev) | |||
new--; | |||
} | |||
files[new].flags &= ~FF_WARN; | |||
fileidx = current = new; | |||
close_info(); | |||
open_info(); | |||
arl_setup(&arl, files[fileidx].path); | |||
if (img.multi.cnt > 0 && img.multi.animate) | |||
set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); | |||
else | |||
reset_timeout(animate); | |||
} | |||
bool mark_image(int n, bool on) | |||
{ | |||
markidx = n; | |||
if (!!(files[n].flags & FF_MARK) != on) { | |||
files[n].flags ^= FF_MARK; | |||
markcnt += on ? 1 : -1; | |||
if (mode == MODE_THUMB) | |||
tns_mark(&tns, n, on); | |||
return true; | |||
} | |||
return false; | |||
} | |||
void bar_put(win_bar_t *bar, const char *fmt, ...) | |||
{ | |||
size_t len = bar->size - (bar->p - bar->buf), n; | |||
va_list ap; | |||
va_start(ap, fmt); | |||
n = vsnprintf(bar->p, len, fmt, ap); | |||
bar->p += MIN(len, n); | |||
va_end(ap); | |||
} | |||
#define BAR_SEP " " | |||
void update_info(void) | |||
{ | |||
unsigned int i, fn, fw; | |||
const char * mark; | |||
win_bar_t *l = &win.bar.l, *r = &win.bar.r; | |||
/* update bar contents */ | |||
if (win.bar.h == 0) | |||
return; | |||
for (fw = 0, i = filecnt; i > 0; fw++, i /= 10); | |||
mark = files[fileidx].flags & FF_MARK ? "* " : ""; | |||
l->p = l->buf; | |||
r->p = r->buf; | |||
if (mode == MODE_THUMB) { | |||
if (tns.loadnext < tns.end) | |||
bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1); | |||
else if (tns.initnext < filecnt) | |||
bar_put(l, "Caching... %0*d", fw, tns.initnext + 1); | |||
else | |||
strncpy(l->buf, files[fileidx].name, l->size); | |||
bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt); | |||
} else { | |||
bar_put(r, "%s", mark); | |||
if (img.ss.on) { | |||
if (img.ss.delay % 10 != 0) | |||
bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10); | |||
else | |||
bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10); | |||
} | |||
if (img.gamma != 0) | |||
bar_put(r, "G%+d" BAR_SEP, img.gamma); | |||
bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0)); | |||
if (img.multi.cnt > 0) { | |||
for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10); | |||
bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt); | |||
} | |||
bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt); | |||
if (info.f.err) | |||
strncpy(l->buf, files[fileidx].name, l->size); | |||
} | |||
} | |||
int ptr_third_x(void) | |||
{ | |||
int x, y; | |||
win_cursor_pos(&win, &x, &y); | |||
return MAX(0, MIN(2, (x / (win.w * 0.33)))); | |||
} | |||
void redraw(void) | |||
{ | |||
int t; | |||
if (mode == MODE_IMAGE) { | |||
img_render(&img); | |||
if (img.ss.on) { | |||
t = img.ss.delay * 100; | |||
if (img.multi.cnt > 0 && img.multi.animate) | |||
t = MAX(t, img.multi.length); | |||
set_timeout(slideshow, t, false); | |||
} | |||
} else { | |||
tns_render(&tns); | |||
} | |||
update_info(); | |||
win_draw(&win); | |||
reset_timeout(redraw); | |||
reset_cursor(); | |||
} | |||
void reset_cursor(void) | |||
{ | |||
int c, i; | |||
cursor_t cursor = CURSOR_NONE; | |||
if (mode == MODE_IMAGE) { | |||
for (i = 0; i < ARRLEN(timeouts); i++) { | |||
if (timeouts[i].handler == reset_cursor) { | |||
if (timeouts[i].active) { | |||
c = ptr_third_x(); | |||
c = MAX(fileidx > 0 ? 0 : 1, c); | |||
c = MIN(fileidx + 1 < filecnt ? 2 : 1, c); | |||
cursor = imgcursor[c]; | |||
} | |||
break; | |||
} | |||
} | |||
} else { | |||
if (tns.loadnext < tns.end || tns.initnext < filecnt) | |||
cursor = CURSOR_WATCH; | |||
else | |||
cursor = CURSOR_ARROW; | |||
} | |||
win_set_cursor(&win, cursor); | |||
} | |||
void animate(void) | |||
{ | |||
if (img_frame_animate(&img)) { | |||
redraw(); | |||
set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); | |||
} | |||
} | |||
void slideshow(void) | |||
{ | |||
load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0); | |||
redraw(); | |||
} | |||
void clear_resize(void) | |||
{ | |||
resized = false; | |||
} | |||
Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg) | |||
{ | |||
return ev->type == ButtonPress || ev->type == KeyPress; | |||
} | |||
void run_key_handler(const char *key, unsigned int mask) | |||
{ | |||
pid_t pid; | |||
FILE *pfs; | |||
bool marked = mode == MODE_THUMB && markcnt > 0; | |||
bool changed = false; | |||
int f, i, pfd[2]; | |||
int fcnt = marked ? markcnt : 1; | |||
char kstr[32]; | |||
struct stat *oldst, st; | |||
XEvent dump; | |||
if (keyhandler.f.err != 0) { | |||
if (!keyhandler.warned) { | |||
error(0, keyhandler.f.err, "%s", keyhandler.f.cmd); | |||
keyhandler.warned = true; | |||
} | |||
return; | |||
} | |||
if (key == NULL) | |||
return; | |||
if (pipe(pfd) < 0) { | |||
error(0, errno, "pipe"); | |||
return; | |||
} | |||
if ((pfs = fdopen(pfd[1], "w")) == NULL) { | |||
error(0, errno, "open pipe"); | |||
close(pfd[0]), close(pfd[1]); | |||
return; | |||
} | |||
oldst = emalloc(fcnt * sizeof(*oldst)); | |||
close_info(); | |||
strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size); | |||
win_draw(&win); | |||
win_set_cursor(&win, CURSOR_WATCH); | |||
snprintf(kstr, sizeof(kstr), "%s%s%s%s", | |||
mask & ControlMask ? "C-" : "", | |||
mask & Mod1Mask ? "M-" : "", | |||
mask & ShiftMask ? "S-" : "", key); | |||
if ((pid = fork()) == 0) { | |||
close(pfd[1]); | |||
dup2(pfd[0], 0); | |||
execl(keyhandler.f.cmd, keyhandler.f.cmd, kstr, NULL); | |||
error(EXIT_FAILURE, errno, "exec: %s", keyhandler.f.cmd); | |||
} | |||
close(pfd[0]); | |||
if (pid < 0) { | |||
error(0, errno, "fork"); | |||
fclose(pfs); | |||
goto end; | |||
} | |||
for (f = i = 0; f < fcnt; i++) { | |||
if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { | |||
stat(files[i].path, &oldst[f]); | |||
fprintf(pfs, "%s\n", files[i].name); | |||
f++; | |||
} | |||
} | |||
fclose(pfs); | |||
while (waitpid(pid, NULL, 0) == -1 && errno == EINTR); | |||
for (f = i = 0; f < fcnt; i++) { | |||
if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { | |||
if (stat(files[i].path, &st) != 0 || | |||
memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0) | |||
{ | |||
if (tns.thumbs != NULL) { | |||
tns_unload(&tns, i); | |||
tns.loadnext = MIN(tns.loadnext, i); | |||
} | |||
changed = true; | |||
} | |||
f++; | |||
} | |||
} | |||
/* drop user input events that occurred while running the key handler */ | |||
while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL)); | |||
end: | |||
if (mode == MODE_IMAGE) { | |||
if (changed) { | |||
img_close(&img, true); | |||
load_image(fileidx); | |||
} else { | |||
open_info(); | |||
} | |||
} | |||
free(oldst); | |||
reset_cursor(); | |||
redraw(); | |||
} | |||
#define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask)) | |||
void on_keypress(XKeyEvent *kev) | |||
{ | |||
int i; | |||
unsigned int sh = 0; | |||
KeySym ksym, shksym; | |||
char dummy, key; | |||
bool dirty = false; | |||
XLookupString(kev, &key, 1, &ksym, NULL); | |||
if (kev->state & ShiftMask) { | |||
kev->state &= ~ShiftMask; | |||
XLookupString(kev, &dummy, 1, &shksym, NULL); | |||
kev->state |= ShiftMask; | |||
if (ksym != shksym) | |||
sh = ShiftMask; | |||
} | |||
if (IsModifierKey(ksym)) | |||
return; | |||
if (ksym == XK_Escape && MODMASK(kev->state) == 0) { | |||
extprefix = False; | |||
} else if (extprefix) { | |||
run_key_handler(XKeysymToString(ksym), kev->state & ~sh); | |||
extprefix = False; | |||
} else if (key >= '0' && key <= '9') { | |||
/* number prefix for commands */ | |||
prefix = prefix * 10 + (int) (key - '0'); | |||
return; | |||
} else for (i = 0; i < ARRLEN(keys); i++) { | |||
if (keys[i].ksym == ksym && | |||
MODMASK(keys[i].mask | sh) == MODMASK(kev->state) && | |||
keys[i].cmd >= 0 && keys[i].cmd < CMD_COUNT && | |||
(cmds[keys[i].cmd].mode < 0 || cmds[keys[i].cmd].mode == mode)) | |||
{ | |||
if (cmds[keys[i].cmd].func(keys[i].arg)) | |||
dirty = true; | |||
} | |||
} | |||
if (dirty) | |||
redraw(); | |||
prefix = 0; | |||
} | |||
void on_buttonpress(XButtonEvent *bev) | |||
{ | |||
int i, sel; | |||
bool dirty = false; | |||
static Time firstclick; | |||
if (mode == MODE_IMAGE) { | |||
set_timeout(reset_cursor, TO_CURSOR_HIDE, true); | |||
reset_cursor(); | |||
for (i = 0; i < ARRLEN(buttons); i++) { | |||
if (buttons[i].button == bev->button && | |||
MODMASK(buttons[i].mask) == MODMASK(bev->state) && | |||
buttons[i].cmd >= 0 && buttons[i].cmd < CMD_COUNT && | |||
(cmds[buttons[i].cmd].mode < 0 || cmds[buttons[i].cmd].mode == mode)) | |||
{ | |||
if (cmds[buttons[i].cmd].func(buttons[i].arg)) | |||
dirty = true; | |||
} | |||
} | |||
if (dirty) | |||
redraw(); | |||
} else { | |||
/* thumbnail mode (hard-coded) */ | |||
switch (bev->button) { | |||
case Button1: | |||
if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { | |||
if (sel != fileidx) { | |||
tns_highlight(&tns, fileidx, false); | |||
tns_highlight(&tns, sel, true); | |||
fileidx = sel; | |||
firstclick = bev->time; | |||
redraw(); | |||
} else if (bev->time - firstclick <= TO_DOUBLE_CLICK) { | |||
mode = MODE_IMAGE; | |||
set_timeout(reset_cursor, TO_CURSOR_HIDE, true); | |||
load_image(fileidx); | |||
redraw(); | |||
} else { | |||
firstclick = bev->time; | |||
} | |||
} | |||
break; | |||
case Button3: | |||
if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { | |||
bool on = !(files[sel].flags & FF_MARK); | |||
XEvent e; | |||
for (;;) { | |||
if (sel >= 0 && mark_image(sel, on)) | |||
redraw(); | |||
XMaskEvent(win.env.dpy, | |||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); | |||
if (e.type == ButtonPress || e.type == ButtonRelease) | |||
break; | |||
while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)); | |||
sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y); | |||
} | |||
} | |||
break; | |||
case Button4: | |||
case Button5: | |||
if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN, | |||
(bev->state & ControlMask) != 0)) | |||
redraw(); | |||
break; | |||
} | |||
} | |||
prefix = 0; | |||
} | |||
const struct timespec ten_ms = {0, 10000000}; | |||
void run(void) | |||
{ | |||
int xfd; | |||
fd_set fds; | |||
struct timeval timeout; | |||
bool discard, init_thumb, load_thumb, to_set; | |||
XEvent ev, nextev; | |||
while (true) { | |||
to_set = check_timeouts(&timeout); | |||
init_thumb = mode == MODE_THUMB && tns.initnext < filecnt; | |||
load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end; | |||
if ((init_thumb || load_thumb || to_set || info.fd != -1 || | |||
arl.fd != -1) && XPending(win.env.dpy) == 0) | |||
{ | |||
if (load_thumb) { | |||
set_timeout(redraw, TO_REDRAW_THUMBS, false); | |||
if (!tns_load(&tns, tns.loadnext, false, false)) { | |||
remove_file(tns.loadnext, false); | |||
tns.dirty = true; | |||
} | |||
if (tns.loadnext >= tns.end) | |||
redraw(); | |||
} else if (init_thumb) { | |||
set_timeout(redraw, TO_REDRAW_THUMBS, false); | |||
if (!tns_load(&tns, tns.initnext, false, true)) | |||
remove_file(tns.initnext, false); | |||
} else { | |||
xfd = ConnectionNumber(win.env.dpy); | |||
FD_ZERO(&fds); | |||
FD_SET(xfd, &fds); | |||
if (info.fd != -1) { | |||
FD_SET(info.fd, &fds); | |||
xfd = MAX(xfd, info.fd); | |||
} | |||
if (arl.fd != -1) { | |||
FD_SET(arl.fd, &fds); | |||
xfd = MAX(xfd, arl.fd); | |||
} | |||
select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL); | |||
if (info.fd != -1 && FD_ISSET(info.fd, &fds)) | |||
read_info(); | |||
if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) { | |||
if (arl_handle(&arl)) { | |||
/* when too fast, imlib2 can't load the image */ | |||
nanosleep(&ten_ms, NULL); | |||
img_close(&img, true); | |||
load_image(fileidx); | |||
redraw(); | |||
} | |||
} | |||
} | |||
continue; | |||
} | |||
do { | |||
XNextEvent(win.env.dpy, &ev); | |||
discard = false; | |||
if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) { | |||
XPeekEvent(win.env.dpy, &nextev); | |||
switch (ev.type) { | |||
case ConfigureNotify: | |||
case MotionNotify: | |||
discard = ev.type == nextev.type; | |||
break; | |||
case KeyPress: | |||
discard = (nextev.type == KeyPress || nextev.type == KeyRelease) | |||
&& ev.xkey.keycode == nextev.xkey.keycode; | |||
break; | |||
} | |||
} | |||
} while (discard); | |||
switch (ev.type) { | |||
/* handle events */ | |||
case ButtonPress: | |||
on_buttonpress(&ev.xbutton); | |||
break; | |||
case ClientMessage: | |||
if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW]) | |||
cmds[g_quit].func(0); | |||
break; | |||
case ConfigureNotify: | |||
if (win_configure(&win, &ev.xconfigure)) { | |||
if (mode == MODE_IMAGE) { | |||
img.dirty = true; | |||
img.checkpan = true; | |||
} else { | |||
tns.dirty = true; | |||
} | |||
if (!resized) { | |||
redraw(); | |||
set_timeout(clear_resize, TO_REDRAW_RESIZE, false); | |||
resized = true; | |||
} else { | |||
set_timeout(redraw, TO_REDRAW_RESIZE, false); | |||
} | |||
} | |||
break; | |||
case KeyPress: | |||
on_keypress(&ev.xkey); | |||
break; | |||
case MotionNotify: | |||
if (mode == MODE_IMAGE) { | |||
set_timeout(reset_cursor, TO_CURSOR_HIDE, true); | |||
reset_cursor(); | |||
} | |||
break; | |||
} | |||
} | |||
} | |||
int fncmp(const void *a, const void *b) | |||
{ | |||
return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name); | |||
} | |||
void sigchld(int sig) | |||
{ | |||
while (waitpid(-1, NULL, WNOHANG) > 0); | |||
} | |||
void setup_signal(int sig, void (*handler)(int sig)) | |||
{ | |||
struct sigaction sa; | |||
sa.sa_handler = handler; | |||
sigemptyset(&sa.sa_mask); | |||
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; | |||
if (sigaction(sig, &sa, 0) == -1) | |||
error(EXIT_FAILURE, errno, "signal %d", sig); | |||
} | |||
int main(int argc, char **argv) | |||
{ | |||
int i, start; | |||
size_t n; | |||
ssize_t len; | |||
char *filename; | |||
const char *homedir, *dsuffix = ""; | |||
struct stat fstats; | |||
r_dir_t dir; | |||
setup_signal(SIGCHLD, sigchld); | |||
setup_signal(SIGPIPE, SIG_IGN); | |||
setlocale(LC_COLLATE, ""); | |||
parse_options(argc, argv); | |||
if (options->clean_cache) { | |||
tns_init(&tns, NULL, NULL, NULL, NULL); | |||
tns_clean_cache(&tns); | |||
exit(EXIT_SUCCESS); | |||
} | |||
if (options->filecnt == 0 && !options->from_stdin) { | |||
print_usage(); | |||
exit(EXIT_FAILURE); | |||
} | |||
if (options->recursive || options->from_stdin) | |||
filecnt = 1024; | |||
else | |||
filecnt = options->filecnt; | |||
files = emalloc(filecnt * sizeof(*files)); | |||
memset(files, 0, filecnt * sizeof(*files)); | |||
fileidx = 0; | |||
if (options->from_stdin) { | |||
n = 0; | |||
filename = NULL; | |||
while ((len = getline(&filename, &n, stdin)) > 0) { | |||
if (filename[len-1] == '\n') | |||
filename[len-1] = '\0'; | |||
check_add_file(filename, true); | |||
} | |||
free(filename); | |||
} | |||
for (i = 0; i < options->filecnt; i++) { | |||
filename = options->filenames[i]; | |||
if (stat(filename, &fstats) < 0) { | |||
error(0, errno, "%s", filename); | |||
continue; | |||
} | |||
if (!S_ISDIR(fstats.st_mode)) { | |||
check_add_file(filename, true); | |||
} else { | |||
if (r_opendir(&dir, filename, options->recursive) < 0) { | |||
error(0, errno, "%s", filename); | |||
continue; | |||
} | |||
start = fileidx; | |||
while ((filename = r_readdir(&dir, true)) != NULL) { | |||
check_add_file(filename, false); | |||
free((void*) filename); | |||
} | |||
r_closedir(&dir); | |||
if (fileidx - start > 1) | |||
qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp); | |||
} | |||
} | |||
if (fileidx == 0) | |||
error(EXIT_FAILURE, 0, "No valid image file given, aborting"); | |||
filecnt = fileidx; | |||
fileidx = options->startnum < filecnt ? options->startnum : 0; | |||
for (i = 0; i < ARRLEN(buttons); i++) { | |||
if (buttons[i].cmd == i_cursor_navigate) { | |||
imgcursor[0] = CURSOR_LEFT; | |||
imgcursor[2] = CURSOR_RIGHT; | |||
break; | |||
} | |||
} | |||
win_init(&win); | |||
img_init(&img, &win); | |||
arl_init(&arl); | |||
if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') { | |||
homedir = getenv("HOME"); | |||
dsuffix = "/.config"; | |||
} | |||
if (homedir != NULL) { | |||
extcmd_t *cmd[] = { &info.f, &keyhandler.f }; | |||
const char *name[] = { "image-info", "key-handler" }; | |||
for (i = 0; i < ARRLEN(cmd); i++) { | |||
n = strlen(homedir) + strlen(dsuffix) + strlen(name[i]) + 12; | |||
cmd[i]->cmd = (char*) emalloc(n); | |||
snprintf(cmd[i]->cmd, n, "%s%s/sxiv/exec/%s", homedir, dsuffix, name[i]); | |||
if (access(cmd[i]->cmd, X_OK) != 0) | |||
cmd[i]->err = errno; | |||
} | |||
} else { | |||
error(0, 0, "Exec directory not found"); | |||
} | |||
info.fd = -1; | |||
if (options->thumb_mode) { | |||
mode = MODE_THUMB; | |||
tns_init(&tns, files, &filecnt, &fileidx, &win); | |||
while (!tns_load(&tns, fileidx, false, false)) | |||
remove_file(fileidx, false); | |||
} else { | |||
mode = MODE_IMAGE; | |||
tns.thumbs = NULL; | |||
load_image(fileidx); | |||
} | |||
win_open(&win); | |||
win_set_cursor(&win, CURSOR_WATCH); | |||
atexit(cleanup); | |||
set_timeout(redraw, 25, false); | |||
run(); | |||
return 0; | |||
} |
@ -0,0 +1,180 @@ | |||
/* Copyright 2011 Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
#define _IMAGE_CONFIG | |||
#include "config.h" | |||
#include "version.h" | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
opt_t _options; | |||
const opt_t *options = (const opt_t*) &_options; | |||
void print_usage(void) | |||
{ | |||
printf("usage: sxiv [-abcfhiopqrtvZ] [-A FRAMERATE] [-e WID] [-G GAMMA] " | |||
"[-g GEOMETRY] [-N NAME] [-n NUM] [-S DELAY] [-s MODE] [-z ZOOM] " | |||
"FILES...\n"); | |||
} | |||
void print_version(void) | |||
{ | |||
puts("sxiv " VERSION); | |||
} | |||
void parse_options(int argc, char **argv) | |||
{ | |||
int n, opt; | |||
char *end, *s; | |||
const char *scalemodes = "dfwh"; | |||
progname = strrchr(argv[0], '/'); | |||
progname = progname ? progname + 1 : argv[0]; | |||
_options.from_stdin = false; | |||
_options.to_stdout = false; | |||
_options.recursive = false; | |||
_options.startnum = 0; | |||
_options.scalemode = SCALE_DOWN; | |||
_options.zoom = 1.0; | |||
_options.animate = false; | |||
_options.gamma = 0; | |||
_options.slideshow = 0; | |||
_options.framerate = 0; | |||
_options.fullscreen = false; | |||
_options.embed = 0; | |||
_options.hide_bar = false; | |||
_options.geometry = NULL; | |||
_options.res_name = NULL; | |||
_options.quiet = false; | |||
_options.thumb_mode = false; | |||
_options.clean_cache = false; | |||
_options.private_mode = false; | |||
while ((opt = getopt(argc, argv, "A:abce:fG:g:hin:N:opqrS:s:tvZz:")) != -1) { | |||
switch (opt) { | |||
case '?': | |||
print_usage(); | |||
exit(EXIT_FAILURE); | |||
case 'A': | |||
n = strtol(optarg, &end, 0); | |||
if (*end != '\0' || n <= 0) | |||
error(EXIT_FAILURE, 0, "Invalid argument for option -A: %s", optarg); | |||
_options.framerate = n; | |||
/* fall through */ | |||
case 'a': | |||
_options.animate = true; | |||
break; | |||
case 'b': | |||
_options.hide_bar = true; | |||
break; | |||
case 'c': | |||
_options.clean_cache = true; | |||
break; | |||
case 'e': | |||
n = strtol(optarg, &end, 0); | |||
if (*end != '\0') | |||
error(EXIT_FAILURE, 0, "Invalid argument for option -e: %s", optarg); | |||
_options.embed = n; | |||
break; | |||
case 'f': | |||
_options.fullscreen = true; | |||
break; | |||
case 'G': | |||
n = strtol(optarg, &end, 0); | |||
if (*end != '\0') | |||
error(EXIT_FAILURE, 0, "Invalid argument for option -G: %s", optarg); | |||
_options.gamma = n; | |||
break; | |||
case 'g': | |||
_options.geometry = optarg; | |||
break; | |||
case 'h': | |||
print_usage(); | |||
exit(EXIT_SUCCESS); | |||
case 'i': | |||
_options.from_stdin = true; | |||
break; | |||
case 'n': | |||
n = strtol(optarg, &end, 0); | |||
if (*end != '\0' || n <= 0) | |||
error(EXIT_FAILURE, 0, "Invalid argument for option -n: %s", optarg); | |||
_options.startnum = n - 1; | |||
break; | |||
case 'N': | |||
_options.res_name = optarg; | |||
break; | |||
case 'o': | |||
_options.to_stdout = true; | |||
break; | |||
case 'p': | |||
_options.private_mode = true; | |||
break; | |||
case 'q': | |||
_options.quiet = true; | |||
break; | |||
case 'r': | |||
_options.recursive = true; | |||
break; | |||
case 'S': | |||
n = strtof(optarg, &end) * 10; | |||
if (*end != '\0' || n <= 0) | |||
error(EXIT_FAILURE, 0, "Invalid argument for option -S: %s", optarg); | |||
_options.slideshow = n; | |||
break; | |||
case 's': | |||
s = strchr(scalemodes, optarg[0]); | |||
if (s == NULL || *s == '\0' || strlen(optarg) != 1) | |||
error(EXIT_FAILURE, 0, "Invalid argument for option -s: %s", optarg); | |||
_options.scalemode = s - scalemodes; | |||
break; | |||
case 't': | |||
_options.thumb_mode = true; | |||
break; | |||
case 'v': | |||
print_version(); | |||
exit(EXIT_SUCCESS); | |||
case 'Z': | |||
_options.scalemode = SCALE_ZOOM; | |||
_options.zoom = 1.0; | |||
break; | |||
case 'z': | |||
n = strtol(optarg, &end, 0); | |||
if (*end != '\0' || n <= 0) | |||
error(EXIT_FAILURE, 0, "Invalid argument for option -z: %s", optarg); | |||
_options.scalemode = SCALE_ZOOM; | |||
_options.zoom = (float) n / 100.0; | |||
break; | |||
} | |||
} | |||
_options.filenames = argv + optind; | |||
_options.filecnt = argc - optind; | |||
if (_options.filecnt == 1 && STREQ(_options.filenames[0], "-")) { | |||
_options.filenames++; | |||
_options.filecnt--; | |||
_options.from_stdin = true; | |||
} | |||
} |
@ -0,0 +1,448 @@ | |||
.TH SXIV 1 sxiv\-VERSION | |||
.SH NAME | |||
sxiv \- Simple X Image Viewer | |||
.SH SYNOPSIS | |||
.B sxiv | |||
.RB [ \-abcfhiopqrtvZ ] | |||
.RB [ \-A | |||
.IR FRAMERATE ] | |||
.RB [ \-e | |||
.IR WID ] | |||
.RB [ \-G | |||
.IR GAMMA ] | |||
.RB [ \-g | |||
.IR GEOMETRY ] | |||
.RB [ \-N | |||
.IR NAME ] | |||
.RB [ \-n | |||
.IR NUM ] | |||
.RB [ \-S | |||
.IR DELAY ] | |||
.RB [ \-s | |||
.IR MODE ] | |||
.RB [ \-z | |||
.IR ZOOM ] | |||
.IR FILE ... | |||
.SH DESCRIPTION | |||
sxiv is a simple image viewer for X. | |||
.P | |||
It has two modes of operation: image and thumbnail mode. The default is image | |||
mode, in which only the current image is shown. In thumbnail mode a grid of | |||
small previews is displayed, making it easy to choose an image to open. | |||
.P | |||
Please note, that the fullscreen mode requires an EWMH/NetWM compliant window | |||
manager. | |||
.SH OPTIONS | |||
.TP | |||
.BI "\-A " FRAMERATE | |||
Play animations with a constant frame rate set to | |||
.IR FRAMERATE . | |||
.TP | |||
.B \-a | |||
Play animations of multi-frame images. | |||
.TP | |||
.B \-b | |||
Do not show info bar on bottom of window. | |||
.TP | |||
.B \-c | |||
Remove all orphaned cache files from the thumbnail cache directory and exit. | |||
.TP | |||
.BI "\-e " WID | |||
Embed sxiv's window into window whose ID is | |||
.IR WID . | |||
.TP | |||
.B \-f | |||
Start in fullscreen mode. | |||
.TP | |||
.BI "\-G " GAMMA | |||
Set image gamma to GAMMA (-32..32). | |||
.TP | |||
.BI "\-g " GEOMETRY | |||
Set window position and size. See section GEOMETRY SPECIFICATIONS of X(7) for | |||
more information on GEOMETRY argument. | |||
.TP | |||
.BI "\-N " NAME | |||
Set the resource name of sxiv's X window to NAME. | |||
.TP | |||
.BI "\-n " NUM | |||
Start at picture number NUM. | |||
.TP | |||
.B \-h | |||
Print brief usage information to standard output and exit. | |||
.TP | |||
.B \-i | |||
Read names of files to open from standard input. Also done if FILE is `-'. | |||
.TP | |||
.B \-o | |||
Write list of all marked files to standard output when quitting. In combination | |||
with | |||
.B \-i | |||
sxiv can be used as a visual filter/pipe. | |||
.TP | |||
.B \-p | |||
Enable private mode, in which sxiv does not write any cache or temporary files. | |||
.TP | |||
.B \-q | |||
Be quiet, disable warnings to standard error stream. | |||
.TP | |||
.B \-r | |||
Search the given directories recursively for images to view. | |||
.TP | |||
.BI "\-S " DELAY | |||
Start in slideshow mode. Set the delay between images to | |||
.I DELAY | |||
seconds. | |||
.I DELAY | |||
may be a floating point number. | |||
.TP | |||
.BI "\-s " MODE | |||
Set scale mode according to MODE character. Supported modes are: [d]own, | |||
[f]it, [w]idth, [h]eight. | |||
.TP | |||
.B \-t | |||
Start in thumbnail mode. | |||
.TP | |||
.B \-v | |||
Print version information to standard output and exit. | |||
.TP | |||
.B \-Z | |||
The same as `\-z 100'. | |||
.TP | |||
.BI "\-z " ZOOM | |||
Set zoom level to ZOOM percent. | |||
.SH KEYBOARD COMMANDS | |||
.SS General | |||
The following keyboard commands are available in both image and thumbnail mode: | |||
.TP | |||
.BR 0 \- 9 | |||
Prefix the next command with a number (denoted via | |||
.IR count ). | |||
.TP | |||
.B q | |||
Quit sxiv. | |||
.TP | |||
.B Return | |||
Switch to thumbnail mode / open selected image in image mode. | |||
.TP | |||
.B f | |||
Toggle fullscreen mode. | |||
.TP | |||
.B b | |||
Toggle visibility of info bar on bottom of window. | |||
.TP | |||
.B Ctrl-x | |||
Send the next key to the external key-handler. See section EXTERNAL KEY HANDLER | |||
for more information. | |||
.TP | |||
.B g | |||
Go to the first image. | |||
.TP | |||
.B G | |||
Go to the last image, or image number | |||
.IR count . | |||
.TP | |||
.B r | |||
Reload image. | |||
.TP | |||
.B D | |||
Remove current image from file list and go to next image. | |||
.TP | |||
.BR Ctrl-h ", " Ctrl-Left | |||
Scroll left one screen width. | |||
.TP | |||
.BR Ctrl-j ", " Ctrl-Down | |||
Scroll down one screen height. | |||
.TP | |||
.BR Ctrl-k ", " Ctrl-Up | |||
Scroll up one screen height. | |||
.TP | |||
.BR Ctrl-l ", " Ctrl-Right | |||
Scroll right one screen width. | |||
.TP | |||
.BR + | |||
Zoom in. | |||
.TP | |||
.B \- | |||
Zoom out. | |||
.TP | |||
.B m | |||
Mark/unmark the current image. | |||
.TP | |||
.B M | |||
Reverse all image marks. | |||
.TP | |||
.B Ctrl-M | |||
Repeat last mark action on all images from the last marked/unmarked up to the | |||
current one. | |||
.TP | |||
.B Ctrl-m | |||
Remove all image marks. | |||
.TP | |||
.B N | |||
Go | |||
.I count | |||
marked images forward. | |||
.TP | |||
.B P | |||
Go | |||
.I count | |||
marked images backward. | |||
.TP | |||
.B { | |||
Decrease gamma correction by | |||
.I count | |||
steps. | |||
.TP | |||
.B } | |||
Increase gamma correction by | |||
.I count | |||
steps. | |||
.TP | |||
.B Ctrl-g | |||
Reset gamma correction. | |||
.SS Thumbnail mode | |||
The following keyboard commands are only available in thumbnail mode: | |||
.TP | |||
.BR h ", " Left | |||
Move selection left | |||
.I count | |||
times. | |||
.TP | |||
.BR j ", " Down | |||
Move selection down | |||
.I count | |||
times. | |||
.TP | |||
.BR k ", " Up | |||
Move selection up | |||
.I count | |||
times. | |||
.TP | |||
.BR l ", " Right | |||
Move selection right | |||
.I count | |||
times. | |||
.TP | |||
.B R | |||
Reload all thumbnails. | |||
.SS Image mode | |||
The following keyboard commands are only available in image mode: | |||
.TP | |||
Navigate image list: | |||
.TP | |||
.BR n ", " Space | |||
Go | |||
.I count | |||
images forward. | |||
.TP | |||
.BR p ", " Backspace | |||
Go | |||
.I count | |||
images backward. | |||
.TP | |||
.B [ | |||
Go | |||
.I count | |||
* 10 images backward. | |||
.TP | |||
.B ] | |||
Go | |||
.I count | |||
* 10 images forward. | |||
.TP | |||
Handle multi-frame images: | |||
.TP | |||
.B Ctrl-n | |||
Go | |||
.I count | |||
frames of a multi-frame image forward. | |||
.TP | |||
.B Ctrl-p | |||
Go | |||
.I count | |||
frames of a multi-frame image backward. | |||
.TP | |||
.B Ctrl-Space | |||
Play/stop animations of multi-frame images. | |||
.TP | |||
Panning: | |||
.TP | |||
.BR h ", " Left | |||
Scroll image 1/5 of window width or | |||
.I count | |||
pixel left. | |||
.TP | |||
.BR j ", " Down | |||
Scroll image 1/5 of window height or | |||
.I count | |||
pixel down. | |||
.TP | |||
.BR k ", " Up | |||
Scroll image 1/5 of window height or | |||
.I count | |||
pixel up. | |||
.TP | |||
.BR l ", " Right | |||
Scroll image 1/5 of window width or | |||
.I count | |||
pixel right. | |||
.TP | |||
.B H | |||
Scroll to left image edge. | |||
.TP | |||
.B J | |||
Scroll to bottom image edge. | |||
.TP | |||
.B K | |||
Scroll to top image edge. | |||
.TP | |||
.B L | |||
Scroll to right image edge. | |||
.TP | |||
Zooming: | |||
.TP | |||
.B = | |||
Set zoom level to 100%, or | |||
.IR count %. | |||
.TP | |||
.B w | |||
Set zoom level to 100%, but fit large images into window. | |||
.TP | |||
.B W | |||
Fit image to window. | |||
.TP | |||
.B e | |||
Fit image to window width. | |||
.TP | |||
.B E | |||
Fit image to window height. | |||
.TP | |||
Rotation: | |||
.TP | |||
.B < | |||
Rotate image counter-clockwise by 90 degrees. | |||
.TP | |||
.B > | |||
Rotate image clockwise by 90 degrees. | |||
.TP | |||
.B ? | |||
Rotate image by 180 degrees. | |||
.TP | |||
Flipping: | |||
.TP | |||
.B | | |||
Flip image horizontally. | |||
.TP | |||
.B _ | |||
Flip image vertically. | |||
.TP | |||
Miscellaneous: | |||
.TP | |||
.B a | |||
Toggle anti-aliasing. | |||
.TP | |||
.B A | |||
Toggle visibility of alpha-channel, i.e. image transparency. | |||
.TP | |||
.B s | |||
Toggle slideshow mode and/or set the delay between images to | |||
.I count | |||
seconds. | |||
.SH MOUSE COMMANDS | |||
The following mouse mappings are available in image mode: | |||
.TP | |||
General: | |||
.TP | |||
.B Button3 | |||
Switch to thumbnail mode. | |||
.TP | |||
Navigate image list: | |||
.TP | |||
.B Button1 | |||
Go to the next image if the mouse cursor is in the right part of the window or | |||
to the previous image if it is in the left part. | |||
.TP | |||
Panning: | |||
.TP | |||
.B Button2 | |||
Pan the image according to the mouse cursor position in the window while | |||
keeping this button pressed down. | |||
.TP | |||
Zooming: | |||
.TP | |||
.B ScrollUp | |||
Zoom in. | |||
.TP | |||
.B ScrollDown | |||
Zoom out. | |||
.SH CONFIGURATION | |||
The following X resources are supported: | |||
.TP | |||
.B background | |||
Color of the window background and bar foreground | |||
.TP | |||
.B foreground | |||
Color of the window foreground and bar background | |||
.TP | |||
.B font | |||
Name of Xft bar font | |||
.TP | |||
Please see xrdb(1) on how to change them. | |||
.SH STATUS BAR | |||
The information displayed on the left side of the status bar can be replaced | |||
with the output of a user-provided script, which is called by sxiv whenever an | |||
image gets loaded. The path of this script is | |||
.I $XDG_CONFIG_HOME/sxiv/exec/image-info | |||
and the arguments given to it are: 1) path to image file, 2) image width, | |||
3) image height. | |||
.P | |||
There is also an example script installed together with sxiv as | |||
.IR PREFIX/share/sxiv/exec/image-info . | |||
.SH EXTERNAL KEY HANDLER | |||
Additional external keyboard commands can be defined using a handler program | |||
located in | |||
.IR $XDG_CONFIG_HOME/sxiv/exec/key-handler . | |||
The handler is invoked by pressing | |||
.BR Ctrl-x . | |||
The next key combo is passed as its first argument. Passed via stdin are the | |||
images to act upon, one path per line: all marked images, if in thumbnail mode | |||
and at least one image has been marked, otherwise the current image. | |||
sxiv(1) will block until the handler terminates. It then checks which images | |||
have been modified and reloads them. | |||
The key combo argument has the following form: "[C-][M-][S-]KEY", | |||
where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X | |||
keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. | |||
There is also an example script installed together with sxiv as | |||
.IR PREFIX/share/sxiv/exec/key-handler . | |||
.SH THUMBNAIL CACHING | |||
sxiv stores all thumbnails under | |||
.IR $XDG_CACHE_HOME/sxiv/ . | |||
.P | |||
Use the command line option | |||
.I \-c | |||
to remove all orphaned cache files. Additionally, run the following command | |||
afterwards inside the cache directory to remove empty subdirectories: | |||
.P | |||
.RS | |||
find . \-depth \-type d \-empty ! \-name '.' \-exec rmdir {} \\; | |||
.RE | |||
.SH AUTHOR | |||
.EX | |||
Bert Muennich <ber.t at posteo.de> | |||
.EE | |||
.SH CONTRIBUTORS | |||
.EX | |||
Bastien Dejean <nihilhill at gmail.com> | |||
Dave Reisner <d at falconindy.com> | |||
Fung SzeTat <sthorde at gmail.com> | |||
Max Voit <mvdev at with-eyes.net> | |||
.EE | |||
.SH HOMEPAGE | |||
.EX | |||
https://github.com/muennich/sxiv | |||
.EE | |||
.SH SEE ALSO | |||
.BR X (7), | |||
.BR xrdb (1) |
@ -0,0 +1,8 @@ | |||
[Desktop Entry] | |||
Type=Application | |||
Name=sxiv | |||
GenericName=Image Viewer | |||
Exec=sxiv %F | |||
MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-tga;image/x-xpixmap; | |||
NoDisplay=true | |||
Icon=sxiv |
@ -0,0 +1,448 @@ | |||
/* Copyright 2011 Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#ifndef SXIV_H | |||
#define SXIV_H | |||
#include <stdarg.h> | |||
#include <stdbool.h> | |||
#include <stdio.h> | |||
#include <sys/time.h> | |||
#include <sys/types.h> | |||
#include <Imlib2.h> | |||
#include <X11/Xlib.h> | |||
/* | |||
* Annotation for functions called in cleanup(). | |||
* These functions are not allowed to call error(!0, ...) or exit(). | |||
*/ | |||
#define CLEANUP | |||
#ifndef MIN | |||
#define MIN(a,b) ((a) < (b) ? (a) : (b)) | |||
#endif | |||
#ifndef MAX | |||
#define MAX(a,b) ((a) > (b) ? (a) : (b)) | |||
#endif | |||
#define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) | |||
#define STREQ(s1,s2) (strcmp((s1), (s2)) == 0) | |||
#define TV_DIFF(t1,t2) (((t1)->tv_sec - (t2)->tv_sec ) * 1000 + \ | |||
((t1)->tv_usec - (t2)->tv_usec) / 1000) | |||
#define TV_SET_MSEC(tv,t) { \ | |||
(tv)->tv_sec = (t) / 1000; \ | |||
(tv)->tv_usec = (t) % 1000 * 1000; \ | |||
} | |||
#define TV_ADD_MSEC(tv,t) { \ | |||
(tv)->tv_sec += (t) / 1000; \ | |||
(tv)->tv_usec += (t) % 1000 * 1000; \ | |||
} | |||
typedef enum { | |||
BO_BIG_ENDIAN, | |||
BO_LITTLE_ENDIAN | |||
} byteorder_t; | |||
typedef enum { | |||
MODE_IMAGE, | |||
MODE_THUMB | |||
} appmode_t; | |||
typedef enum { | |||
DIR_LEFT = 1, | |||
DIR_RIGHT = 2, | |||
DIR_UP = 4, | |||
DIR_DOWN = 8 | |||
} direction_t; | |||
typedef enum { | |||
DEGREE_90 = 1, | |||
DEGREE_180 = 2, | |||
DEGREE_270 = 3 | |||
} degree_t; | |||
typedef enum { | |||
FLIP_HORIZONTAL = 1, | |||
FLIP_VERTICAL = 2 | |||
} flipdir_t; | |||
typedef enum { | |||
SCALE_DOWN, | |||
SCALE_FIT, | |||
SCALE_WIDTH, | |||
SCALE_HEIGHT, | |||
SCALE_ZOOM | |||
} scalemode_t; | |||
typedef enum { | |||
DRAG_RELATIVE, | |||
DRAG_ABSOLUTE | |||
} dragmode_t; | |||
typedef enum { | |||
CURSOR_ARROW, | |||
CURSOR_DRAG, | |||
CURSOR_WATCH, | |||
CURSOR_LEFT, | |||
CURSOR_RIGHT, | |||
CURSOR_NONE, | |||
CURSOR_COUNT | |||
} cursor_t; | |||
typedef enum { | |||
FF_WARN = 1, | |||
FF_MARK = 2, | |||
FF_TN_INIT = 4 | |||
} fileflags_t; | |||
typedef struct { | |||
const char *name; /* as given by user */ | |||
const char *path; /* always absolute */ | |||
fileflags_t flags; | |||
} fileinfo_t; | |||
/* timeouts in milliseconds: */ | |||
enum { | |||
TO_REDRAW_RESIZE = 75, | |||
TO_REDRAW_THUMBS = 200, | |||
TO_CURSOR_HIDE = 1200, | |||
TO_DOUBLE_CLICK = 300 | |||
}; | |||
typedef void (*timeout_f)(void); | |||
typedef struct arl arl_t; | |||
typedef struct img img_t; | |||
typedef struct opt opt_t; | |||
typedef struct tns tns_t; | |||
typedef struct win win_t; | |||
/* autoreload.c */ | |||
struct arl { | |||
int fd; | |||
int wd_dir; | |||
int wd_file; | |||
char *filename; | |||
}; | |||
void arl_init(arl_t*); | |||
void arl_cleanup(arl_t*); | |||
void arl_setup(arl_t*, const char* /* result of realpath(3) */); | |||
bool arl_handle(arl_t*); | |||
/* commands.c */ | |||
typedef int arg_t; | |||
typedef bool (*cmd_f)(arg_t); | |||
#define G_CMD(c) g_##c, | |||
#define I_CMD(c) i_##c, | |||
#define T_CMD(c) t_##c, | |||
typedef enum { | |||
#include "commands.lst" | |||
CMD_COUNT | |||
} cmd_id_t; | |||
typedef struct { | |||
int mode; | |||
cmd_f func; | |||
} cmd_t; | |||
typedef struct { | |||
unsigned int mask; | |||
KeySym ksym; | |||
cmd_id_t cmd; | |||
arg_t arg; | |||
} keymap_t; | |||
typedef struct { | |||
unsigned int mask; | |||
unsigned int button; | |||
cmd_id_t cmd; | |||
arg_t arg; | |||
} button_t; | |||
extern const cmd_t cmds[CMD_COUNT]; | |||
/* image.c */ | |||
typedef struct { | |||
Imlib_Image im; | |||
unsigned int delay; | |||
} img_frame_t; | |||
typedef struct { | |||
img_frame_t *frames; | |||
int cap; | |||
int cnt; | |||
int sel; | |||
bool animate; | |||
int framedelay; | |||
int length; | |||
} multi_img_t; | |||
struct img { | |||
Imlib_Image im; | |||
int w; | |||
int h; | |||
win_t *win; | |||
float x; | |||
float y; | |||
scalemode_t scalemode; | |||
float zoom; | |||
bool checkpan; | |||
bool dirty; | |||
bool aa; | |||
bool alpha; | |||
Imlib_Color_Modifier cmod; | |||
int gamma; | |||
struct { | |||
bool on; | |||
int delay; | |||
} ss; | |||
multi_img_t multi; | |||
}; | |||
void img_init(img_t*, win_t*); | |||
bool img_load(img_t*, const fileinfo_t*); | |||
CLEANUP void img_close(img_t*, bool); | |||
void img_render(img_t*); | |||
bool img_fit_win(img_t*, scalemode_t); | |||
bool img_zoom(img_t*, float); | |||
bool img_zoom_in(img_t*); | |||
bool img_zoom_out(img_t*); | |||
bool img_pos(img_t*, float, float); | |||
bool img_move(img_t*, float, float); | |||
bool img_pan(img_t*, direction_t, int); | |||
bool img_pan_edge(img_t*, direction_t); | |||
void img_rotate(img_t*, degree_t); | |||
void img_flip(img_t*, flipdir_t); | |||
void img_toggle_antialias(img_t*); | |||
bool img_change_gamma(img_t*, int); | |||
bool img_frame_navigate(img_t*, int); | |||
bool img_frame_animate(img_t*); | |||
/* options.c */ | |||
struct opt { | |||
/* file list: */ | |||
char **filenames; | |||
bool from_stdin; | |||
bool to_stdout; | |||
bool recursive; | |||
int filecnt; | |||
int startnum; | |||
/* image: */ | |||
scalemode_t scalemode; | |||
float zoom; | |||
bool animate; | |||
int gamma; | |||
int slideshow; | |||
int framerate; | |||
/* window: */ | |||
bool fullscreen; | |||
bool hide_bar; | |||
long embed; | |||
char *geometry; | |||
char *res_name; | |||
/* misc flags: */ | |||
bool quiet; | |||
bool thumb_mode; | |||
bool clean_cache; | |||
bool private_mode; | |||
}; | |||
extern const opt_t *options; | |||
void print_usage(void); | |||
void print_version(void); | |||
void parse_options(int, char**); | |||
/* thumbs.c */ | |||
typedef struct { | |||
Imlib_Image im; | |||
int w; | |||
int h; | |||
int x; | |||
int y; | |||
} thumb_t; | |||
struct tns { | |||
fileinfo_t *files; | |||
thumb_t *thumbs; | |||
const int *cnt; | |||
int *sel; | |||
int initnext; | |||
int loadnext; | |||
int first, end; | |||
int r_first, r_end; | |||
win_t *win; | |||
int x; | |||
int y; | |||
int cols; | |||
int rows; | |||
int zl; | |||
int bw; | |||
int dim; | |||
bool dirty; | |||
}; | |||
void tns_clean_cache(tns_t*); | |||
void tns_init(tns_t*, fileinfo_t*, const int*, int*, win_t*); | |||
CLEANUP void tns_free(tns_t*); | |||
bool tns_load(tns_t*, int, bool, bool); | |||
void tns_unload(tns_t*, int); | |||
void tns_render(tns_t*); | |||
void tns_mark(tns_t*, int, bool); | |||
void tns_highlight(tns_t*, int, bool); | |||
bool tns_move_selection(tns_t*, direction_t, int); | |||
bool tns_scroll(tns_t*, direction_t, bool); | |||
bool tns_zoom(tns_t*, int); | |||
int tns_translate(tns_t*, int, int); | |||
/* util.c */ | |||
#include <dirent.h> | |||
typedef struct { | |||
DIR *dir; | |||
char *name; | |||
int d; | |||
bool recursive; | |||
char **stack; | |||
int stcap; | |||
int stlen; | |||
} r_dir_t; | |||
extern const char *progname; | |||
void* emalloc(size_t); | |||
void* erealloc(void*, size_t); | |||
char* estrdup(const char*); | |||
void error(int, int, const char*, ...); | |||
void size_readable(float*, const char**); | |||
int r_opendir(r_dir_t*, const char*, bool); | |||
int r_closedir(r_dir_t*); | |||
char* r_readdir(r_dir_t*, bool); | |||
int r_mkdir(char*); | |||
/* window.c */ | |||
#include <X11/Xutil.h> | |||
#include <X11/Xft/Xft.h> | |||
enum { | |||
BAR_L_LEN = 512, | |||
BAR_R_LEN = 64 | |||
}; | |||
enum { | |||
ATOM_WM_DELETE_WINDOW, | |||
ATOM__NET_WM_NAME, | |||
ATOM__NET_WM_ICON_NAME, | |||
ATOM__NET_WM_ICON, | |||
ATOM__NET_WM_STATE, | |||
ATOM__NET_WM_STATE_FULLSCREEN, | |||
ATOM_COUNT | |||
}; | |||
typedef struct { | |||
Display *dpy; | |||
int scr; | |||
int scrw, scrh; | |||
Visual *vis; | |||
Colormap cmap; | |||
int depth; | |||
} win_env_t; | |||
typedef struct { | |||
size_t size; | |||
char *p; | |||
char *buf; | |||
} win_bar_t; | |||
struct win { | |||
Window xwin; | |||
win_env_t env; | |||
XftColor bg; | |||
XftColor fg; | |||
int x; | |||
int y; | |||
unsigned int w; | |||
unsigned int h; /* = win height - bar height */ | |||
unsigned int bw; | |||
struct { | |||
int w; | |||
int h; | |||
Pixmap pm; | |||
} buf; | |||
struct { | |||
unsigned int h; | |||
win_bar_t l; | |||
win_bar_t r; | |||
} bar; | |||
}; | |||
extern Atom atoms[ATOM_COUNT]; | |||
void win_init(win_t*); | |||
void win_open(win_t*); | |||
CLEANUP void win_close(win_t*); | |||
bool win_configure(win_t*, XConfigureEvent*); | |||
void win_toggle_fullscreen(win_t*); | |||
void win_toggle_bar(win_t*); | |||
void win_clear(win_t*); | |||
void win_draw(win_t*); | |||
void win_draw_rect(win_t*, int, int, int, int, bool, int, unsigned long); | |||
void win_set_title(win_t*, const char*); | |||
void win_set_cursor(win_t*, cursor_t); | |||
void win_cursor_pos(win_t*, int*, int*); | |||
#endif /* SXIV_H */ | |||
@ -0,0 +1,594 @@ | |||
/* Copyright 2011 Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
#define _THUMBS_CONFIG | |||
#include "config.h" | |||
#include <errno.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | |||
#include <unistd.h> | |||
#include <utime.h> | |||
#if HAVE_LIBEXIF | |||
#include <libexif/exif-data.h> | |||
void exif_auto_orientate(const fileinfo_t*); | |||
#endif | |||
Imlib_Image img_open(const fileinfo_t*); | |||
static char *cache_dir; | |||
char* tns_cache_filepath(const char *filepath) | |||
{ | |||
size_t len; | |||
char *cfile = NULL; | |||
if (*filepath != '/') | |||
return NULL; | |||
if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) { | |||
/* don't cache images inside the cache directory! */ | |||
len = strlen(cache_dir) + strlen(filepath) + 2; | |||
cfile = (char*) emalloc(len); | |||
snprintf(cfile, len, "%s/%s", cache_dir, filepath + 1); | |||
} | |||
return cfile; | |||
} | |||
Imlib_Image tns_cache_load(const char *filepath, bool *outdated) | |||
{ | |||
char *cfile; | |||
struct stat cstats, fstats; | |||
Imlib_Image im = NULL; | |||
if (stat(filepath, &fstats) < 0) | |||
return NULL; | |||
if ((cfile = tns_cache_filepath(filepath)) != NULL) { | |||
if (stat(cfile, &cstats) == 0) { | |||
if (cstats.st_mtime == fstats.st_mtime) | |||
im = imlib_load_image(cfile); | |||
else | |||
*outdated = true; | |||
} | |||
free(cfile); | |||
} | |||
return im; | |||
} | |||
void tns_cache_write(Imlib_Image im, const char *filepath, bool force) | |||
{ | |||
char *cfile, *dirend; | |||
struct stat cstats, fstats; | |||
struct utimbuf times; | |||
Imlib_Load_Error err; | |||
if (options->private_mode) | |||
return; | |||
if (stat(filepath, &fstats) < 0) | |||
return; | |||
if ((cfile = tns_cache_filepath(filepath)) != NULL) { | |||
if (force || stat(cfile, &cstats) < 0 || | |||
cstats.st_mtime != fstats.st_mtime) | |||
{ | |||
if ((dirend = strrchr(cfile, '/')) != NULL) { | |||
*dirend = '\0'; | |||
if (r_mkdir(cfile) == -1) { | |||
error(0, errno, "%s", cfile); | |||
goto end; | |||
} | |||
*dirend = '/'; | |||
} | |||
imlib_context_set_image(im); | |||
if (imlib_image_has_alpha()) { | |||
imlib_image_set_format("png"); | |||
} else { | |||
imlib_image_set_format("jpg"); | |||
imlib_image_attach_data_value("quality", NULL, 90, NULL); | |||
} | |||
imlib_save_image_with_error_return(cfile, &err); | |||
if (err) | |||
goto end; | |||
times.actime = fstats.st_atime; | |||
times.modtime = fstats.st_mtime; | |||
utime(cfile, ×); | |||
} | |||
end: | |||
free(cfile); | |||
} | |||
} | |||
void tns_clean_cache(tns_t *tns) | |||
{ | |||
int dirlen; | |||
char *cfile, *filename; | |||
r_dir_t dir; | |||
if (r_opendir(&dir, cache_dir, true) < 0) { | |||
error(0, errno, "%s", cache_dir); | |||
return; | |||
} | |||
dirlen = strlen(cache_dir); | |||
while ((cfile = r_readdir(&dir, false)) != NULL) { | |||
filename = cfile + dirlen; | |||
if (access(filename, F_OK) < 0) { | |||
if (unlink(cfile) < 0) | |||
error(0, errno, "%s", cfile); | |||
} | |||
free(cfile); | |||
} | |||
r_closedir(&dir); | |||
} | |||
void tns_init(tns_t *tns, fileinfo_t *files, const int *cnt, int *sel, | |||
win_t *win) | |||
{ | |||
int len; | |||
const char *homedir, *dsuffix = ""; | |||
if (cnt != NULL && *cnt > 0) { | |||
tns->thumbs = (thumb_t*) emalloc(*cnt * sizeof(thumb_t)); | |||
memset(tns->thumbs, 0, *cnt * sizeof(thumb_t)); | |||
} else { | |||
tns->thumbs = NULL; | |||
} | |||
tns->files = files; | |||
tns->cnt = cnt; | |||
tns->initnext = tns->loadnext = 0; | |||
tns->first = tns->end = tns->r_first = tns->r_end = 0; | |||
tns->sel = sel; | |||
tns->win = win; | |||
tns->dirty = false; | |||
tns->zl = THUMB_SIZE; | |||
tns_zoom(tns, 0); | |||
if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') { | |||
homedir = getenv("HOME"); | |||
dsuffix = "/.cache"; | |||
} | |||
if (homedir != NULL) { | |||
free(cache_dir); | |||
len = strlen(homedir) + strlen(dsuffix) + 6; | |||
cache_dir = (char*) emalloc(len); | |||
snprintf(cache_dir, len, "%s%s/sxiv", homedir, dsuffix); | |||
} else { | |||
error(0, 0, "Cache directory not found"); | |||
} | |||
} | |||
CLEANUP void tns_free(tns_t *tns) | |||
{ | |||
int i; | |||
if (tns->thumbs != NULL) { | |||
for (i = 0; i < *tns->cnt; i++) { | |||
if (tns->thumbs[i].im != NULL) { | |||
imlib_context_set_image(tns->thumbs[i].im); | |||
imlib_free_image(); | |||
} | |||
} | |||
free(tns->thumbs); | |||
tns->thumbs = NULL; | |||
} | |||
free(cache_dir); | |||
cache_dir = NULL; | |||
} | |||
Imlib_Image tns_scale_down(Imlib_Image im, int dim) | |||
{ | |||
int w, h; | |||
float z, zw, zh; | |||
imlib_context_set_image(im); | |||
w = imlib_image_get_width(); | |||
h = imlib_image_get_height(); | |||
zw = (float) dim / (float) w; | |||
zh = (float) dim / (float) h; | |||
z = MIN(zw, zh); | |||
z = MIN(z, 1.0); | |||
if (z < 1.0) { | |||
imlib_context_set_anti_alias(1); | |||
im = imlib_create_cropped_scaled_image(0, 0, w, h, | |||
MAX(z * w, 1), MAX(z * h, 1)); | |||
if (im == NULL) | |||
error(EXIT_FAILURE, ENOMEM, NULL); | |||
imlib_free_image_and_decache(); | |||
} | |||
return im; | |||
} | |||
bool tns_load(tns_t *tns, int n, bool force, bool cache_only) | |||
{ | |||
int maxwh = thumb_sizes[ARRLEN(thumb_sizes)-1]; | |||
bool cache_hit = false; | |||
char *cfile; | |||
thumb_t *t; | |||
fileinfo_t *file; | |||
Imlib_Image im = NULL; | |||
if (n < 0 || n >= *tns->cnt) | |||
return false; | |||
file = &tns->files[n]; | |||
if (file->name == NULL || file->path == NULL) | |||
return false; | |||
t = &tns->thumbs[n]; | |||
if (t->im != NULL) { | |||
imlib_context_set_image(t->im); | |||
imlib_free_image(); | |||
t->im = NULL; | |||
} | |||
if (!force) { | |||
if ((im = tns_cache_load(file->path, &force)) != NULL) { | |||
imlib_context_set_image(im); | |||
if (imlib_image_get_width() < maxwh && | |||
imlib_image_get_height() < maxwh) | |||
{ | |||
if ((cfile = tns_cache_filepath(file->path)) != NULL) { | |||
unlink(cfile); | |||
free(cfile); | |||
} | |||
imlib_free_image_and_decache(); | |||
im = NULL; | |||
} else { | |||
cache_hit = true; | |||
} | |||
#if HAVE_LIBEXIF | |||
} else if (!force && !options->private_mode) { | |||
int pw = 0, ph = 0, w, h, x = 0, y = 0; | |||
bool err; | |||
float zw, zh; | |||
ExifData *ed; | |||
ExifEntry *entry; | |||
ExifContent *ifd; | |||
ExifByteOrder byte_order; | |||
int tmpfd; | |||
char tmppath[] = "/tmp/sxiv-XXXXXX"; | |||
Imlib_Image tmpim; | |||
if ((ed = exif_data_new_from_file(file->path)) != NULL) { | |||
if (ed->data != NULL && ed->size > 0 && | |||
(tmpfd = mkstemp(tmppath)) >= 0) | |||
{ | |||
err = write(tmpfd, ed->data, ed->size) != ed->size; | |||
close(tmpfd); | |||
if (!err && (tmpim = imlib_load_image(tmppath)) != NULL) { | |||
byte_order = exif_data_get_byte_order(ed); | |||
ifd = ed->ifd[EXIF_IFD_EXIF]; | |||
entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_X_DIMENSION); | |||
if (entry != NULL) | |||
pw = exif_get_long(entry->data, byte_order); | |||
entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_Y_DIMENSION); | |||
if (entry != NULL) | |||
ph = exif_get_long(entry->data, byte_order); | |||
imlib_context_set_image(tmpim); | |||
w = imlib_image_get_width(); | |||
h = imlib_image_get_height(); | |||
if (pw > w && ph > h && (pw - ph >= 0) == (w - h >= 0)) { | |||
zw = (float) pw / (float) w; | |||
zh = (float) ph / (float) h; | |||
if (zw < zh) { | |||
pw /= zh; | |||
x = (w - pw) / 2; | |||
w = pw; | |||
} else if (zw > zh) { | |||
ph /= zw; | |||
y = (h - ph) / 2; | |||
h = ph; | |||
} | |||
} | |||
if (w >= maxwh || h >= maxwh) { | |||
if ((im = imlib_create_cropped_image(x, y, w, h)) == NULL) | |||
error(EXIT_FAILURE, ENOMEM, NULL); | |||
} | |||
imlib_free_image_and_decache(); | |||
} | |||
unlink(tmppath); | |||
} | |||
exif_data_unref(ed); | |||
} | |||
#endif | |||
} | |||
} | |||
if (im == NULL) { | |||
if ((im = img_open(file)) == NULL) | |||
return false; | |||
} | |||
imlib_context_set_image(im); | |||
if (!cache_hit) { | |||
#if HAVE_LIBEXIF | |||
exif_auto_orientate(file); | |||
#endif | |||
im = tns_scale_down(im, maxwh); | |||
imlib_context_set_image(im); | |||
if (imlib_image_get_width() == maxwh || imlib_image_get_height() == maxwh) | |||
tns_cache_write(im, file->path, true); | |||
} | |||
if (cache_only) { | |||
imlib_free_image_and_decache(); | |||
} else { | |||
t->im = tns_scale_down(im, thumb_sizes[tns->zl]); | |||
imlib_context_set_image(t->im); | |||
t->w = imlib_image_get_width(); | |||
t->h = imlib_image_get_height(); | |||
tns->dirty = true; | |||
} | |||
file->flags |= FF_TN_INIT; | |||
if (n == tns->initnext) | |||
while (++tns->initnext < *tns->cnt && ((++file)->flags & FF_TN_INIT)); | |||
if (n == tns->loadnext && !cache_only) | |||
while (++tns->loadnext < tns->end && (++t)->im != NULL); | |||
return true; | |||
} | |||
void tns_unload(tns_t *tns, int n) | |||
{ | |||
thumb_t *t; | |||
if (n < 0 || n >= *tns->cnt) | |||
return; | |||
t = &tns->thumbs[n]; | |||
if (t->im != NULL) { | |||
imlib_context_set_image(t->im); | |||
imlib_free_image(); | |||
t->im = NULL; | |||
} | |||
} | |||
void tns_check_view(tns_t *tns, bool scrolled) | |||
{ | |||
int r; | |||
if (tns == NULL) | |||
return; | |||
tns->first -= tns->first % tns->cols; | |||
r = *tns->sel % tns->cols; | |||
if (scrolled) { | |||
/* move selection into visible area */ | |||
if (*tns->sel >= tns->first + tns->cols * tns->rows) | |||
*tns->sel = tns->first + r + tns->cols * (tns->rows - 1); | |||
else if (*tns->sel < tns->first) | |||
*tns->sel = tns->first + r; | |||
} else { | |||
/* scroll to selection */ | |||
if (tns->first + tns->cols * tns->rows <= *tns->sel) { | |||
tns->first = *tns->sel - r - tns->cols * (tns->rows - 1); | |||
tns->dirty = true; | |||
} else if (tns->first > *tns->sel) { | |||
tns->first = *tns->sel - r; | |||
tns->dirty = true; | |||
} | |||
} | |||
} | |||
void tns_render(tns_t *tns) | |||
{ | |||
thumb_t *t; | |||
win_t *win; | |||
int i, cnt, r, x, y; | |||
if (!tns->dirty) | |||
return; | |||
win = tns->win; | |||
win_clear(win); | |||
imlib_context_set_drawable(win->buf.pm); | |||
tns->cols = MAX(1, win->w / tns->dim); | |||
tns->rows = MAX(1, win->h / tns->dim); | |||
if (*tns->cnt < tns->cols * tns->rows) { | |||
tns->first = 0; | |||
cnt = *tns->cnt; | |||
} else { | |||
tns_check_view(tns, false); | |||
cnt = tns->cols * tns->rows; | |||
if ((r = tns->first + cnt - *tns->cnt) >= tns->cols) | |||
tns->first -= r - r % tns->cols; | |||
if (r > 0) | |||
cnt -= r % tns->cols; | |||
} | |||
r = cnt % tns->cols ? 1 : 0; | |||
tns->x = x = (win->w - MIN(cnt, tns->cols) * tns->dim) / 2 + tns->bw + 3; | |||
tns->y = y = (win->h - (cnt / tns->cols + r) * tns->dim) / 2 + tns->bw + 3; | |||
tns->loadnext = *tns->cnt; | |||
tns->end = tns->first + cnt; | |||
for (i = tns->r_first; i < tns->r_end; i++) { | |||
if ((i < tns->first || i >= tns->end) && tns->thumbs[i].im != NULL) | |||
tns_unload(tns, i); | |||
} | |||
tns->r_first = tns->first; | |||
tns->r_end = tns->end; | |||
for (i = tns->first; i < tns->end; i++) { | |||
t = &tns->thumbs[i]; | |||
if (t->im != NULL) { | |||
t->x = x + (thumb_sizes[tns->zl] - t->w) / 2; | |||
t->y = y + (thumb_sizes[tns->zl] - t->h) / 2; | |||
imlib_context_set_image(t->im); | |||
imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h); | |||
if (tns->files[i].flags & FF_MARK) | |||
tns_mark(tns, i, true); | |||
} else { | |||
tns->loadnext = MIN(tns->loadnext, i); | |||
} | |||
if ((i + 1) % tns->cols == 0) { | |||
x = tns->x; | |||
y += tns->dim; | |||
} else { | |||
x += tns->dim; | |||
} | |||
} | |||
tns->dirty = false; | |||
tns_highlight(tns, *tns->sel, true); | |||
} | |||
void tns_mark(tns_t *tns, int n, bool mark) | |||
{ | |||
if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { | |||
win_t *win = tns->win; | |||
thumb_t *t = &tns->thumbs[n]; | |||
unsigned long col = win->bg.pixel; | |||
int x = t->x + t->w, y = t->y + t->h; | |||
win_draw_rect(win, x - 1, y + 1, 1, tns->bw, true, 1, col); | |||
win_draw_rect(win, x + 1, y - 1, tns->bw, 1, true, 1, col); | |||
if (mark) | |||
col = win->fg.pixel; | |||
win_draw_rect(win, x, y, tns->bw + 2, tns->bw + 2, true, 1, col); | |||
if (!mark && n == *tns->sel) | |||
tns_highlight(tns, n, true); | |||
} | |||
} | |||
void tns_highlight(tns_t *tns, int n, bool hl) | |||
{ | |||
if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { | |||
win_t *win = tns->win; | |||
thumb_t *t = &tns->thumbs[n]; | |||
unsigned long col = hl ? win->fg.pixel : win->bg.pixel; | |||
int oxy = (tns->bw + 1) / 2 + 1, owh = tns->bw + 2; | |||
win_draw_rect(win, t->x - oxy, t->y - oxy, t->w + owh, t->h + owh, | |||
false, tns->bw, col); | |||
if (tns->files[n].flags & FF_MARK) | |||
tns_mark(tns, n, true); | |||
} | |||
} | |||
bool tns_move_selection(tns_t *tns, direction_t dir, int cnt) | |||
{ | |||
int old, max; | |||
old = *tns->sel; | |||
cnt = cnt > 1 ? cnt : 1; | |||
switch (dir) { | |||
case DIR_UP: | |||
*tns->sel = MAX(*tns->sel - cnt * tns->cols, *tns->sel % tns->cols); | |||
break; | |||
case DIR_DOWN: | |||
max = tns->cols * ((*tns->cnt - 1) / tns->cols) + | |||
MIN((*tns->cnt - 1) % tns->cols, *tns->sel % tns->cols); | |||
*tns->sel = MIN(*tns->sel + cnt * tns->cols, max); | |||
break; | |||
case DIR_LEFT: | |||
*tns->sel = MAX(*tns->sel - cnt, 0); | |||
break; | |||
case DIR_RIGHT: | |||
*tns->sel = MIN(*tns->sel + cnt, *tns->cnt - 1); | |||
break; | |||
} | |||
if (*tns->sel != old) { | |||
tns_highlight(tns, old, false); | |||
tns_check_view(tns, false); | |||
if (!tns->dirty) | |||
tns_highlight(tns, *tns->sel, true); | |||
} | |||
return *tns->sel != old; | |||
} | |||
bool tns_scroll(tns_t *tns, direction_t dir, bool screen) | |||
{ | |||
int d, max, old; | |||
old = tns->first; | |||
d = tns->cols * (screen ? tns->rows : 1); | |||
if (dir == DIR_DOWN) { | |||
max = *tns->cnt - tns->cols * tns->rows; | |||
if (*tns->cnt % tns->cols != 0) | |||
max += tns->cols - *tns->cnt % tns->cols; | |||
tns->first = MIN(tns->first + d, max); | |||
} else if (dir == DIR_UP) { | |||
tns->first = MAX(tns->first - d, 0); | |||
} | |||
if (tns->first != old) { | |||
tns_check_view(tns, true); | |||
tns->dirty = true; | |||
} | |||
return tns->first != old; | |||
} | |||
bool tns_zoom(tns_t *tns, int d) | |||
{ | |||
int i, oldzl; | |||
oldzl = tns->zl; | |||
tns->zl += -(d < 0) + (d > 0); | |||
tns->zl = MAX(tns->zl, 0); | |||
tns->zl = MIN(tns->zl, ARRLEN(thumb_sizes)-1); | |||
tns->bw = ((thumb_sizes[tns->zl] - 1) >> 5) + 1; | |||
tns->bw = MIN(tns->bw, 4); | |||
tns->dim = thumb_sizes[tns->zl] + 2 * tns->bw + 6; | |||
if (tns->zl != oldzl) { | |||
for (i = 0; i < *tns->cnt; i++) | |||
tns_unload(tns, i); | |||
tns->dirty = true; | |||
} | |||
return tns->zl != oldzl; | |||
} | |||
int tns_translate(tns_t *tns, int x, int y) | |||
{ | |||
int n; | |||
if (x < tns->x || y < tns->y) | |||
return -1; | |||
n = tns->first + (y - tns->y) / tns->dim * tns->cols + | |||
(x - tns->x) / tns->dim; | |||
if (n >= *tns->cnt) | |||
n = -1; | |||
return n; | |||
} |
@ -0,0 +1,68 @@ | |||
/* Branchless UTF-8 decoder | |||
* | |||
* This is free and unencumbered software released into the public domain. | |||
*/ | |||
#ifndef UTF8_H | |||
#define UTF8_H | |||
#include <stdint.h> | |||
/* Decode the next character, C, from BUF, reporting errors in E. | |||
* | |||
* Since this is a branchless decoder, four bytes will be read from the | |||
* buffer regardless of the actual length of the next character. This | |||
* means the buffer _must_ have at least three bytes of zero padding | |||
* following the end of the data stream. | |||
* | |||
* Errors are reported in E, which will be non-zero if the parsed | |||
* character was somehow invalid: invalid byte sequence, non-canonical | |||
* encoding, or a surrogate half. | |||
* | |||
* The function returns a pointer to the next character. When an error | |||
* occurs, this pointer will be a guess that depends on the particular | |||
* error, but it will always advance at least one byte. | |||
*/ | |||
static void * | |||
utf8_decode(void *buf, uint32_t *c, int *e) | |||
{ | |||
static const char lengths[] = { | |||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 | |||
}; | |||
static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; | |||
static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; | |||
static const int shiftc[] = {0, 18, 12, 6, 0}; | |||
static const int shifte[] = {0, 6, 4, 2, 0}; | |||
unsigned char *s = buf; | |||
int len = lengths[s[0] >> 3]; | |||
/* Compute the pointer to the next character early so that the next | |||
* iteration can start working on the next character. Neither Clang | |||
* nor GCC figure out this reordering on their own. | |||
*/ | |||
unsigned char *next = s + len + !len; | |||
/* Assume a four-byte character and load four bytes. Unused bits are | |||
* shifted out. | |||
*/ | |||
*c = (uint32_t)(s[0] & masks[len]) << 18; | |||
*c |= (uint32_t)(s[1] & 0x3f) << 12; | |||
*c |= (uint32_t)(s[2] & 0x3f) << 6; | |||
*c |= (uint32_t)(s[3] & 0x3f) << 0; | |||
*c >>= shiftc[len]; | |||
/* Accumulate the various error conditions. */ | |||
*e = (*c < mins[len]) << 6; // non-canonical encoding | |||
*e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? | |||
*e |= (*c > 0x10FFFF) << 8; // out of range? | |||
*e |= (s[1] & 0xc0) >> 2; | |||
*e |= (s[2] & 0xc0) >> 4; | |||
*e |= (s[3] ) >> 6; | |||
*e ^= 0x2a; // top two bits of each tail byte correct? | |||
*e >>= shifte[len]; | |||
return next; | |||
} | |||
#endif |
@ -0,0 +1,213 @@ | |||
/* Copyright 2011 Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <sys/types.h> | |||
#include <sys/stat.h> | |||
#include <unistd.h> | |||
#include <errno.h> | |||
const char *progname; | |||
void* emalloc(size_t size) | |||
{ | |||
void *ptr; | |||
ptr = malloc(size); | |||
if (ptr == NULL) | |||
error(EXIT_FAILURE, errno, NULL); | |||
return ptr; | |||
} | |||
void* erealloc(void *ptr, size_t size) | |||
{ | |||
ptr = realloc(ptr, size); | |||
if (ptr == NULL) | |||
error(EXIT_FAILURE, errno, NULL); | |||
return ptr; | |||
} | |||
char* estrdup(const char *s) | |||
{ | |||
char *d; | |||
size_t n = strlen(s) + 1; | |||
d = malloc(n); | |||
if (d == NULL) | |||
error(EXIT_FAILURE, errno, NULL); | |||
memcpy(d, s, n); | |||
return d; | |||
} | |||
void error(int eval, int err, const char* fmt, ...) | |||
{ | |||
va_list ap; | |||
if (eval == 0 && options->quiet) | |||
return; | |||
fflush(stdout); | |||
fprintf(stderr, "%s: ", progname); | |||
va_start(ap, fmt); | |||
if (fmt != NULL) | |||
vfprintf(stderr, fmt, ap); | |||
va_end(ap); | |||
if (err != 0) | |||
fprintf(stderr, "%s%s", fmt != NULL ? ": " : "", strerror(err)); | |||
fputc('\n', stderr); | |||
if (eval != 0) | |||
exit(eval); | |||
} | |||
void size_readable(float *size, const char **unit) | |||
{ | |||
const char *units[] = { "", "K", "M", "G" }; | |||
int i; | |||
for (i = 0; i < ARRLEN(units) && *size > 1024.0; i++) | |||
*size /= 1024.0; | |||
*unit = units[MIN(i, ARRLEN(units) - 1)]; | |||
} | |||
int r_opendir(r_dir_t *rdir, const char *dirname, bool recursive) | |||
{ | |||
if (*dirname == '\0') | |||
return -1; | |||
if ((rdir->dir = opendir(dirname)) == NULL) { | |||
rdir->name = NULL; | |||
rdir->stack = NULL; | |||
return -1; | |||
} | |||
rdir->stcap = 512; | |||
rdir->stack = (char**) emalloc(rdir->stcap * sizeof(char*)); | |||
rdir->stlen = 0; | |||
rdir->name = (char*) dirname; | |||
rdir->d = 0; | |||
rdir->recursive = recursive; | |||
return 0; | |||
} | |||
int r_closedir(r_dir_t *rdir) | |||
{ | |||
int ret = 0; | |||
if (rdir->stack != NULL) { | |||
while (rdir->stlen > 0) | |||
free(rdir->stack[--rdir->stlen]); | |||
free(rdir->stack); | |||
rdir->stack = NULL; | |||
} | |||
if (rdir->dir != NULL) { | |||
if ((ret = closedir(rdir->dir)) == 0) | |||
rdir->dir = NULL; | |||
} | |||
if (rdir->d != 0) { | |||
free(rdir->name); | |||
rdir->name = NULL; | |||
} | |||
return ret; | |||
} | |||
char* r_readdir(r_dir_t *rdir, bool skip_dotfiles) | |||
{ | |||
size_t len; | |||
char *filename; | |||
struct dirent *dentry; | |||
struct stat fstats; | |||
while (true) { | |||
if (rdir->dir != NULL && (dentry = readdir(rdir->dir)) != NULL) { | |||
if (dentry->d_name[0] == '.') { | |||
if (skip_dotfiles) | |||
continue; | |||
if (dentry->d_name[1] == '\0') | |||
continue; | |||
if (dentry->d_name[1] == '.' && dentry->d_name[2] == '\0') | |||
continue; | |||
} | |||
len = strlen(rdir->name) + strlen(dentry->d_name) + 2; | |||
filename = (char*) emalloc(len); | |||
snprintf(filename, len, "%s%s%s", rdir->name, | |||
rdir->name[strlen(rdir->name)-1] == '/' ? "" : "/", | |||
dentry->d_name); | |||
if (stat(filename, &fstats) < 0) | |||
continue; | |||
if (S_ISDIR(fstats.st_mode)) { | |||
/* put subdirectory on the stack */ | |||
if (rdir->stlen == rdir->stcap) { | |||
rdir->stcap *= 2; | |||
rdir->stack = (char**) erealloc(rdir->stack, | |||
rdir->stcap * sizeof(char*)); | |||
} | |||
rdir->stack[rdir->stlen++] = filename; | |||
continue; | |||
} | |||
return filename; | |||
} | |||
if (rdir->recursive && rdir->stlen > 0) { | |||
/* open next subdirectory */ | |||
closedir(rdir->dir); | |||
if (rdir->d != 0) | |||
free(rdir->name); | |||
rdir->name = rdir->stack[--rdir->stlen]; | |||
rdir->d = 1; | |||
if ((rdir->dir = opendir(rdir->name)) == NULL) | |||
error(0, errno, "%s", rdir->name); | |||
continue; | |||
} | |||
/* no more entries */ | |||
break; | |||
} | |||
return NULL; | |||
} | |||
int r_mkdir(char *path) | |||
{ | |||
char c, *s = path; | |||
struct stat st; | |||
while (*s != '\0') { | |||
if (*s == '/') { | |||
s++; | |||
continue; | |||
} | |||
for (; *s != '\0' && *s != '/'; s++); | |||
c = *s; | |||
*s = '\0'; | |||
if (mkdir(path, 0755) == -1) | |||
if (errno != EEXIST || stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) | |||
return -1; | |||
*s = c; | |||
} | |||
return 0; | |||
} | |||
@ -0,0 +1 @@ | |||
#define VERSION "26" |
@ -0,0 +1,476 @@ | |||
/* Copyright 2011-2013 Bert Muennich | |||
* | |||
* This file is part of sxiv. | |||
* | |||
* sxiv is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published | |||
* by the Free Software Foundation; either version 2 of the License, | |||
* or (at your option) any later version. | |||
* | |||
* sxiv is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with sxiv. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
#include "sxiv.h" | |||
#define _WINDOW_CONFIG | |||
#include "config.h" | |||
#include "icon/data.h" | |||
#include "utf8.h" | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <locale.h> | |||
#include <X11/cursorfont.h> | |||
#include <X11/Xatom.h> | |||
#include <X11/Xresource.h> | |||
#define RES_CLASS "Sxiv" | |||
enum { | |||
H_TEXT_PAD = 5, | |||
V_TEXT_PAD = 1 | |||
}; | |||
static struct { | |||
int name; | |||
Cursor icon; | |||
} cursors[CURSOR_COUNT] = { | |||
{ XC_left_ptr }, { XC_dotbox }, { XC_watch }, | |||
{ XC_sb_left_arrow }, { XC_sb_right_arrow } | |||
}; | |||
static GC gc; | |||
static XftFont *font; | |||
static int fontheight; | |||
static double fontsize; | |||
static int barheight; | |||
Atom atoms[ATOM_COUNT]; | |||
void win_init_font(const win_env_t *e, const char *fontstr) | |||
{ | |||
if ((font = XftFontOpenName(e->dpy, e->scr, fontstr)) == NULL) | |||
error(EXIT_FAILURE, 0, "Error loading font '%s'", fontstr); | |||
fontheight = font->ascent + font->descent; | |||
FcPatternGetDouble(font->pattern, FC_SIZE, 0, &fontsize); | |||
barheight = fontheight + 2 * V_TEXT_PAD; | |||
} | |||
void win_alloc_color(const win_env_t *e, const char *name, XftColor *col) | |||
{ | |||
if (!XftColorAllocName(e->dpy, DefaultVisual(e->dpy, e->scr), | |||
DefaultColormap(e->dpy, e->scr), name, col)) | |||
{ | |||
error(EXIT_FAILURE, 0, "Error allocating color '%s'", name); | |||
} | |||
} | |||
const char* win_res(XrmDatabase db, const char *name, const char *def) | |||
{ | |||
char *type; | |||
XrmValue ret; | |||
if (db != None && | |||
XrmGetResource(db, name, name, &type, &ret) && | |||
STREQ(type, "String")) | |||
{ | |||
return ret.addr; | |||
} else { | |||
return def; | |||
} | |||
} | |||
#define INIT_ATOM_(atom) \ | |||
atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False); | |||
void win_init(win_t *win) | |||
{ | |||
win_env_t *e; | |||
const char *bg, *fg, *f; | |||
char *res_man; | |||
XrmDatabase db; | |||
memset(win, 0, sizeof(win_t)); | |||
e = &win->env; | |||
if ((e->dpy = XOpenDisplay(NULL)) == NULL) | |||
error(EXIT_FAILURE, 0, "Error opening X display"); | |||
e->scr = DefaultScreen(e->dpy); | |||
e->scrw = DisplayWidth(e->dpy, e->scr); | |||
e->scrh = DisplayHeight(e->dpy, e->scr); | |||
e->vis = DefaultVisual(e->dpy, e->scr); | |||
e->cmap = DefaultColormap(e->dpy, e->scr); | |||
e->depth = DefaultDepth(e->dpy, e->scr); | |||
if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0) | |||
error(0, 0, "No locale support"); | |||
XrmInitialize(); | |||
res_man = XResourceManagerString(e->dpy); | |||
db = res_man != NULL ? XrmGetStringDatabase(res_man) : None; | |||
f = win_res(db, RES_CLASS ".font", "monospace-8"); | |||
win_init_font(e, f); | |||
bg = win_res(db, RES_CLASS ".background", "white"); | |||
fg = win_res(db, RES_CLASS ".foreground", "black"); | |||
win_alloc_color(e, bg, &win->bg); | |||
win_alloc_color(e, fg, &win->fg); | |||
win->bar.l.size = BAR_L_LEN; | |||
win->bar.r.size = BAR_R_LEN; | |||
/* 3 padding bytes needed by utf8_decode */ | |||
win->bar.l.buf = emalloc(win->bar.l.size + 3); | |||
win->bar.l.buf[0] = '\0'; | |||
win->bar.r.buf = emalloc(win->bar.r.size + 3); | |||
win->bar.r.buf[0] = '\0'; | |||
win->bar.h = options->hide_bar ? 0 : barheight; | |||
INIT_ATOM_(WM_DELETE_WINDOW); | |||
INIT_ATOM_(_NET_WM_NAME); | |||
INIT_ATOM_(_NET_WM_ICON_NAME); | |||
INIT_ATOM_(_NET_WM_ICON); | |||
INIT_ATOM_(_NET_WM_STATE); | |||
INIT_ATOM_(_NET_WM_STATE_FULLSCREEN); | |||
} | |||
void win_open(win_t *win) | |||
{ | |||
int c, i, j, n; | |||
long parent; | |||
win_env_t *e; | |||
XClassHint classhint; | |||
unsigned long *icon_data; | |||
XColor col; | |||
Cursor *cnone = &cursors[CURSOR_NONE].icon; | |||
char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; | |||
Pixmap none; | |||
int gmask; | |||
XSizeHints sizehints; | |||
e = &win->env; | |||
parent = options->embed != 0 ? options->embed : RootWindow(e->dpy, e->scr); | |||
sizehints.flags = PWinGravity; | |||
sizehints.win_gravity = NorthWestGravity; | |||
/* determine window offsets, width & height */ | |||
if (options->geometry == NULL) | |||
gmask = 0; | |||
else | |||
gmask = XParseGeometry(options->geometry, &win->x, &win->y, | |||
&win->w, &win->h); | |||
if ((gmask & WidthValue) != 0) | |||
sizehints.flags |= USSize; | |||
else | |||
win->w = WIN_WIDTH; | |||
if ((gmask & HeightValue) != 0) | |||
sizehints.flags |= USSize; | |||
else | |||
win->h = WIN_HEIGHT; | |||
if ((gmask & XValue) != 0) { | |||
if ((gmask & XNegative) != 0) { | |||
win->x += e->scrw - win->w; | |||
sizehints.win_gravity = NorthEastGravity; | |||
} | |||
sizehints.flags |= USPosition; | |||
} else { | |||
win->x = 0; | |||
} | |||
if ((gmask & YValue) != 0) { | |||
if ((gmask & YNegative) != 0) { | |||
win->y += e->scrh - win->h; | |||
sizehints.win_gravity = sizehints.win_gravity == NorthEastGravity | |||
? SouthEastGravity : SouthWestGravity; | |||
} | |||
sizehints.flags |= USPosition; | |||
} else { | |||
win->y = 0; | |||
} | |||
win->xwin = XCreateWindow(e->dpy, parent, | |||
win->x, win->y, win->w, win->h, 0, | |||
e->depth, InputOutput, e->vis, 0, NULL); | |||
if (win->xwin == None) | |||
error(EXIT_FAILURE, 0, "Error creating X window"); | |||
XSelectInput(e->dpy, win->xwin, | |||
ButtonReleaseMask | ButtonPressMask | KeyPressMask | | |||
PointerMotionMask | StructureNotifyMask); | |||
for (i = 0; i < ARRLEN(cursors); i++) { | |||
if (i != CURSOR_NONE) | |||
cursors[i].icon = XCreateFontCursor(e->dpy, cursors[i].name); | |||
} | |||
if (XAllocNamedColor(e->dpy, DefaultColormap(e->dpy, e->scr), "black", | |||
&col, &col) == 0) | |||
{ | |||
error(EXIT_FAILURE, 0, "Error allocating color 'black'"); | |||
} | |||
none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8); | |||
*cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0); | |||
gc = XCreateGC(e->dpy, win->xwin, 0, None); | |||
n = icons[ARRLEN(icons)-1].size; | |||
icon_data = emalloc((n * n + 2) * sizeof(*icon_data)); | |||
for (i = 0; i < ARRLEN(icons); i++) { | |||
n = 0; | |||
icon_data[n++] = icons[i].size; | |||
icon_data[n++] = icons[i].size; | |||
for (j = 0; j < icons[i].cnt; j++) { | |||
for (c = icons[i].data[j] >> 4; c >= 0; c--) | |||
icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F]; | |||
} | |||
XChangeProperty(e->dpy, win->xwin, | |||
atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32, | |||
i == 0 ? PropModeReplace : PropModeAppend, | |||
(unsigned char *) icon_data, n); | |||
} | |||
free(icon_data); | |||
win_set_title(win, "sxiv"); | |||
classhint.res_class = RES_CLASS; | |||
classhint.res_name = options->res_name != NULL ? options->res_name : "sxiv"; | |||
XSetClassHint(e->dpy, win->xwin, &classhint); | |||
XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1); | |||
sizehints.width = win->w; | |||
sizehints.height = win->h; | |||
sizehints.x = win->x; | |||
sizehints.y = win->y; | |||
XSetWMNormalHints(win->env.dpy, win->xwin, &sizehints); | |||
win->h -= win->bar.h; | |||
win->buf.w = e->scrw; | |||
win->buf.h = e->scrh; | |||
win->buf.pm = XCreatePixmap(e->dpy, win->xwin, | |||
win->buf.w, win->buf.h, e->depth); | |||
XSetForeground(e->dpy, gc, win->bg.pixel); | |||
XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); | |||
XSetWindowBackgroundPixmap(e->dpy, win->xwin, win->buf.pm); | |||
XMapWindow(e->dpy, win->xwin); | |||
XFlush(e->dpy); | |||
if (options->fullscreen) | |||
win_toggle_fullscreen(win); | |||
} | |||
CLEANUP void win_close(win_t *win) | |||
{ | |||
int i; | |||
for (i = 0; i < ARRLEN(cursors); i++) | |||
XFreeCursor(win->env.dpy, cursors[i].icon); | |||
XFreeGC(win->env.dpy, gc); | |||
XDestroyWindow(win->env.dpy, win->xwin); | |||
XCloseDisplay(win->env.dpy); | |||
} | |||
bool win_configure(win_t *win, XConfigureEvent *c) | |||
{ | |||
bool changed; | |||
changed = win->w != c->width || win->h + win->bar.h != c->height; | |||
win->x = c->x; | |||
win->y = c->y; | |||
win->w = c->width; | |||
win->h = c->height - win->bar.h; | |||
win->bw = c->border_width; | |||
return changed; | |||
} | |||
void win_toggle_fullscreen(win_t *win) | |||
{ | |||
XEvent ev; | |||
XClientMessageEvent *cm; | |||
memset(&ev, 0, sizeof(ev)); | |||
ev.type = ClientMessage; | |||
cm = &ev.xclient; | |||
cm->window = win->xwin; | |||
cm->message_type = atoms[ATOM__NET_WM_STATE]; | |||
cm->format = 32; | |||
cm->data.l[0] = 2; // toggle | |||
cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN]; | |||
XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False, | |||
SubstructureNotifyMask | SubstructureRedirectMask, &ev); | |||
} | |||
void win_toggle_bar(win_t *win) | |||
{ | |||
if (win->bar.h != 0) { | |||
win->h += win->bar.h; | |||
win->bar.h = 0; | |||
} else { | |||
win->bar.h = barheight; | |||
win->h -= win->bar.h; | |||
} | |||
} | |||
void win_clear(win_t *win) | |||
{ | |||
win_env_t *e; | |||
e = &win->env; | |||
if (win->w > win->buf.w || win->h + win->bar.h > win->buf.h) { | |||
XFreePixmap(e->dpy, win->buf.pm); | |||
win->buf.w = MAX(win->buf.w, win->w); | |||
win->buf.h = MAX(win->buf.h, win->h + win->bar.h); | |||
win->buf.pm = XCreatePixmap(e->dpy, win->xwin, | |||
win->buf.w, win->buf.h, e->depth); | |||
} | |||
XSetForeground(e->dpy, gc, win->bg.pixel); | |||
XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); | |||
} | |||
#define TEXTWIDTH(win, text, len) \ | |||
win_draw_text(win, NULL, NULL, 0, 0, text, len, 0) | |||
int win_draw_text(win_t *win, XftDraw *d, const XftColor *color, int x, int y, | |||
char *text, int len, int w) | |||
{ | |||
int err, tw = 0; | |||
char *t, *next; | |||
uint32_t rune; | |||
XftFont *f; | |||
FcCharSet *fccharset; | |||
XGlyphInfo ext; | |||
for (t = text; t - text < len; t = next) { | |||
next = utf8_decode(t, &rune, &err); | |||
if (XftCharExists(win->env.dpy, font, rune)) { | |||
f = font; | |||
} else { /* fallback font */ | |||
fccharset = FcCharSetCreate(); | |||
FcCharSetAddChar(fccharset, rune); | |||
f = XftFontOpen(win->env.dpy, win->env.scr, FC_CHARSET, FcTypeCharSet, | |||
fccharset, FC_SCALABLE, FcTypeBool, FcTrue, | |||
FC_SIZE, FcTypeDouble, fontsize, NULL); | |||
FcCharSetDestroy(fccharset); | |||
} | |||
XftTextExtentsUtf8(win->env.dpy, f, (XftChar8*)t, next - t, &ext); | |||
tw += ext.xOff; | |||
if (tw <= w) { | |||
XftDrawStringUtf8(d, color, f, x, y, (XftChar8*)t, next - t); | |||
x += ext.xOff; | |||
} | |||
if (f != font) | |||
XftFontClose(win->env.dpy, f); | |||
} | |||
return tw; | |||
} | |||
void win_draw_bar(win_t *win) | |||
{ | |||
int len, x, y, w, tw; | |||
win_env_t *e; | |||
win_bar_t *l, *r; | |||
XftDraw *d; | |||
if ((l = &win->bar.l)->buf == NULL || (r = &win->bar.r)->buf == NULL) | |||
return; | |||
e = &win->env; | |||
y = win->h + font->ascent + V_TEXT_PAD; | |||
w = win->w - 2*H_TEXT_PAD; | |||
d = XftDrawCreate(e->dpy, win->buf.pm, DefaultVisual(e->dpy, e->scr), | |||
DefaultColormap(e->dpy, e->scr)); | |||
XSetForeground(e->dpy, gc, win->fg.pixel); | |||
XFillRectangle(e->dpy, win->buf.pm, gc, 0, win->h, win->w, win->bar.h); | |||
XSetForeground(e->dpy, gc, win->bg.pixel); | |||
XSetBackground(e->dpy, gc, win->fg.pixel); | |||
if ((len = strlen(r->buf)) > 0) { | |||
if ((tw = TEXTWIDTH(win, r->buf, len)) > w) | |||
return; | |||
x = win->w - tw - H_TEXT_PAD; | |||
w -= tw; | |||
win_draw_text(win, d, &win->bg, x, y, r->buf, len, tw); | |||
} | |||
if ((len = strlen(l->buf)) > 0) { | |||
x = H_TEXT_PAD; | |||
w -= 2 * H_TEXT_PAD; /* gap between left and right parts */ | |||
win_draw_text(win, d, &win->bg, x, y, l->buf, len, w); | |||
} | |||
XftDrawDestroy(d); | |||
} | |||
void win_draw(win_t *win) | |||
{ | |||
if (win->bar.h > 0) | |||
win_draw_bar(win); | |||
XSetWindowBackgroundPixmap(win->env.dpy, win->xwin, win->buf.pm); | |||
XClearWindow(win->env.dpy, win->xwin); | |||
XFlush(win->env.dpy); | |||
} | |||
void win_draw_rect(win_t *win, int x, int y, int w, int h, bool fill, int lw, | |||
unsigned long col) | |||
{ | |||
XGCValues gcval; | |||
gcval.line_width = lw; | |||
gcval.foreground = col; | |||
XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval); | |||
if (fill) | |||
XFillRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); | |||
else | |||
XDrawRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); | |||
} | |||
void win_set_title(win_t *win, const char *title) | |||
{ | |||
XStoreName(win->env.dpy, win->xwin, title); | |||
XSetIconName(win->env.dpy, win->xwin, title); | |||
XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_NAME], | |||
XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, | |||
PropModeReplace, (unsigned char *) title, strlen(title)); | |||
XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_ICON_NAME], | |||
XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, | |||
PropModeReplace, (unsigned char *) title, strlen(title)); | |||
} | |||
void win_set_cursor(win_t *win, cursor_t cursor) | |||
{ | |||
if (cursor >= 0 && cursor < ARRLEN(cursors)) { | |||
XDefineCursor(win->env.dpy, win->xwin, cursors[cursor].icon); | |||
XFlush(win->env.dpy); | |||
} | |||
} | |||
void win_cursor_pos(win_t *win, int *x, int *y) | |||
{ | |||
int i; | |||
unsigned int ui; | |||
Window w; | |||
if (!XQueryPointer(win->env.dpy, win->xwin, &w, &w, &i, &i, x, y, &ui)) | |||
*x = *y = 0; | |||
} | |||
@ -0,0 +1,57 @@ | |||
# Prerequisites | |||
*.d | |||
# Object files | |||
*.o | |||
*.ko | |||
*.obj | |||
*.elf | |||
# Linker output | |||
*.ilk | |||
*.map | |||
*.exp | |||
# Precompiled Headers | |||
*.gch | |||
*.pch | |||
# Libraries | |||
*.lib | |||
*.a | |||
*.la | |||
*.lo | |||
# Shared objects (inc. Windows DLLs) | |||
*.dll | |||
*.so | |||
*.so.* | |||
*.dylib | |||
# Executables | |||
*.exe | |||
*.out | |||
*.app | |||
*.i*86 | |||
*.x86_64 | |||
*.hex | |||
# Debug files | |||
*.dSYM/ | |||
*.su | |||
*.idb | |||
*.pdb | |||
# Kernel Module Compile Results | |||
*.mod* | |||
*.cmd | |||
.tmp_versions/ | |||
modules.order | |||
Module.symvers | |||
Mkfile.old | |||
dkms.conf | |||
slock | |||
*.orig | |||
*.rej |
@ -0,0 +1,14 @@ | |||
xbanish | |||
Copyright (c) 2013-2021 joshua stein <jcs@jcs.org> | |||
Permission to use, copy, modify, and distribute this software for any | |||
purpose with or without fee is hereby granted, provided that the above | |||
copyright notice and this permission notice appear in all copies. | |||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
@ -0,0 +1,39 @@ | |||
# vim:ts=8 | |||
CC ?= cc | |||
CFLAGS ?= -O2 | |||
CFLAGS += -Wall -Wunused -Wmissing-prototypes -Wstrict-prototypes -Wunused | |||
PREFIX ?= /usr/local | |||
BINDIR ?= $(PREFIX)/bin | |||
MANDIR ?= $(PREFIX)/man/man1 | |||
INSTALL_PROGRAM ?= install -s | |||
INSTALL_DATA ?= install | |||
X11BASE ?= /usr/X11R6 | |||
INCLUDES?= -I$(X11BASE)/include | |||
LDPATH ?= -L$(X11BASE)/lib | |||
LIBS += -lX11 -lXfixes -lXi | |||
PROG = xbanish | |||
OBJS = xbanish.o | |||
all: $(PROG) | |||
$(PROG): $(OBJS) | |||
$(CC) $(OBJS) $(LDPATH) $(LIBS) -o $@ | |||
$(OBJS): *.c | |||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ | |||
install: all | |||
mkdir -p $(DESTDIR)$(BINDIR) | |||
$(INSTALL_PROGRAM) $(PROG) $(DESTDIR)$(BINDIR) | |||
mkdir -p $(DESTDIR)$(MANDIR) | |||
$(INSTALL_DATA) -m 644 xbanish.1 $(DESTDIR)$(MANDIR)/xbanish.1 | |||
clean: | |||
rm -f $(PROG) $(OBJS) | |||
.PHONY: all install clean |
@ -0,0 +1,60 @@ | |||
.Dd $Mdocdate: September 16 2019$ | |||
.Dt XBANISH 1 | |||
.Os | |||
.Sh NAME | |||
.Nm xbanish | |||
.Nd hide the X11 mouse cursor when a key is pressed | |||
.Sh SYNOPSIS | |||
.Nm | |||
.Op Fl a | |||
.Op Fl d | |||
.Op Fl i Ar modifier | |||
.Op Fl m Ar nw|ne|sw|se | |||
.Sh DESCRIPTION | |||
.Nm | |||
hides the X11 mouse cursor when a key is pressed. | |||
The cursor is shown again when it is moved or a mouse button is pressed. | |||
This is similar to the | |||
.Xr xterm 1 | |||
setting | |||
.Ic pointerMode | |||
but the effect is global in the X11 session. | |||
.Sh OPTIONS | |||
.Bl -tag -width Ds | |||
.It Fl a | |||
Always keep mouse cursor hidden while | |||
.Nm | |||
is running. | |||
.It Fl d | |||
Print debugging messages to stdout. | |||
.It Fl i Ar modifier | |||
Ignore pressed key if | |||
.Ar modifier | |||
is used. | |||
Modifiers are: | |||
.Ic shift , | |||
.Ic lock | |||
(CapsLock), | |||
.Ic control , | |||
.Ic mod1 | |||
(Alt or Meta), | |||
.Ic mod2 | |||
(NumLock), | |||
.Ic mod3 | |||
(Hyper), | |||
.Ic mod4 | |||
(Super, Windows, or Command), | |||
.Ic mod5 | |||
(ISO Level 3 Shift), and | |||
.Ic all | |||
(All of the above). | |||
.It Fl m Ar nw|ne|sw|se | |||
When hiding the mouse cursor, move it to this corner of the screen, | |||
then move it back when showing the cursor. | |||
.El | |||
.Sh SEE ALSO | |||
.Xr XFixes 3 | |||
.Sh AUTHORS | |||
.Nm | |||
was written by | |||
.An joshua stein Aq Mt jcs@jcs.org . |
@ -0,0 +1,487 @@ | |||
/* | |||
* xbanish | |||
* Copyright (c) 2013-2021 joshua stein <jcs@jcs.org> | |||
* | |||
* Permission to use, copy, modify, and distribute this software for any | |||
* purpose with or without fee is hereby granted, provided that the above | |||
* copyright notice and this permission notice appear in all copies. | |||
* | |||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
*/ | |||
#include <err.h> | |||
#include <stdlib.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <X11/X.h> | |||
#include <X11/Xlib.h> | |||
#include <X11/Intrinsic.h> | |||
#include <X11/extensions/Xfixes.h> | |||
#include <X11/extensions/XInput.h> | |||
#include <X11/extensions/XInput2.h> | |||
void hide_cursor(void); | |||
void show_cursor(void); | |||
void snoop_root(void); | |||
int snoop_xinput(Window); | |||
void snoop_legacy(Window); | |||
void usage(char *); | |||
int swallow_error(Display *, XErrorEvent *); | |||
/* xinput event type ids to be filled in later */ | |||
static int button_press_type = -1; | |||
static int button_release_type = -1; | |||
static int key_press_type = -1; | |||
static int key_release_type = -1; | |||
static int motion_type = -1; | |||
static int device_change_type = -1; | |||
static long last_device_change = -1; | |||
static Display *dpy; | |||
static int hiding = 0, legacy = 0, always_hide = 0; | |||
static unsigned char ignored; | |||
static int debug = 0; | |||
#define DPRINTF(x) { if (debug) { printf x; } }; | |||
static int move = 0, move_x, move_y; | |||
enum move_types { | |||
MOVE_NW = 1, | |||
MOVE_NE, | |||
MOVE_SW, | |||
MOVE_SE, | |||
}; | |||
int | |||
main(int argc, char *argv[]) | |||
{ | |||
int ch, i; | |||
XEvent e; | |||
XGenericEventCookie *cookie; | |||
struct mod_lookup { | |||
char *name; | |||
int mask; | |||
} mods[] = { | |||
{"shift", ShiftMask}, {"lock", LockMask}, | |||
{"control", ControlMask}, {"mod1", Mod1Mask}, | |||
{"mod2", Mod2Mask}, {"mod3", Mod3Mask}, | |||
{"mod4", Mod4Mask}, {"mod5", Mod5Mask}, | |||
{"all", -1}, | |||
}; | |||
while ((ch = getopt(argc, argv, "adi:m:")) != -1) | |||
switch (ch) { | |||
case 'a': | |||
always_hide = 1; | |||
break; | |||
case 'd': | |||
debug = 1; | |||
break; | |||
case 'i': | |||
for (i = 0; | |||
i < sizeof(mods) / sizeof(struct mod_lookup); i++) | |||
if (strcasecmp(optarg, mods[i].name) == 0) | |||
ignored |= mods[i].mask; | |||
break; | |||
case 'm': | |||
if (strcmp(optarg, "nw") == 0) | |||
move = MOVE_NW; | |||
else if (strcmp(optarg, "ne") == 0) | |||
move = MOVE_NE; | |||
else if (strcmp(optarg, "sw") == 0) | |||
move = MOVE_SW; | |||
else if (strcmp(optarg, "se") == 0) | |||
move = MOVE_SE; | |||
else { | |||
warnx("invalid '-m' argument"); | |||
usage(argv[0]); | |||
} | |||
break; | |||
default: | |||
usage(argv[0]); | |||
} | |||
argc -= optind; | |||
argv += optind; | |||
if (!(dpy = XOpenDisplay(NULL))) | |||
errx(1, "can't open display %s", XDisplayName(NULL)); | |||
#ifdef __OpenBSD__ | |||
if (pledge("stdio", NULL) == -1) | |||
err(1, "pledge"); | |||
#endif | |||
XSetErrorHandler(swallow_error); | |||
snoop_root(); | |||
if (always_hide) | |||
hide_cursor(); | |||
for (;;) { | |||
cookie = &e.xcookie; | |||
XNextEvent(dpy, &e); | |||
int etype = e.type; | |||
if (e.type == motion_type) | |||
etype = MotionNotify; | |||
else if (e.type == key_press_type || | |||
e.type == key_release_type) | |||
etype = KeyRelease; | |||
else if (e.type == button_press_type || | |||
e.type == button_release_type) | |||
etype = ButtonRelease; | |||
else if (e.type == device_change_type) { | |||
XDevicePresenceNotifyEvent *xdpe = | |||
(XDevicePresenceNotifyEvent *)&e; | |||
if (last_device_change == xdpe->serial) | |||
continue; | |||
snoop_root(); | |||
last_device_change = xdpe->serial; | |||
continue; | |||
} | |||
switch (etype) { | |||
case KeyRelease: | |||
if (ignored) { | |||
unsigned int state = 0; | |||
/* masks are only set on key release, if | |||
* ignore is set we must throw out non-release | |||
* events here */ | |||
if (e.type == key_press_type) { | |||
break; | |||
} | |||
/* extract modifier state */ | |||
if (e.type == key_release_type) { | |||
/* xinput device event */ | |||
XDeviceKeyEvent *key = | |||
(XDeviceKeyEvent *) &e; | |||
state = key->state; | |||
} else if (e.type == KeyRelease) { | |||
/* legacy event */ | |||
state = e.xkey.state; | |||
} | |||
if (state & ignored) { | |||
DPRINTF(("ignoring key %d\n", state)); | |||
break; | |||
} | |||
} | |||
hide_cursor(); | |||
break; | |||
case ButtonRelease: | |||
case MotionNotify: | |||
if (!always_hide) | |||
show_cursor(); | |||
break; | |||
case CreateNotify: | |||
if (legacy) { | |||
DPRINTF(("new window, snooping on it\n")); | |||
/* not sure why snooping directly on the window | |||
* doesn't work, so snoop on all windows from | |||
* its parent (probably root) */ | |||
snoop_legacy(e.xcreatewindow.parent); | |||
} | |||
break; | |||
case GenericEvent: | |||
/* xi2 raw event */ | |||
XGetEventData(dpy, cookie); | |||
XIDeviceEvent *xie = (XIDeviceEvent *)cookie->data; | |||
switch (xie->evtype) { | |||
case XI_RawMotion: | |||
case XI_RawButtonPress: | |||
if (!always_hide) | |||
show_cursor(); | |||
break; | |||
case XI_RawButtonRelease: | |||
break; | |||
default: | |||
DPRINTF(("unknown XI event type %d\n", | |||
xie->evtype)); | |||
} | |||
XFreeEventData(dpy, cookie); | |||
break; | |||
default: | |||
DPRINTF(("unknown event type %d\n", e.type)); | |||
} | |||
} | |||
} | |||
void | |||
hide_cursor(void) | |||
{ | |||
Window win; | |||
int x, y, h, w, junk; | |||
unsigned int ujunk; | |||
DPRINTF(("keystroke, %shiding cursor\n", (hiding ? "already " : ""))); | |||
if (hiding) | |||
return; | |||
if (move) { | |||
if (XQueryPointer(dpy, DefaultRootWindow(dpy), | |||
&win, &win, &x, &y, &junk, &junk, &ujunk)) { | |||
move_x = x; | |||
move_y = y; | |||
h = XHeightOfScreen(DefaultScreenOfDisplay(dpy)); | |||
w = XWidthOfScreen(DefaultScreenOfDisplay(dpy)); | |||
switch (move) { | |||
case MOVE_NW: | |||
x = 0; | |||
y = 0; | |||
break; | |||
case MOVE_NE: | |||
x = w; | |||
y = 0; | |||
break; | |||
case MOVE_SW: | |||
x = 0; | |||
y = h; | |||
break; | |||
case MOVE_SE: | |||
x = w; | |||
y = h; | |||
break; | |||
} | |||
XWarpPointer(dpy, None, DefaultRootWindow(dpy), | |||
0, 0, 0, 0, x, y); | |||
} else { | |||
move_x = -1; | |||
move_y = -1; | |||
warn("failed finding cursor coordinates"); | |||
} | |||
} | |||
XFixesHideCursor(dpy, DefaultRootWindow(dpy)); | |||
hiding = 1; | |||
} | |||
void | |||
show_cursor(void) | |||
{ | |||
DPRINTF(("mouse moved, %sunhiding cursor\n", | |||
(hiding ? "" : "already "))); | |||
if (!hiding) | |||
return; | |||
if (move && move_x != -1 && move_y != -1) | |||
XWarpPointer(dpy, None, DefaultRootWindow(dpy), 0, 0, 0, 0, | |||
move_x, move_y); | |||
XFixesShowCursor(dpy, DefaultRootWindow(dpy)); | |||
hiding = 0; | |||
} | |||
void | |||
snoop_root(void) | |||
{ | |||
if (snoop_xinput(DefaultRootWindow(dpy)) == 0) { | |||
DPRINTF(("no XInput devices found, using legacy snooping")); | |||
legacy = 1; | |||
snoop_legacy(DefaultRootWindow(dpy)); | |||
} | |||
} | |||
int | |||
snoop_xinput(Window win) | |||
{ | |||
int opcode, event, error, numdevs, i, j; | |||
int major, minor, rc, rawmotion = 0; | |||
int ev = 0; | |||
unsigned char mask[(XI_LASTEVENT + 7)/8]; | |||
XDeviceInfo *devinfo = NULL; | |||
XInputClassInfo *ici; | |||
XDevice *device; | |||
XIEventMask evmasks[1]; | |||
XEventClass class_presence; | |||
if (!XQueryExtension(dpy, "XInputExtension", &opcode, &event, &error)) { | |||
DPRINTF(("XInput extension not available")); | |||
return 0; | |||
} | |||
/* | |||
* If we support xinput 2, use that for raw motion and button events to | |||
* get pointer data when the cursor is over a Chromium window. We | |||
* could also use this to get raw key input and avoid the other XInput | |||
* stuff, but we may need to be able to examine the key value later to | |||
* filter out ignored keys. | |||
*/ | |||
major = minor = 2; | |||
rc = XIQueryVersion(dpy, &major, &minor); | |||
if (rc != BadRequest) { | |||
memset(mask, 0, sizeof(mask)); | |||
XISetMask(mask, XI_RawMotion); | |||
XISetMask(mask, XI_RawButtonPress); | |||
evmasks[0].deviceid = XIAllMasterDevices; | |||
evmasks[0].mask_len = sizeof(mask); | |||
evmasks[0].mask = mask; | |||
XISelectEvents(dpy, win, evmasks, 1); | |||
XFlush(dpy); | |||
rawmotion = 1; | |||
DPRINTF(("using xinput2 raw motion events\n")); | |||
} | |||
devinfo = XListInputDevices(dpy, &numdevs); | |||
XEventClass event_list[numdevs * 2]; | |||
for (i = 0; i < numdevs; i++) { | |||
if (devinfo[i].use != IsXExtensionKeyboard && | |||
devinfo[i].use != IsXExtensionPointer) | |||
continue; | |||
if (!(device = XOpenDevice(dpy, devinfo[i].id))) | |||
break; | |||
for (ici = device->classes, j = 0; j < devinfo[i].num_classes; | |||
ici++, j++) { | |||
switch (ici->input_class) { | |||
case KeyClass: | |||
DPRINTF(("attaching to keyboard device %s " | |||
"(use %d)\n", devinfo[i].name, | |||
devinfo[i].use)); | |||
DeviceKeyPress(device, key_press_type, | |||
event_list[ev]); ev++; | |||
DeviceKeyRelease(device, key_release_type, | |||
event_list[ev]); ev++; | |||
break; | |||
case ButtonClass: | |||
if (rawmotion) | |||
continue; | |||
DPRINTF(("attaching to buttoned device %s " | |||
"(use %d)\n", devinfo[i].name, | |||
devinfo[i].use)); | |||
DeviceButtonPress(device, button_press_type, | |||
event_list[ev]); ev++; | |||
DeviceButtonRelease(device, | |||
button_release_type, event_list[ev]); ev++; | |||
break; | |||
case ValuatorClass: | |||
if (rawmotion) | |||
continue; | |||
DPRINTF(("attaching to pointing device %s " | |||
"(use %d)\n", devinfo[i].name, | |||
devinfo[i].use)); | |||
DeviceMotionNotify(device, motion_type, | |||
event_list[ev]); ev++; | |||
break; | |||
} | |||
} | |||
XCloseDevice(dpy, device); | |||
if (XSelectExtensionEvent(dpy, win, event_list, ev)) { | |||
warn("error selecting extension events"); | |||
ev = 0; | |||
goto done; | |||
} | |||
} | |||
DevicePresence(dpy, device_change_type, class_presence); | |||
if (XSelectExtensionEvent(dpy, win, &class_presence, 1)) { | |||
warn("error selecting extension events"); | |||
ev = 0; | |||
goto done; | |||
} | |||
done: | |||
if (devinfo != NULL) | |||
XFreeDeviceList(devinfo); | |||
return ev; | |||
} | |||
void | |||
snoop_legacy(Window win) | |||
{ | |||
Window parent, root, *kids = NULL; | |||
XSetWindowAttributes sattrs; | |||
unsigned int nkids = 0, i; | |||
/* | |||
* Firefox stops responding to keys when KeyPressMask is used, so | |||
* settle for KeyReleaseMask | |||
*/ | |||
int type = PointerMotionMask | KeyReleaseMask | Button1MotionMask | | |||
Button2MotionMask | Button3MotionMask | Button4MotionMask | | |||
Button5MotionMask | ButtonMotionMask; | |||
if (XQueryTree(dpy, win, &root, &parent, &kids, &nkids) == FALSE) { | |||
warn("can't query window tree\n"); | |||
goto done; | |||
} | |||
XSelectInput(dpy, root, type); | |||
/* listen for newly mapped windows */ | |||
sattrs.event_mask = SubstructureNotifyMask; | |||
XChangeWindowAttributes(dpy, root, CWEventMask, &sattrs); | |||
for (i = 0; i < nkids; i++) { | |||
XSelectInput(dpy, kids[i], type); | |||
snoop_legacy(kids[i]); | |||
} | |||
done: | |||
if (kids != NULL) | |||
XFree(kids); /* hide yo kids */ | |||
} | |||
void | |||
usage(char *progname) | |||
{ | |||
fprintf(stderr, "usage: %s [-a] [-d] [-i mod] [-m nw|ne|sw|se]\n", | |||
progname); | |||
exit(1); | |||
} | |||
int | |||
swallow_error(Display *d, XErrorEvent *e) | |||
{ | |||
if (e->error_code == BadWindow) | |||
/* no biggie */ | |||
return 0; | |||
else if (e->error_code & FirstExtensionError) | |||
/* error requesting input on a particular xinput device */ | |||
return 0; | |||
else | |||
errx(1, "got X error %d", e->error_code); | |||
} |