pax_global_header00006660000000000000000000000064135315221320014507gustar00rootroot0000000000000052 comment=4c0123bb3b449a2ea45b3e831db725d853bb03a1 imv-4.0.1/000077500000000000000000000000001353152213200123045ustar00rootroot00000000000000imv-4.0.1/.builds/000077500000000000000000000000001353152213200136445ustar00rootroot00000000000000imv-4.0.1/.builds/archlinux.yml000066400000000000000000000010531353152213200163630ustar00rootroot00000000000000image: archlinux packages: - clang - cmocka - freeimage - libjpeg-turbo - libpng - librsvg - libtiff - libx11 - libxcb - libxkbcommon - libxkbcommon-x11 - pango - wayland sources: - https://git.sr.ht/~exec64/imv tasks: - configure: | cd imv sed -i -e 's/BACKEND_\(.*\)=no/BACKEND_\1=yes/' config.mk cat config.mk - gcc: | cd imv CC=gcc BUILDDIR=gcc make CC=gcc BUILDDIR=gcc make check - clang: | cd imv CC=clang BUILDDIR=clang make CC=clang BUILDDIR=clang make check imv-4.0.1/.builds/debian.yml000066400000000000000000000012471353152213200156150ustar00rootroot00000000000000image: debian/stable packages: - clang - libcmocka-dev - libegl1-mesa-dev - libfreeimage-dev - libglu-dev - libpango1.0-dev - libpng-dev - librsvg2-dev - libtiff-dev - libturbojpeg-dev - libwayland-dev - libx11-dev - libxcb1-dev - libxkbcommon-dev - libxkbcommon-x11-dev - mesa-common-dev sources: - https://git.sr.ht/~exec64/imv tasks: - configure: | cd imv sed -i -e 's/BACKEND_\(.*\)=no/BACKEND_\1=yes/' config.mk cat config.mk - gcc: | cd imv CC=gcc BUILDDIR=gcc make CC=gcc BUILDDIR=gcc make check - clang: | cd imv CC=clang BUILDDIR=clang make CC=clang BUILDDIR=clang make check imv-4.0.1/.builds/fedora.yml000066400000000000000000000012701353152213200156270ustar00rootroot00000000000000image: fedora/latest packages: - clang - freeimage-devel - libX11-devel - libcmocka-devel - libpng-devel - librsvg2-devel - libtiff-devel - libxcb-devel - libxkbcommon-devel - libxkbcommon-x11-devel - mesa-libEGL-devel - mesa-libGL-devel - mesa-libGLU-devel - pango-devel - turbojpeg-devel - wayland-devel sources: - https://git.sr.ht/~exec64/imv tasks: - configure: | cd imv sed -i -e 's/BACKEND_\(.*\)=no/BACKEND_\1=yes/' config.mk cat config.mk - gcc: | cd imv CC=gcc BUILDDIR=gcc make CC=gcc BUILDDIR=gcc make check - clang: | cd imv CC=clang BUILDDIR=clang make CC=clang BUILDDIR=clang make check imv-4.0.1/.builds/freebsd.yml000066400000000000000000000007541353152213200160070ustar00rootroot00000000000000image: freebsd/latest packages: - devel/gmake - devel/pkgconf - graphics/freeimage - graphics/libGLU - graphics/libjpeg-turbo - graphics/librsvg2 - graphics/png - graphics/tiff - sysutils/cmocka - x11-toolkits/pango - x11/libxcb - x11/libxkbcommon sources: - https://git.sr.ht/~exec64/imv tasks: - configure: | cd imv sed -i -e 's/BACKEND_\(.*\)=no/BACKEND_\1=yes/' config.mk cat config.mk - build: | cd imv gmake gmake check imv-4.0.1/.builds/ubuntu.yml000066400000000000000000000012471353152213200157150ustar00rootroot00000000000000image: ubuntu/latest packages: - clang - libcmocka-dev - libegl1-mesa-dev - libfreeimage-dev - libglu-dev - libpango1.0-dev - libpng-dev - librsvg2-dev - libtiff-dev - libturbojpeg-dev - libwayland-dev - libx11-dev - libxcb1-dev - libxkbcommon-dev - libxkbcommon-x11-dev - mesa-common-dev sources: - https://git.sr.ht/~exec64/imv tasks: - configure: | cd imv sed -i -e 's/BACKEND_\(.*\)=no/BACKEND_\1=yes/' config.mk cat config.mk - gcc: | cd imv CC=gcc BUILDDIR=gcc make CC=gcc BUILDDIR=gcc make check - clang: | cd imv CC=clang BUILDDIR=clang make CC=clang BUILDDIR=clang make check imv-4.0.1/.gitignore000066400000000000000000000000171353152213200142720ustar00rootroot00000000000000imv *.o test_* imv-4.0.1/AUTHORS000066400000000000000000000006231353152213200133550ustar00rootroot00000000000000Author and maintainer of imv: * Harry Jeffery People who have contributed to imv: * Guillaume Brogi * Dmitrij D. Czarkoff * Jose Diez * Kenneth Hanley * Julian Heinzel * Hannes Körber * Aleksandra Kosiacka * Michal Koutenský * Sebastian Parborg --- When submitting a patch or pull request to imv, feel free to add yourself to this list, in alphabetical surname order. imv-4.0.1/CHANGELOG000066400000000000000000000136601353152213200135240ustar00rootroot00000000000000imv Changelog ============= v4.0.1 - 2019-08-28 * Documented default binds in man page * Added icon to imv.desktop * Added builtin aliases to provide backwards compatibility for removed commands * Added warning when legacy bind syntax is detected v4.0.0 - 2019-08-27 BREAKING CHANGES: * Fixed keyboard layout handling, changing bind syntax * Renamed many commands (select_rel -> next/prev, select_abs -> goto, etc.) * Temporarily removed autoresize option * Dropped SDL2 dependency, implementing Wayland and X11 support natively through imv-wayland and imv-x11 binaries * Added support for displaying SVGs at native resolution regardless of zoom level * Added hidpi support on Wayland * Added '-c' argument to specify commands to run at startup * Added 'bind' command to add new binds at runtime * Added 'background' command to change background colour at runtime * Added 'upscaling' command to modify upscaling method at runtime * Added optional argument to close command to specify an index or all images * Added initial_pan option to configure which part of an image is initially focused on * Added support for aliases passing arguments to underlying their commands * Added imv-msg program to send commands to a running instance of imv * Added $imv_pid environment variable * Allowed imv to remain open with no images open * Improved unicode support in overlay * Fixed typo in $imv_slideshow_duration environment variable * Added new crop scaling method, which will zoom in until an image completely fills the window * Fixed a bug where 16-bit greyscale images would not load * Fixed a memory corruption bug in generic list implementation * Fixed several memory leaks v3.1.2 - 2019-06-24 * Fix manpage packaging regression introduced in v3.1.1 v3.1.1 - 2019-06-22 * Adjusted Makefile to improve packaging on BSDs v3.1.0 - 2019-06-17 * Added support for multiple image loaders, allowing imv to use libraries other than FreeImage. This adds support for SVGs, and in the future, other formats as required. * Loaders added for libpng, libtiff, librsvg, libturbojpeg. * Added support for binding multiple commands to a single key * Support for hidpi rendering with SDL >= 2.0.10 * Added -v flag to show version number * Allow 'Escape' to be bound, changing bind abort sequence to 2x'Escape' * Fixed bug where path list from stdin would sometime be truncated * New releases only published under the MIT license, with FreeImage optionally used under the FIPL * Fixed several memory leaks * Miscellaneous code cleanup and documentation fixes v3.0.0 - 2018-05-08 BREAKING CHANGES: * Change a,s,S flags to -s syntax * Make -u take an argument * Dual-license under MIT and GPL * Large refactor of entire codebase * Added config file * Added command system * Added bind system * Improved power consumption by sleeping more opportunistically * Show image scale in window title by default * Prevent scrolling images offscreen * Fix bug where slideshow timer is not reset when an image is closed v2.1.3 - 2016-10-22 * Fix various resource leaks * Fix a bug where imv would to try and catch up on long periods of lost gif playback time if it were suspended for a second or more. * Improve handling of unusual read() return codes * Fix a linking issue with unit tests on some platforms v2.1.2 - 2016-06-15 ------------------- * Fix build issues introduced by v2.1.1 v2.1.1 - 2016-05-12 ------------------- * Fix compatibility with older gcc versions * Fix bug where unchanged files were unnecessarily reloaded * Fix bug where first frame of gif or first slide was changed to quickly * Minor performance improvements v2.1.0 - 2016-04-27 ------------------- * Add `-x` option to quit imv when end of files reached * Honour EXIF rotation information in JPGs * Read file list from stdin continuously * Fix fullscreen bug with i3 * Fix bug where gifs would sometimes not auto-scale when opened * Add commit hash to version string of non-release builds * Fix bug where '+' did not work on foriegn keyboard layouts v2.0.0 - 2016-02-08 ------------------- BREAKING CHANGE: * When no arguments given, read paths from stdin * When '-' given as an argument, read file data from stdin * Use 'Monospace' as default font * Fixed some types of animated gifs that did not display correctly * Added third scaling mode: 'best fit' where images will be scaled down to fit, but *not* scaled up to fill the window * Trimmed output of '-h', making the manpage the single source of truth * Improvements to documentation * Improved portability across platforms * Allow non-integral slideshow times * Improved test coverage * Various improvements to the build - `uninstall` target added to Makefile - `V` option added to Makefile - Respect `PREFIX`, `CFLAGS`, and `LDFLAGS` v1.2.0 - 2015-12-11 ------------------- * Added a text overlay (-d to enable, 'd' to toggle) - Font used is configurable with -e option * Added slideshow support (-t option) * Added -l option to list all open images on exit * Automatically reload images if they change on disk * Moved image loading into background thread to improve UI responsiveness * Auto hide the mouse when appropriate * Added support for upper and lower case hex in the -b option. * Fixed a couple of crashes v1.1.0 - 2015-11-14 ------------------- * Relicensed to GPL * Added support for transparency * Added 'p' hotkey to print the current image's path to stdout * Added '-n' option to start at a specific image * Added '-b' option to set the background * Added '-u' option to set resampling to nearest-neighbour * Changed '-i' option to '-' for reading paths from stdin * Added a .desktop file, for xdg-open support * Fixed compilation on Fedora 22 * Fixed crash when using SDL's software renderer * Fixed bug where single frame gifs would not be rendered * Fixed animated gif playback speed for some unusual gifs * Fixed slow zoom speed on large images * Fixed a memory leak * Added a FreeImage copyright notice for GPL compliance v1.0.0 - 2015-11-11 ------------------- Intitial release imv-4.0.1/CONTRIBUTING000066400000000000000000000010651353152213200141400ustar00rootroot00000000000000 imv Contributing Guidelines =========================== 1) Please keep pull requests rebased onto the latest master. All merges should be simple fast-forward merges. While rebasing, tidy up and merge your commits as you think appropriate. 2) Please ensure that your changes fit with the existing coding style of imv. Strictly no tabs, and 2 spaces of indentation. 3) All contributions are provided under the terms of the MIT license. You retain the copyright ownership of any contributions you make. imv-4.0.1/LICENSE000066400000000000000000000020301353152213200133040ustar00rootroot00000000000000Copyright Harry Jeffery Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. imv-4.0.1/Makefile000066400000000000000000000123211353152213200137430ustar00rootroot00000000000000.PHONY: imv debug clean check install uninstall doc include config.mk PREFIX ?= /usr BINPREFIX ?= $(PREFIX)/bin MANPREFIX ?= $(PREFIX)/share/man DATAPREFIX ?= $(PREFIX)/share CONFIGPREFIX ?= /etc INSTALL_DATA ?= install -m 0644 INSTALL_MAN ?= install -m 0644 INSTALL_PROGRAM ?= install -m 0755 INSTALL_SCRIPT ?= install -m 0755 override CFLAGS += -std=c99 -W -Wall -Wpedantic -Wextra $(shell pkg-config --cflags pangocairo) override CPPFLAGS += -D_XOPEN_SOURCE=700 override LIBS := -lGL -lpthread -lxkbcommon $(shell pkg-config --libs pangocairo) BUILDDIR ?= build TARGET_WAYLAND = $(BUILDDIR)/imv-wayland TARGET_X11 = $(BUILDDIR)/imv-x11 TARGET_MSG = $(BUILDDIR)/imv-msg ifeq ($(WINDOWS),wayland) TARGETS := $(TARGET_WAYLAND) $(TARGET_MSG) else ifeq ($(WINDOWS),x11) TARGETS := $(TARGET_X11) $(TARGET_MSG) else ifeq ($(WINDOWS),all) TARGETS := $(TARGET_WAYLAND) $(TARGET_X11) $(TARGET_MSG) endif SOURCES := src/main.c SOURCES += src/binds.c SOURCES += src/bitmap.c SOURCES += src/canvas.c SOURCES += src/commands.c SOURCES += src/console.c SOURCES += src/image.c SOURCES += src/imv.c SOURCES += src/ini.c SOURCES += src/ipc.c SOURCES += src/ipc_common.c SOURCES += src/keyboard.c SOURCES += src/list.c SOURCES += src/log.c SOURCES += src/navigator.c SOURCES += src/viewport.c WL_SOURCES = src/wl_window.c src/xdg-shell-protocol.c WL_LIBS = -lwayland-client -lwayland-egl -lEGL -lrt X11_SOURCES = src/x11_window.c X11_LIBS = -lX11 -lGL -lGLU -lxcb -lxkbcommon-x11 MSG_SOURCES = src/imv_msg.c src/ipc_common.c MSG_LIBS = # Add backends to build as configured ifeq ($(BACKEND_FREEIMAGE),yes) SOURCES += src/backend_freeimage.c override CPPFLAGS += -DIMV_BACKEND_FREEIMAGE override LIBS += -lfreeimage endif ifeq ($(BACKEND_LIBTIFF),yes) SOURCES += src/backend_libtiff.c override CPPFLAGS += -DIMV_BACKEND_LIBTIFF override LIBS += -ltiff endif ifeq ($(BACKEND_LIBPNG),yes) SOURCES += src/backend_libpng.c override CPPFLAGS += -DIMV_BACKEND_LIBPNG override LIBS += -lpng endif ifeq ($(BACKEND_LIBJPEG),yes) SOURCES += src/backend_libjpeg.c override CPPFLAGS += -DIMV_BACKEND_LIBJPEG override LIBS += -lturbojpeg endif ifeq ($(BACKEND_LIBRSVG),yes) SOURCES += src/backend_librsvg.c override CPPFLAGS += -DIMV_BACKEND_LIBRSVG $(shell pkg-config --cflags librsvg-2.0) override LIBS += $(shell pkg-config --libs librsvg-2.0) endif TEST_SOURCES := test/list.c test/navigator.c OBJECTS := $(patsubst src/%.c,$(BUILDDIR)/%.o,$(SOURCES)) WL_OBJECTS := $(patsubst src/%.c,$(BUILDDIR)/%.o,$(WL_SOURCES)) X11_OBJECTS := $(patsubst src/%.c,$(BUILDDIR)/%.o,$(X11_SOURCES)) MSG_OBJECTS := $(patsubst src/%.c,$(BUILDDIR)/%.o,$(MSG_SOURCES)) TESTS := $(patsubst test/%.c,$(BUILDDIR)/test_%,$(TEST_SOURCES)) VERSION != git describe --dirty --always --tags 2> /dev/null || echo v4.0.0 override CPPFLAGS += -DIMV_VERSION=\""$(VERSION)"\" TFLAGS ?= -g $(CFLAGS) $(CPPFLAGS) $(shell pkg-config --cflags cmocka) TLIBS := $(LIBS) $(shell pkg-config --libs cmocka) imv: $(TARGETS) $(TARGET_WAYLAND): $(OBJECTS) $(WL_OBJECTS) $(CC) -o $@ $^ $(LIBS) $(WL_LIBS) $(LDFLAGS) $(TARGET_X11): $(OBJECTS) $(X11_OBJECTS) $(CC) -o $@ $^ $(LIBS) $(X11_LIBS) $(LDFLAGS) $(TARGET_MSG): $(MSG_OBJECTS) $(CC) -o $@ $^ $(MSG_LIBS) $(LDFLAGS) debug: CFLAGS += -DDEBUG -g -pg debug: $(TARGETS) $(OBJECTS): | $(BUILDDIR) $(BUILDDIR): mkdir -p $(BUILDDIR) $(BUILDDIR)/%.o: src/%.c Makefile $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< $(BUILDDIR)/test_%: test/%.c src/dummy_window.c $(filter-out src/main.c, $(SOURCES)) $(CC) -o $@ -Isrc $(TFLAGS) $^ $(LDFLAGS) $(TLIBS) check: $(BUILDDIR) $(TESTS) for t in $(TESTS); do $$t; done clean: $(RM) -Rf $(BUILDDIR) $(RM) doc/imv.1 doc/imv-msg.1 doc/imv.5 doc: doc/imv.1 doc/imv-msg.1 doc/imv.5 doc/%: doc/%.txt a2x --no-xmllint --doctype manpage --format manpage $< install: $(TARGETS) doc mkdir -p $(DESTDIR)$(BINPREFIX) ifeq ($(WINDOWS),wayland) $(INSTALL_PROGRAM) $(TARGET_WAYLAND) $(DESTDIR)$(BINPREFIX)/imv else ifeq ($(WINDOWS),x11) $(INSTALL_PROGRAM) $(TARGET_X11) $(DESTDIR)$(BINPREFIX)/imv else ifeq ($(WINDOWS),all) $(INSTALL_PROGRAM) $(TARGET_WAYLAND) $(DESTDIR)$(BINPREFIX)/imv-wayland $(INSTALL_PROGRAM) $(TARGET_X11) $(DESTDIR)$(BINPREFIX)/imv-x11 $(INSTALL_SCRIPT) files/imv $(DESTDIR)$(BINPREFIX)/imv endif $(INSTALL_PROGRAM) $(TARGET_MSG) $(DESTDIR)$(BINPREFIX)/imv-msg mkdir -p $(DESTDIR)$(MANPREFIX)/man1 $(INSTALL_MAN) doc/imv.1 $(DESTDIR)$(MANPREFIX)/man1/imv.1 $(INSTALL_MAN) doc/imv-msg.1 $(DESTDIR)$(MANPREFIX)/man1/imv-msg.1 mkdir -p $(DESTDIR)$(MANPREFIX)/man5 $(INSTALL_MAN) doc/imv.5 $(DESTDIR)$(MANPREFIX)/man5/imv.5 mkdir -p $(DESTDIR)$(DATAPREFIX)/applications $(INSTALL_DATA) files/imv.desktop $(DESTDIR)$(DATAPREFIX)/applications/imv.desktop mkdir -p $(DESTDIR)$(CONFIGPREFIX) $(INSTALL_DATA) files/imv_config $(DESTDIR)$(CONFIGPREFIX)/imv_config uninstall: ifeq ($(WINDOWS),all) $(RM) $(DESTDIR)$(BINPREFIX)/imv-wayland $(RM) $(DESTDIR)$(BINPREFIX)/imv-x11 endif $(RM) $(DESTDIR)$(BINPREFIX)/imv $(RM) $(DESTDIR)$(BINPREFIX)/imv-msg $(RM) $(DESTDIR)$(MANPREFIX)/man1/imv.1 $(RM) $(DESTDIR)$(MANPREFIX)/man1/imv-msg.1 $(RM) $(DESTDIR)$(MANPREFIX)/man5/imv.5 $(RM) $(DESTDIR)$(DATAPREFIX)/applications/imv.desktop @echo "$(DESTDIR)$(CONFIGPREFIX)/imv_config has not been removed. Please remove it manually." imv-4.0.1/PACKAGERS.md000066400000000000000000000032071353152213200141300ustar00rootroot00000000000000# Dear Packager, This document is a quick summary of all you need to know to package imv for your favourite operating system. ## 1. Select window systems to support Your options here are Wayland or X11, or both. By default both are included, with a separate binary for each being built. `/usr/bin/imv` will be a script that checks for a Wayland compositor before running the appropriate binary. If you only care about one of these, you can specify to build only one of these in [config.mk](config.mk), in which case only that binary shall be packaged without the need for a launcher script to select between the two. Alternatively, you could provide separate packages for X11 and Wayland that act as alternatives to each other. ## 2. Select backends to include imv supports multiple "backends" in a plugin style architecture. Each backend provides support for different image formats using different underlying libraries. The ones that are right for your operating system depend on which formats you need support for, and your licensing requirements. imv is published under the MIT license, but its backends may have different licensing requirements. You can configure the backends to use in [config.mk](config.mk). Sensible defaults are pre-configured to provide maximum coverage with the least overlap and fewest dependencies. ## 3. $ make && make install Once your backends have been configured and you've confirmed the library each backend uses is installed, you can simply follow the Installation section of the [README](README.md) to build imv. ## 4. Package Package the resulting binary and man pages in your operating system's native package format. imv-4.0.1/README.md000066400000000000000000000131261353152213200135660ustar00rootroot00000000000000[![builds.sr.ht status](https://builds.sr.ht/~exec64/imv.svg)](https://builds.sr.ht/~exec64/imv?) imv - X11/Wayland Image Viewer ============================== `imv` is a command line image viewer intended for use with tiling window managers. Features -------- * Native Wayland and X11 support * Support for dozens of image formats including: * PNG * JPEG * Animated GIFs * SVG * TIFF * Various RAW formats * Photoshop PSD files * Configurable key bindings and behaviour * Highly scriptable with IPC via imv-msg Packages -------- imv is officially packaged by several operating systems: - [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=imv) - [Arch Linux](https://www.archlinux.org/packages/community/x86_64/imv/) - [Debian](https://packages.debian.org/search?searchon=sourcenames&keywords=imv) - [FreeBSD](https://www.freshports.org/graphics/imv/) - [Gentoo](https://wiki.gentoo.org/wiki/Imv) - [OpenBSD](http://openports.se/graphics/imv) - [Ubuntu](https://packages.ubuntu.com/search?keywords=imv) The age of prepackaged versions of imv may vary greatly. Please ensure you are using the latest release before reporting bugs. Example Usage ------------- The following examples are a quick illustration of how you can use imv. For detailed documentation see the man page. # Opening images imv image1.png another_image.jpeg a_directory # Opening a directory recursively imv -r Photos # Opening images via stdin find . -type f -name "*.svg" | imv # Open an image fullscreen imv -f image.jpeg # Viewing images in a random order find . -type f -name "*.png" | shuf | imv # Viewing images from stdin curl http://somesi.te/img.png | imv - # Viewing multiple images from the web curl -Osw '%{filename_effective}\n' 'http://www.example.com/[1-10].jpg' | imv ### Slideshow imv can be used to display slideshows. You can set the number of seconds to show each image for with the `-t` option at start up, or you can configure it at runtime using the `t` and `T` hotkeys to increase and decrease the image display time, respectively. To cycle through a folder of pictures, showing each one for 10 seconds: imv -t 10 ~/Pictures/London #### Custom configuration imv's key bindings can be customised to trigger custom behaviour: [binds] # Delete and then close an open image by pressing 'X' = exec rm "$imv_current_file"; close # Rotate the currently open image by 90 degrees by pressing 'R' = exec mogrify -rotate 90 "$imv_current_file" # Use dmenu as a prompt for tagging the current image u = exec echo "$imv_current_file" >> ~/tags/$(ls ~/tags | dmenu -p "tag") ### Scripting With the default bindings, imv can be used to select images in a pipeline by using the `p` hotkey to print the current image's path to stdout. The `-l` flag can also be used to tell imv to list the remaining paths on exit for a "open set of images, close unwanted ones with `x`, then quit imv to pass the remaining images through" workflow. Key bindings can be customised to run arbitrary shell commands. Environment variables are exported to expose imv's state to scripts run by it. These scripts can in turn modify imv's behaviour by invoking `imv-msg` with `$imv_pid`. For example: #!/usr/bin/bash imv "$@" & imv_pid = $! while true; do # Some custom logic # ... # Close all open files imv-msg $imv_pid close all # Open some new files imv-msg $imv_pid open ~/new_path # Run another script against the currently open file imv-msg $imv_pid exec another-script.sh '$imv_current_file' done Installation ------------ ### Dependencies | Library | Version | Notes | |---------------:|:---------|------------------------------------------------| | pthreads | | Required. | | xkbcommon | | Required. | | pangocairo | | Required. | | X11 | | Optional. Required for X11 support. | | GLU | | Optional. Required for X11 support. | | xcb | | Optional. Required for X11 support. | | xkbcommon-x11 | | Optional. Required for X11 support. | | wayland-client | | Optional. Required for Wayland support. | | wayland-egl | | Optional. Required for Wayland support. | | EGL | | Optional. Required for Wayland support. | | FreeImage | | Optional. Provides PNG, JPEG, TIFF, GIF, etc. | | libtiff | | Optional. Provides TIFF support. | | libpng | | Optional. Provides PNG support. | | libjpeg | | Optional. Provides JPEG support. | | librsvg | >=v2.44 | Optional. Provides SVG support. | Dependencies are determined by which backends and window systems are enabled when building `imv`. You can find a summary of which backends are available and control which ones `imv` is built with in [config.mk](config.mk) $ $EDITOR config.mk $ make # make install Macro `PREFIX` controls installation prefix. If more control over installation paths is required, macros `BINPREFIX`, `MANPREFIX` and `DATAPREFIX` are available. Eg. to install `imv` to home directory, run: $ BINPREFIX=~/bin PREFIX=~/.local make install License ------- `imv`'s source is published under the terms of the [MIT](LICENSE) license. imv-4.0.1/config.mk000066400000000000000000000016351353152213200141070ustar00rootroot00000000000000# Configure window system to use #Choices: # all - Build both, determine which to use at runtime # wayland - Only provide Wayland support # x11 - Only provide X11 support WINDOWS=all # Configure available backends: # FreeImage http://freeimage.sourceforge.net # provides: png, jpg, animated gif, raw, psd, bmp, tiff, webp, etc. # depends: libjpeg, openexr, openjpeg2, libwebp, libraw, jxrlib # license: FIPL v1.0 BACKEND_FREEIMAGE=yes # libtiff # provides: tiff # dependws: libjpeg zlib xz zstd # license: MIT BACKEND_LIBTIFF=no # libpng http://www.libpng.org/pub/png/libpng.html # provides: png # depends: zlib # license: libpng license BACKEND_LIBPNG=no # libjpeg-turbo https://libjpeg-turbo.org/ # provides: jpeg # depends: none # license: modified bsd BACKEND_LIBJPEG=no # librsvg https://wiki.gnome.org/Projects/LibRsvg # provides: svg # depends: gdk-pixbuf2 pango libcroco # license: LGPL BACKEND_LIBRSVG=yes imv-4.0.1/doc/000077500000000000000000000000001353152213200130515ustar00rootroot00000000000000imv-4.0.1/doc/imv-msg.1.txt000066400000000000000000000011371353152213200153320ustar00rootroot00000000000000///// vim:set ts=4 sw=4 tw=82 noet: ///// :quotes.~: imv-msg (1) =========== Name ---- imv-msg - Utility for sending commands to a running imv instance Description ----------- imv-msg is a tool to simplify the sending of commands to a running instance of imv. Given an instance's pid it opens the corresponding unix socket and sends the provided command. Synopsis -------- 'imv-msg' Authors ------- imv-msg is written and maintained by Harry Jeffery Full source code and other information can be found at . See Also -------- **imv**(1) imv-4.0.1/doc/imv.1.txt000066400000000000000000000152161353152213200145510ustar00rootroot00000000000000///// vim:set ts=4 sw=4 tw=82 noet: ///// :quotes.~: imv (1) ======= Name ---- imv - Image viewer for X11 and Wayland Description ----------- imv is an image viewer for X11 and Wayland, aimed at users of tiling window managers. It supports a wide variety of image file formats, including animated gif files. imv will automatically reload the current image, if it is changed on disk. Synopsis -------- 'imv' [options] [paths...] Options ------- *-h*:: Show help message and quit. *-v*:: Show version and quit. *-b* :: Set the background colour. Can either be a 6-digit hexadecimal colour code or 'checks' to show a chequered background. *-c* :: Specify a command to be run on launch, after the configuration has been loaded. Can be used to configure custom keys with the bind command. This option can be used multiple times. Commands are run in the order that they have been passed to imv. *-d*:: Start with overlay visible. *-f*:: Start fullscreen. *-l*:: List open files to stdout at exit. *-n* :: Start with the given path, or index selected. *-r*:: Load directories recursively. *-s* :: Set scaling mode to use. 'none' will show each image at its actual size. 'shrink' will scale down the image to fit inside the window. 'full' will both scale up and scale down the image to fit perfectly inside the window. Defaults to 'full'. *-t* :: Start in slideshow mode, with each image shown for the given number of seconds. *-u* :: Set upscaling method used by imv. *-x*:: Disable looping of input paths. Commands -------- Commands can be entered by pressing *:*. imv supports the following commands: *quit*:: Quit imv. Aliased to 'q'. *pan* :: Pan the view by the given amounts. *next* :: Move forwards by a given number of images. Aliased to 'n' *prev* :: Move backwards by a given number of images. Aliased to 'p' *goto* :: Select an image by index. '1' is the first image, '2' the second, etc. The last image can be indexed as '-1', the second last as '-2'. Aliased to 'g'. *zoom* :: Zoom into the image by the given amount. Negative values zoom out. 'actual' resets the zoom to 100%, showing the image at its actual size. Aliased to 'z'. *open* [-r] :: Add the given paths to the list of open images. If the '-r' option is specified, do so recursively. Shell expansions may be used. Aliased to 'o'. *close* [index|'all']:: Close the currently selected image, or the image at the given index, or all images. *fullscreen*:: Toggle fullscreen. *overlay*:: Toggle the overlay. *exec* :: Execute a shell command. imv provides various environment variables to the command executed. These are documented in the 'Environment Variables' section. *center*:: Recenter the selected image. *reset*:: Reset the view, centering the image and using the current scaling mode to rescale it. *next_frame*:: If an animated gif is currently being displayed, load the next frame. *toggle_playing*:: Toggle playback of the current image if it is an animated gif. *scaling* :: Set the current scaling mode. Setting the mode to 'next' advances it to the next mode in the list. *upscaling* :: Set the current upscaling method. Setting the method to 'next' advances it to the next method in the list. *slideshow* <+amount|-amount|duration>:: Increase or decrease the slideshow duration by the given amount in seconds, or set its duration directly. Aliased to 'ss'. *background* :: Set the background color. 'checks' for a chequerboard pattern, or specify a 6-digit hexadecimal color code. Aliased to 'bg'. *bind* :: Binds an action to a set of key inputs. Uses the same syntax as the config file, but without an equals sign between the keys and the commands. For more information on syntax, see **imv**(5). Default Binds ------------- imv comes with several binds configured by default *q*:: Quit *Left arrow,*:: Previous image *Right arrow*:: Next Image *gg*:: Go to first image *G*:: Go to last image *j*:: Pan down *k*:: Pan up *h*:: Pan left *l*:: Pan right *x*:: Close current image *f*:: Toggle fullscreen *d*:: Toggle overlay *p*:: Print current image to stdout *Up arrow*:: Zoom in *Down arrow*:: Zoom out *i*:: Zoom in *o*:: Zoom out *+*:: Zoom in *-*:: Zoom out *c*:: Center image *s*:: Next scaling mode *S*:: Next upscaling mode *a*:: Zoom to actual size *r*:: Reset zoom and pan *.*:: Next frame (for animations) *Space*:: Pause/play animations *t*:: Start slideshow/increase delay by 1 second *T*:: Stop slideshow/decrease delay by 1 second Configuration ------------- The path to a config file can be given via the *$imv_config* environment variable. If not found, imv will search for it in the following locations: - $XDG_CONFIG_HOME/imv/config (recommended) - $HOME/.config/imv/config - $HOME/.imv_config - $HOME/.imv/config - /usr/local/etc/imv_config - /etc/imv_config A default config file is shipped with imv into /etc/imv_config For documentation on the config file format, see **imv**(5). Environment Variables --------------------- When imv executes a shell command, it provides a number of environment variables, exposing imv's state. These environment variables are also available when customising the window's title, or the overlay text. *$imv_pid*:: The pid of this instance of imv. Useful for running imv-msg in scripts. *$imv_current_file*:: Path of currently selected image. *$imv_scaling_mode*:: Name of the current scaling mode. *$imv_loading*:: 1 if a new image is loading, 0 otherwise. *$imv_current_index*:: Index of current image, from 1-N. *$imv_file_count*:: Total number of files. *$imv_width*:: Width of the current image. *$imv_height*:: Height of the current image. *$imv_scale*:: Scaling of current image in percent. *$imv_slideshow_duration*:: Number of seconds each image is shown for. *$imv_slideshow_elapsed*:: How long the current image has been shown for. IPC --- imv can accept commands from another process over a unix socket. Each instance of imv will open a unix socket named '$XDG_RUNTIME_DIR/imv-$PID.sock'. If $XDG_RUNTIME_DIR is undefined, the socket is placed into '/tmp/' instead. The **imv-msg**(1) utility is provided to simpliy this from shell scripts. Authors ------- imv is written and maintained by Harry Jeffery with contributions from other developers. Full source code and other information can be found at . See Also -------- **imv**(5) **imv-msg**(1) imv-4.0.1/doc/imv.5.txt000066400000000000000000000105551353152213200145560ustar00rootroot00000000000000///// vim:set ts=4 sw=4 tw=82 noet: ///// :quotes.~: imv (5) ======= Name ---- imv - imv configuration file Description ----------- imv can be customised with this configuration file, changing its default behaviour, key bindings, and appearance. The imv configuration file is an ini-style file, with multiple 'key = value' settings, separated into several '[section]'s. Options ------- The *[options]* section accepts the following settings: *background* = :: Set the background in imv. Can either be a 6-digit hexadecimal colour code, or 'checks' for a chequered background. Defaults to '000000' *fullscreen* = :: Start imv fullscreen. Defaults to 'false'. *width* = :: Initial width of the imv window. Defaults to 1280. *height* = :: Initial height of the imv window. Defaults to 720. *initial_pan* = :: Initial pan/focus position factor of the opened images. A value of 50 represents the middle point of the image (50%). Defaults to '50 50' *list_files_at_exit* = :: Print open files to stdout at exit, each on a separate line. Defaults to 'false'. *loop_input* = :: Return to first image after viewing the last one. Defaults to 'true'. *overlay* = :: Start with the overlay visible. Defaults to 'false'. *overlay_font* = :: Use the specified font in the overlay. Defaults to 'Monospace:24'. *overlay_text* = :: Use the given text as the overlay's text. The provided text is shell expanded, so the output of commands can be used: '$(ls)' as can environment variables, including the ones accessible to imv's 'exec' command. *recursively* = :: Load input paths recursively. Defaults to 'false'. *scaling_mode* = :: Set scaling mode to use. 'none' will show each image at its actual size. 'shrink' will scale down the image to fit inside the window. 'full' will both scale up and scale down the image to fit perfectly inside the window. 'crop' willl scale and crop the image to fill the window. Defaults to 'full'. *slideshow_duration* = :: Start imv in slideshow mode, and set the amount of time to show each image for in seconds. Defaults to '0', i.e. no slideshow. *suppress_default_binds* = :: Disable imv's built-in binds so they don't conflict with custom ones. Defaults to 'false'. *title_text* = :: Use the given text as the window's title. The provided text is shell expanded, so the output of commands can be used: '$(ls)' as can environment variables, including the ones accessible to imv's 'exec' command. *upscaling_method* = :: Use the specified method to upscale images. Defaults to 'linear'. Aliases ------- The *[aliases]* section allows aliases to be added for imv's build in commands. For example, 'x = close' would add a 'x' command that simply executes the 'close' command. Any arguments provided to an alias are appended to the command configured by the alias. Binds ----- The *[binds]* section allows custom key bindings to be added to imv. Binds are in the format 'key combination = command'. A key combination can consist of multiple keys in succession. Multiple commands for a single key combination can be defined by separating each command with a ';'. Single and double quotes are honoured, as is escaping with a backslash, to allow the proper quoting of shell commands. Single keys such as 'q' are just that: 'q = quit' will bind the 'q' key to the 'quit' command. Modifier keys can be specified by prefixing them: 'Ctrl+q', 'Meta+f', 'Shift+G'. If multiple modifier keys are desired, they are specified in the order 'Ctrl+Meta+Shift'. When a key's name is more than a single character, or a modifier is used it must be wrapped in '<' and '>', for example: ''. Multiple keys in succession can be specified by listing them in order: 'gg = goto 0' will bind two presses of the 'g' key to jump to the first image, and 'p = exec echo hi' will bind the key sequence of 'Ctrl+a' followed by 'p' to executing the shell command 'echo hi'. Many keys, such as '<', and '>' have special names. On X11, these can be easily found with the xev(1) command. For example, '!' is called 'exclam', '<' is called 'less', '>' is called 'greater'. A complete list of keysyms can also be found on most systems with the 'dumpkeys -l' command. **imv**(1) imv-4.0.1/files/000077500000000000000000000000001353152213200134065ustar00rootroot00000000000000imv-4.0.1/files/imv000077500000000000000000000001441353152213200141260ustar00rootroot00000000000000#!/bin/sh if [ -n "${WAYLAND_DISPLAY}" ]; then exec imv-wayland "$@" else exec imv-x11 "$@" fi imv-4.0.1/files/imv.desktop000066400000000000000000000007611353152213200156000ustar00rootroot00000000000000[Desktop Entry] Name=imv GenericName=Image viewer GenericName[en_US]=Image viewer Comment=Fast freeimage-based Image Viewer Exec=imv %F Terminal=false Type=Application Categories=Graphics;2DGraphics;Viewer; MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/pjpeg;image/png;image/tiff;image/x-bmp;image/x-pcx;image/x-png;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-tga;image/x-xbitmap; Name[en_US]=imv Icon=multimedia-photo-viewer imv-4.0.1/files/imv_config000066400000000000000000000016431353152213200154550ustar00rootroot00000000000000# Default config for imv [options] # Suppress built-in key bindings, and specify them explicitly in this # config file. suppress_default_binds = true [aliases] # Define aliases here. Any arguments passed to an alias are appended to the # command. # alias = command to run [binds] # Deinfe some key bindings q = quit y = exec echo working! # Image navigation = prev = prev = next = next gg = goto 0 = goto -1 # Panning j = pan 0 -50 k = pan 0 50 h = pan 50 0 l = pan -50 0 # Zooming = zoom 1 = zoom 1 i = zoom 1 = zoom -1 = zoom -1 o = zoom -1 # Other commands x = close f = fullscreen d = overlay p = exec echo $imv_current_file c = center s = scaling next = upscaling next a = zoom actual r = reset # Gif playback = next_frame = toggle_playing # Slideshow control t = slideshow +1 = slideshow -1 imv-4.0.1/src/000077500000000000000000000000001353152213200130735ustar00rootroot00000000000000imv-4.0.1/src/backend.h000066400000000000000000000022621353152213200146350ustar00rootroot00000000000000#ifndef IMV_BACKEND_H #define IMV_BACKEND_H #include struct imv_source; enum backend_result { BACKEND_SUCCESS = 0, /* successful load */ BACKEND_BAD_PATH = 1, /* no such file, bad permissions, etc. */ BACKEND_UNSUPPORTED = 2, /* unsupported file format */ }; /* A backend is responsible for taking a path, or a raw data pointer, and * converting that into an imv_source. Each backend may be powered by a * different image library and support different image formats. */ struct imv_backend { /* Name of the backend, for debug and user informational purposes */ const char *name; /* Information about the backend, displayed by help dialog */ const char *description; /* Official website address */ const char *website; /* License the backend is used under */ const char *license; /* Input: path to open * Output: initialises the imv_source instance passed in */ enum backend_result (*open_path)(const char *path, struct imv_source **src); /* Input: pointer to data and length of data * Output: initialises the imv_source instance passed in */ enum backend_result (*open_memory)(void *data, size_t len, struct imv_source **src); }; #endif imv-4.0.1/src/backend_freeimage.c000066400000000000000000000236631353152213200166440ustar00rootroot00000000000000#include "backend.h" #include "source.h" #include "log.h" #include #include #include #include #include struct private { FIMEMORY *memory; FREE_IMAGE_FORMAT format; FIMULTIBITMAP *multibitmap; FIBITMAP *last_frame; }; static void source_free(struct imv_source *src) { pthread_mutex_lock(&src->busy); free(src->name); src->name = NULL; struct private *private = src->private; if (private->memory) { FreeImage_CloseMemory(private->memory); private->memory = NULL; } if (private->multibitmap) { FreeImage_CloseMultiBitmap(private->multibitmap, 0); private->multibitmap = NULL; } if (private->last_frame) { FreeImage_Unload(private->last_frame); private->last_frame = NULL; } free(private); src->private = NULL; pthread_mutex_unlock(&src->busy); pthread_mutex_destroy(&src->busy); free(src); } static struct imv_image *to_image(FIBITMAP *in_bmp) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = FreeImage_GetWidth(in_bmp); bmp->height = FreeImage_GetHeight(in_bmp); bmp->format = IMV_ARGB; bmp->data = malloc(4 * bmp->width * bmp->height); FreeImage_ConvertToRawBits(bmp->data, in_bmp, 4 * bmp->width, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, TRUE); struct imv_image *image = imv_image_create_from_bitmap(bmp); return image; } static FIBITMAP *normalise_bitmap(FIBITMAP *input) { FIBITMAP *output = NULL; switch (FreeImage_GetImageType(input)) { case FIT_RGB16: case FIT_RGBA16: case FIT_RGBF: case FIT_RGBAF: output = FreeImage_ConvertTo32Bits(input); FreeImage_Unload(input); break; case FIT_UINT16: case FIT_INT16: case FIT_UINT32: case FIT_INT32: case FIT_FLOAT: case FIT_DOUBLE: case FIT_COMPLEX: output = FreeImage_ConvertTo8Bits(input); FreeImage_Unload(input); break; case FIT_BITMAP: default: output = input; } imv_log(IMV_DEBUG, "freeimage: bitmap normalised to 32 bits: before=%p after=%p\n", input, output); return output; } static void report_error(struct imv_source *src, const char *error) { imv_log(IMV_ERROR, "freeimage: %s\n", error); assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = NULL; msg.error = error; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void send_bitmap(struct imv_source *src, FIBITMAP *fibitmap, int frametime) { imv_log(IMV_DEBUG, "freeimage: returning bitmap\n"); assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = to_image(fibitmap); msg.frametime = frametime; msg.error = NULL; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void *first_frame(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { return NULL; } imv_log(IMV_DEBUG, "freeimage: first_frame called\n"); FIBITMAP *bmp = NULL; struct private *private = src->private; int frametime = 0; if (private->format == FIF_GIF) { if (src->name) { private->multibitmap = FreeImage_OpenMultiBitmap(FIF_GIF, src->name, /* don't create file */ 0, /* read only */ 1, /* keep in memory */ 1, /* flags */ GIF_LOAD256); } else if (private->memory) { private->multibitmap = FreeImage_LoadMultiBitmapFromMemory(FIF_GIF, private->memory, /* flags */ GIF_LOAD256); } else { report_error(src, "src->name and private->memory both NULL"); return NULL; } if (!private->multibitmap) { report_error(src, "first frame already loaded"); return NULL; } FIBITMAP *frame = FreeImage_LockPage(private->multibitmap, 0); src->num_frames = FreeImage_GetPageCount(private->multibitmap); /* Get duration of first frame */ FITAG *tag = NULL; FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag); if(FreeImage_GetTagValue(tag)) { frametime = *(int*)FreeImage_GetTagValue(tag); } else { frametime = 100; /* default value for gifs */ } bmp = FreeImage_ConvertTo24Bits(frame); FreeImage_UnlockPage(private->multibitmap, frame, 0); } else { /* not a gif */ src->num_frames = 1; int flags = (private->format == FIF_JPEG) ? JPEG_EXIFROTATE : 0; FIBITMAP *fibitmap = NULL; if (src->name) { fibitmap = FreeImage_Load(private->format, src->name, flags); } else if (private->memory) { fibitmap = FreeImage_LoadFromMemory(private->format, private->memory, flags); } if (!fibitmap) { report_error(src, "FreeImage_Load returned NULL"); return NULL; } bmp = normalise_bitmap(fibitmap); } src->width = FreeImage_GetWidth(bmp); src->height = FreeImage_GetHeight(bmp); private->last_frame = bmp; send_bitmap(src, bmp, frametime); return NULL; } static void *next_frame(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { return NULL; } struct private *private = src->private; if (src->num_frames == 1) { send_bitmap(src, private->last_frame, 0); return NULL; } FITAG *tag = NULL; char disposal_method = 0; int frametime = 0; short top = 0; short left = 0; FIBITMAP *frame = FreeImage_LockPage(private->multibitmap, src->next_frame); FIBITMAP *frame32 = FreeImage_ConvertTo32Bits(frame); /* First frame is always going to use the raw frame */ if(src->next_frame > 0) { FreeImage_GetMetadata(FIMD_ANIMATION, frame, "DisposalMethod", &tag); if(FreeImage_GetTagValue(tag)) { disposal_method = *(char*)FreeImage_GetTagValue(tag); } } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameLeft", &tag); if(FreeImage_GetTagValue(tag)) { left = *(short*)FreeImage_GetTagValue(tag); } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTop", &tag); if(FreeImage_GetTagValue(tag)) { top = *(short*)FreeImage_GetTagValue(tag); } FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag); if(FreeImage_GetTagValue(tag)) { frametime = *(int*)FreeImage_GetTagValue(tag); } /* some gifs don't provide a frame time at all */ if(frametime == 0) { frametime = 100; } FreeImage_UnlockPage(private->multibitmap, frame, 0); /* If this frame is inset, we need to expand it for compositing */ if(src->width != (int)FreeImage_GetWidth(frame32) || src->height != (int)FreeImage_GetHeight(frame32)) { FIBITMAP *expanded = FreeImage_Allocate(src->width, src->height, 32, 0,0,0); FreeImage_Paste(expanded, frame32, left, top, 255); FreeImage_Unload(frame32); frame32 = expanded; } switch(disposal_method) { case 0: /* nothing specified, fall through to compositing */ case 1: /* composite over previous frame */ if(private->last_frame && src->next_frame > 0) { FIBITMAP *bg_frame = FreeImage_ConvertTo24Bits(private->last_frame); FreeImage_Unload(private->last_frame); FIBITMAP *comp = FreeImage_Composite(frame32, 1, NULL, bg_frame); FreeImage_Unload(bg_frame); FreeImage_Unload(frame32); private->last_frame = comp; } else { /* No previous frame, just render directly */ if(private->last_frame) { FreeImage_Unload(private->last_frame); } private->last_frame = frame32; } break; case 2: /* TODO - set to background, composite over that */ if(private->last_frame) { FreeImage_Unload(private->last_frame); } private->last_frame = frame32; break; case 3: /* TODO - restore to previous content */ if(private->last_frame) { FreeImage_Unload(private->last_frame); } private->last_frame = frame32; break; } src->next_frame = (src->next_frame + 1) % src->num_frames; send_bitmap(src, private->last_frame, frametime); return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) { imv_log(IMV_DEBUG, "freeimage: open_path(%s)\n", path); FREE_IMAGE_FORMAT fmt = FreeImage_GetFileType(path, 0); if (fmt == FIF_UNKNOWN) { imv_log(IMV_DEBUG, "freeimage: unknown file format\n"); return BACKEND_UNSUPPORTED; } struct private *private = calloc(1, sizeof(struct private)); private->format = fmt; struct imv_source *source = calloc(1, sizeof *source); source->name = strdup(path); source->width = 0; source->height = 0; source->num_frames = 0; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &first_frame; source->load_next_frame = &next_frame; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = private; *src = source; return BACKEND_SUCCESS; } static enum backend_result open_memory(void *data, size_t len, struct imv_source **src) { FIMEMORY *fmem = FreeImage_OpenMemory(data, len); FREE_IMAGE_FORMAT fmt = FreeImage_GetFileTypeFromMemory(fmem, 0); if (fmt == FIF_UNKNOWN) { FreeImage_CloseMemory(fmem); return BACKEND_UNSUPPORTED; } struct private *private = calloc(1, sizeof(struct private)); private->format = fmt; private->memory = fmem; struct imv_source *source = calloc(1, sizeof *source); source->name = NULL; source->width = 0; source->height = 0; source->num_frames = 0; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &first_frame; source->load_next_frame = &next_frame; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = private; *src = source; return BACKEND_SUCCESS; } const struct imv_backend imv_backend_freeimage = { .name = "FreeImage", .description = "Open source image library supporting a large number of formats", .website = "http://freeimage.sourceforge.net/", .license = "FreeImage Public License v1.0", .open_path = &open_path, .open_memory = &open_memory, }; imv-4.0.1/src/backend_libjpeg.c000066400000000000000000000117771353152213200163370ustar00rootroot00000000000000#include "backend.h" #include "source.h" #include #include #include #include #include #include #include #include struct private { int fd; void *data; size_t len; tjhandle jpeg; }; static void source_free(struct imv_source *src) { pthread_mutex_lock(&src->busy); free(src->name); src->name = NULL; struct private *private = src->private; tjDestroy(private->jpeg); if (private->fd >= 0) { munmap(private->data, private->len); close(private->fd); } else { free(private->data); } private->data = NULL; free(src->private); src->private = NULL; pthread_mutex_unlock(&src->busy); pthread_mutex_destroy(&src->busy); free(src); } static struct imv_image *to_image(int width, int height, void *bitmap) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = width; bmp->height = height; bmp->format = IMV_ABGR; bmp->data = bitmap; struct imv_image *image = imv_image_create_from_bitmap(bmp); return image; } static void report_error(struct imv_source *src) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void send_bitmap(struct imv_source *src, void *bitmap) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = to_image(src->width, src->height, bitmap); msg.frametime = 0; msg.error = NULL; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void *load_image(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { return NULL; } struct private *private = src->private; void *bitmap = malloc(src->height * src->width * 4); int rcode = tjDecompress2(private->jpeg, private->data, private->len, bitmap, src->width, 0, src->height, TJPF_RGBA, TJFLAG_FASTDCT); if (rcode) { free(bitmap); report_error(src); return NULL; } send_bitmap(src, bitmap); return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) { struct private private; private.fd = open(path, O_RDONLY); if (private.fd < 0) { return BACKEND_BAD_PATH; } off_t len = lseek(private.fd, 0, SEEK_END); if (len < 0) { close(private.fd); return BACKEND_BAD_PATH; } private.len = len; private.data = mmap(NULL, private.len, PROT_READ, MAP_PRIVATE, private.fd, 0); if (private.data == MAP_FAILED || !private.data) { close(private.fd); return BACKEND_BAD_PATH; } private.jpeg = tjInitDecompress(); if (!private.jpeg) { munmap(private.data, private.len); close(private.fd); return BACKEND_UNSUPPORTED; } int width, height; int rcode = tjDecompressHeader(private.jpeg, private.data, private.len, &width, &height); if (rcode) { tjDestroy(private.jpeg); munmap(private.data, private.len); close(private.fd); return BACKEND_UNSUPPORTED; } struct imv_source *source = calloc(1, sizeof *source); source->name = strdup(path); source->width = width; source->height = height; source->num_frames = 1; source->next_frame = 1; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &load_image; source->load_next_frame = NULL; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = malloc(sizeof private); memcpy(source->private, &private, sizeof private); *src = source; return BACKEND_SUCCESS; } static enum backend_result open_memory(void *data, size_t len, struct imv_source **src) { struct private private; private.fd = -1; private.data = data; private.len = len; private.jpeg = tjInitDecompress(); if (!private.jpeg) { return BACKEND_UNSUPPORTED; } int width, height; int rcode = tjDecompressHeader(private.jpeg, private.data, private.len, &width, &height); if (rcode) { tjDestroy(private.jpeg); return BACKEND_UNSUPPORTED; } struct imv_source *source = calloc(1, sizeof *source); source->name = strdup("-"); source->width = width; source->height = height; source->num_frames = 1; source->next_frame = 1; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &load_image; source->load_next_frame = NULL; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = malloc(sizeof private); memcpy(source->private, &private, sizeof private); *src = source; return BACKEND_SUCCESS; } const struct imv_backend imv_backend_libjpeg = { .name = "libjpeg-turbo", .description = "Fast JPEG codec based on libjpeg. " "This software is based in part on the work " "of the Independent JPEG Group.", .website = "https://libjpeg-turbo.org/", .license = "The Modified BSD License", .open_path = &open_path, .open_memory = &open_memory, }; imv-4.0.1/src/backend_libpng.c000066400000000000000000000121151353152213200161610ustar00rootroot00000000000000#include "backend.h" #include "source.h" #include "log.h" #include #include #include #include #include struct private { FILE *file; png_structp png; png_infop info; }; static void source_free(struct imv_source *src) { pthread_mutex_lock(&src->busy); free(src->name); src->name = NULL; struct private *private = src->private; png_destroy_read_struct(&private->png, &private->info, NULL); if (private->file) { fclose(private->file); } free(src->private); src->private = NULL; pthread_mutex_unlock(&src->busy); pthread_mutex_destroy(&src->busy); free(src); } static struct imv_image *to_image(int width, int height, void *bitmap) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = width; bmp->height = height; bmp->format = IMV_ABGR; bmp->data = bitmap; struct imv_image *image = imv_image_create_from_bitmap(bmp); return image; } static void report_error(struct imv_source *src) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void send_bitmap(struct imv_source *src, void *bitmap) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = to_image(src->width, src->height, bitmap); msg.frametime = 0; msg.error = NULL; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void *load_image(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { return NULL; } struct private *private = src->private; if (setjmp(png_jmpbuf(private->png))) { report_error(src); return NULL; } png_bytep *rows = malloc(sizeof(png_bytep) * src->height); size_t row_len = png_get_rowbytes(private->png, private->info); rows[0] = malloc(src->height * row_len); for (int y = 1; y < src->height; ++y) { rows[y] = rows[0] + row_len * y; } if (setjmp(png_jmpbuf(private->png))) { free(rows[0]); free(rows); report_error(src); return NULL; } png_read_image(private->png, rows); void *bmp = rows[0]; free(rows); fclose(private->file); private->file = NULL; send_bitmap(src, bmp); return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) { unsigned char header[8]; FILE *f = fopen(path, "rb"); if (!f) { return BACKEND_BAD_PATH; } fread(header, 1, sizeof header, f); if (png_sig_cmp(header, 0, sizeof header)) { fclose(f); return BACKEND_UNSUPPORTED; } struct private *private = malloc(sizeof *private); private->file = f; private->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!private->png) { fclose(private->file); free(private); return BACKEND_UNSUPPORTED; } private->info = png_create_info_struct(private->png); if (!private->info) { png_destroy_read_struct(&private->png, NULL, NULL); fclose(private->file); free(private); return BACKEND_UNSUPPORTED; } if (setjmp(png_jmpbuf(private->png))) { png_destroy_read_struct(&private->png, &private->info, NULL); fclose(private->file); free(private); return BACKEND_UNSUPPORTED; } png_init_io(private->png, private->file); png_set_sig_bytes(private->png, sizeof header); png_read_info(private->png, private->info); /* Tell libpng to give us a consistent output format */ png_set_gray_to_rgb(private->png); png_set_filler(private->png, 0xff, PNG_FILLER_AFTER); png_set_strip_16(private->png); png_set_expand(private->png); png_set_packing(private->png); png_read_update_info(private->png, private->info); imv_log(IMV_DEBUG, "libpng: info width=%d height=%d bit_depth=%d color_type=%d\n", png_get_image_width(private->png, private->info), png_get_image_height(private->png, private->info), png_get_bit_depth(private->png, private->info), png_get_color_type(private->png, private->info)); struct imv_source *source = calloc(1, sizeof *source); source->name = strdup(path); if (setjmp(png_jmpbuf(private->png))) { free(source->name); free(source); png_destroy_read_struct(&private->png, &private->info, NULL); fclose(private->file); free(private); return BACKEND_UNSUPPORTED; } source->width = png_get_image_width(private->png, private->info); source->height = png_get_image_height(private->png, private->info); source->num_frames = 1; source->next_frame = 1; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &load_image; source->load_next_frame = NULL; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = private; *src = source; return BACKEND_SUCCESS; } const struct imv_backend imv_backend_libpng = { .name = "libpng", .description = "The official PNG reference implementation", .website = "http://www.libpng.org/pub/png/libpng.html", .license = "The libpng license", .open_path = &open_path, }; imv-4.0.1/src/backend_librsvg.c000066400000000000000000000101421353152213200163540ustar00rootroot00000000000000#include "backend.h" #include "source.h" #include #include #include #include /* Some systems like GNU/Hurd don't define PATH_MAX */ #ifndef PATH_MAX #define PATH_MAX 4096 #endif struct private { void *data; size_t len; }; static void source_free(struct imv_source *src) { pthread_mutex_lock(&src->busy); free(src->name); src->name = NULL; struct private *private = src->private; free(private); src->private = NULL; pthread_mutex_unlock(&src->busy); pthread_mutex_destroy(&src->busy); free(src); } static void report_error(struct imv_source *src) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void send_svg(struct imv_source *src, RsvgHandle *handle) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = imv_image_create_from_svg(handle); msg.frametime = 0; msg.error = NULL; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void *load_image(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { return NULL; } RsvgHandle *handle = NULL; GError *error = NULL; struct private *private = src->private; if (private->data) { handle = rsvg_handle_new_from_data(private->data, private->len, &error); } else { char path[PATH_MAX+8]; snprintf(path, sizeof path, "file://%s", src->name); handle = rsvg_handle_new_from_file(path, &error); } if (!handle) { report_error(src); return NULL; } RsvgDimensionData dim; rsvg_handle_get_dimensions(handle, &dim); src->width = dim.width; src->height = dim.height; send_svg(src, handle); return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) { /* Look for an tag near the start of the file */ char header[4096]; FILE *f = fopen(path, "rb"); if (!f) { return BACKEND_BAD_PATH; } fread(header, 1, sizeof header, f); fclose(f); header[(sizeof header) - 1] = 0; if (!strstr(header, "data = NULL; private->len = 0; struct imv_source *source = calloc(1, sizeof *source); source->name = strdup(path); source->width = 1024; source->height = 1024; source->num_frames = 1; source->next_frame = 1; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &load_image; source->load_next_frame = NULL; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = private; *src = source; return BACKEND_SUCCESS; } static enum backend_result open_memory(void *data, size_t len, struct imv_source **src) { /* Look for an tag near the start of the file */ char header[4096]; size_t header_len = sizeof header; if (header_len > len) { header_len = len; } memcpy(header, data, header_len); header[header_len - 1] = 0; if (!strstr(header, "data = data; private->len = len; struct imv_source *source = calloc(1, sizeof *source); source->name = strdup("-"); source->width = 1024; source->height = 1024; source->num_frames = 1; source->next_frame = 1; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &load_image; source->load_next_frame = NULL; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = private; *src = source; return BACKEND_SUCCESS; } const struct imv_backend imv_backend_librsvg = { .name = "libRSVG", .description = "SVG library developed by GNOME", .website = "https://wiki.gnome.org/Projects/LibRsvg", .license = "Lesser GNU Public License", .open_path = &open_path, .open_memory = &open_memory, }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/backend_libtiff.c���������������������������������������������������������������������0000664�0000000�0000000�00000013032�13531522132�0016324�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "backend.h" #include "source.h" #include #include #include #include #include #include #include #include struct private { TIFF *tiff; void *data; size_t pos, len; }; static tsize_t mem_read(thandle_t data, tdata_t buffer, tsize_t len) { struct private *private = (struct private*)data; memcpy(buffer, (char*)private->data + private->pos, len); private->pos += len; return len; } static tsize_t mem_write(thandle_t data, tdata_t buffer, tsize_t len) { struct private *private = (struct private*)data; memcpy((char*)private->data + private->pos, buffer, len); private->pos += len; return len; } static int mem_close(thandle_t data) { (void)data; return 0; } static toff_t mem_seek(thandle_t data, toff_t pos, int whence) { struct private *private = (struct private*)data; if (whence == SEEK_SET) { private->pos = pos; } else if (whence == SEEK_CUR) { private->pos += pos; } else if (whence == SEEK_END) { private->pos = private->len + pos; } else { return -1; } return private->pos; } static toff_t mem_size(thandle_t data) { struct private *private = (struct private*)data; return private->len; } static void source_free(struct imv_source *src) { pthread_mutex_lock(&src->busy); free(src->name); src->name = NULL; struct private *private = src->private; TIFFClose(private->tiff); private->tiff = NULL; free(src->private); src->private = NULL; pthread_mutex_unlock(&src->busy); pthread_mutex_destroy(&src->busy); free(src); } static struct imv_image *to_image(int width, int height, void *bitmap) { struct imv_bitmap *bmp = malloc(sizeof *bmp); bmp->width = width; bmp->height = height; bmp->format = IMV_ABGR; bmp->data = bitmap; struct imv_image *image = imv_image_create_from_bitmap(bmp); return image; } static void report_error(struct imv_source *src) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = NULL; msg.error = "Internal error"; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void send_bitmap(struct imv_source *src, void *bitmap) { assert(src->callback); struct imv_source_message msg; msg.source = src; msg.user_data = src->user_data; msg.image = to_image(src->width, src->height, bitmap); msg.frametime = 0; msg.error = NULL; pthread_mutex_unlock(&src->busy); src->callback(&msg); } static void *load_image(struct imv_source *src) { /* Don't run if this source is already active */ if (pthread_mutex_trylock(&src->busy)) { return NULL; } struct private *private = src->private; /* libtiff suggests using their own allocation routines to support systems * with segmented memory. I have no desire to support that, so I'm just * going to use vanilla malloc/free. Systems where that isn't acceptable * don't have upstream support from imv. */ void *bitmap = malloc(src->height * src->width * 4); int rcode = TIFFReadRGBAImageOriented(private->tiff, src->width, src->height, bitmap, ORIENTATION_TOPLEFT, 0); /* 1 = success, unlike the rest of *nix */ if (rcode != 1) { free(bitmap); report_error(src); return NULL; } send_bitmap(src, bitmap); return NULL; } static enum backend_result open_path(const char *path, struct imv_source **src) { struct private private; private.tiff = TIFFOpen(path, "r"); if (!private.tiff) { /* Header is read, so no BAD_PATH check here */ return BACKEND_UNSUPPORTED; } unsigned int width, height; TIFFGetField(private.tiff, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(private.tiff, TIFFTAG_IMAGELENGTH, &height); struct imv_source *source = calloc(1, sizeof *source); source->name = strdup(path); source->width = width; source->height = height; source->num_frames = 1; source->next_frame = 1; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &load_image; source->load_next_frame = NULL; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = malloc(sizeof private); memcpy(source->private, &private, sizeof private); *src = source; return BACKEND_SUCCESS; } static enum backend_result open_memory(void *data, size_t len, struct imv_source **src) { struct private *private = malloc(sizeof *private); private->data = data; private->len = len; private->pos = 0; private->tiff = TIFFClientOpen("-", "rm", (thandle_t)private, &mem_read, &mem_write, &mem_seek, &mem_close, &mem_size, NULL, NULL); if (!private->tiff) { /* Header is read, so no BAD_PATH check here */ free(private); return BACKEND_UNSUPPORTED; } unsigned int width, height; TIFFGetField(private->tiff, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(private->tiff, TIFFTAG_IMAGELENGTH, &height); struct imv_source *source = calloc(1, sizeof *source); source->name = strdup("-"); source->width = width; source->height = height; source->num_frames = 1; source->next_frame = 1; pthread_mutex_init(&source->busy, NULL); source->load_first_frame = &load_image; source->load_next_frame = NULL; source->free = &source_free; source->callback = NULL; source->user_data = NULL; source->private = private; *src = source; return BACKEND_SUCCESS; } const struct imv_backend imv_backend_libtiff = { .name = "libtiff", .description = "The de-facto tiff library", .website = "http://www.libtiff.org/", .license = "MIT", .open_path = &open_path, .open_memory = &open_memory, }; ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/binds.c�������������������������������������������������������������������������������0000664�0000000�0000000�00000016445�13531522132�0014350�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "binds.h" #include "list.h" #include #include struct bind_node { char *key; /* input key to reach this node */ struct list *commands; /* commands to run for this node, or NULL if not leaf node */ struct list *suffixes; /* list of bind_node* suffixes, or NULL if leaf node */ }; struct imv_binds { struct bind_node bind_tree; struct list *keys; bool aborting_sequence; }; static int compare_node_key(const void* item, const void *key) { const struct bind_node *node = item; const char *keystr = key; return strcmp(node->key, keystr); } static void init_bind_node(struct bind_node *bn) { bn->key = NULL; bn->commands = NULL; bn->suffixes = list_create(); } static void destroy_bind_node(struct bind_node *bn) { for(size_t i = 0; i < bn->suffixes->len; ++i) { destroy_bind_node(bn->suffixes->items[i]); } free(bn->key); if(bn->commands) { list_deep_free(bn->commands); } list_deep_free(bn->suffixes); } struct imv_binds *imv_binds_create(void) { struct imv_binds *binds = calloc(1, sizeof *binds); init_bind_node(&binds->bind_tree); binds->keys = list_create(); return binds; } void imv_binds_free(struct imv_binds *binds) { destroy_bind_node(&binds->bind_tree); list_deep_free(binds->keys); free(binds); } enum bind_result imv_binds_add(struct imv_binds *binds, const struct list *keys, const char *command) { if(!command) { return BIND_INVALID_COMMAND; } if(!keys) { return BIND_INVALID_KEYS; } /* Prepare our return code */ int result = BIND_SUCCESS; /* Traverse the trie */ struct bind_node *node = &binds->bind_tree; for(size_t i = 0; i < keys->len; ++i) { /* If we've reached a node that already has a command, there's a conflict */ if(node->commands) { result = BIND_INVALID_COMMAND; break; } /* Find / create a child with the current key */ struct bind_node *next_node = NULL; int child_index = list_find(node->suffixes, &compare_node_key, keys->items[i]); if(child_index == -1) { /* Create our new node */ next_node = malloc(sizeof *next_node); init_bind_node(next_node); next_node->key = strdup(keys->items[i]); list_append(node->suffixes, next_node); } else { next_node = node->suffixes->items[child_index]; } /* We've now found the correct node for this key */ /* Check if the node has a command */ if(next_node->commands) { if(i + 1 < keys->len) { /* If we're not at the end, it's a conflict */ result = BIND_CONFLICTS; break; } else { /* Otherwise we just need to append a new command to the existing bind. */ list_append(next_node->commands, strdup(command)); result = BIND_SUCCESS; break; } } if(i + 1 == keys->len) { /* this is the last key part, try to insert command */ /* but first, make sure there's no children */ if(next_node->suffixes->len > 0) { result = BIND_CONFLICTS; } else { next_node->commands = list_create(); list_append(next_node->commands, strdup(command)); } } else { /* Otherwise, move down the trie */ node = next_node; } } return result; } void imv_binds_clear(struct imv_binds *binds) { destroy_bind_node(&binds->bind_tree); init_bind_node(&binds->bind_tree); } void imv_binds_clear_key(struct imv_binds *binds, const struct list *keys) { struct bind_node *node = &binds->bind_tree; for(size_t i = 0; i < keys->len; ++i) { /* Traverse the trie to find the right node for the input keys */ int child_index = list_find(node->suffixes, &compare_node_key, keys->items[i]); if(child_index == -1) { /* No such node, no more work to do */ return; } else { node = node->suffixes->items[child_index]; } } /* We've now found the correct node for the input */ /* Clear the commands for this node */ if(node->commands) { list_deep_free(node->commands); node->commands = NULL; } } enum lookup_result { LOOKUP_PARTIAL, LOOKUP_INVALID, LOOKUP_MATCH, }; static enum lookup_result bind_lookup(struct bind_node *node, struct list *keys, struct list **out_list) { for(size_t part = 0; part < keys->len; ++part) { const char* cur_key = keys->items[part]; int found = 0; for(size_t i = 0; i < node->suffixes->len; ++i) { struct bind_node* cur_node = node->suffixes->items[i]; if(strcmp(cur_node->key, cur_key) == 0) { node = node->suffixes->items[i]; found = 1; break; } } if(!found) { return LOOKUP_INVALID; } } if(node->commands) { *out_list = node->commands; return LOOKUP_MATCH; } return LOOKUP_PARTIAL; } void imv_bind_clear_input(struct imv_binds *binds) { list_deep_free(binds->keys); binds->keys = list_create(); } struct list *imv_bind_handle_event(struct imv_binds *binds, const char *event) { /* If the user hits Escape twice in a row, treat that as backtracking out * of the current key sequence. */ if (!strcmp("Escape", event)) { if (binds->aborting_sequence) { /* The last thing they hit was escape, so abort the current entry */ binds->aborting_sequence = false; imv_bind_clear_input(binds); return NULL; } else { /* Start the aborting sequence */ binds->aborting_sequence = true; } } else { /* They didn't hit escape, if they were in an abort sequence, cancel it */ binds->aborting_sequence = false; } list_append(binds->keys, strdup(event)); struct list *commands = NULL; enum lookup_result result = bind_lookup(&binds->bind_tree, binds->keys, &commands); if(result == LOOKUP_PARTIAL) { return NULL; } else if(result == LOOKUP_MATCH) { imv_bind_clear_input(binds); return commands; } else if(result == LOOKUP_INVALID) { imv_bind_clear_input(binds); return NULL; } /* Should not happen */ imv_bind_clear_input(binds); return NULL; } struct list *imv_bind_parse_keys(const char *keys) { struct list *list = list_create(); /* Iterate through the string, breaking it into its parts */ while(*keys) { if(*keys == '<') { /* Keyname block, need to extract the name, and convert it */ const char *end = keys; while(*end && *end != '>') { ++end; } if(*end == '>') { /* We've got a block. Check if it's a valid special key name. */ const size_t key_len = end - keys; char *key = malloc(key_len); memcpy(key, keys + 1 /* skip the '<' */, key_len - 1); key[key_len - 1] = 0; list_append(list, key); keys = end + 1; } else { /* A block that didn't have a closing '<'. Abort. */ list_deep_free(list); return NULL; } } else { /* Just a regular character */ char *item = malloc(2); item[0] = *keys; item[1] = 0; list_append(list, item); ++keys; } } return list; } size_t imv_bind_print_keylist(const struct list *keys, char *buf, size_t len) { size_t printed = 0; /* iterate through all the keys, wrapping them in '<' and '>' if needed */ for(size_t i = 0; i < keys->len; ++i) { const char *key = keys->items[i]; const char *format = strlen(key) > 1 ? "<%s>" : "%s"; printed += snprintf(buf, len - printed, format, key); } return printed; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/binds.h�������������������������������������������������������������������������������0000664�0000000�0000000�00000002371�13531522132�0014346�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_BINDS_H #define IMV_BINDS_H #include struct imv_binds; struct list; enum bind_result { BIND_SUCCESS, BIND_INVALID_KEYS, BIND_INVALID_COMMAND, BIND_CONFLICTS, }; /* Create an imv_binds instance */ struct imv_binds *imv_binds_create(void); /* Clean up an imv_binds instance */ void imv_binds_free(struct imv_binds *binds); /* Create a key binding */ enum bind_result imv_binds_add(struct imv_binds *binds, const struct list *keys, const char *cmd); /* Remove all key bindings */ void imv_binds_clear(struct imv_binds *binds); /* Clear all bindings for a key*/ void imv_binds_clear_key(struct imv_binds *binds, const struct list *keys); /* Fetch the list of keys pressed so far */ const struct list *imv_bind_input_buffer(struct imv_binds *binds); /* Abort the current input key sequence */ void imv_bind_clear_input(struct imv_binds *binds); /* Handle an input event, if a bind is triggered, return its command */ struct list *imv_bind_handle_event(struct imv_binds *binds, const char *event); /* Convert a string (such as from a config) to a key list */ struct list *imv_bind_parse_keys(const char *keys); /* Convert a key list to a string */ size_t imv_bind_print_keylist(const struct list *keys, char *buf, size_t len); #endif �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/bitmap.c������������������������������������������������������������������������������0000664�0000000�0000000�00000000760�13531522132�0014516�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "bitmap.h" #include #include struct imv_bitmap *imv_bitmap_clone(struct imv_bitmap *bmp) { struct imv_bitmap *copy = malloc(sizeof *copy); const size_t num_bytes = 4 * bmp->width * bmp->height; copy->width = bmp->width; copy->height = bmp->height; copy->format = bmp->format; copy->data = malloc(num_bytes); memcpy(copy->data, bmp->data, num_bytes); return copy; } void imv_bitmap_free(struct imv_bitmap *bmp) { free(bmp->data); free(bmp); } ����������������imv-4.0.1/src/bitmap.h������������������������������������������������������������������������������0000664�0000000�0000000�00000000554�13531522132�0014524�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_BITMAP_H #define IMV_BITMAP_H enum imv_pixelformat { IMV_ARGB, IMV_ABGR, }; struct imv_bitmap { int width; int height; enum imv_pixelformat format; unsigned char *data; }; /* Copy an imv_bitmap */ struct imv_bitmap *imv_bitmap_clone(struct imv_bitmap *bmp); /* Clean up a bitmap */ void imv_bitmap_free(struct imv_bitmap *bmp); #endif ����������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/canvas.c������������������������������������������������������������������������������0000664�0000000�0000000�00000020300�13531522132�0014505�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "canvas.h" #include "image.h" #include "log.h" #include #include #include #include #include #include #include #ifdef IMV_BACKEND_LIBRSVG #include #endif struct imv_canvas { cairo_surface_t *surface; cairo_t *cairo; PangoFontDescription *font; GLuint texture; int width; int height; struct { struct imv_bitmap *bitmap; GLuint texture; } cache; }; struct imv_canvas *imv_canvas_create(int width, int height) { struct imv_canvas *canvas = calloc(1, sizeof *canvas); canvas->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); assert(canvas->surface); canvas->cairo = cairo_create(canvas->surface); assert(canvas->cairo); canvas->font = pango_font_description_new(); assert(canvas->font); glGenTextures(1, &canvas->texture); assert(canvas->texture); canvas->width = width; canvas->height = height; return canvas; } void imv_canvas_free(struct imv_canvas *canvas) { if (!canvas) { return; } pango_font_description_free(canvas->font); canvas->font = NULL; cairo_destroy(canvas->cairo); canvas->cairo = NULL; cairo_surface_destroy(canvas->surface); canvas->surface = NULL; glDeleteTextures(1, &canvas->texture); if (canvas->cache.texture) { glDeleteTextures(1, &canvas->cache.texture); } free(canvas); } void imv_canvas_resize(struct imv_canvas *canvas, int width, int height) { cairo_destroy(canvas->cairo); cairo_surface_destroy(canvas->surface); canvas->width = width; canvas->height = height; canvas->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, canvas->width, canvas->height); assert(canvas->surface); canvas->cairo = cairo_create(canvas->surface); assert(canvas->cairo); } void imv_canvas_clear(struct imv_canvas *canvas) { cairo_save(canvas->cairo); cairo_set_source_rgba(canvas->cairo, 0, 0, 0, 0); cairo_set_operator(canvas->cairo, CAIRO_OPERATOR_SOURCE); cairo_paint(canvas->cairo); cairo_restore(canvas->cairo); } void imv_canvas_color(struct imv_canvas *canvas, float r, float g, float b, float a) { cairo_set_source_rgba(canvas->cairo, r, g, b, a); } void imv_canvas_fill_rectangle(struct imv_canvas *canvas, int x, int y, int width, int height) { cairo_rectangle(canvas->cairo, x, y, width, height); cairo_fill(canvas->cairo); } void imv_canvas_fill(struct imv_canvas *canvas) { cairo_rectangle(canvas->cairo, 0, 0, canvas->width, canvas->height); cairo_fill(canvas->cairo); } void imv_canvas_fill_checkers(struct imv_canvas *canvas, int size) { for (int x = 0; x < canvas->width; x += size) { for (int y = 0; y < canvas->height; y += size) { float color = ((x/size + y/size) % 2 == 0) ? 0.25 : 0.75; cairo_set_source_rgba(canvas->cairo, color, color, color, 1); cairo_rectangle(canvas->cairo, x, y, size, size); cairo_fill(canvas->cairo); } } } void imv_canvas_font(struct imv_canvas *canvas, const char *name, int size) { pango_font_description_set_family(canvas->font, name); pango_font_description_set_weight(canvas->font, PANGO_WEIGHT_NORMAL); pango_font_description_set_absolute_size(canvas->font, size * PANGO_SCALE); } void imv_canvas_printf(struct imv_canvas *canvas, int x, int y, const char *fmt, ...) { char line[1024]; va_list args; va_start(args, fmt); vsnprintf(line, sizeof line, fmt, args); PangoLayout *layout = pango_cairo_create_layout(canvas->cairo); pango_layout_set_font_description(layout, canvas->font); pango_layout_set_text(layout, line, -1); cairo_move_to(canvas->cairo, x, y); pango_cairo_show_layout(canvas->cairo, layout); g_object_unref(layout); va_end(args); } void imv_canvas_draw(struct imv_canvas *canvas) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glPushMatrix(); glOrtho(0.0, 1.0, 1.0, 0.0, 0.0, 10.0); void *data = cairo_image_surface_get_data(canvas->surface); glEnable(GL_TEXTURE_RECTANGLE); glBindTexture(GL_TEXTURE_RECTANGLE, canvas->texture); glPixelStorei(GL_UNPACK_ROW_LENGTH, canvas->width); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, canvas->width, canvas->height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBegin(GL_TRIANGLE_FAN); glTexCoord2i(0, 0); glVertex2i(0.0, 0.0); glTexCoord2i(canvas->width, 0); glVertex2i(1.0, 0.0); glTexCoord2i(canvas->width, canvas->height); glVertex2i(1.0, 1.0); glTexCoord2i(0, canvas->height); glVertex2i(0.0, 1.0); glEnd(); glDisable(GL_BLEND); glBindTexture(GL_TEXTURE_RECTANGLE, 0); glDisable(GL_TEXTURE_RECTANGLE); glPopMatrix(); } struct imv_bitmap *imv_image_get_bitmap(const struct imv_image *image); static int convert_pixelformat(enum imv_pixelformat fmt) { /* opengl uses RGBA order, not ARGB, so we get it to * flip the bytes around so ARGB -> BGRA */ if (fmt == IMV_ARGB) { return GL_BGRA; } else if (fmt == IMV_ABGR) { return GL_RGBA; } else { imv_log(IMV_WARNING, "Unknown pixel format. Defaulting to ARGB\n"); return GL_BGRA; } } static void draw_bitmap(struct imv_canvas *canvas, struct imv_bitmap *bitmap, int bx, int by, double scale, enum upscaling_method upscaling_method) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glPushMatrix(); glOrtho(0.0, viewport[2], viewport[3], 0.0, 0.0, 10.0); if (!canvas->cache.texture) { glGenTextures(1, &canvas->cache.texture); } const int format = convert_pixelformat(bitmap->format); glBindTexture(GL_TEXTURE_RECTANGLE, canvas->cache.texture); if (canvas->cache.bitmap != bitmap) { glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glPixelStorei(GL_UNPACK_ROW_LENGTH, bitmap->width); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, bitmap->width, bitmap->height, 0, format, GL_UNSIGNED_INT_8_8_8_8_REV, bitmap->data); } canvas->cache.bitmap = bitmap; glEnable(GL_TEXTURE_RECTANGLE); if (upscaling_method == UPSCALING_LINEAR) { glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else if (upscaling_method == UPSCALING_NEAREST_NEIGHBOUR) { glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } else { imv_log(IMV_ERROR, "Unknown upscaling method: %d\n", upscaling_method); abort(); } const int left = bx; const int top = by; const int right = left + bitmap->width * scale; const int bottom = top + bitmap->height * scale; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBegin(GL_TRIANGLE_FAN); glTexCoord2i(0, 0); glVertex2i(left, top); glTexCoord2i(bitmap->width, 0); glVertex2i(right, top); glTexCoord2i(bitmap->width, bitmap->height); glVertex2i(right, bottom); glTexCoord2i(0, bitmap->height); glVertex2i(left, bottom); glEnd(); glDisable(GL_BLEND); glBindTexture(GL_TEXTURE_RECTANGLE, 0); glDisable(GL_TEXTURE_RECTANGLE); glPopMatrix(); } #ifdef IMV_BACKEND_LIBRSVG RsvgHandle *imv_image_get_svg(const struct imv_image *image); #endif void imv_canvas_draw_image(struct imv_canvas *canvas, struct imv_image *image, int x, int y, double scale, enum upscaling_method upscaling_method) { struct imv_bitmap *bitmap = imv_image_get_bitmap(image); if (bitmap) { draw_bitmap(canvas, bitmap, x, y, scale, upscaling_method); return; } #ifdef IMV_BACKEND_LIBRSVG RsvgHandle *svg = imv_image_get_svg(image); if (svg) { imv_canvas_clear(canvas); cairo_translate(canvas->cairo, x, y); cairo_scale(canvas->cairo, scale, scale); rsvg_handle_render_cairo(svg, canvas->cairo); cairo_identity_matrix(canvas->cairo); imv_canvas_draw(canvas); return; } #endif } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/canvas.h������������������������������������������������������������������������������0000664�0000000�0000000�00000003254�13531522132�0014523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_CANVAS_H #define IMV_CANVAS_H struct imv_canvas; struct imv_image; enum upscaling_method { UPSCALING_LINEAR, UPSCALING_NEAREST_NEIGHBOUR, UPSCALING_METHOD_COUNT, }; /* Create a canvas instance */ struct imv_canvas *imv_canvas_create(int width, int height); /* Clean up a canvas */ void imv_canvas_free(struct imv_canvas *canvas); /* Set the buffer size of the canvas */ void imv_canvas_resize(struct imv_canvas *canvas, int width, int height); /* Blank the canvas to be empty and transparent */ void imv_canvas_clear(struct imv_canvas *canvas); /* Set the current drawing color of the canvas */ void imv_canvas_color(struct imv_canvas *canvas, float r, float g, float b, float a); /* Fill a rectangle on the canvas with the current color */ void imv_canvas_fill_rectangle(struct imv_canvas *canvas, int x, int y, int width, int height); /* Fill the whole canvas with the current color */ void imv_canvas_fill(struct imv_canvas *canvas); /* Fill the whole canvas with a chequerboard pattern */ void imv_canvas_fill_checkers(struct imv_canvas *canvas, int size); /* Select the font to draw text with */ void imv_canvas_font(struct imv_canvas *canvas, const char *name, int size); /* Draw some text on the canvas */ void imv_canvas_printf(struct imv_canvas *canvas, int x, int y, const char *fmt, ...); /* Blit the canvas to the current OpenGL framebuffer */ void imv_canvas_draw(struct imv_canvas *canvas); /* Blit the given image to the current OpenGL framebuffer */ void imv_canvas_draw_image(struct imv_canvas *canvas, struct imv_image *image, int x, int y, double scale, enum upscaling_method upscaling_method); #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/commands.c����������������������������������������������������������������������������0000664�0000000�0000000�00000005102�13531522132�0015036�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "commands.h" #include "list.h" #include #include #include struct imv_commands { struct list *command_list; }; struct command { char* command; void (*handler)(struct list *args, const char *argstr, void *data); char* alias; }; struct imv_commands *imv_commands_create(void) { struct imv_commands *cmds = malloc(sizeof *cmds); cmds->command_list = list_create(); return cmds; } void imv_commands_free(struct imv_commands *cmds) { for(size_t i = 0; i < cmds->command_list->len; ++i) { struct command *cmd = cmds->command_list->items[i]; free(cmd->command); if(cmd->alias) { free(cmd->alias); } free(cmd); } list_free(cmds->command_list); free(cmds); } void imv_command_register(struct imv_commands *cmds, const char *command, void (*handler)(struct list*, const char*, void*)) { struct command *cmd = malloc(sizeof *cmd); cmd->command = strdup(command); cmd->handler = handler; cmd->alias = NULL; list_append(cmds->command_list, cmd); } void imv_command_alias(struct imv_commands *cmds, const char *command, const char *alias) { struct command *cmd = malloc(sizeof *cmd); cmd->command = strdup(command); cmd->handler = NULL; cmd->alias = strdup(alias); list_append(cmds->command_list, cmd); } int imv_command_exec(struct imv_commands *cmds, const char *command, void *data) { struct list *args = list_from_string(command, ' '); int ret = 1; if(args->len > 0) { for(size_t i = 0; i < cmds->command_list->len; ++i) { struct command *cmd = cmds->command_list->items[i]; if(!strcmp(cmd->command, args->items[0])) { if(cmd->handler) { /* argstr = all args as a single string */ const char *argstr = command + strlen(cmd->command) + 1; cmd->handler(args, argstr, data); ret = 0; } else if(cmd->alias) { char *new_args = list_to_string(args, " ", 1); size_t cmd_len = strlen(cmd->alias) + 1 + strlen(new_args) + 1; char *new_cmd = malloc(cmd_len); snprintf(new_cmd, cmd_len, "%s %s", cmd->alias, new_args); ret = imv_command_exec(cmds, new_cmd, data); free(new_args); free(new_cmd); } break; } } } list_deep_free(args); return ret; } int imv_command_exec_list(struct imv_commands *cmds, struct list *commands, void *data) { int ret = 0; for(size_t i = 0; i < commands->len; ++i) { const char *command = commands->items[i]; ret += imv_command_exec(cmds, command, data); } return ret; } /* vim:set ts=2 sts=2 sw=2 et: */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/commands.h����������������������������������������������������������������������������0000664�0000000�0000000�00000001777�13531522132�0015061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef COMMANDS_H #define COMMANDS_H struct list; struct imv_commands; /* Create an imv_commands instance */ struct imv_commands *imv_commands_create(void); /* Cleans up an imv_commands instance */ void imv_commands_free(struct imv_commands *cmds); /* Register a new command. When a command is executed, the appropriate handler * is called, with a void* for passing context. */ void imv_command_register(struct imv_commands *cmds, const char *command, void (*handler)(struct list*, const char*, void*)); /* Add a command alias. Any arguments provided when invoking an alias are * appended to the arguments being passed to the command. */ void imv_command_alias(struct imv_commands *cmds, const char *command, const char *alias); /* Execute a single command */ int imv_command_exec(struct imv_commands *cmds, const char *command, void *data); /* Execute a list of commands */ int imv_command_exec_list(struct imv_commands *cmds, struct list *commands, void *data); #endif /* vim:set ts=2 sts=2 sw=2 et: */ �imv-4.0.1/src/console.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000004174�13531522132�0014707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "console.h" #include #include struct imv_console { char *buffer; size_t buffer_len; imv_console_callback callback; void *callback_data; }; struct imv_console *imv_console_create(void) { struct imv_console *console = calloc(1, sizeof *console); console->buffer_len = 1024; return console; } void imv_console_free(struct imv_console *console) { if (console->buffer) { free(console->buffer); console->buffer = NULL; } free(console); } void imv_console_set_command_callback(struct imv_console *console, imv_console_callback callback, void *data) { console->callback = callback; console->callback_data = data; } bool imv_console_is_active(struct imv_console *console) { return console->buffer != NULL; } void imv_console_activate(struct imv_console *console) { if (console->buffer) { return; } console->buffer = calloc(1, console->buffer_len); } void imv_console_input(struct imv_console *console, const char *text) { if (!console || !console->buffer) { return; } strncat(console->buffer, text, console->buffer_len - 1); } bool imv_console_key(struct imv_console *console, const char *key) { if (!console || !console->buffer) { return false; } if (!strcmp("Escape", key)) { free(console->buffer); console->buffer = NULL; return true; } if (!strcmp("Return", key)) { if (console->callback) { console->callback(console->buffer, console->callback_data); } free(console->buffer); console->buffer = NULL; return true; } if (!strcmp("BackSpace", key)) { const size_t len = strlen(console->buffer); if (len > 0) { console->buffer[len - 1] = '\0'; } return true; } return false; } const char *imv_console_prompt(struct imv_console *console) { return console->buffer; } const char *imv_console_backlog(struct imv_console *console) { (void)console; return NULL; } void imv_console_write(struct imv_console *console, const char *text) { (void)console; (void)text; } void imv_console_add_completion(struct imv_console *console, const char *template) { (void)console; (void)template; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/console.h�����������������������������������������������������������������������������0000664�0000000�0000000�00000003175�13531522132�0014714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_CONSOLE #define IMV_CONSOLE #include struct imv_console; /* Create a console instance */ struct imv_console *imv_console_create(void); /* Clean up a console */ void imv_console_free(struct imv_console *console); /* Set the callback to be invoked when a command to run by the console */ typedef void (*imv_console_callback)(const char *command, void *data); void imv_console_set_command_callback(struct imv_console *console, imv_console_callback callback, void *data); /* Returns true if console is still active (i.e. user hasn't hit enter/escape yet */ bool imv_console_is_active(struct imv_console *console); /* Mark console as activated until user exits or submits a command */ void imv_console_activate(struct imv_console *console); /* Pass text input to the console */ void imv_console_input(struct imv_console *console, const char *text); /* Pass a key input to the console. Returns true if consumed. If so, * do not also send input text to the console. */ bool imv_console_key(struct imv_console *console, const char *key); /* What is the console prompt's current text? */ const char *imv_console_prompt(struct imv_console *console); /* What is the output history of the console? */ const char *imv_console_backlog(struct imv_console *console); /* Write some text to the console's backlog */ void imv_console_write(struct imv_console *console, const char *text); /* Add a tab-completion template. If the users hits tab, the rest of the * command will be suggested. If multiple matches, tab will cycle through them. */ void imv_console_add_completion(struct imv_console *console, const char *template); #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/dummy_window.c������������������������������������������������������������������������0000664�0000000�0000000�00000003171�13531522132�0015763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "window.h" #include #include struct imv_window *imv_window_create(int w, int h, const char *title) { (void)w; (void)h; (void)title; return NULL; } void imv_window_free(struct imv_window *window) { (void)window; } void imv_window_clear(struct imv_window *window, unsigned char r, unsigned char g, unsigned char b) { (void)window; (void)r; (void)g; (void)b; } void imv_window_get_size(struct imv_window *window, int *w, int *h) { (void)window; (void)w; (void)h; } void imv_window_get_framebuffer_size(struct imv_window *window, int *w, int *h) { (void)window; (void)w; (void)h; } void imv_window_set_title(struct imv_window *window, const char *title) { (void)window; (void)title; } bool imv_window_is_fullscreen(struct imv_window *window) { (void)window; return false; } void imv_window_set_fullscreen(struct imv_window *window, bool fullscreen) { (void)window; (void)fullscreen; } bool imv_window_get_mouse_button(struct imv_window *window, int button) { (void)window; (void)button; return false; } void imv_window_get_mouse_position(struct imv_window *window, double *x, double *y) { (void)window; (void)x; (void)y; } void imv_window_present(struct imv_window *window) { (void)window; } void imv_window_wait_for_event(struct imv_window *window, double timeout) { (void)window; (void)timeout; } void imv_window_push_event(struct imv_window *window, struct imv_event *e) { (void)window; (void)e; } void imv_window_pump_events(struct imv_window *window, imv_event_handler handler, void *data) { (void)window; (void)handler; (void)data; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/image.c�������������������������������������������������������������������������������0000664�0000000�0000000�00000002747�13531522132�0014333�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "image.h" #include "bitmap.h" struct imv_image { int width; int height; struct imv_bitmap *bitmap; #ifdef IMV_BACKEND_LIBRSVG RsvgHandle *svg; #endif }; struct imv_image *imv_image_create_from_bitmap(struct imv_bitmap *bmp) { struct imv_image *image = calloc(1, sizeof *image); image->width = bmp->width; image->height = bmp->height; image->bitmap = bmp; return image; } #ifdef IMV_BACKEND_LIBRSVG struct imv_image *imv_image_create_from_svg(RsvgHandle *handle) { struct imv_image *image = calloc(1, sizeof *image); image->svg = handle; RsvgDimensionData dim; rsvg_handle_get_dimensions(handle, &dim); image->width = dim.width; image->height = dim.height; return image; } #endif void imv_image_free(struct imv_image *image) { if (!image) { return; } if (image->bitmap) { imv_bitmap_free(image->bitmap); } #ifdef IMV_BACKEND_LIBRSVG if (image->svg) { GError *error = NULL; rsvg_handle_close(image->svg, &error); } #endif free(image); } int imv_image_width(const struct imv_image *image) { return image ? image->width : 0; } int imv_image_height(const struct imv_image *image) { return image ? image->height : 0; } /* Non-public functions, only used by imv_canvas */ struct imv_bitmap *imv_image_get_bitmap(const struct imv_image *image) { return image->bitmap; } #ifdef IMV_BACKEND_LIBRSVG RsvgHandle *imv_image_get_svg(const struct imv_image *image) { return image->svg; } #endif /* vim:set ts=2 sts=2 sw=2 et: */ �������������������������imv-4.0.1/src/image.h�������������������������������������������������������������������������������0000664�0000000�0000000�00000001133�13531522132�0014324�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_IMAGE_H #define IMV_IMAGE_H #include "bitmap.h" #ifdef IMV_BACKEND_LIBRSVG #include #endif struct imv_image; struct imv_image *imv_image_create_from_bitmap(struct imv_bitmap *bmp); #ifdef IMV_BACKEND_LIBRSVG struct imv_image *imv_image_create_from_svg(RsvgHandle *handle); #endif /* Cleans up an imv_image instance */ void imv_image_free(struct imv_image *image); /* Get the image width */ int imv_image_width(const struct imv_image *image); /* Get the image height */ int imv_image_height(const struct imv_image *image); #endif /* vim:set ts=2 sts=2 sw=2 et: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/imv.c���������������������������������������������������������������������������������0000664�0000000�0000000�00000141700�13531522132�0014035�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "imv.h" #include #include #include #include #include #include #include #include #include #include "backend.h" #include "binds.h" #include "canvas.h" #include "commands.h" #include "console.h" #include "image.h" #include "ini.h" #include "ipc.h" #include "list.h" #include "log.h" #include "navigator.h" #include "source.h" #include "viewport.h" #include "window.h" /* Some systems like GNU/Hurd don't define PATH_MAX */ #ifndef PATH_MAX #define PATH_MAX 4096 #endif static const char *scaling_label[] = { "actual size", "shrink to fit", "scale to fit", "crop" }; enum background_type { BACKGROUND_SOLID, BACKGROUND_CHEQUERED, BACKGROUND_TYPE_COUNT }; enum internal_event_type { NEW_IMAGE, BAD_IMAGE, NEW_PATH, COMMAND }; struct internal_event { enum internal_event_type type; union { struct { struct imv_image *image; int frametime; bool is_new_image; } new_image; struct { char *error; } bad_image; struct { char *path; } new_path; struct { char *text; } command; } data; }; struct imv { /* set to true to trigger clean exit */ bool quit; /* indicates a new image is being loaded */ bool loading; /* initial fullscreen state */ bool start_fullscreen; /* initial window dimensions */ int initial_width; int initial_height; /* display some textual info onscreen */ bool overlay_enabled; /* method for scaling up images: interpolate or nearest neighbour */ enum upscaling_method upscaling_method; /* dirty state flags */ bool need_redraw; bool need_rescale; /* traverse sub-directories for more images */ bool recursive_load; /* 'next' on the last image goes back to the first */ bool loop_input; /* print all paths to stdout on clean exit */ bool list_files_at_exit; /* read paths from stdin, as opposed to image data */ bool paths_from_stdin; /* scale up / down images to match window, or actual size */ enum scaling_mode scaling_mode; /* initial pan factor when opening new images */ bool custom_start_pan; double initial_pan_x, initial_pan_y; struct { /* show a solid background colour, or chequerboard pattern */ enum background_type type; /* the aforementioned background colour */ struct { unsigned char r, g, b; } color; } background; /* slideshow state tracking */ struct { double duration; double elapsed; } slideshow; struct { /* for animated images, the getTime() time to display the next frame */ double due; /* how long the next frame to be put onscreen should be displayed for */ double duration; /* the next frame of an animated image, pre-fetched */ struct imv_image *image; /* force the next frame to load, even if early */ bool force_next_frame; } next_frame; struct imv_image *current_image; /* overlay font */ struct { char *name; int size; } font; /* if specified by user, the path of the first image to display */ char *starting_path; /* list of startup commands to be run on launch, after loading the config */ struct list *startup_commands; /* the user-specified format strings for the overlay and window title */ char *title_text; char *overlay_text; /* imv subsystems */ struct imv_binds *binds; struct imv_navigator *navigator; struct list *backends; struct imv_source *current_source; struct imv_source *last_source; struct imv_commands *commands; struct imv_console *console; struct imv_ipc *ipc; struct imv_viewport *view; struct imv_canvas *canvas; struct imv_window *window; /* if reading an image from stdin, this is the buffer for it */ void *stdin_image_data; size_t stdin_image_data_len; }; static void command_quit(struct list *args, const char *argstr, void *data); static void command_pan(struct list *args, const char *argstr, void *data); static void command_next(struct list *args, const char *argstr, void *data); static void command_prev(struct list *args, const char *argstr, void *data); static void command_goto(struct list *args, const char *argstr, void *data); static void command_zoom(struct list *args, const char *argstr, void *data); static void command_open(struct list *args, const char *argstr, void *data); static void command_close(struct list *args, const char *argstr, void *data); static void command_fullscreen(struct list *args, const char *argstr, void *data); static void command_overlay(struct list *args, const char *argstr, void *data); static void command_exec(struct list *args, const char *argstr, void *data); static void command_center(struct list *args, const char *argstr, void *data); static void command_reset(struct list *args, const char *argstr, void *data); static void command_next_frame(struct list *args, const char *argstr, void *data); static void command_toggle_playing(struct list *args, const char *argstr, void *data); static void command_set_scaling_mode(struct list *args, const char *argstr, void *data); static void command_set_upscaling_method(struct list *args, const char *argstr, void *data); static void command_set_slideshow_duration(struct list *args, const char *argstr, void *data); static void command_set_background(struct list *args, const char *argstr, void *data); static void command_bind(struct list *args, const char *argstr, void *data); static bool setup_window(struct imv *imv); static void consume_internal_event(struct imv *imv, struct internal_event *event); static void render_window(struct imv *imv); static void update_env_vars(struct imv *imv); static size_t generate_env_text(struct imv *imv, char *buf, size_t len, const char *format); static size_t read_from_stdin(void **buffer); /* Finds the next split between commands in a string (';'). Provides a pointer * to the next character after the delimiter as out, or a pointer to '\0' if * nothing is left. Also provides the len from start up to the delimiter. */ static void split_commands(const char *start, const char **out, size_t *len) { bool in_single_quotes = false; bool in_double_quotes = false; const char *str = start; while (*str) { if (!in_single_quotes && *str == '"') { in_double_quotes = !in_double_quotes; } else if (!in_double_quotes && *str == '\'') { in_single_quotes = !in_single_quotes; } else if (*str == '\\') { /* We don't care about the behaviour of any escaped character, just * make sure to skip over them. We do need to make sure not to allow * escaping of the null terminator though. */ if (str[1] != '\0') { ++str; } } else if (!in_single_quotes && !in_double_quotes && *str == ';') { /* Found a command split that wasn't escaped or quoted */ *len = str - start; *out = str + 1; return; } ++str; } *out = str; *len = str - start; } static bool is_legacy_bind(const char *keys) { const char *prefix = "') { return true; } return false; } static bool add_bind(struct imv *imv, const char *keys, const char *commands) { if (is_legacy_bind(keys)) { imv_log(IMV_WARNING, "'%s' is the legacy bind syntax.\n" " would now be .\n" "Check the imv(5) man page for more syntax examples.\n", keys); return true; } struct list *list = imv_bind_parse_keys(keys); if (!list) { imv_log(IMV_ERROR, "Invalid key combination\n"); return false; } char command_buf[512]; const char *next_command; size_t command_len; bool success = true; imv_binds_clear_key(imv->binds, list); while (*commands != '\0') { split_commands(commands, &next_command, &command_len); if (command_len >= sizeof command_buf) { imv_log(IMV_ERROR, "Command exceeded max length, not binding: %.*s\n", (int)command_len, commands); imv_binds_clear_key(imv->binds, list); success = false; break; } strncpy(command_buf, commands, command_len); command_buf[command_len] = '\0'; enum bind_result result = imv_binds_add(imv->binds, list, command_buf); if (result == BIND_INVALID_KEYS) { imv_log(IMV_ERROR, "Invalid keys to bind to"); success = false; break; } else if (result == BIND_INVALID_COMMAND) { imv_log(IMV_ERROR, "No command given to bind to"); success = false; break; } else if (result == BIND_CONFLICTS) { imv_log(IMV_ERROR, "Key combination conflicts with existing bind"); success = false; break; } commands = next_command; } list_deep_free(list); return success; } static double cur_time(void) { struct timespec ts; const int rc = clock_gettime(CLOCK_MONOTONIC, &ts); assert(!rc); return ts.tv_sec + (double)ts.tv_nsec * 0.000000001; } static void *async_free_source_thread(void *raw) { struct imv_source *src = raw; src->free(src); return NULL; } static void async_free_source(struct imv_source *src) { typedef void *(*thread_func)(void*); pthread_t thread; pthread_create(&thread, NULL, (thread_func)async_free_source_thread, src); pthread_detach(thread); } static void async_load_first_frame(struct imv_source *src) { typedef void *(*thread_func)(void*); pthread_t thread; pthread_create(&thread, NULL, (thread_func)src->load_first_frame, src); pthread_detach(thread); } static void async_load_next_frame(struct imv_source *src) { typedef void *(*thread_func)(void*); pthread_t thread; pthread_create(&thread, NULL, (thread_func)src->load_next_frame, src); pthread_detach(thread); } static void source_callback(struct imv_source_message *msg) { struct imv *imv = msg->user_data; if (msg->source != imv->current_source) { /* We received a message from an old source, tidy up contents * as required, but ignore it. */ if (msg->image) { imv_image_free(msg->image); } return; } struct internal_event *event = calloc(1, sizeof *event); if (msg->image) { event->type = NEW_IMAGE; event->data.new_image.image = msg->image; event->data.new_image.frametime = msg->frametime; /* Keep track of the last source to send us an image in order to detect * when we're getting a new image, as opposed to a new frame from the * same image. */ event->data.new_image.is_new_image = msg->source != imv->last_source; imv->last_source = msg->source; } else { event->type = BAD_IMAGE; /* TODO: Something more elegant with error messages */ event->data.bad_image.error = strdup(msg->error); } struct imv_event e = { .type = IMV_EVENT_CUSTOM, .data = { .custom = event } }; imv_window_push_event(imv->window, &e); } static void command_callback(const char *text, void *data) { struct imv *imv = data; struct internal_event *event = calloc(1, sizeof *event); event->type = COMMAND; event->data.command.text = strdup(text); struct imv_event e = { .type = IMV_EVENT_CUSTOM, .data = { .custom = event } }; imv_window_push_event(imv->window, &e); } static void key_handler(struct imv *imv, const struct imv_event *event) { if (imv_console_is_active(imv->console)) { if (imv_console_key(imv->console, event->data.keyboard.keyname)) { imv->need_redraw = true; return; } imv_console_input(imv->console, event->data.keyboard.text); } else { /* In regular mode see if we should enter command mode, otherwise send input * to the bind system. */ if (!strcmp("colon", event->data.keyboard.keyname)) { imv_console_activate(imv->console); imv->need_redraw = true; return; } struct list *cmds = imv_bind_handle_event(imv->binds, event->data.keyboard.description); if (cmds) { imv_command_exec_list(imv->commands, cmds, imv); } } imv->need_redraw = true; } static void event_handler(void *data, const struct imv_event *e) { struct imv *imv = data; switch (e->type) { case IMV_EVENT_CLOSE: imv->quit = true; break; case IMV_EVENT_RESIZE: { const int ww = e->data.resize.width; const int wh = e->data.resize.height; const int bw = e->data.resize.buffer_width; const int bh = e->data.resize.buffer_height; imv_viewport_update(imv->view, ww, wh, bw, bh, imv->current_image, imv->scaling_mode); imv_canvas_resize(imv->canvas, bw, bh); break; } case IMV_EVENT_KEYBOARD: key_handler(imv, e); break; case IMV_EVENT_MOUSE_MOTION: if (imv_window_get_mouse_button(imv->window, 1)) { imv_viewport_move(imv->view, e->data.mouse_motion.dx, e->data.mouse_motion.dy, imv->current_image); } break; case IMV_EVENT_MOUSE_SCROLL: { double x, y; imv_window_get_mouse_position(imv->window, &x, &y); imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_MOUSE, x, y, -e->data.mouse_scroll.dy); } break; case IMV_EVENT_CUSTOM: consume_internal_event(imv, e->data.custom); break; default: break; } } static void log_to_stderr(enum imv_log_level level, const char *text, void *data) { (void)data; if (level >= IMV_INFO) { fprintf(stderr, "%s", text); } } struct imv *imv_create(void) { /* Attach log to stderr */ imv_log_add_log_callback(&log_to_stderr, NULL); struct imv *imv = calloc(1, sizeof *imv); imv->initial_width = 1280; imv->initial_height = 720; imv->need_redraw = true; imv->need_rescale = true; imv->scaling_mode = SCALING_FULL; imv->loop_input = true; imv->font.name = strdup("Monospace"); imv->font.size = 24; imv->binds = imv_binds_create(); imv->navigator = imv_navigator_create(); imv->backends = list_create(); imv->commands = imv_commands_create(); imv->console = imv_console_create(); imv_console_set_command_callback(imv->console, &command_callback, imv); imv->ipc = imv_ipc_create(); imv_ipc_set_command_callback(imv->ipc, &command_callback, imv); imv->title_text = strdup( "imv - [${imv_current_index}/${imv_file_count}]" " [${imv_width}x${imv_height}] [${imv_scale}%]" " $imv_current_file [$imv_scaling_mode]" ); imv->overlay_text = strdup( "[${imv_current_index}/${imv_file_count}]" " [${imv_width}x${imv_height}] [${imv_scale}%]" " $imv_current_file [$imv_scaling_mode]" ); imv->startup_commands = list_create(); imv_command_register(imv->commands, "quit", &command_quit); imv_command_register(imv->commands, "pan", &command_pan); imv_command_register(imv->commands, "next", &command_next); imv_command_register(imv->commands, "prev", &command_prev); imv_command_register(imv->commands, "goto", &command_goto); imv_command_register(imv->commands, "zoom", &command_zoom); imv_command_register(imv->commands, "open", &command_open); imv_command_register(imv->commands, "close", &command_close); imv_command_register(imv->commands, "fullscreen", &command_fullscreen); imv_command_register(imv->commands, "overlay", &command_overlay); imv_command_register(imv->commands, "exec", &command_exec); imv_command_register(imv->commands, "center", &command_center); imv_command_register(imv->commands, "reset", &command_reset); imv_command_register(imv->commands, "next_frame", &command_next_frame); imv_command_register(imv->commands, "toggle_playing", &command_toggle_playing); imv_command_register(imv->commands, "scaling", &command_set_scaling_mode); imv_command_register(imv->commands, "upscaling", &command_set_upscaling_method); imv_command_register(imv->commands, "slideshow", &command_set_slideshow_duration); imv_command_register(imv->commands, "background", &command_set_background); imv_command_register(imv->commands, "bind", &command_bind); imv_command_alias(imv->commands, "q", "quit"); imv_command_alias(imv->commands, "n", "next"); imv_command_alias(imv->commands, "p", "prev"); imv_command_alias(imv->commands, "g", "goto"); imv_command_alias(imv->commands, "z", "zoom"); imv_command_alias(imv->commands, "o", "open"); imv_command_alias(imv->commands, "bg", "background"); imv_command_alias(imv->commands, "ss", "slideshow"); /* aliases to improve backwards compatibility with commands, "select_rel", "next"); imv_command_alias(imv->commands, "select_abs", "goto"); imv_command_alias(imv->commands, "scaling_method", "scaling"); add_bind(imv, "q", "quit"); add_bind(imv, "", "prev"); add_bind(imv, "", "prev"); add_bind(imv, "", "next"); add_bind(imv, "", "next"); add_bind(imv, "gg", "goto 0"); add_bind(imv, "", "goto -1"); add_bind(imv, "j", "pan 0 -50"); add_bind(imv, "k", "pan 0 50"); add_bind(imv, "h", "pan 50 0"); add_bind(imv, "l", "pan -50 0"); add_bind(imv, "x", "close"); add_bind(imv, "f", "fullscreen"); add_bind(imv, "d", "overlay"); add_bind(imv, "p", "exec echo $imv_current_file"); add_bind(imv, "", "zoom 1"); add_bind(imv, "", "zoom 1"); add_bind(imv, "i", "zoom 1"); add_bind(imv, "", "zoom -1"); add_bind(imv, "", "zoom -1"); add_bind(imv, "o", "zoom -1"); add_bind(imv, "c", "center"); add_bind(imv, "s", "scaling next"); add_bind(imv, "", "upscaling next"); add_bind(imv, "a", "zoom actual"); add_bind(imv, "r", "reset"); add_bind(imv, "", "next_frame"); add_bind(imv, "", "toggle_playing"); add_bind(imv, "t", "slideshow +1"); add_bind(imv, "", "slideshow -1"); return imv; } void imv_free(struct imv *imv) { free(imv->font.name); free(imv->title_text); free(imv->overlay_text); imv_binds_free(imv->binds); imv_navigator_free(imv->navigator); if (imv->current_source) { imv->current_source->free(imv->current_source); } imv_commands_free(imv->commands); imv_console_free(imv->console); imv_ipc_free(imv->ipc); imv_viewport_free(imv->view); imv_canvas_free(imv->canvas); if (imv->current_image) { imv_image_free(imv->current_image); } if (imv->next_frame.image) { imv_image_free(imv->next_frame.image); } if (imv->stdin_image_data) { free(imv->stdin_image_data); } if (imv->window) { imv_window_free(imv->window); } list_free(imv->backends); list_free(imv->startup_commands); free(imv); } void imv_install_backend(struct imv *imv, const struct imv_backend *backend) { list_append(imv->backends, (void*)backend); } static bool parse_bg(struct imv *imv, const char *bg) { if (!strcmp("checks", bg)) { imv->background.type = BACKGROUND_CHEQUERED; } else { imv->background.type = BACKGROUND_SOLID; if (*bg == '#') ++bg; char *ep; uint32_t n = strtoul(bg, &ep, 16); if (*ep != '\0' || ep - bg != 6 || n > 0xFFFFFF) { imv_log(IMV_ERROR, "Invalid hex color: '%s'\n", bg); return false; } imv->background.color.b = n & 0xFF; imv->background.color.g = (n >> 8) & 0xFF; imv->background.color.r = (n >> 16); } return true; } static bool parse_slideshow_duration(struct imv *imv, const char *duration) { char *decimal; imv->slideshow.duration = strtod(duration, &decimal); return true; } static bool parse_scaling_mode(struct imv *imv, const char *mode) { if (!strcmp(mode, "shrink")) { imv->scaling_mode = SCALING_DOWN; return true; } if (!strcmp(mode, "full")) { imv->scaling_mode = SCALING_FULL; return true; } if (!strcmp(mode, "crop")) { imv->scaling_mode = SCALING_CROP; return true; } if (!strcmp(mode, "none")) { imv->scaling_mode = SCALING_NONE; return true; } return false; } static bool parse_upscaling_method(struct imv *imv, const char *method) { if (!strcmp(method, "linear")) { imv->upscaling_method = UPSCALING_LINEAR; return true; } if (!strcmp(method, "nearest_neighbour")) { imv->upscaling_method = UPSCALING_NEAREST_NEIGHBOUR; return true; } return false; } static bool parse_initial_pan(struct imv *imv, const char *pan_params) { char *next_val; long int val_x = strtol(pan_params, &next_val, 10); long int val_y = strtol(next_val, NULL, 10); imv->custom_start_pan = true; imv->initial_pan_x = (double)val_x / (double)100; imv->initial_pan_y = (double)val_y / (double)100; return true; } static void *load_paths_from_stdin(void *data) { struct imv *imv = data; imv_log(IMV_INFO, "Reading paths from stdin..."); char buf[PATH_MAX]; while (fgets(buf, sizeof(buf), stdin) != NULL) { size_t len = strlen(buf); if (buf[len-1] == '\n') { buf[--len] = 0; } if (len > 0) { struct internal_event *event = calloc(1, sizeof *event); event->type = NEW_PATH; event->data.new_path.path = strdup(buf); struct imv_event e = { .type = IMV_EVENT_CUSTOM, .data = { .custom = event } }; imv_window_push_event(imv->window, &e); } } return NULL; } static void print_help(struct imv *imv) { printf("imv %s\nSee manual for usage information.\n", IMV_VERSION); puts("This version of imv has been compiled with the following backends:\n"); for (size_t i = 0; i < imv->backends->len; ++i) { struct imv_backend *backend = imv->backends->items[i]; printf("Name: %s\n" "Description: %s\n" "Website: %s\n" "License: %s\n\n", backend->name, backend->description, backend->website, backend->license); } puts("imv's full source code is published under the terms of the MIT\n" "license, and can be found at https://github.com/eXeC64/imv\n" "\n" "imv uses the inih library to parse ini files.\n" "See https://github.com/benhoyt/inih for details.\n" "inih is used under the New (3-clause) BSD license."); } bool imv_parse_args(struct imv *imv, int argc, char **argv) { /* Do not print getopt errors */ opterr = 0; int o; /* TODO getopt_long */ while ((o = getopt(argc, argv, "frdxhvlu:s:n:b:t:c:")) != -1) { switch(o) { case 'f': imv->start_fullscreen = true; break; case 'r': imv->recursive_load = true; break; case 'd': imv->overlay_enabled = true; break; case 'x': imv->loop_input = false; break; case 'l': imv->list_files_at_exit = true; break; case 'n': imv->starting_path = optarg; break; case 'h': print_help(imv); imv->quit = true; return true; case 'v': printf("Version: %s\n", IMV_VERSION); imv->quit = true; return false; case 's': if (!parse_scaling_mode(imv, optarg)) { imv_log(IMV_ERROR, "Invalid scaling mode. Aborting.\n"); return false; } break; case 'u': if (!parse_upscaling_method(imv, optarg)) { imv_log(IMV_ERROR, "Invalid upscaling method. Aborting.\n"); return false; } break; case 'b': if (!parse_bg(imv, optarg)) { imv_log(IMV_ERROR, "Invalid background. Aborting.\n"); return false; } break; case 't': if (!parse_slideshow_duration(imv, optarg)) { imv_log(IMV_ERROR, "Invalid slideshow duration. Aborting.\n"); return false; } break; case 'c': list_append(imv->startup_commands, optarg); break; case '?': imv_log(IMV_ERROR, "Unknown argument '%c'. Aborting.\n", optopt); return false; } } argc -= optind; argv += optind; /* if no paths are given as args, expect them from stdin */ if (argc == 0) { imv->paths_from_stdin = true; } else { /* otherwise, add the paths */ bool data_from_stdin = false; for (int i = 0; i < argc; ++i) { /* Special case: '-' denotes reading image data from stdin */ if (!strcmp("-", argv[i])) { if (imv->paths_from_stdin) { imv_log(IMV_ERROR, "Can't read paths AND image data from stdin. Aborting.\n"); return false; } else if (data_from_stdin) { imv_log(IMV_ERROR, "Can't read image data from stdin twice. Aborting.\n"); return false; } data_from_stdin = true; imv->stdin_image_data_len = read_from_stdin(&imv->stdin_image_data); } imv_add_path(imv, argv[i]); } } return true; } void imv_add_path(struct imv *imv, const char *path) { imv_navigator_add(imv->navigator, path, imv->recursive_load); } int imv_run(struct imv *imv) { if (imv->quit) return 0; if (!setup_window(imv)) return 1; /* if loading paths from stdin, kick off a thread to do that - we'll receive * events back via internal events */ if (imv->paths_from_stdin) { pthread_t thread; pthread_create(&thread, NULL, load_paths_from_stdin, imv); pthread_detach(thread); } if (imv->starting_path) { ssize_t index = imv_navigator_find_path(imv->navigator, imv->starting_path); if (index == -1) { index = (int) strtol(imv->starting_path, NULL, 10); index -= 1; /* input is 1-indexed, internally we're 0 indexed */ if (errno == EINVAL) { index = -1; } } if (index >= 0) { imv_navigator_select_abs(imv->navigator, index); } else { imv_log(IMV_ERROR, "Invalid starting image: %s\n", imv->starting_path); } } /* Push any startup commands into the event queue */ for (size_t i = 0; i < imv->startup_commands->len; ++i) { command_callback(imv->startup_commands->items[i], imv); } list_free(imv->startup_commands); imv->startup_commands = NULL; /* time keeping */ double last_time = cur_time(); double current_time; while (!imv->quit) { /* Check if navigator wrapped around paths lists */ if (!imv->loop_input && imv_navigator_wrapped(imv->navigator)) { break; } /* If the user has changed image, start loading the new one. It's possible * that there are lots of unsupported files listed back to back, so we * may immediate close one and navigate onto the next. So we attempt to * load in a while loop until the navigation stops. */ while (imv_navigator_poll_changed(imv->navigator)) { const char *current_path = imv_navigator_selection(imv->navigator); /* check we got a path back */ if (strcmp("", current_path)) { const bool path_is_stdin = !strcmp("-", current_path); struct imv_source *new_source; enum backend_result result = BACKEND_UNSUPPORTED; if (!imv->backends) { imv_log(IMV_ERROR, "No backends installed. Unable to load image.\n"); } for (size_t i = 0; i < imv->backends->len; ++i) { const struct imv_backend *backend = imv->backends->items[i]; if (path_is_stdin) { if (!backend->open_memory) { /* memory loading unsupported by backend */ continue; } result = backend->open_memory(imv->stdin_image_data, imv->stdin_image_data_len, &new_source); } else { if (!backend->open_path) { /* path loading unsupported by backend */ continue; } result = backend->open_path(current_path, &new_source); } if (result == BACKEND_UNSUPPORTED) { /* Try the next backend */ continue; } else { break; } } if (result == BACKEND_SUCCESS) { if (imv->current_source) { async_free_source(imv->current_source); } imv->current_source = new_source; imv->current_source->callback = &source_callback; imv->current_source->user_data = imv; async_load_first_frame(imv->current_source); imv->loading = true; imv_viewport_set_playing(imv->view, true); char title[1024]; generate_env_text(imv, title, sizeof title, imv->title_text); imv_window_set_title(imv->window, title); } else { /* Error loading path so remove it from the navigator */ imv_navigator_remove(imv->navigator, current_path); } } else { /* No image currently selected */ if (imv->current_image) { imv_image_free(imv->current_image); imv->current_image = NULL; } } } if (imv->need_rescale) { imv->need_rescale = false; imv_viewport_rescale(imv->view, imv->current_image, imv->scaling_mode); } current_time = cur_time(); /* Check if a new frame is due */ bool should_change_frame = false; if (imv->next_frame.force_next_frame && imv->next_frame.image) { should_change_frame = true; } if (imv_viewport_is_playing(imv->view) && imv->next_frame.image && imv->next_frame.due && imv->next_frame.due <= current_time) { should_change_frame = true; } if (should_change_frame) { if (imv->current_image) { imv_image_free(imv->current_image); } imv->current_image = imv->next_frame.image; imv->next_frame.image = NULL; imv->next_frame.due = current_time + imv->next_frame.duration; imv->next_frame.duration = 0; imv->next_frame.force_next_frame = false; imv->need_redraw = true; /* Trigger loading of a new frame, now this one's being displayed */ if (imv->current_source && imv->current_source->load_next_frame) { async_load_next_frame(imv->current_source); } } /* handle slideshow */ if (imv->slideshow.duration != 0.0) { double dt = current_time - last_time; imv->slideshow.elapsed += dt; if (imv->slideshow.elapsed >= imv->slideshow.duration) { imv_navigator_select_rel(imv->navigator, 1); imv->slideshow.elapsed = 0; imv->need_redraw = true; } } last_time = current_time; /* check if the viewport needs a redraw */ if (imv_viewport_needs_redraw(imv->view)) { imv->need_redraw = true; } if (imv->need_redraw) { imv_window_clear(imv->window, 0, 0, 0); render_window(imv); imv_window_present(imv->window); } /* sleep until we have something to do */ double timeout = 1.0; /* seconds */ /* If we need to display the next frame of an animation soon we should * limit our sleep until the next frame is due. */ if (imv_viewport_is_playing(imv->view) && imv->next_frame.due != 0.0) { timeout = imv->next_frame.due - current_time; if (timeout < 0.001) { timeout = 0.001; } } if (imv->slideshow.duration > 0) { double timeleft = imv->slideshow.duration - imv->slideshow.elapsed; if (timeleft > 0.0 && timeleft < timeout) { timeout = timeleft + 0.001; } } /* Go to sleep until an input/internal event or the timeout expires */ imv_window_wait_for_event(imv->window, timeout); /* Handle the new events that have arrived */ imv_window_pump_events(imv->window, event_handler, imv); } if (imv->list_files_at_exit) { for (size_t i = 0; i < imv_navigator_length(imv->navigator); ++i) puts(imv_navigator_at(imv->navigator, i)); } return 0; } static bool setup_window(struct imv *imv) { imv->window = imv_window_create(imv->initial_width, imv->initial_height, "imv"); if (!imv->window) { imv_log(IMV_ERROR, "Failed to create window\n"); return false; } { int ww, wh, bw, bh; imv_window_get_size(imv->window, &ww, &wh); imv_window_get_framebuffer_size(imv->window, &bw, &bh); imv->view = imv_viewport_create(ww, wh, bw, bh); } if (imv->custom_start_pan) { imv_viewport_set_default_pan_factor(imv->view, imv->initial_pan_x, imv->initial_pan_y); } /* put us in fullscren mode to begin with if requested */ imv_window_set_fullscreen(imv->window, imv->start_fullscreen); { int ww, wh; imv_window_get_size(imv->window, &ww, &wh); imv->canvas = imv_canvas_create(ww, wh); imv_canvas_font(imv->canvas, imv->font.name, imv->font.size); } return true; } static void handle_new_image(struct imv *imv, struct imv_image *image, int frametime) { if (imv->current_image) { imv_image_free(imv->current_image); } imv->current_image = image; imv->need_redraw = true; imv->need_rescale = true; imv->loading = false; imv->next_frame.due = frametime ? cur_time() + frametime * 0.001 : 0.0; imv->next_frame.duration = 0.0; /* If this is an animated image, we should kick off loading the next frame */ if (imv->current_source && imv->current_source->load_next_frame && frametime) { async_load_next_frame(imv->current_source); } } static void handle_new_frame(struct imv *imv, struct imv_image *image, int frametime) { if (imv->next_frame.image) { imv_image_free(imv->next_frame.image); } imv->next_frame.image = image; imv->next_frame.duration = frametime * 0.001; } static void consume_internal_event(struct imv *imv, struct internal_event *event) { if (event->type == NEW_IMAGE) { /* New image vs just a new frame of the same image */ if (event->data.new_image.is_new_image) { handle_new_image(imv, event->data.new_image.image, event->data.new_image.frametime); } else { handle_new_frame(imv, event->data.new_image.image, event->data.new_image.frametime); } } else if (event->type == BAD_IMAGE) { /* An image failed to load, remove it from our image list */ const char *err_path = imv_navigator_selection(imv->navigator); /* Special case: the image came from stdin */ if (strcmp(err_path, "-") == 0) { if (imv->stdin_image_data) { free(imv->stdin_image_data); imv->stdin_image_data = NULL; imv->stdin_image_data_len = 0; } imv_log(IMV_ERROR, "Failed to load image from stdin.\n"); } imv_navigator_remove(imv->navigator, err_path); } else if (event->type == NEW_PATH) { /* Received a new path from the stdin reading thread */ imv_add_path(imv, event->data.new_path.path); free(event->data.new_path.path); /* Need to update image count in title */ imv->need_redraw = true; } else if (event->type == COMMAND) { struct list *commands = list_create(); list_append(commands, event->data.command.text); imv_command_exec_list(imv->commands, commands, imv); list_deep_free(commands); imv->need_redraw = true; } free(event); return; } static void render_window(struct imv *imv) { int ww, wh; imv_window_get_size(imv->window, &ww, &wh); /* update window title */ char title_text[1024]; generate_env_text(imv, title_text, sizeof title_text, imv->title_text); imv_window_set_title(imv->window, title_text); /* first we draw the background */ if (imv->background.type == BACKGROUND_SOLID) { imv_canvas_clear(imv->canvas); imv_canvas_color(imv->canvas, imv->background.color.r / 255.f, imv->background.color.g / 255.f, imv->background.color.b / 255.f, 1.0); imv_canvas_fill(imv->canvas); imv_canvas_draw(imv->canvas); } else { /* chequered background */ imv_canvas_fill_checkers(imv->canvas, 16); imv_canvas_draw(imv->canvas); } /* draw our actual image */ if (imv->current_image) { int x, y; double scale; imv_viewport_get_offset(imv->view, &x, &y); imv_viewport_get_scale(imv->view, &scale); imv_canvas_draw_image(imv->canvas, imv->current_image, x, y, scale, imv->upscaling_method); } imv_canvas_clear(imv->canvas); /* if the overlay needs to be drawn, draw that too */ if (imv->overlay_enabled) { const int height = imv->font.size * 1.2; imv_canvas_color(imv->canvas, 0, 0, 0, 0.75); imv_canvas_fill_rectangle(imv->canvas, 0, 0, ww, height); imv_canvas_color(imv->canvas, 1, 1, 1, 1); char overlay_text[1024]; generate_env_text(imv, overlay_text, sizeof overlay_text, imv->overlay_text); imv_canvas_printf(imv->canvas, 0, 0, "%s", overlay_text); } /* draw command entry bar if needed */ if (imv_console_prompt(imv->console)) { const int bottom_offset = 5; const int height = imv->font.size * 1.2; imv_canvas_color(imv->canvas, 0, 0, 0, 0.75); imv_canvas_fill_rectangle(imv->canvas, 0, wh - height - bottom_offset, ww, height + bottom_offset); imv_canvas_color(imv->canvas, 1, 1, 1, 1); imv_canvas_printf(imv->canvas, 0, wh - height - bottom_offset, ":%s\u2588", imv_console_prompt(imv->console)); } imv_canvas_draw(imv->canvas); /* redraw complete, unset the flag */ imv->need_redraw = false; } static char *get_config_path(void) { const char *config_paths[] = { "$imv_config", "$XDG_CONFIG_HOME/imv/config", "$HOME/.config/imv/config", "$HOME/.imv_config", "$HOME/.imv/config", "/usr/local/etc/imv_config", "/etc/imv_config", }; for (size_t i = 0; i < sizeof(config_paths) / sizeof(char*); ++i) { wordexp_t word; if (wordexp(config_paths[i], &word, 0) == 0) { if (!word.we_wordv[0]) { wordfree(&word); continue; } char *path = strdup(word.we_wordv[0]); wordfree(&word); if (!path || access(path, R_OK) == -1) { free(path); continue; } return path; } } return NULL; } static bool parse_bool(const char *str) { return ( !strcmp(str, "1") || !strcmp(str, "yes") || !strcmp(str, "true") || !strcmp(str, "on") ); } static int handle_ini_value(void *user, const char *section, const char *name, const char *value) { struct imv *imv = user; if (!strcmp(section, "binds")) { return add_bind(imv, name, value); } if (!strcmp(section, "aliases")) { imv_command_alias(imv->commands, name, value); return 1; } if (!strcmp(section, "options")) { if (!strcmp(name, "fullscreen")) { imv->start_fullscreen = parse_bool(value); return 1; } if (!strcmp(name, "width")) { imv->initial_width = strtol(value, NULL, 10); return 1; } if (!strcmp(name, "height")) { imv->initial_height = strtol(value, NULL, 10); return 1; } if (!strcmp(name, "overlay")) { imv->overlay_enabled = parse_bool(value); return 1; } if (!strcmp(name, "upscaling_method")) { return parse_upscaling_method(imv, value); } if (!strcmp(name, "recursive")) { imv->recursive_load = parse_bool(value); return 1; } if (!strcmp(name, "loop_input")) { imv->loop_input = parse_bool(value); return 1; } if (!strcmp(name, "list_files_at_exit")) { imv->list_files_at_exit = parse_bool(value); return 1; } if (!strcmp(name, "scaling_mode")) { return parse_scaling_mode(imv, value); } if (!strcmp(name, "initial_pan")) { return parse_initial_pan(imv, value); } if (!strcmp(name, "background")) { if (!parse_bg(imv, value)) { return false; } return 1; } if (!strcmp(name, "slideshow_duration")) { if (!parse_slideshow_duration(imv, value)) { return false; } return 1; } if (!strcmp(name, "overlay_font")) { free(imv->font.name); imv->font.name = strdup(value); char *sep = strchr(imv->font.name, ':'); if (sep) { *sep = 0; imv->font.size = atoi(sep + 1); } else { imv->font.size = 24; } return 1; } if (!strcmp(name, "overlay_text")) { free(imv->overlay_text); imv->overlay_text = strdup(value); return 1; } if (!strcmp(name, "title_text")) { free(imv->title_text); imv->title_text = strdup(value); return 1; } if (!strcmp(name, "suppress_default_binds")) { const bool suppress_default_binds = parse_bool(value); if (suppress_default_binds) { /* clear out any default binds if requested */ imv_binds_clear(imv->binds); } return 1; } /* No matches so far */ imv_log(IMV_WARNING, "Ignoring unknown option: %s\n", name); return 1; } return 0; } bool imv_load_config(struct imv *imv) { char *path = get_config_path(); if (!path) { /* no config, no problem - we have defaults */ return true; } bool result = true; const int err = ini_parse(path, handle_ini_value, imv); if (err == -1) { imv_log(IMV_ERROR, "Unable to open config file: %s\n", path); result = false; } else if (err > 0) { imv_log(IMV_ERROR, "Error in config file: %s:%d\n", path, err); result = false; } free(path); return result; } static void command_quit(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; imv->quit = true; } static void command_pan(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; if (args->len != 3) { return; } long int x = strtol(args->items[1], NULL, 10); long int y = strtol(args->items[2], NULL, 10); imv_viewport_move(imv->view, x, y, imv->current_image); } static void command_next(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; long int index = 1; if (args->len >= 2) { index = strtol(args->items[1], NULL, 10); } imv_navigator_select_rel(imv->navigator, index); imv->slideshow.elapsed = 0; } static void command_prev(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; long int index = 1; if (args->len >= 2) { index = strtol(args->items[1], NULL, 10); } imv_navigator_select_rel(imv->navigator, -index); imv->slideshow.elapsed = 0; } static void command_goto(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; if (args->len != 2) { return; } long int index = strtol(args->items[1], NULL, 10); imv_navigator_select_abs(imv->navigator, index - 1); imv->slideshow.elapsed = 0; } static void command_zoom(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; if (args->len == 2) { const char *str = args->items[1]; if (!strcmp(str, "actual")) { imv_viewport_scale_to_actual(imv->view, imv->current_image); } else { long int amount = strtol(args->items[1], NULL, 10); imv_viewport_zoom(imv->view, imv->current_image, IMV_ZOOM_KEYBOARD, 0, 0, amount); } } } static void command_open(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; bool recursive = imv->recursive_load; update_env_vars(imv); for (size_t i = 1; i < args->len; ++i) { /* allow -r arg to specify recursive */ if (i == 1 && !strcmp(args->items[i], "-r")) { recursive = true; continue; } wordexp_t word; if (wordexp(args->items[i], &word, 0) == 0) { for (size_t j = 0; j < word.we_wordc; ++j) { imv_navigator_add(imv->navigator, word.we_wordv[j], recursive); } wordfree(&word); } } } static void command_close(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; size_t index = imv_navigator_index(imv->navigator); if (args->len == 2) { const char *arg = args->items[1]; if (!strcmp("all", arg)) { imv_navigator_remove_all(imv->navigator); imv->slideshow.elapsed = 0; return; } index = (size_t)strtol(arg, NULL, 10) - 1; } imv_navigator_remove_at(imv->navigator, index); imv->slideshow.elapsed = 0; } static void command_fullscreen(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; imv_window_set_fullscreen(imv->window, !imv_window_is_fullscreen(imv->window)); } static void command_overlay(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; imv->overlay_enabled = !imv->overlay_enabled; imv->need_redraw = true; } static void command_exec(struct list *args, const char *argstr, void *data) { (void)args; struct imv *imv = data; update_env_vars(imv); system(argstr); } static void command_center(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; imv_viewport_center(imv->view, imv->current_image); } static void command_reset(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; imv->need_rescale = true; imv->need_redraw = true; } static void command_next_frame(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; if (imv->current_source && imv->current_source->load_next_frame) { async_load_next_frame(imv->current_source); imv->next_frame.force_next_frame = true; } } static void command_toggle_playing(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; imv_viewport_toggle_playing(imv->view); } static void command_set_scaling_mode(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; if (args->len != 2) { return; } const char *mode = args->items[1]; if (!strcmp(mode, "next")) { imv->scaling_mode++; imv->scaling_mode %= SCALING_MODE_COUNT; } else if (!parse_scaling_mode(imv, mode)) { /* no changes, don't bother to redraw */ return; } imv->need_rescale = true; imv->need_redraw = true; } static void command_set_upscaling_method(struct list *args, const char *argstr, void *data) { (void)args; (void)argstr; struct imv *imv = data; if (args->len != 2) { return; } const char *mode = args->items[1]; if (!strcmp(mode, "next")) { imv->upscaling_method++; imv->upscaling_method %= UPSCALING_METHOD_COUNT; } else if (!parse_upscaling_method(imv, mode)) { /* no changes, don't bother to redraw */ return; } imv->need_redraw = true; } static void command_set_slideshow_duration(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; if (args->len == 2) { const char *arg = args->items[1]; const char prefix = *arg; int new_duration = imv->slideshow.duration; long int arg_num = strtol(arg, NULL, 10); if (prefix == '+' || prefix == '-') { new_duration += arg_num; } else { new_duration = arg_num; } if (new_duration < 0) { new_duration = 0; } imv->slideshow.duration = new_duration; imv->need_redraw = true; } } static void command_set_background(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; if (args->len == 2) { parse_bg(imv, args->items[1]); } } static void command_bind(struct list *args, const char *argstr, void *data) { (void)argstr; struct imv *imv = data; if (args->len >= 3) { const char *keys = args->items[1]; char *commands = list_to_string(args, " ", 2); add_bind(imv, keys, commands); free(commands); } } static void update_env_vars(struct imv *imv) { char str[64]; snprintf(str, sizeof str, "%d", getpid()); setenv("imv_pid", str, 1); setenv("imv_current_file", imv_navigator_selection(imv->navigator), 1); setenv("imv_scaling_mode", scaling_label[imv->scaling_mode], 1); setenv("imv_loading", imv->loading ? "1" : "0", 1); if (imv_navigator_length(imv->navigator)) { snprintf(str, sizeof str, "%zu", imv_navigator_index(imv->navigator) + 1); setenv("imv_current_index", str, 1); } else { setenv("imv_current_index", "0", 1); } snprintf(str, sizeof str, "%zu", imv_navigator_length(imv->navigator)); setenv("imv_file_count", str, 1); snprintf(str, sizeof str, "%d", imv_image_width(imv->current_image)); setenv("imv_width", str, 1); snprintf(str, sizeof str, "%d", imv_image_height(imv->current_image)); setenv("imv_height", str, 1); { double scale; imv_viewport_get_scale(imv->view, &scale); snprintf(str, sizeof str, "%d", (int)(scale * 100.0)); setenv("imv_scale", str, 1); } snprintf(str, sizeof str, "%f", imv->slideshow.duration); setenv("imv_slideshow_duration", str, 1); snprintf(str, sizeof str, "%f", imv->slideshow.elapsed); setenv("imv_slideshow_elapsed", str, 1); } static size_t generate_env_text(struct imv *imv, char *buf, size_t buf_len, const char *format) { update_env_vars(imv); size_t len = 0; wordexp_t word; if (wordexp(format, &word, 0) == 0) { for (size_t i = 0; i < word.we_wordc; ++i) { len += snprintf(buf + len, buf_len - len, "%s ", word.we_wordv[i]); } wordfree(&word); } else { len += snprintf(buf, buf_len, "error expanding text"); } return len; } static size_t read_from_stdin(void **buffer) { size_t len = 0; ssize_t r; size_t step = 4096; /* Arbitrary value of 4 KiB */ void *p; errno = 0; /* clear errno */ for (*buffer = NULL; (*buffer = realloc((p = *buffer), len + step)); len += (size_t)r) { if ((r = read(STDIN_FILENO, (uint8_t *)*buffer + len, step)) == -1) { perror(NULL); break; } else if (r == 0) { break; } } /* realloc(3) leaves old buffer allocated in case of error */ if (*buffer == NULL && p != NULL) { int save = errno; free(p); errno = save; len = 0; } return len; } /* vim:set ts=2 sts=2 sw=2 et: */ ����������������������������������������������������������������imv-4.0.1/src/imv.h���������������������������������������������������������������������������������0000664�0000000�0000000�00000000707�13531522132�0014043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_H #define IMV_H #include struct imv; struct imv_backend; struct imv *imv_create(void); void imv_free(struct imv *imv); void imv_install_backend(struct imv *imv, const struct imv_backend *backend); bool imv_load_config(struct imv *imv); bool imv_parse_args(struct imv *imv, int argc, char **argv); void imv_add_path(struct imv *imv, const char *path); int imv_run(struct imv *imv); #endif /* vim:set ts=2 sts=2 sw=2 et: */ ���������������������������������������������������������imv-4.0.1/src/imv_msg.c�����������������������������������������������������������������������������0000664�0000000�0000000�00000001551�13531522132�0014702�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include #include #include #include #include #include #include "ipc.h" int main(int argc, char **argv) { if (argc < 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 0; } int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); assert(sockfd); struct sockaddr_un desc = { .sun_family = AF_UNIX }; imv_ipc_path(desc.sun_path, sizeof desc.sun_path, atoi(argv[1])); if (connect(sockfd, (struct sockaddr *)&desc, sizeof desc) < 0) { perror("Failed to connect"); return 1; } char buf[4096] = {0}; for (int i = 2; i < argc; ++i) { strncat(buf, argv[i], sizeof buf - 1); if (i + 1 < argc) { strncat(buf, " ", sizeof buf - 1); } } strncat(buf, "\n", sizeof buf - 1); write(sockfd, buf, strlen(buf)); close(sockfd); return 0; } �������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/ini.c���������������������������������������������������������������������������������0000664�0000000�0000000�00000014602�13531522132�0014021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* inih -- simple .INI file parser inih is released under the New BSD license (see LICENSE.txt). Go to the project home page for more info: https://github.com/benhoyt/inih */ #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include "ini.h" #if !INI_USE_STACK #include #endif #define MAX_SECTION 50 #define MAX_NAME 50 /* Used by ini_parse_string() to keep track of string parsing state. */ typedef struct { const char* ptr; size_t num_left; } ini_parse_string_ctx; /* Strip whitespace chars off end of given string, in place. Return s. */ static char* rstrip(char* s) { char* p = s + strlen(s); while (p > s && isspace((unsigned char)(*--p))) *p = '\0'; return s; } /* Return pointer to first non-whitespace char in given string. */ static char* lskip(const char* s) { while (*s && isspace((unsigned char)(*s))) s++; return (char*)s; } /* Return pointer to first char (of chars) or inline comment in given string, or pointer to null at end of string if neither found. Inline comment must be prefixed by a whitespace character to register as a comment. */ static char* find_chars_or_comment(const char* s, const char* chars) { #if INI_ALLOW_INLINE_COMMENTS int was_space = 0; while (*s && (!chars || !strchr(chars, *s)) && !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { was_space = isspace((unsigned char)(*s)); s++; } #else while (*s && (!chars || !strchr(chars, *s))) { s++; } #endif return (char*)s; } /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ static char* strncpy0(char* dest, const char* src, size_t size) { strncpy(dest, src, size); dest[size - 1] = '\0'; return dest; } /* See documentation in header file. */ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, void* user) { /* Uses a fair bit of stack (use heap instead if you need to) */ #if INI_USE_STACK char line[INI_MAX_LINE]; #else char* line; #endif char section[MAX_SECTION] = ""; char prev_name[MAX_NAME] = ""; char* start; char* end; char* name; char* value; int lineno = 0; int error = 0; #if !INI_USE_STACK line = (char*)malloc(INI_MAX_LINE); if (!line) { return -2; } #endif #if INI_HANDLER_LINENO #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) #else #define HANDLER(u, s, n, v) handler(u, s, n, v) #endif /* Scan through stream line by line */ while (reader(line, INI_MAX_LINE, stream) != NULL) { lineno++; start = line; #if INI_ALLOW_BOM if (lineno == 1 && (unsigned char)start[0] == 0xEF && (unsigned char)start[1] == 0xBB && (unsigned char)start[2] == 0xBF) { start += 3; } #endif start = lskip(rstrip(start)); if (*start == ';' || *start == '#') { /* Per Python configparser, allow both ; and # comments at the start of a line */ } #if INI_ALLOW_MULTILINE else if (*prev_name && *start && start > line) { /* Non-blank line with leading whitespace, treat as continuation of previous name's value (as per Python configparser). */ if (!HANDLER(user, section, prev_name, start) && !error) error = lineno; } #endif else if (*start == '[') { /* A "[section]" line */ end = find_chars_or_comment(start + 1, "]"); if (*end == ']') { *end = '\0'; strncpy0(section, start + 1, sizeof(section)); *prev_name = '\0'; } else if (!error) { /* No ']' found on section line */ error = lineno; } } else if (*start) { /* Not a comment, must be a name[=:]value pair */ end = find_chars_or_comment(start, "=:"); if (*end == '=' || *end == ':') { *end = '\0'; name = rstrip(start); value = end + 1; #if INI_ALLOW_INLINE_COMMENTS end = find_chars_or_comment(value, NULL); if (*end) *end = '\0'; #endif value = lskip(value); rstrip(value); /* Valid name[=:]value pair found, call handler */ strncpy0(prev_name, name, sizeof(prev_name)); if (!HANDLER(user, section, name, value) && !error) error = lineno; } else if (!error) { /* No '=' or ':' found on name[=:]value line */ error = lineno; } } #if INI_STOP_ON_FIRST_ERROR if (error) break; #endif } #if !INI_USE_STACK free(line); #endif return error; } /* See documentation in header file. */ int ini_parse_file(FILE* file, ini_handler handler, void* user) { return ini_parse_stream((ini_reader)fgets, file, handler, user); } /* See documentation in header file. */ int ini_parse(const char* filename, ini_handler handler, void* user) { FILE* file; int error; file = fopen(filename, "r"); if (!file) return -1; error = ini_parse_file(file, handler, user); fclose(file); return error; } /* An ini_reader function to read the next line from a string buffer. This is the fgets() equivalent used by ini_parse_string(). */ static char* ini_reader_string(char* str, int num, void* stream) { ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; const char* ctx_ptr = ctx->ptr; size_t ctx_num_left = ctx->num_left; char* strp = str; char c; if (ctx_num_left == 0 || num < 2) return NULL; while (num > 1 && ctx_num_left != 0) { c = *ctx_ptr++; ctx_num_left--; *strp++ = c; if (c == '\n') break; num--; } *strp = '\0'; ctx->ptr = ctx_ptr; ctx->num_left = ctx_num_left; return str; } /* See documentation in header file. */ int ini_parse_string(const char* string, ini_handler handler, void* user) { ini_parse_string_ctx ctx; ctx.ptr = string; ctx.num_left = strlen(string); return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, user); } ������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/ini.h���������������������������������������������������������������������������������0000664�0000000�0000000�00000007206�13531522132�0014030�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* inih -- simple .INI file parser inih is released under the New BSD license (see LICENSE.txt). Go to the project home page for more info: https://github.com/benhoyt/inih */ #ifndef __INI_H__ #define __INI_H__ /* Make this header file easier to include in C++ code */ #ifdef __cplusplus extern "C" { #endif #include /* Nonzero if ini_handler callback should accept lineno parameter. */ #ifndef INI_HANDLER_LINENO #define INI_HANDLER_LINENO 0 #endif /* Typedef for prototype of handler function. */ #if INI_HANDLER_LINENO typedef int (*ini_handler)(void* user, const char* section, const char* name, const char* value, int lineno); #else typedef int (*ini_handler)(void* user, const char* section, const char* name, const char* value); #endif /* Typedef for prototype of fgets-style reader function. */ typedef char* (*ini_reader)(char* str, int num, void* stream); /* Parse given INI-style file. May have [section]s, name=value pairs (whitespace stripped), and comments starting with ';' (semicolon). Section is "" if name=value pair parsed before any section heading. name:value pairs are also supported as a concession to Python's configparser. For each name=value pair parsed, call handler function with given user pointer as well as section, name, and value (data only valid for duration of handler call). Handler should return nonzero on success, zero on error. Returns 0 on success, line number of first error on parse error (doesn't stop on first error), -1 on file open error, or -2 on memory allocation error (only when INI_USE_STACK is zero). */ int ini_parse(const char* filename, ini_handler handler, void* user); /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't close the file when it's finished -- the caller must do that. */ int ini_parse_file(FILE* file, ini_handler handler, void* user); /* Same as ini_parse(), but takes an ini_reader function pointer instead of filename. Used for implementing custom or string-based I/O (see also ini_parse_string). */ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, void* user); /* Same as ini_parse(), but takes a zero-terminated string with the INI data instead of a file. Useful for parsing INI data from a network socket or already in memory. */ int ini_parse_string(const char* string, ini_handler handler, void* user); /* Nonzero to allow multi-line value parsing, in the style of Python's configparser. If allowed, ini_parse() will call the handler with the same name for each subsequent line parsed. */ #ifndef INI_ALLOW_MULTILINE #define INI_ALLOW_MULTILINE 1 #endif /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of the file. See http://code.google.com/p/inih/issues/detail?id=21 */ #ifndef INI_ALLOW_BOM #define INI_ALLOW_BOM 1 #endif /* Nonzero to allow inline comments (with valid inline comment characters specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match Python 3.2+ configparser behaviour. */ #ifndef INI_ALLOW_INLINE_COMMENTS #define INI_ALLOW_INLINE_COMMENTS 1 #endif #ifndef INI_INLINE_COMMENT_PREFIXES #define INI_INLINE_COMMENT_PREFIXES ";" #endif /* Nonzero to use stack, zero to use heap (malloc/free). */ #ifndef INI_USE_STACK #define INI_USE_STACK 1 #endif /* Stop parsing on first error (default is to keep parsing). */ #ifndef INI_STOP_ON_FIRST_ERROR #define INI_STOP_ON_FIRST_ERROR 0 #endif /* Maximum line length for any line in INI file. */ #ifndef INI_MAX_LINE #define INI_MAX_LINE 200 #endif #ifdef __cplusplus } #endif #endif /* __INI_H__ */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/ipc.c���������������������������������������������������������������������������������0000664�0000000�0000000�00000004254�13531522132�0014017�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "ipc.h" #include #include #include #include #include #include #include struct imv_ipc { int fd; imv_ipc_callback callback; void *data; }; struct connection { struct imv_ipc *ipc; int fd; }; static void *wait_for_commands(void* void_conn) { struct connection *conn = void_conn; while (1) { char buf[1024]; ssize_t len = recv(conn->fd, buf, sizeof buf - 1, 0); if (len <= 0) { break; } buf[len] = 0; while (len > 0 && isspace(buf[len-1])) { buf[len-1] = 0; --len; } if (conn->ipc->callback) { conn->ipc->callback(buf, conn->ipc->data); } } close(conn->fd); free(conn); return NULL; } static void *wait_for_connections(void* void_ipc) { struct imv_ipc *ipc = void_ipc; (void)ipc; while (1) { int client = accept(ipc->fd, NULL, NULL); if (client == -1) { break; } struct connection *conn = calloc(1, sizeof *conn); conn->ipc = ipc; conn->fd = client; pthread_t thread; pthread_create(&thread, NULL, wait_for_commands, conn); pthread_detach(thread); } return NULL; } struct imv_ipc *imv_ipc_create(void) { int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) { return NULL; } struct sockaddr_un desc = { .sun_family = AF_UNIX }; imv_ipc_path(desc.sun_path, sizeof desc.sun_path, getpid()); unlink(desc.sun_path); if (bind(sockfd, (struct sockaddr*)&desc, sizeof desc) == -1) { close(sockfd); return NULL; } if (listen(sockfd, 5) == -1) { close(sockfd); return NULL; } struct imv_ipc *ipc = calloc(1, sizeof *ipc); ipc->fd = sockfd; pthread_t thread; pthread_create(&thread, NULL, wait_for_connections, ipc); pthread_detach(thread); return ipc; } void imv_ipc_free(struct imv_ipc *ipc) { if (!ipc) { return; } char ipc_filename[1024]; imv_ipc_path(ipc_filename, sizeof ipc_filename, getpid()); unlink(ipc_filename); close(ipc->fd); free(ipc); } void imv_ipc_set_command_callback(struct imv_ipc *ipc, imv_ipc_callback callback, void *data) { ipc->callback = callback; ipc->data = data; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/ipc.h���������������������������������������������������������������������������������0000664�0000000�0000000�00000001637�13531522132�0014026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_IPC_H #define IMV_IPC_H #include /* imv_ipc provides a listener on a unix socket that listens for commands. * When a command is received, a callback function is called. */ struct imv_ipc; /* Creates an imv_ipc instance */ struct imv_ipc *imv_ipc_create(void); /* Cleans up an imv_ipc instance */ void imv_ipc_free(struct imv_ipc *ipc); typedef void (*imv_ipc_callback)(const char *command, void *data); /* When a command is received, imv_ipc will call the callback function passed * in. Only one callback function at a time can be connected. The data argument * is passed back to the callback to allow for context passing */ void imv_ipc_set_command_callback(struct imv_ipc *ipc, imv_ipc_callback callback, void *data); /* Given a pid, emits the path of the unix socket that would connect to an imv * instance with that pid */ void imv_ipc_path(char *buf, size_t len, int pid); #endif �������������������������������������������������������������������������������������������������imv-4.0.1/src/ipc_common.c��������������������������������������������������������������������������0000664�0000000�0000000�00000000372�13531522132�0015364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "ipc.h" #include #include void imv_ipc_path(char *buf, size_t len, int pid) { const char *base = getenv("XDG_RUNTIME_DIR"); if (!base) { base = "/tmp"; } snprintf(buf, len, "%s/imv-%d.sock", base, pid); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/keyboard.c����������������������������������������������������������������������������0000664�0000000�0000000�00000010113�13531522132�0015033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "keyboard.h" #include #include #include #include #include #include struct imv_keyboard { struct xkb_context *context; struct xkb_keymap *keymap; struct xkb_state *state; }; struct imv_keyboard *imv_keyboard_create(void) { struct imv_keyboard *keyboard = calloc(1, sizeof *keyboard); keyboard->context = xkb_context_new(0); assert(keyboard->context); struct xkb_rule_names names = { .rules = NULL, .model = NULL, .layout = NULL, .variant = NULL, .options = NULL, }; keyboard->keymap = xkb_keymap_new_from_names(keyboard->context, &names, 0); assert(keyboard->keymap); keyboard->state = xkb_state_new(keyboard->keymap); assert(keyboard->state); return keyboard; } void imv_keyboard_free(struct imv_keyboard *keyboard) { if (!keyboard) { return; } xkb_state_unref(keyboard->state); keyboard->state = NULL; xkb_keymap_unref(keyboard->keymap); keyboard->keymap = NULL; xkb_context_unref(keyboard->context); keyboard->context = NULL; free(keyboard); } static const int scancode_offset = 8; void imv_keyboard_update_key(struct imv_keyboard *keyboard, int scancode, bool pressed) { xkb_state_update_key(keyboard->state, scancode + scancode_offset, pressed ? XKB_KEY_DOWN : XKB_KEY_UP); } void imv_keyboard_update_mods(struct imv_keyboard *keyboard, int depressed, int latched, int locked) { xkb_state_update_mask(keyboard->state, depressed, latched, locked, 0, 0, 0); } static const char *describe_prefix(struct imv_keyboard *keyboard) { const bool ctrl = (xkb_state_mod_name_is_active(keyboard->state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0); const bool alt = (xkb_state_mod_name_is_active(keyboard->state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) > 0); const bool shift = (xkb_state_mod_name_is_active(keyboard->state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) > 0); if (ctrl && !alt && !shift) { return "Ctrl+"; } else if (!ctrl && alt && !shift) { return "Meta+"; } else if (!ctrl && !alt && shift) { return "Shift+"; } else if (ctrl && alt && !shift) { return "Ctrl+Meta+"; } else if (ctrl && !alt && shift) { return "Ctrl+Shift+"; } else if (!ctrl && alt && shift) { return "Meta+Shift+"; } else if (ctrl && alt && shift) { return "Ctrl+Meta+Shift+"; } else { return ""; } } size_t imv_keyboard_keyname(struct imv_keyboard *keyboard, int scancode, char *buf, size_t buflen) { xkb_keysym_t keysym = xkb_state_key_get_one_sym(keyboard->state, scancode + scancode_offset); return xkb_keysym_get_name(keysym, buf, buflen); } char *imv_keyboard_describe_key(struct imv_keyboard *keyboard, int scancode) { char keyname[128] = {0}; imv_keyboard_keyname(keyboard, scancode, keyname, sizeof keyname); /* Modifier keys don't count on their own, only when pressed with another key */ if (!strcmp(keyname, "Control_L") || !strcmp(keyname, "Control_R") || !strcmp(keyname, "Alt_L") || !strcmp(keyname, "Alt_R") || !strcmp(keyname, "Shift_L") || !strcmp(keyname, "Shift_R") || !strcmp(keyname, "Meta_L") || !strcmp(keyname, "Meta_R") || !strcmp(keyname, "Super_L") || !strcmp(keyname, "Super_R")) { return NULL; } const char *prefix = describe_prefix(keyboard); char buf[128]; snprintf(buf, sizeof buf, "%s%s", prefix, keyname); return strdup(buf); } size_t imv_keyboard_get_text(struct imv_keyboard *keyboard, int scancode, char *buf, size_t buflen) { return xkb_state_key_get_utf8(keyboard->state, scancode + scancode_offset, buf, buflen); } void imv_keyboard_set_keymap(struct imv_keyboard *keyboard, const char *keymap) { xkb_keymap_unref(keyboard->keymap); keyboard->keymap = xkb_keymap_new_from_string(keyboard->context, keymap, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); xkb_state_unref(keyboard->state); keyboard->state = xkb_state_new(keyboard->keymap); } bool imv_keyboard_should_key_repeat(struct imv_keyboard *keyboard, int scancode) { return xkb_keymap_key_repeats(keyboard->keymap, scancode + scancode_offset); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/keyboard.h����������������������������������������������������������������������������0000664�0000000�0000000�00000002575�13531522132�0015055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_KEYBOARD_H #define IMV_KEYBOARD_H #include #include struct imv_keyboard; /* Create a keyboard instance */ struct imv_keyboard *imv_keyboard_create(void); /* Clean up a keyboard */ void imv_keyboard_free(struct imv_keyboard *keyboard); /* Notify the keyboard of the state of a key */ void imv_keyboard_update_key(struct imv_keyboard *keyboard, int scancode, bool pressed); /* Notify the keyboard of the state of the modifiers */ void imv_keyboard_update_mods(struct imv_keyboard *keyboard, int depressed, int latched, int locked); /* Write the null-terminated name of the key corresponding to scancode into buf */ size_t imv_keyboard_keyname(struct imv_keyboard *keyboard, int scancode, char *buf, size_t buflen); /* Describe the key corresponding to scancode, with modifier keys prefixed */ char *imv_keyboard_describe_key(struct imv_keyboard *keyboard, int scancode); /* Write the null-terminated text generated by scancode being pressed into buf */ size_t imv_keyboard_get_text(struct imv_keyboard *keyboard, int scancode, char *buf, size_t buflen); /* Initialise the keymap from a string containing the description */ void imv_keyboard_set_keymap(struct imv_keyboard *keyboard, const char *keymap); /* Should the key on a given scancode repeat when held down */ bool imv_keyboard_should_key_repeat(struct imv_keyboard *keyboard, int scancode); #endif �����������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/list.c��������������������������������������������������������������������������������0000664�0000000�0000000�00000005041�13531522132�0014212�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "list.h" #include struct list *list_create(void) { struct list *list = malloc(sizeof *list); list->len = 0; list->cap = 64; list->items = malloc(sizeof(void*) * list->cap); return list; } void list_free(struct list *list) { if (list) { free(list->items); free(list); } } void list_deep_free(struct list *list) { for(size_t i = 0; i < list->len; ++i) { free(list->items[i]); } list_free(list); } void list_append(struct list *list, void *item) { list_grow(list, list->len + 1); list->items[list->len++] = item; } void list_grow(struct list *list, size_t min_size) { if(list->cap >= min_size) { return; } while(list->cap < min_size) { list->cap *= 2; } list->items = realloc(list->items, sizeof(void*) * list->cap); } void list_remove(struct list *list, size_t index) { if(index >= list->len) { return; } memmove(&list->items[index], &list->items[index + 1], sizeof(void*) * (list->len - index)); list->len -= 1; } void list_insert(struct list *list, size_t index, void *item) { list_grow(list, list->len + 1); if(index > list->len) { index = list->len; } memmove(&list->items[index + 1], &list->items[index], sizeof(void*) * (list->len - index)); list->items[index] = item; list->len += 1; } void list_clear(struct list *list) { list->len = 0; } struct list *list_from_string(const char *string, char delim) { struct list *list = list_create(); const char *base = string; while(*base) { while(*base && *base == delim) { ++base; } const char *end = base; while(*end && *end != delim) { ++end; } if(*base && base != end) { char *item = strndup(base, end - base); list_append(list, item); base = end; } } return list; } int list_find(struct list *list, int (*cmp)(const void *, const void *), const void *key) { for(size_t i = 0; i < list->len; ++i) { if(!cmp(list->items[i], key)) { return (int)i; } } return -1; } char *list_to_string(struct list *list, const char *sep, size_t start) { size_t len = 0; size_t cap = 512; char *buf = malloc(cap); buf[0] = 0; size_t sep_len = strlen(sep); for (size_t i = start; i < list->len; ++i) { size_t item_len = strlen(list->items[i]); if (len + item_len + sep_len >= cap) { cap *= 2; buf = realloc(buf, cap); assert(buf); } strncat(buf, list->items[i], cap - 1); len += item_len; strncat(buf, sep, cap - 1); len += sep_len; } return buf; } /* vim:set ts=2 sts=2 sw=2 et: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/list.h��������������������������������������������������������������������������������0000664�0000000�0000000�00000003414�13531522132�0014221�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef LIST_H #define LIST_H #include #include /* Generic list. You know what this is. */ struct list { size_t len; size_t cap; void **items; }; /* Create a list instance */ struct list *list_create(void); /* Clean up a list, caller should clean up contents of the list first */ void list_free(struct list *list); /* Clean up a list, calling free() on each item first */ void list_deep_free(struct list *list); /* Append an item to the list. Automatically resizes the list if needed */ void list_append(struct list *list, void *item); /* Grow the list's storage to a given size, useful for avoiding unneccessary * reallocations prior to inserting many items */ void list_grow(struct list *list, size_t min_size); /* Remove an item from the list at a given 0-based index */ void list_remove(struct list *list, size_t index); /* Insert an item into the list before the given index */ void list_insert(struct list *list, size_t index, void *item); /* Empty the list. Caller should clean up the contents of the list first */ void list_clear(struct list *list); /* Split a null-terminated string, separating by the given delimiter. * Multiple consecutive delimiters count as a single delimiter, so no empty * string list items are emitted */ struct list *list_from_string(const char *string, char delim); /* Returns the index of the first item to match key, determined by the cmp * function */ int list_find( struct list *list, int (*cmp)(const void *item, const void *key), const void *key ); /* Assumes all list items are null-terminated strings, and concatenates them * separated by sep, starting at the index given by start */ char *list_to_string(struct list *list, const char *sep, size_t start); #endif /* vim:set ts=2 sts=2 sw=2 et: */ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/log.c���������������������������������������������������������������������������������0000664�0000000�0000000�00000002603�13531522132�0014021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "log.h" #include "list.h" #include #include #include #include static struct list *g_log_clients = NULL; static char g_log_buffer[16384]; struct log_client { imv_log_callback callback; void *data; }; void imv_log(enum imv_log_level level, const char *fmt, ...) { assert(fmt); /* Exit early if no one's listening */ if (!g_log_clients || !g_log_clients->len) { return; } va_list args; va_start(args, fmt); vsnprintf(g_log_buffer, sizeof g_log_buffer, fmt, args); va_end(args); for (size_t i = 0; i < g_log_clients->len; ++i) { struct log_client *client = g_log_clients->items[i]; client->callback(level, g_log_buffer, client->data); } } void imv_log_add_log_callback(imv_log_callback callback, void *data) { assert(callback); struct log_client *client = calloc(1, sizeof *client); client->callback = callback; client->data = data; if (!g_log_clients) { g_log_clients = list_create(); } list_append(g_log_clients, client); } void imv_log_remove_log_callback(imv_log_callback callback) { assert(callback); if (!callback || !g_log_clients) { return; } for (size_t i = 0; i < g_log_clients->len; ++i) { struct log_client *client = g_log_clients->items[i]; if (client->callback == callback) { free(client); list_remove(g_log_clients, i); return; } } } �����������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/log.h���������������������������������������������������������������������������������0000664�0000000�0000000�00000001036�13531522132�0014025�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_LOG_H #define IMV_LOG_H enum imv_log_level { IMV_DEBUG, IMV_INFO, IMV_WARNING, IMV_ERROR }; /* Write to the log */ void imv_log(enum imv_log_level level, const char *fmt, ...); typedef void (*imv_log_callback)(enum imv_log_level level, const char *text, void *data); /* Subscribe to the log, callback is called whenever a log entry is written */ void imv_log_add_log_callback(imv_log_callback callback, void *data); /* Unsubscribe from the log */ void imv_log_remove_log_callback(imv_log_callback callback); #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/main.c��������������������������������������������������������������������������������0000664�0000000�0000000�00000002070�13531522132�0014162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "imv.h" struct imv_backend; extern const struct imv_backend imv_backend_freeimage; extern const struct imv_backend imv_backend_libpng; extern const struct imv_backend imv_backend_librsvg; extern const struct imv_backend imv_backend_libtiff; extern const struct imv_backend imv_backend_libjpeg; int main(int argc, char **argv) { struct imv *imv = imv_create(); if (!imv) { return 1; } #ifdef IMV_BACKEND_LIBTIFF imv_install_backend(imv, &imv_backend_libtiff); #endif #ifdef IMV_BACKEND_LIBPNG imv_install_backend(imv, &imv_backend_libpng); #endif #ifdef IMV_BACKEND_LIBJPEG imv_install_backend(imv, &imv_backend_libjpeg); #endif #ifdef IMV_BACKEND_LIBRSVG imv_install_backend(imv, &imv_backend_librsvg); #endif #ifdef IMV_BACKEND_FREEIMAGE imv_install_backend(imv, &imv_backend_freeimage); #endif if (!imv_load_config(imv)) { imv_free(imv); return 1; } if (!imv_parse_args(imv, argc, argv)) { imv_free(imv); return 1; } int ret = imv_run(imv); imv_free(imv); return ret; } /* vim:set ts=2 sts=2 sw=2 et: */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/navigator.c���������������������������������������������������������������������������0000664�0000000�0000000�00000016713�13531522132�0015241�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "navigator.h" #include #include #include #include #include #include #include #include #include #include "list.h" /* Some systems like GNU/Hurd don't define PATH_MAX */ #ifndef PATH_MAX #define PATH_MAX 4096 #endif struct nav_item { char *path; }; struct imv_navigator { struct list *paths; size_t cur_path; time_t last_change; time_t last_check; int last_move_direction; int changed; int wrapped; }; struct imv_navigator *imv_navigator_create(void) { struct imv_navigator *nav = calloc(1, sizeof *nav); nav->last_move_direction = 1; nav->paths = list_create(); return nav; } void imv_navigator_free(struct imv_navigator *nav) { for (size_t i = 0; i < nav->paths->len; ++i) { struct nav_item *nav_item = nav->paths->items[i]; free(nav_item->path); } list_deep_free(nav->paths); free(nav); } static int add_item(struct imv_navigator *nav, const char *path) { struct nav_item *nav_item = calloc(1, sizeof *nav_item); nav_item->path = realpath(path, NULL); if (!nav_item->path) { nav_item->path = strdup(path); } list_append(nav->paths, nav_item); if (nav->paths->len == 1) { nav->cur_path = 0; nav->changed = 1; } return 0; } int imv_navigator_add(struct imv_navigator *nav, const char *path, int recursive) { char path_buf[PATH_MAX+1]; struct stat path_info; if (stat(path, &path_info)) { return 1; } if (S_ISDIR(path_info.st_mode)) { int result = 0; DIR *d = opendir(path); if (d) { struct dirent *dir; while ((dir = readdir(d)) != NULL) { if (strcmp(dir->d_name, "..") == 0 || strcmp(dir->d_name, ".") == 0) { continue; } snprintf(path_buf, sizeof path_buf, "%s/%s", path, dir->d_name); struct stat new_path_info; if (stat(path_buf, &new_path_info)) { result = 1; break; } int is_dir = S_ISDIR(new_path_info.st_mode); if (is_dir && recursive) { if (imv_navigator_add(nav, path_buf, recursive) != 0) { result = 1; break; } } else if (!is_dir) { if (add_item(nav, path_buf) != 0) { result = 1; break; } } } closedir(d); } return result; } else { return add_item(nav, path); } return 0; } const char *imv_navigator_selection(struct imv_navigator *nav) { const char *path = imv_navigator_at(nav, nav->cur_path); return path ? path : ""; } size_t imv_navigator_index(struct imv_navigator *nav) { return nav->cur_path; } void imv_navigator_select_rel(struct imv_navigator *nav, ssize_t direction) { const ssize_t prev_path = nav->cur_path; if (nav->paths->len == 0) { return; } if (direction > 1) { direction = direction % nav->paths->len; } else if (direction < -1) { direction = direction % nav->paths->len; } else if (direction == 0) { return; } ssize_t new_path = nav->cur_path + direction; if (new_path >= (ssize_t)nav->paths->len) { /* Wrap after the end of the list */ new_path -= (ssize_t)nav->paths->len; nav->wrapped = 1; } else if (new_path < 0) { /* Wrap before the start of the list */ new_path += (ssize_t)nav->paths->len; nav->wrapped = 1; } nav->cur_path = (size_t)new_path; nav->last_move_direction = direction; nav->changed = prev_path != new_path; return; } void imv_navigator_select_abs(struct imv_navigator *nav, ssize_t index) { /* allow -1 to indicate the last image */ if (index < 0) { index += (ssize_t)nav->paths->len; /* but if they go farther back than the first image, stick to first image */ if (index < 0) { index = 0; } } /* stick to last image if we go beyond it */ if (index >= (ssize_t)nav->paths->len) { index = (ssize_t)nav->paths->len - 1; } const size_t prev_path = nav->cur_path; nav->cur_path = (size_t)index; nav->changed = prev_path != nav->cur_path; nav->last_move_direction = (index >= (ssize_t)prev_path) ? 1 : -1; } void imv_navigator_remove(struct imv_navigator *nav, const char *path) { bool found = false; size_t removed = 0; for (size_t i = 0; i < nav->paths->len; ++i) { struct nav_item *item = nav->paths->items[i]; if (!strcmp(item->path, path)) { free(item->path); free(item); list_remove(nav->paths, i); removed = i; found = true; break; } } if (!found) { return; } if (nav->cur_path == removed) { /* We just removed the current path */ if (nav->last_move_direction < 0) { /* Move left */ imv_navigator_select_rel(nav, -1); } else { /* Try to stay where we are, unless we ran out of room */ if (nav->cur_path == nav->paths->len) { nav->cur_path = 0; nav->wrapped = 1; } } } nav->changed = 1; } void imv_navigator_remove_at(struct imv_navigator *nav, size_t index) { if (index >= nav->paths->len) { return; } struct nav_item *item = nav->paths->items[index]; free(item->path); free(item); list_remove(nav->paths, index); if (nav->cur_path == index) { /* We just removed the current path */ if (nav->last_move_direction < 0) { /* Move left */ imv_navigator_select_rel(nav, -1); } else { /* Try to stay where we are, unless we ran out of room */ if (nav->cur_path == nav->paths->len) { nav->cur_path = 0; nav->wrapped = 1; } } } nav->changed = 1; } void imv_navigator_remove_all(struct imv_navigator *nav) { for (size_t i = 0; i < nav->paths->len; ++i) { struct nav_item *item = nav->paths->items[i]; free(item->path); free(item); } list_clear(nav->paths); nav->cur_path = 0; nav->changed = 1; } ssize_t imv_navigator_find_path(struct imv_navigator *nav, const char *path) { /* first try to match the exact path */ for (size_t i = 0; i < nav->paths->len; ++i) { struct nav_item *item = nav->paths->items[i]; if (!strcmp(item->path, path)) { return (ssize_t)i; } } /* no exact matches, try the final portion of the path */ for (size_t i = 0; i < nav->paths->len; ++i) { struct nav_item *item = nav->paths->items[i]; char *last_sep = strrchr(item->path, '/'); if (last_sep && !strcmp(last_sep+1, path)) { return (ssize_t)i; } } /* no matches at all, give up */ return -1; } int imv_navigator_poll_changed(struct imv_navigator *nav) { if (nav->changed) { nav->changed = 0; nav->last_change = time(NULL); return 1; } if (nav->paths->len == 0) { return 0; }; time_t cur_time = time(NULL); /* limit polling to once per second */ if (nav->last_check < cur_time - 1) { nav->last_check = cur_time; struct stat file_info; struct nav_item *cur_item = nav->paths->items[nav->cur_path]; if (stat(cur_item->path, &file_info) == -1) { return 0; } time_t file_changed = file_info.st_mtim.tv_sec; if (file_changed > nav->last_change) { nav->last_change = file_changed; return 1; } } return 0; } int imv_navigator_wrapped(struct imv_navigator *nav) { return nav->wrapped; } size_t imv_navigator_length(struct imv_navigator *nav) { return nav->paths->len; } char *imv_navigator_at(struct imv_navigator *nav, size_t index) { if (index < nav->paths->len) { struct nav_item *item = nav->paths->items[index]; return item->path; } return NULL; } /* vim:set ts=2 sts=2 sw=2 et: */ �����������������������������������������������������imv-4.0.1/src/navigator.h���������������������������������������������������������������������������0000664�0000000�0000000�00000004562�13531522132�0015245�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_NAVIGATOR_H #define IMV_NAVIGATOR_H #include /* Creates an instance of imv_navigator */ struct imv_navigator *imv_navigator_create(void); /* Cleans up an imv_navigator instance */ void imv_navigator_free(struct imv_navigator *nav); /* Adds the given path to the navigator's internal list. * If a directory is given, all files within that directory are added. * An internal copy of path is made. * If recursive is non-zero then subdirectories are recursed into. * Non-zero return code denotes failure. */ int imv_navigator_add(struct imv_navigator *nav, const char *path, int recursive); /* Returns a read-only reference to the current path. The pointer is only * guaranteed to be valid until the next call to an imv_navigator method. */ const char *imv_navigator_selection(struct imv_navigator *nav); /* Returns the index of the currently selected path */ size_t imv_navigator_index(struct imv_navigator *nav); /* Change the currently selected path. dir = -1 for previous, 1 for next. */ void imv_navigator_select_rel(struct imv_navigator *nav, ssize_t dir); /* Change the currently selected path. 0 = first, 1 = second, etc. */ void imv_navigator_select_abs(struct imv_navigator *nav, ssize_t index); /* Removes the given path. The current selection is updated if necessary, * based on the last direction the selection moved. */ void imv_navigator_remove(struct imv_navigator *nav, const char *path); /* Removes the given index. The current selection is updated if necessary, * based on the last direction the selection moved. */ void imv_navigator_remove_at(struct imv_navigator *nav, size_t index); /* Removes all paths */ void imv_navigator_remove_all(struct imv_navigator *nav); /* Return the index of the path given. Returns -1 if not found. */ ssize_t imv_navigator_find_path(struct imv_navigator *nav, const char *path); /* Returns 1 if either the currently selected path or underlying file has * changed since last called */ int imv_navigator_poll_changed(struct imv_navigator *nav); /* Check whether navigator wrapped around paths list */ int imv_navigator_wrapped(struct imv_navigator *nav); /* Return how many paths in navigator */ size_t imv_navigator_length(struct imv_navigator *nav); /* Return a path for a given index */ char *imv_navigator_at(struct imv_navigator *nav, size_t index); #endif /* vim:set ts=2 sts=2 sw=2 et: */ ����������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/source.h������������������������������������������������������������������������������0000664�0000000�0000000�00000003614�13531522132�0014550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_SOURCE_H #define IMV_SOURCE_H #include #include #include "image.h" struct imv_source_message { /* Pointer to sender of message */ struct imv_source *source; /* User-supplied pointer */ void *user_data; /* Receiver is responsible for destroying image */ struct imv_image *image; /* If an animated gif, the frame's duration in milliseconds, else 0 */ int frametime; /* Error message if image was NULL */ const char *error; }; /* While imv_image represents a single frame of an image, be it a bitmap or * vector image, imv_source represents an open handle to an image file, which * can emit one or more imv_images. */ struct imv_source { /* usually the path of the image this is the source of */ char *name; /* source's image dimensions */ int width; int height; /* Usually 1, more if animated */ int num_frames; /* Next frame to be loaded, 0-indexed */ int next_frame; /* Attempted to be locked by load_first_frame or load_next_frame. * If the mutex can't be locked, the call is aborted. * Used to prevent the source from having multiple worker threads at once. * Released by the source before calling the message callback with a result. */ pthread_mutex_t busy; /* Trigger loading of the first frame. Returns 0 on success. */ void *(*load_first_frame)(struct imv_source *src); /* Trigger loading of next frame. Returns 0 on success. */ void *(*load_next_frame)(struct imv_source *src); /* Safely free contents of this source. After this returns * it is safe to dealocate/overwrite the imv_source instance. */ void (*free)(struct imv_source *src); /* User-specified callback for returning messages */ void (*callback)(struct imv_source_message *message); /* User-specified pointer, included in returned messages */ void *user_data; /* Implementation private data */ void *private; }; #endif ��������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/viewport.c����������������������������������������������������������������������������0000664�0000000�0000000�00000017251�13531522132�0015124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "viewport.h" struct imv_viewport { double scale; struct { int width, height; } window; /* window dimensions */ struct { int width, height; } buffer; /* rendering buffer dimensions */ int x, y; double pan_factor_x, pan_factor_y; int redraw; int playing; int locked; }; static void input_xy_to_render_xy(struct imv_viewport *view, int *x, int *y) { *x *= view->buffer.width / view->window.width; *y *= view->buffer.height / view->window.height; } struct imv_viewport *imv_viewport_create(int window_width, int window_height, int buffer_width, int buffer_height) { struct imv_viewport *view = malloc(sizeof *view); view->window.width = window_width; view->window.height = window_height; view->buffer.width = buffer_width; view->buffer.height = buffer_height; view->scale = 1; view->x = view->y = view->redraw = 0; view->pan_factor_x = view->pan_factor_y = 0.5; view->playing = 1; view->locked = 0; return view; } void imv_viewport_free(struct imv_viewport *view) { free(view); } void imv_viewport_set_playing(struct imv_viewport *view, bool playing) { view->playing = playing; } bool imv_viewport_is_playing(struct imv_viewport *view) { return view->playing; } void imv_viewport_toggle_playing(struct imv_viewport *view) { view->playing = !view->playing; } void imv_viewport_scale_to_actual(struct imv_viewport *view, const struct imv_image *image) { view->scale = 1; view->redraw = 1; view->locked = 1; imv_viewport_center(view, image); } void imv_viewport_get_offset(struct imv_viewport *view, int *x, int *y) { if(x) { *x = view->x; } if(y) { *y = view->y; } } void imv_viewport_get_scale(struct imv_viewport *view, double *scale) { if(scale) { *scale = view->scale; } } void imv_viewport_set_default_pan_factor(struct imv_viewport *view, double pan_factor_x, double pan_factor_y) { view->pan_factor_x = pan_factor_x; view->pan_factor_y = pan_factor_y; } void imv_viewport_move(struct imv_viewport *view, int x, int y, const struct imv_image *image) { input_xy_to_render_xy(view, &x, &y); view->x += x; view->y += y; view->redraw = 1; view->locked = 1; int w = (int)(imv_image_width(image) * view->scale); int h = (int)(imv_image_height(image) * view->scale); if (view->x < -w) { view->x = -w; } if (view->x > view->buffer.width) { view->x = view->buffer.width; } if (view->y < -h) { view->y = -h; } if (view->y > view->buffer.height) { view->y = view->buffer.height; } } void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image, enum imv_zoom_source src, int mouse_x, int mouse_y, int amount) { double prev_scale = view->scale; int x, y; const int image_width = imv_image_width(image); const int image_height = imv_image_height(image); /* x and y cordinates are relative to the image */ if(src == IMV_ZOOM_MOUSE) { input_xy_to_render_xy(view, &mouse_x, &mouse_y); x = mouse_x - view->x; y = mouse_y - view->y; } else { x = view->scale * image_width / 2; y = view->scale * image_height / 2; } const int scaled_width = image_width * view->scale; const int scaled_height = image_height * view->scale; const int ic_x = view->x + scaled_width/2; const int ic_y = view->y + scaled_height/2; const int wc_x = view->buffer.width/2; const int wc_y = view->buffer.height/2; double delta_scale = 0.04 * view->buffer.width * amount / image_width; view->scale += delta_scale; const double min_scale = 0.1; const double max_scale = 100; if(view->scale > max_scale) { view->scale = max_scale; } else if (view->scale < min_scale) { view->scale = min_scale; } if(view->scale < prev_scale) { if(scaled_width < view->buffer.width) { x = scaled_width/2 - (ic_x - wc_x)*2; } if(scaled_height < view->buffer.height) { y = scaled_height/2 - (ic_y - wc_y)*2; } } else { if(scaled_width < view->buffer.width) { x = scaled_width/2; } if(scaled_height < view->buffer.height) { y = scaled_height/2; } } const double changeX = x - (x * (view->scale / prev_scale)); const double changeY = y - (y * (view->scale / prev_scale)); view->x += changeX; view->y += changeY; view->redraw = 1; view->locked = 1; } void imv_viewport_center(struct imv_viewport *view, const struct imv_image *image) { const int image_width = imv_image_width(image); const int image_height = imv_image_height(image); view->x = view->buffer.width - image_width * view->scale; view->y = view->buffer.height - image_height * view->scale; if (view->x > 0) { /* Image is smaller than the window. Center the image */ view->x *= 0.5; } else { view->x *= view->pan_factor_x; } if (view->y > 0) { /* Image is smaller than the window. Center the image */ view->y *= 0.5; } else { view->y *= view->pan_factor_y; } view->locked = 1; view->redraw = 1; } void imv_viewport_scale_to_window(struct imv_viewport *view, const struct imv_image *image) { const int image_width = imv_image_width(image); const int image_height = imv_image_height(image); const double window_aspect = (double)view->buffer.width / (double)view->buffer.height; const double image_aspect = (double)image_width / (double)image_height; if(window_aspect > image_aspect) { /* Image will become too tall before it becomes too wide */ view->scale = (double)view->buffer.height / (double)image_height; } else { /* Image will become too wide before it becomes too tall */ view->scale = (double)view->buffer.width / (double)image_width; } imv_viewport_center(view, image); view->locked = 0; } void imv_viewport_crop_to_window(struct imv_viewport *view, const struct imv_image *image) { const int image_width = imv_image_width(image); const int image_height = imv_image_height(image); const double window_aspect = (double)view->buffer.width / (double)view->buffer.height; const double image_aspect = (double)image_width / (double)image_height; /* Scale the image so that it fills the whole window */ if(window_aspect > image_aspect) { view->scale = (double)view->buffer.width / (double)image_width; } else { view->scale = (double)view->buffer.height / (double)image_height; } imv_viewport_center(view, image); view->locked = 0; } void imv_viewport_set_redraw(struct imv_viewport *view) { view->redraw = 1; } void imv_viewport_rescale(struct imv_viewport *view, const struct imv_image *image, enum scaling_mode scaling_mode) { if (scaling_mode == SCALING_NONE || (scaling_mode == SCALING_DOWN && view->buffer.width > imv_image_width(image) && view->buffer.height > imv_image_height(image))) { imv_viewport_scale_to_actual(view, image); } else if (scaling_mode == SCALING_CROP) { imv_viewport_crop_to_window(view, image); } else { imv_viewport_scale_to_window(view, image); } } void imv_viewport_update(struct imv_viewport *view, int window_width, int window_height, int buffer_width, int buffer_height, struct imv_image *image, enum scaling_mode scaling_mode) { view->window.width = window_width; view->window.height = window_height; view->buffer.width = buffer_width; view->buffer.height = buffer_height; view->redraw = 1; if(view->locked) { return; } imv_viewport_center(view, image); imv_viewport_rescale(view, image, scaling_mode); } int imv_viewport_needs_redraw(struct imv_viewport *view) { int redraw = 0; if(view->redraw) { redraw = 1; view->redraw = 0; } return redraw; } /* vim:set ts=2 sts=2 sw=2 et: */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/viewport.h����������������������������������������������������������������������������0000664�0000000�0000000�00000006261�13531522132�0015130�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_VIEWPORT_H #define IMV_VIEWPORT_H #include #include "image.h" struct imv_viewport; enum scaling_mode { SCALING_NONE, SCALING_DOWN, SCALING_FULL, SCALING_CROP, SCALING_MODE_COUNT }; /* Used to signify how a a user requested a zoom */ enum imv_zoom_source { IMV_ZOOM_MOUSE, IMV_ZOOM_KEYBOARD }; /* Creates an instance of imv_viewport */ struct imv_viewport *imv_viewport_create(int window_width, int window_height, int buffer_width, int buffer_height); /* Cleans up an imv_viewport instance */ void imv_viewport_free(struct imv_viewport *view); /* Set playback of animated gifs */ void imv_viewport_set_playing(struct imv_viewport *view, bool playing); /* Get playback status of animated gifs */ bool imv_viewport_is_playing(struct imv_viewport *view); /* Toggle playback of animated gifs */ void imv_viewport_toggle_playing(struct imv_viewport *view); /* Fetch viewport offset/position */ void imv_viewport_get_offset(struct imv_viewport *view, int *x, int *y); /* Fetch viewport scale */ void imv_viewport_get_scale(struct imv_viewport *view, double *scale); /* Set the default pan_factor factor for the x and y position */ void imv_viewport_set_default_pan_factor(struct imv_viewport *view, double pan_factor_x, double pan_factor_y); /* Pan the view by the given amounts without letting the image get too far * off-screen */ void imv_viewport_move(struct imv_viewport *view, int x, int y, const struct imv_image *image); /* Zoom the view by the given amount. imv_image* is used to get the image * dimensions */ void imv_viewport_zoom(struct imv_viewport *view, const struct imv_image *image, enum imv_zoom_source, int mouse_x, int mouse_y, int amount); /* Recenter the view to be in the middle of the image */ void imv_viewport_center(struct imv_viewport *view, const struct imv_image *image); /* Scale the view so that the image appears at its actual resolution */ void imv_viewport_scale_to_actual(struct imv_viewport *view, const struct imv_image *image); /* Scale the view so that the image fits in the window */ void imv_viewport_scale_to_window(struct imv_viewport *view, const struct imv_image *image); /* Scale the view so that the image fills the window */ void imv_viewport_crop_to_window(struct imv_viewport *view, const struct imv_image *image); /* Rescale the view with the chosen scaling method */ void imv_viewport_rescale(struct imv_viewport *view, const struct imv_image *image, enum scaling_mode); /* Tell the viewport that it needs to be redrawn */ void imv_viewport_set_redraw(struct imv_viewport *view); /* Tell the viewport the window or image has changed */ void imv_viewport_update(struct imv_viewport *view, int window_width, int window_height, int buffer_width, int buffer_height, struct imv_image *image, enum scaling_mode); /* Poll whether we need to redraw */ int imv_viewport_needs_redraw(struct imv_viewport *view); #endif /* vim:set ts=2 sts=2 sw=2 et: */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/window.h������������������������������������������������������������������������������0000664�0000000�0000000�00000005237�13531522132�0014562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef IMV_WINDOW_H #define IMV_WINDOW_H #include struct imv_window; enum imv_event_type { IMV_EVENT_CLOSE, IMV_EVENT_RESIZE, IMV_EVENT_KEYBOARD, IMV_EVENT_KEYBOARD_MODS, IMV_EVENT_MOUSE_MOTION, IMV_EVENT_MOUSE_BUTTON, IMV_EVENT_MOUSE_SCROLL, IMV_EVENT_CUSTOM }; struct imv_event { enum imv_event_type type; union { struct { int width; int height; int buffer_width; int buffer_height; } resize; struct { int scancode; char *keyname; char *description; char *text; } keyboard; struct { double x, y, dx, dy; } mouse_motion; struct { int button; bool pressed; } mouse_button; struct { double dx, dy; } mouse_scroll; void *custom; } data; }; /* Create a new window */ struct imv_window *imv_window_create(int w, int h, const char *title); /* Clean up an imv_window instance */ void imv_window_free(struct imv_window *window); /* Clears the window with the given colour */ void imv_window_clear(struct imv_window *window, unsigned char r, unsigned char g, unsigned char b); /* Get the logical/event/window manager size of the window */ void imv_window_get_size(struct imv_window *window, int *w, int *h); /* Get the pixel dimensions that the window is rendered at */ void imv_window_get_framebuffer_size(struct imv_window *window, int *w, int *h); /* Set the window's title */ void imv_window_set_title(struct imv_window *window, const char *title); /* Returns true if the window is fullscreen */ bool imv_window_is_fullscreen(struct imv_window *window); /* Set the window's fullscreen state */ void imv_window_set_fullscreen(struct imv_window *window, bool fullscreen); /* Check whether a given mouse button is pressed */ bool imv_window_get_mouse_button(struct imv_window *window, int button); /* Gets the mouse's position */ void imv_window_get_mouse_position(struct imv_window *window, double *x, double *y); /* Swap the framebuffers. Present anything rendered since the last call. */ void imv_window_present(struct imv_window *window); /* Blocks until an event is received, or the timeout (in seconds) expires */ void imv_window_wait_for_event(struct imv_window *window, double timeout); /* Push an event to the event queue. An internal copy of the event is made. * Wakes up imv_window_wait_for_event */ void imv_window_push_event(struct imv_window *window, struct imv_event *e); typedef void (*imv_event_handler)(void *data, const struct imv_event *e); /* When called, the handler function is called for each event on the event * queue */ void imv_window_pump_events(struct imv_window *window, imv_event_handler handler, void *data); #endif �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������imv-4.0.1/src/wl_window.c���������������������������������������������������������������������������0000664�0000000�0000000�00000056622�13531522132�0015263�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "window.h" #include "keyboard.h" #include "list.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xdg-shell-client-protocol.h" struct imv_window { struct wl_display *wl_display; struct wl_registry *wl_registry; struct wl_compositor *wl_compositor; struct wl_surface *wl_surface; struct xdg_wm_base *wl_xdg; struct xdg_surface *wl_xdg_surface; struct xdg_toplevel *wl_xdg_toplevel; struct wl_seat *wl_seat; struct wl_keyboard *wl_keyboard; struct wl_pointer *wl_pointer; EGLDisplay egl_display; EGLContext egl_context; EGLSurface egl_surface; struct wl_egl_window *egl_window; struct imv_keyboard *keyboard; struct list *wl_outputs; int display_fd; int pipe_fds[2]; timer_t timer_id; int repeat_scancode; /* scancode of key to repeat */ int repeat_delay; /* time before repeat in ms */ int repeat_interval; /* time between repeats in ms */ int width; int height; bool fullscreen; int scale; struct { struct { double last; double current; } x; struct { double last; double current; } y; struct { bool last; bool current; } mouse1; struct { double dx; double dy; } scroll; } pointer; }; struct output_data { struct wl_output *wl_output; int scale; int pending_scale; bool contains_window; }; static void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL); assert(flags != -1); flags |= O_NONBLOCK; int rc = fcntl(fd, F_SETFL, flags); assert(rc != -1); } static void handle_ping_xdg_wm_base(void *data, struct xdg_wm_base *xdg, uint32_t serial) { (void)data; xdg_wm_base_pong(xdg, serial); } static const struct xdg_wm_base_listener shell_listener_xdg = { .ping = handle_ping_xdg_wm_base }; static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { (void)keyboard; (void)format; struct imv_window *window = data; char *src = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); imv_keyboard_set_keymap(window->keyboard, src); munmap(src, size); close(fd); } static void keyboard_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { (void)data; (void)keyboard; (void)serial; (void)surface; (void)keys; } static void keyboard_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { (void)data; (void)keyboard; (void)serial; (void)surface; } static void cleanup_event(struct imv_event *event) { if (event->type == IMV_EVENT_KEYBOARD) { free(event->data.keyboard.keyname); free(event->data.keyboard.text); } } static void push_keypress(struct imv_window *window, int scancode) { char keyname[32] = {0}; imv_keyboard_keyname(window->keyboard, scancode, keyname, sizeof keyname); char text[64] = {0}; imv_keyboard_get_text(window->keyboard, scancode, text, sizeof text); char *desc = imv_keyboard_describe_key(window->keyboard, scancode); if (!desc) { desc = strdup(""); } struct imv_event e = { .type = IMV_EVENT_KEYBOARD, .data = { .keyboard = { .scancode = scancode, .keyname = strdup(keyname), .description = desc, .text = strdup(text), } } }; imv_window_push_event(window, &e); } static void keyboard_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { (void)serial; (void)keyboard; (void)time; struct imv_window *window = data; imv_keyboard_update_key(window->keyboard, key, state); if (!state) { /* If a key repeat timer is running, stop it */ struct itimerspec off = { .it_value = { .tv_sec = 0, .tv_nsec = 0, }, .it_interval = { .tv_sec = 0, .tv_nsec = 0, }, }; timer_settime(window->timer_id, 0, &off, NULL); return; } push_keypress(window, key); if (imv_keyboard_should_key_repeat(window->keyboard, key)) { /* Kick off the key-repeat timer for the current key */ window->repeat_scancode = key; struct itimerspec period = { .it_value = { .tv_sec = 0, .tv_nsec = window->repeat_delay * 1000 * 1000, }, .it_interval = { .tv_sec = 0, .tv_nsec = window->repeat_interval * 1000 * 1000, }, }; timer_settime(window->timer_id, 0, &period, NULL); } } static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { (void)keyboard; (void)serial; (void)group; struct imv_window *window = data; imv_keyboard_update_mods(window->keyboard, mods_depressed, mods_latched, mods_locked); } static void keyboard_repeat(void *data, struct wl_keyboard *keyboard, int32_t rate, int32_t delay) { (void)keyboard; struct imv_window *window = data; window->repeat_delay = delay; window->repeat_interval = 1000 / rate; } static const struct wl_keyboard_listener keyboard_listener = { .keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_modifiers, .repeat_info = keyboard_repeat }; static void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { (void)pointer; (void)serial; (void)surface; struct imv_window *window = data; window->pointer.x.last = wl_fixed_to_double(surface_x); window->pointer.y.last = wl_fixed_to_double(surface_y); window->pointer.x.current = wl_fixed_to_double(surface_x); window->pointer.y.current = wl_fixed_to_double(surface_y); } static void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { (void)data; (void)pointer; (void)serial; (void)surface; } static void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { (void)pointer; (void)time; struct imv_window *window = data; window->pointer.x.current = wl_fixed_to_double(surface_x); window->pointer.y.current = wl_fixed_to_double(surface_y); } static void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { (void)pointer; (void)serial; (void)time; struct imv_window *window = data; const uint32_t MOUSE1 = 0x110; if (button == MOUSE1) { window->pointer.mouse1.current = state; } } static void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { (void)pointer; (void)time; struct imv_window *window = data; if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { window->pointer.scroll.dy += wl_fixed_to_double(value); } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { window->pointer.scroll.dx += wl_fixed_to_double(value); } } static void pointer_frame(void *data, struct wl_pointer *pointer) { (void)pointer; struct imv_window *window = data; int dx = window->pointer.x.current - window->pointer.x.last; int dy = window->pointer.y.current - window->pointer.y.last; window->pointer.x.last = window->pointer.x.current; window->pointer.y.last = window->pointer.y.current; if (dx || dy) { struct imv_event e = { .type = IMV_EVENT_MOUSE_MOTION, .data = { .mouse_motion = { .x = window->pointer.x.current, .y = window->pointer.y.current, .dx = dx, .dy = dy, } } }; imv_window_push_event(window, &e); } if (window->pointer.mouse1.current != window->pointer.mouse1.last) { window->pointer.mouse1.last = window->pointer.mouse1.current; struct imv_event e = { .type = IMV_EVENT_MOUSE_BUTTON, .data = { .mouse_button = { .button = 1, .pressed = window->pointer.mouse1.current } } }; imv_window_push_event(window, &e); } if (window->pointer.scroll.dx || window->pointer.scroll.dy) { struct imv_event e = { .type = IMV_EVENT_MOUSE_SCROLL, .data = { .mouse_scroll = { .dx = window->pointer.scroll.dx, .dy = window->pointer.scroll.dy } } }; imv_window_push_event(window, &e); window->pointer.scroll.dx = 0; window->pointer.scroll.dy = 0; } } static void pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) { (void)data; (void)pointer; (void)axis_source; } static void pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) { (void)data; (void)pointer; (void)time; (void)axis; } static void pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete) { (void)data; (void)pointer; (void)axis; (void)discrete; } static const struct wl_pointer_listener pointer_listener = { .enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion, .button = pointer_button, .axis = pointer_axis, .frame = pointer_frame, .axis_source = pointer_axis_source, .axis_stop = pointer_axis_stop, .axis_discrete = pointer_axis_discrete }; static void seat_capabilities(void *data, struct wl_seat *seat, uint32_t capabilities) { (void)seat; struct imv_window *window = data; if (capabilities & WL_SEAT_CAPABILITY_POINTER) { if (!window->wl_pointer) { window->wl_pointer = wl_seat_get_pointer(window->wl_seat); wl_pointer_add_listener(window->wl_pointer, &pointer_listener, window); } } else { if (window->wl_pointer) { wl_pointer_release(window->wl_pointer); window->wl_pointer = NULL; } } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { if (!window->wl_keyboard) { window->wl_keyboard = wl_seat_get_keyboard(window->wl_seat); wl_keyboard_add_listener(window->wl_keyboard, &keyboard_listener, window); } } else { if (window->wl_keyboard) { wl_keyboard_release(window->wl_keyboard); window->wl_keyboard = NULL; } } } static void seat_name(void *data, struct wl_seat *seat, const char *name) { (void)data; (void)seat; (void)name; } static const struct wl_seat_listener seat_listener = { .capabilities = seat_capabilities, .name = seat_name }; static void output_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { (void)data; (void)wl_output; (void)x; (void)y; (void)physical_width; (void)physical_height; (void)subpixel; (void)make; (void)model; (void)transform; } static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { (void)data; (void)wl_output; (void)flags; (void)width; (void)height; (void)refresh; } static void output_done(void *data, struct wl_output *wl_output) { (void)data; struct output_data *output_data = wl_output_get_user_data(wl_output); output_data->scale = output_data->pending_scale; } static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { (void)data; struct output_data *output_data = wl_output_get_user_data(wl_output); output_data->pending_scale = factor; } static const struct wl_output_listener output_listener = { .geometry = output_geometry, .mode = output_mode, .done = output_done, .scale = output_scale }; static void on_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) { struct imv_window *window = data; if (!strcmp(interface, "wl_compositor")) { window->wl_compositor = wl_registry_bind(registry, id, &wl_compositor_interface, version); } else if (!strcmp(interface, "xdg_wm_base")) { window->wl_xdg = wl_registry_bind(registry, id, &xdg_wm_base_interface, version); xdg_wm_base_add_listener(window->wl_xdg, &shell_listener_xdg, window); } else if (!strcmp(interface, "wl_seat")) { window->wl_seat = wl_registry_bind(registry, id, &wl_seat_interface, version); wl_seat_add_listener(window->wl_seat, &seat_listener, window); } else if (!strcmp(interface, "wl_output")) { struct output_data *output_data = calloc(1, sizeof *output_data); output_data->wl_output = wl_registry_bind(registry, id, &wl_output_interface, version); output_data->pending_scale = 1; wl_output_set_user_data(output_data->wl_output, output_data); wl_output_add_listener(output_data->wl_output, &output_listener, output_data); list_append(window->wl_outputs, output_data); } } static void on_remove_global(void *data, struct wl_registry *registry, uint32_t id) { (void)data; (void)registry; (void)id; } static void update_scale(struct imv_window *window) { int new_scale = 1; for (size_t i = 0; i < window->wl_outputs->len; ++i) { struct output_data *data = window->wl_outputs->items[i]; if (data->contains_window && data->scale > new_scale) { new_scale = data->scale; } } if (new_scale != window->scale) { window->scale = new_scale; wl_surface_set_buffer_scale(window->wl_surface, window->scale); wl_surface_commit(window->wl_surface); size_t buffer_width = window->width * window->scale; size_t buffer_height = window->height * window->scale; wl_egl_window_resize(window->egl_window, buffer_width, buffer_height, 0, 0); glViewport(0, 0, buffer_width, buffer_height); struct imv_event e = { .type = IMV_EVENT_RESIZE, .data = { .resize = { .width = window->width, .height = window->height, .buffer_width = buffer_width, .buffer_height = buffer_height } } }; imv_window_push_event(window, &e); } } static const struct wl_registry_listener registry_listener = { .global = on_global, .global_remove = on_remove_global }; static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *output) { (void)wl_surface; struct output_data *output_data = wl_output_get_user_data(output); output_data->contains_window = true; update_scale(data); } static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *output) { (void)wl_surface; struct output_data *output_data = wl_output_get_user_data(output); output_data->contains_window = false; update_scale(data); } static const struct wl_surface_listener surface_listener = { .enter = surface_enter, .leave = surface_leave }; static void surface_configure(void *data, struct xdg_surface *surface, uint32_t serial) { struct imv_window *window = data; xdg_surface_ack_configure(surface, serial); wl_surface_commit(window->wl_surface); } static const struct xdg_surface_listener shell_surface_listener = { .configure = surface_configure }; static void toplevel_configure(void *data, struct xdg_toplevel *toplevel, int width, int height, struct wl_array *states) { (void)toplevel; struct imv_window *window = data; window->width = width; window->height = height; window->fullscreen = false; enum xdg_toplevel_state *state; wl_array_for_each(state, states) { if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN) { window->fullscreen = true; } } size_t buffer_width = window->width * window->scale; size_t buffer_height = window->height * window->scale; wl_egl_window_resize(window->egl_window, buffer_width, buffer_height, 0, 0); glViewport(0, 0, buffer_width, buffer_height); struct imv_event e = { .type = IMV_EVENT_RESIZE, .data = { .resize = { .width = width, .height = height, .buffer_width = width * window->scale, .buffer_height = height * window->scale } } }; imv_window_push_event(window, &e); } static void toplevel_close(void *data, struct xdg_toplevel *toplevel) { (void)toplevel; struct imv_window *window = data; struct imv_event e = { .type = IMV_EVENT_CLOSE }; imv_window_push_event(window, &e); } static const struct xdg_toplevel_listener toplevel_listener = { .configure = toplevel_configure, .close = toplevel_close }; static void connect_to_wayland(struct imv_window *window) { window->wl_display = wl_display_connect(NULL); assert(window->wl_display); window->display_fd = wl_display_get_fd(window->wl_display); pipe(window->pipe_fds); set_nonblocking(window->pipe_fds[0]); set_nonblocking(window->pipe_fds[1]); window->wl_registry = wl_display_get_registry(window->wl_display); assert(window->wl_registry); wl_registry_add_listener(window->wl_registry, ®istry_listener, window); wl_display_dispatch(window->wl_display); assert(window->wl_compositor); assert(window->wl_xdg); assert(window->wl_seat); window->egl_display = eglGetDisplay(window->wl_display); eglInitialize(window->egl_display, NULL, NULL); } static void create_window(struct imv_window *window, int width, int height, const char *title) { eglBindAPI(EGL_OPENGL_API); EGLint attributes[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE }; EGLConfig config; EGLint num_config; eglChooseConfig(window->egl_display, attributes, &config, 1, &num_config); window->egl_context = eglCreateContext(window->egl_display, config, EGL_NO_CONTEXT, NULL); assert(window->egl_context); window->wl_surface = wl_compositor_create_surface(window->wl_compositor); assert(window->wl_surface); wl_surface_add_listener(window->wl_surface, &surface_listener, window); window->wl_xdg_surface = xdg_wm_base_get_xdg_surface(window->wl_xdg, window->wl_surface); assert(window->wl_xdg_surface); xdg_surface_add_listener(window->wl_xdg_surface, &shell_surface_listener, window); window->wl_xdg_toplevel = xdg_surface_get_toplevel(window->wl_xdg_surface); assert(window->wl_xdg_toplevel); xdg_toplevel_add_listener(window->wl_xdg_toplevel, &toplevel_listener, window); xdg_toplevel_set_title(window->wl_xdg_toplevel, title); xdg_toplevel_set_app_id(window->wl_xdg_toplevel, "imv"); window->egl_window = wl_egl_window_create(window->wl_surface, width, height); window->egl_surface = eglCreateWindowSurface(window->egl_display, config, window->egl_window, NULL); eglMakeCurrent(window->egl_display, window->egl_surface, window->egl_surface, window->egl_context); wl_surface_commit(window->wl_surface); wl_display_roundtrip(window->wl_display); } static void shutdown_wayland(struct imv_window *window) { close(window->pipe_fds[0]); close(window->pipe_fds[1]); if (window->wl_pointer) { wl_pointer_destroy(window->wl_pointer); } if (window->wl_keyboard) { wl_keyboard_destroy(window->wl_keyboard); } if (window->wl_seat) { wl_seat_destroy(window->wl_seat); } if (window->wl_xdg_toplevel) { xdg_toplevel_destroy(window->wl_xdg_toplevel); } if (window->wl_xdg_surface) { xdg_surface_destroy(window->wl_xdg_surface); } if (window->wl_xdg) { xdg_wm_base_destroy(window->wl_xdg); } if (window->egl_window) { wl_egl_window_destroy(window->egl_window); } eglTerminate(window->egl_display); if (window->wl_surface) { wl_surface_destroy(window->wl_surface); } if (window->wl_compositor) { wl_compositor_destroy(window->wl_compositor); } if (window->wl_registry) { wl_registry_destroy(window->wl_registry); } if (window->wl_display) { wl_display_disconnect(window->wl_display); } } static void on_timer(union sigval sigval) { struct imv_window *window = sigval.sival_ptr; push_keypress(window, window->repeat_scancode); } struct imv_window *imv_window_create(int width, int height, const char *title) { /* Ensure event writes will always be atomic */ assert(sizeof(struct imv_event) <= PIPE_BUF); struct imv_window *window = calloc(1, sizeof *window); window->scale = 1; window->keyboard = imv_keyboard_create(); assert(window->keyboard); window->wl_outputs = list_create(); connect_to_wayland(window); create_window(window, width, height, title); struct sigevent timer_handler = { .sigev_notify = SIGEV_THREAD, .sigev_value.sival_ptr = window, .sigev_notify_function = on_timer, }; int timer_rc = timer_create(CLOCK_MONOTONIC, &timer_handler, &window->timer_id); assert(timer_rc == 0); return window; } void imv_window_free(struct imv_window *window) { timer_delete(window->timer_id); imv_keyboard_free(window->keyboard); shutdown_wayland(window); list_deep_free(window->wl_outputs); free(window); } void imv_window_clear(struct imv_window *window, unsigned char r, unsigned char g, unsigned char b) { (void)window; glClearColor(r / 255.0f, g / 255.0f, b / 255.0f, 1.0); glClear(GL_COLOR_BUFFER_BIT); } void imv_window_get_size(struct imv_window *window, int *w, int *h) { if (w) { *w = window->width; } if (h) { *h = window->height; } } void imv_window_get_framebuffer_size(struct imv_window *window, int *w, int *h) { if (w) { *w = window->width * window->scale; } if (h) { *h = window->height * window->scale; } } void imv_window_set_title(struct imv_window *window, const char *title) { xdg_toplevel_set_title(window->wl_xdg_toplevel, title); } bool imv_window_is_fullscreen(struct imv_window *window) { return window->fullscreen; } void imv_window_set_fullscreen(struct imv_window *window, bool fullscreen) { if (window->fullscreen && !fullscreen) { xdg_toplevel_unset_fullscreen(window->wl_xdg_toplevel); } else if (!window->fullscreen && fullscreen) { xdg_toplevel_set_fullscreen(window->wl_xdg_toplevel, NULL); } } bool imv_window_get_mouse_button(struct imv_window *window, int button) { if (button == 1) { return window->pointer.mouse1.last; } return false; } void imv_window_get_mouse_position(struct imv_window *window, double *x, double *y) { if (x) { *x = window->pointer.x.last; } if (y) { *y = window->pointer.y.last; } } void imv_window_present(struct imv_window *window) { eglSwapBuffers(window->egl_display, window->egl_surface); } void imv_window_wait_for_event(struct imv_window *window, double timeout) { struct pollfd fds[] = { {.fd = window->display_fd, .events = POLLIN}, {.fd = window->pipe_fds[0], .events = POLLIN} }; nfds_t nfds = sizeof fds / sizeof *fds; if (wl_display_prepare_read(window->wl_display)) { /* If an event's already in the wayland queue we return */ return; } wl_display_flush(window->wl_display); if (poll(fds, nfds, timeout * 1000) <= 0) { wl_display_cancel_read(window->wl_display); return; } if (fds[0].revents & POLLIN) { wl_display_read_events(window->wl_display); } else { wl_display_cancel_read(window->wl_display); } } void imv_window_push_event(struct imv_window *window, struct imv_event *e) { /* Push it down the pipe */ write(window->pipe_fds[1], e, sizeof *e); } void imv_window_pump_events(struct imv_window *window, imv_event_handler handler, void *data) { wl_display_dispatch_pending(window->wl_display); while (1) { struct imv_event e; ssize_t len = read(window->pipe_fds[0], &e, sizeof e); if (len <= 0) { break; } assert(len == sizeof e); if (handler) { handler(data, &e); } cleanup_event(&e); } } ��������������������������������������������������������������������������������������������������������������imv-4.0.1/src/x11_window.c��������������������������������������������������������������������������0000664�0000000�0000000�00000024762�13531522132�0015252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "window.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "keyboard.h" #include "log.h" struct imv_window { Display *x_display; Window x_window; GLXContext x_glc; Atom x_state; Atom x_fullscreen; int width; int height; struct { struct { int x, y; } last; struct { int x, y; } current; bool mouse1; } pointer; struct imv_keyboard *keyboard; int pipe_fds[2]; }; static void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL); assert(flags != -1); flags |= O_NONBLOCK; int rc = fcntl(fd, F_SETFL, flags); assert(rc != -1); } static void setup_keymap(struct imv_window *window) { xcb_connection_t *conn = xcb_connect(NULL, NULL); if (xcb_connection_has_error(conn)) { imv_log(IMV_ERROR, "x11_window: Failed to load keymap. Could not connect via xcb."); return; } if (!xkb_x11_setup_xkb_extension(conn, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION, 0, NULL, NULL, NULL, NULL)) { xcb_disconnect(conn); imv_log(IMV_ERROR, "x11_window: Failed to load keymap. xkb extension not supported by server."); return; } int32_t device = xkb_x11_get_core_keyboard_device_id(conn); struct xkb_context *context = xkb_context_new(0); if (!context) { xcb_disconnect(conn); imv_log(IMV_ERROR, "x11_window: Failed to load keymap. Failed to initialise xkb context."); return; } struct xkb_keymap *keymap = xkb_x11_keymap_new_from_device(context, conn, device, 0); if (keymap) { char *keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_USE_ORIGINAL_FORMAT); imv_keyboard_set_keymap(window->keyboard, keymap_str); free(keymap_str); } else { imv_log(IMV_ERROR, "x11_window: Failed to load keymap. xkb_x11_keymap_new_from_device returned NULL."); } xkb_context_unref(context); xcb_disconnect(conn); } struct imv_window *imv_window_create(int w, int h, const char *title) { /* Ensure event writes will always be atomic */ assert(sizeof(struct imv_event) <= PIPE_BUF); struct imv_window *window = calloc(1, sizeof *window); window->pointer.last.x = -1; window->pointer.last.y = -1; pipe(window->pipe_fds); set_nonblocking(window->pipe_fds[0]); set_nonblocking(window->pipe_fds[1]); window->x_display = XOpenDisplay(NULL); assert(window->x_display); Window root = DefaultRootWindow(window->x_display); assert(root); GLint att[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; XVisualInfo *vi = glXChooseVisual(window->x_display, 0, att); assert(vi); Colormap cmap = XCreateColormap(window->x_display, root, vi->visual, AllocNone); XSetWindowAttributes wa = { .colormap = cmap, .event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask }; window->x_window = XCreateWindow(window->x_display, root, 0, 0, w, h, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &wa); window->x_state = XInternAtom(window->x_display, "_NET_WM_STATE", true); window->x_fullscreen = XInternAtom(window->x_display, "_NET_WM_STATE_FULLSCREEN", true); XClassHint hint = { .res_name = "imv", .res_class= "imv", }; XSetClassHint(window->x_display, window->x_window, &hint); XMapWindow(window->x_display, window->x_window); XStoreName(window->x_display, window->x_window, title); window->x_glc = glXCreateContext(window->x_display, vi, NULL, GL_TRUE); assert(window->x_glc); glXMakeCurrent(window->x_display, window->x_window, window->x_glc); window->keyboard = imv_keyboard_create(); assert(window->keyboard); setup_keymap(window); return window; } void imv_window_free(struct imv_window *window) { imv_keyboard_free(window->keyboard); close(window->pipe_fds[0]); close(window->pipe_fds[1]); glXDestroyContext(window->x_display, window->x_glc); XCloseDisplay(window->x_display); free(window); } void imv_window_clear(struct imv_window *window, unsigned char r, unsigned char g, unsigned char b) { (void)window; glClearColor(r / 255.0f, g / 255.0f, b / 255.0f, 1.0); glClear(GL_COLOR_BUFFER_BIT); } void imv_window_get_size(struct imv_window *window, int *w, int *h) { if (w) { *w = window->width; } if (h) { *h = window->height; } } void imv_window_get_framebuffer_size(struct imv_window *window, int *w, int *h) { if (w) { *w = window->width; } if (h) { *h = window->height; } } void imv_window_set_title(struct imv_window *window, const char *title) { XStoreName(window->x_display, window->x_window, title); } bool imv_window_is_fullscreen(struct imv_window *window) { size_t count = 0; Atom type; int format; size_t after; Atom *props = NULL; XGetWindowProperty(window->x_display, window->x_window, window->x_state, 0, 1024, False, XA_ATOM, &type, &format, &count, &after, (unsigned char**)&props); bool fullscreen = false; for (size_t i = 0; i < count; ++i) { if (props[i] == window->x_fullscreen) { fullscreen = true; break; } } XFree(props); return fullscreen; } void imv_window_set_fullscreen(struct imv_window *window, bool fullscreen) { Window root = DefaultRootWindow(window->x_display); XEvent event = { .xclient = { .type = ClientMessage, .window = window->x_window, .format = 32, .message_type = window->x_state, .data = { .l = { (fullscreen ? 1 : 0), window->x_fullscreen, 0, 1 } } } }; XSendEvent(window->x_display, root, False, SubstructureNotifyMask | SubstructureRedirectMask, &event ); } bool imv_window_get_mouse_button(struct imv_window *window, int button) { if (button == 1) { return window->pointer.mouse1; } return false; } void imv_window_get_mouse_position(struct imv_window *window, double *x, double *y) { if (x) { *x = window->pointer.current.x; } if (y) { *y = window->pointer.current.y; } } void imv_window_present(struct imv_window *window) { glXSwapBuffers(window->x_display, window->x_window); } void imv_window_wait_for_event(struct imv_window *window, double timeout) { struct pollfd fds[] = { {.fd = ConnectionNumber(window->x_display), .events = POLLIN}, {.fd = window->pipe_fds[0], .events = POLLIN} }; nfds_t nfds = sizeof fds / sizeof *fds; poll(fds, nfds, timeout * 1000); } void imv_window_push_event(struct imv_window *window, struct imv_event *e) { /* Push it down the pipe */ write(window->pipe_fds[1], e, sizeof *e); } static void handle_keyboard(struct imv_window *window, imv_event_handler handler, void *data, const XEvent *xev) { imv_keyboard_update_mods(window->keyboard, (int)xev->xkey.state, 0, 0); bool pressed = xev->type == KeyPress; int scancode = xev->xkey.keycode - 8; imv_keyboard_update_key(window->keyboard, scancode, pressed); if (!pressed) { return; } char keyname[32] = {0}; imv_keyboard_keyname(window->keyboard, scancode, keyname, sizeof keyname); char text[64] = {0}; imv_keyboard_get_text(window->keyboard, scancode, text, sizeof text); char *desc = imv_keyboard_describe_key(window->keyboard, scancode); if (!desc) { desc = strdup(""); } struct imv_event e = { .type = IMV_EVENT_KEYBOARD, .data = { .keyboard = { .scancode = scancode, .keyname = keyname, .description = desc, .text = text, } } }; if (handler) { handler(data, &e); } free(desc); } void imv_window_pump_events(struct imv_window *window, imv_event_handler handler, void *data) { XEvent xev; while (XPending(window->x_display)) { XNextEvent(window->x_display, &xev); if (xev.type == Expose) { XWindowAttributes wa; XGetWindowAttributes(window->x_display, window->x_window, &wa); window->width = wa.width; window->height = wa.height; glViewport(0, 0, wa.width, wa.height); struct imv_event e = { .type = IMV_EVENT_RESIZE, .data = { .resize = { .width = wa.width, .height = wa.height, .buffer_width = wa.width, .buffer_height = wa.height } } }; if (handler) { handler(data, &e); } } else if (xev.type == KeyPress || xev.type == KeyRelease) { handle_keyboard(window, handler, data, &xev); } else if (xev.type == ButtonPress || xev.type == ButtonRelease) { if (xev.xbutton.button == Button1) { window->pointer.mouse1 = xev.type == ButtonPress; struct imv_event e = { .type = IMV_EVENT_MOUSE_BUTTON, .data = { .mouse_button = { .button = 1, .pressed = xev.type == ButtonPress } } }; if (handler) { handler(data, &e); } } else if (xev.xbutton.button == Button4 || xev.xbutton.button == Button5) { struct imv_event e = { .type = IMV_EVENT_MOUSE_SCROLL, .data = { .mouse_scroll = { .dx = 0, .dy = xev.xbutton.button == Button4 ? -1 : 1 } } }; if (handler) { handler(data, &e); } } } else if (xev.type == MotionNotify) { window->pointer.current.x = xev.xmotion.x; window->pointer.current.y = xev.xmotion.y; int dx = window->pointer.current.x - window->pointer.last.x; int dy = window->pointer.current.y - window->pointer.last.y; if (window->pointer.last.x == -1) { dx = 0; } if (window->pointer.last.y == -1) { dy = 0; } window->pointer.last.x = window->pointer.current.x; window->pointer.last.y = window->pointer.current.y; struct imv_event e = { .type = IMV_EVENT_MOUSE_MOTION, .data = { .mouse_motion = { .x = window->pointer.current.x, .y = window->pointer.current.y, .dx = dx, .dy = dy } } }; if (handler) { handler(data, &e); } } } /* Handle any events in the pipe */ while (1) { struct imv_event e; ssize_t len = read(window->pipe_fds[0], &e, sizeof e); if (len <= 0) { break; } assert(len == sizeof e); if (handler) { handler(data, &e); } } } ��������������imv-4.0.1/src/xdg-shell-client-protocol.h�����������������������������������������������������������0000664�0000000�0000000�00000173023�13531522132�0020254�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* Generated by wayland-scanner 1.17.0 */ #ifndef XDG_SHELL_CLIENT_PROTOCOL_H #define XDG_SHELL_CLIENT_PROTOCOL_H #include #include #include "wayland-client.h" #ifdef __cplusplus extern "C" { #endif /** * @page page_xdg_shell The xdg_shell protocol * @section page_ifaces_xdg_shell Interfaces * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces * - @subpage page_iface_xdg_positioner - child surface positioner * - @subpage page_iface_xdg_surface - desktop user interface surface base interface * - @subpage page_iface_xdg_toplevel - toplevel surface * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus * @section page_copyright_xdg_shell Copyright *
 *
 * Copyright © 2008-2013 Kristian Høgsberg
 * Copyright © 2013      Rafael Antognolli
 * Copyright © 2013      Jasper St. Pierre
 * Copyright © 2010-2013 Intel Corporation
 * Copyright © 2015-2017 Samsung Electronics Co., Ltd
 * Copyright © 2015-2017 Red Hat Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
*/ struct wl_output; struct wl_seat; struct wl_surface; struct xdg_popup; struct xdg_positioner; struct xdg_surface; struct xdg_toplevel; struct xdg_wm_base; /** * @page page_iface_xdg_wm_base xdg_wm_base * @section page_iface_xdg_wm_base_desc Description * * The xdg_wm_base interface is exposed as a global object enabling clients * to turn their wl_surfaces into windows in a desktop environment. It * defines the basic functionality needed for clients and the compositor to * create windows that can be dragged, resized, maximized, etc, as well as * creating transient windows such as popup menus. * @section page_iface_xdg_wm_base_api API * See @ref iface_xdg_wm_base. */ /** * @defgroup iface_xdg_wm_base The xdg_wm_base interface * * The xdg_wm_base interface is exposed as a global object enabling clients * to turn their wl_surfaces into windows in a desktop environment. It * defines the basic functionality needed for clients and the compositor to * create windows that can be dragged, resized, maximized, etc, as well as * creating transient windows such as popup menus. */ extern const struct wl_interface xdg_wm_base_interface; /** * @page page_iface_xdg_positioner xdg_positioner * @section page_iface_xdg_positioner_desc Description * * The xdg_positioner provides a collection of rules for the placement of a * child surface relative to a parent surface. Rules can be defined to ensure * the child surface remains within the visible area's borders, and to * specify how the child surface changes its position, such as sliding along * an axis, or flipping around a rectangle. These positioner-created rules are * constrained by the requirement that a child surface must intersect with or * be at least partially adjacent to its parent surface. * * See the various requests for details about possible rules. * * At the time of the request, the compositor makes a copy of the rules * specified by the xdg_positioner. Thus, after the request is complete the * xdg_positioner object can be destroyed or reused; further changes to the * object will have no effect on previous usages. * * For an xdg_positioner object to be considered complete, it must have a * non-zero size set by set_size, and a non-zero anchor rectangle set by * set_anchor_rect. Passing an incomplete xdg_positioner object when * positioning a surface raises an error. * @section page_iface_xdg_positioner_api API * See @ref iface_xdg_positioner. */ /** * @defgroup iface_xdg_positioner The xdg_positioner interface * * The xdg_positioner provides a collection of rules for the placement of a * child surface relative to a parent surface. Rules can be defined to ensure * the child surface remains within the visible area's borders, and to * specify how the child surface changes its position, such as sliding along * an axis, or flipping around a rectangle. These positioner-created rules are * constrained by the requirement that a child surface must intersect with or * be at least partially adjacent to its parent surface. * * See the various requests for details about possible rules. * * At the time of the request, the compositor makes a copy of the rules * specified by the xdg_positioner. Thus, after the request is complete the * xdg_positioner object can be destroyed or reused; further changes to the * object will have no effect on previous usages. * * For an xdg_positioner object to be considered complete, it must have a * non-zero size set by set_size, and a non-zero anchor rectangle set by * set_anchor_rect. Passing an incomplete xdg_positioner object when * positioning a surface raises an error. */ extern const struct wl_interface xdg_positioner_interface; /** * @page page_iface_xdg_surface xdg_surface * @section page_iface_xdg_surface_desc Description * * An interface that may be implemented by a wl_surface, for * implementations that provide a desktop-style user interface. * * It provides a base set of functionality required to construct user * interface elements requiring management by the compositor, such as * toplevel windows, menus, etc. The types of functionality are split into * xdg_surface roles. * * Creating an xdg_surface does not set the role for a wl_surface. In order * to map an xdg_surface, the client must create a role-specific object * using, e.g., get_toplevel, get_popup. The wl_surface for any given * xdg_surface can have at most one role, and may not be assigned any role * not based on xdg_surface. * * A role must be assigned before any other requests are made to the * xdg_surface object. * * The client must call wl_surface.commit on the corresponding wl_surface * for the xdg_surface state to take effect. * * Creating an xdg_surface from a wl_surface which has a buffer attached or * committed is a client error, and any attempts by a client to attach or * manipulate a buffer prior to the first xdg_surface.configure call must * also be treated as errors. * * Mapping an xdg_surface-based role surface is defined as making it * possible for the surface to be shown by the compositor. Note that * a mapped surface is not guaranteed to be visible once it is mapped. * * For an xdg_surface to be mapped by the compositor, the following * conditions must be met: * (1) the client has assigned an xdg_surface-based role to the surface * (2) the client has set and committed the xdg_surface state and the * role-dependent state to the surface * (3) the client has committed a buffer to the surface * * A newly-unmapped surface is considered to have met condition (1) out * of the 3 required conditions for mapping a surface if its role surface * has not been destroyed. * @section page_iface_xdg_surface_api API * See @ref iface_xdg_surface. */ /** * @defgroup iface_xdg_surface The xdg_surface interface * * An interface that may be implemented by a wl_surface, for * implementations that provide a desktop-style user interface. * * It provides a base set of functionality required to construct user * interface elements requiring management by the compositor, such as * toplevel windows, menus, etc. The types of functionality are split into * xdg_surface roles. * * Creating an xdg_surface does not set the role for a wl_surface. In order * to map an xdg_surface, the client must create a role-specific object * using, e.g., get_toplevel, get_popup. The wl_surface for any given * xdg_surface can have at most one role, and may not be assigned any role * not based on xdg_surface. * * A role must be assigned before any other requests are made to the * xdg_surface object. * * The client must call wl_surface.commit on the corresponding wl_surface * for the xdg_surface state to take effect. * * Creating an xdg_surface from a wl_surface which has a buffer attached or * committed is a client error, and any attempts by a client to attach or * manipulate a buffer prior to the first xdg_surface.configure call must * also be treated as errors. * * Mapping an xdg_surface-based role surface is defined as making it * possible for the surface to be shown by the compositor. Note that * a mapped surface is not guaranteed to be visible once it is mapped. * * For an xdg_surface to be mapped by the compositor, the following * conditions must be met: * (1) the client has assigned an xdg_surface-based role to the surface * (2) the client has set and committed the xdg_surface state and the * role-dependent state to the surface * (3) the client has committed a buffer to the surface * * A newly-unmapped surface is considered to have met condition (1) out * of the 3 required conditions for mapping a surface if its role surface * has not been destroyed. */ extern const struct wl_interface xdg_surface_interface; /** * @page page_iface_xdg_toplevel xdg_toplevel * @section page_iface_xdg_toplevel_desc Description * * This interface defines an xdg_surface role which allows a surface to, * among other things, set window-like properties such as maximize, * fullscreen, and minimize, set application-specific metadata like title and * id, and well as trigger user interactive operations such as interactive * resize and move. * * Unmapping an xdg_toplevel means that the surface cannot be shown * by the compositor until it is explicitly mapped again. * All active operations (e.g., move, resize) are canceled and all * attributes (e.g. title, state, stacking, ...) are discarded for * an xdg_toplevel surface when it is unmapped. * * Attaching a null buffer to a toplevel unmaps the surface. * @section page_iface_xdg_toplevel_api API * See @ref iface_xdg_toplevel. */ /** * @defgroup iface_xdg_toplevel The xdg_toplevel interface * * This interface defines an xdg_surface role which allows a surface to, * among other things, set window-like properties such as maximize, * fullscreen, and minimize, set application-specific metadata like title and * id, and well as trigger user interactive operations such as interactive * resize and move. * * Unmapping an xdg_toplevel means that the surface cannot be shown * by the compositor until it is explicitly mapped again. * All active operations (e.g., move, resize) are canceled and all * attributes (e.g. title, state, stacking, ...) are discarded for * an xdg_toplevel surface when it is unmapped. * * Attaching a null buffer to a toplevel unmaps the surface. */ extern const struct wl_interface xdg_toplevel_interface; /** * @page page_iface_xdg_popup xdg_popup * @section page_iface_xdg_popup_desc Description * * A popup surface is a short-lived, temporary surface. It can be used to * implement for example menus, popovers, tooltips and other similar user * interface concepts. * * A popup can be made to take an explicit grab. See xdg_popup.grab for * details. * * When the popup is dismissed, a popup_done event will be sent out, and at * the same time the surface will be unmapped. See the xdg_popup.popup_done * event for details. * * Explicitly destroying the xdg_popup object will also dismiss the popup and * unmap the surface. Clients that want to dismiss the popup when another * surface of their own is clicked should dismiss the popup using the destroy * request. * * A newly created xdg_popup will be stacked on top of all previously created * xdg_popup surfaces associated with the same xdg_toplevel. * * The parent of an xdg_popup must be mapped (see the xdg_surface * description) before the xdg_popup itself. * * The x and y arguments passed when creating the popup object specify * where the top left of the popup should be placed, relative to the * local surface coordinates of the parent surface. See * xdg_surface.get_popup. An xdg_popup must intersect with or be at least * partially adjacent to its parent surface. * * The client must call wl_surface.commit on the corresponding wl_surface * for the xdg_popup state to take effect. * @section page_iface_xdg_popup_api API * See @ref iface_xdg_popup. */ /** * @defgroup iface_xdg_popup The xdg_popup interface * * A popup surface is a short-lived, temporary surface. It can be used to * implement for example menus, popovers, tooltips and other similar user * interface concepts. * * A popup can be made to take an explicit grab. See xdg_popup.grab for * details. * * When the popup is dismissed, a popup_done event will be sent out, and at * the same time the surface will be unmapped. See the xdg_popup.popup_done * event for details. * * Explicitly destroying the xdg_popup object will also dismiss the popup and * unmap the surface. Clients that want to dismiss the popup when another * surface of their own is clicked should dismiss the popup using the destroy * request. * * A newly created xdg_popup will be stacked on top of all previously created * xdg_popup surfaces associated with the same xdg_toplevel. * * The parent of an xdg_popup must be mapped (see the xdg_surface * description) before the xdg_popup itself. * * The x and y arguments passed when creating the popup object specify * where the top left of the popup should be placed, relative to the * local surface coordinates of the parent surface. See * xdg_surface.get_popup. An xdg_popup must intersect with or be at least * partially adjacent to its parent surface. * * The client must call wl_surface.commit on the corresponding wl_surface * for the xdg_popup state to take effect. */ extern const struct wl_interface xdg_popup_interface; #ifndef XDG_WM_BASE_ERROR_ENUM #define XDG_WM_BASE_ERROR_ENUM enum xdg_wm_base_error { /** * given wl_surface has another role */ XDG_WM_BASE_ERROR_ROLE = 0, /** * xdg_wm_base was destroyed before children */ XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, /** * the client tried to map or destroy a non-topmost popup */ XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, /** * the client specified an invalid popup parent surface */ XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, /** * the client provided an invalid surface state */ XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, /** * the client provided an invalid positioner */ XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, }; #endif /* XDG_WM_BASE_ERROR_ENUM */ /** * @ingroup iface_xdg_wm_base * @struct xdg_wm_base_listener */ struct xdg_wm_base_listener { /** * check if the client is alive * * The ping event asks the client if it's still alive. Pass the * serial specified in the event back to the compositor by sending * a "pong" request back with the specified serial. See * xdg_wm_base.ping. * * Compositors can use this to determine if the client is still * alive. It's unspecified what will happen if the client doesn't * respond to the ping request, or in what timeframe. Clients * should try to respond in a reasonable amount of time. * * A compositor is free to ping in any way it wants, but a client * must always respond to any xdg_wm_base object it created. * @param serial pass this to the pong request */ void (*ping)(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial); }; /** * @ingroup iface_xdg_wm_base */ static inline int xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base, const struct xdg_wm_base_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base, (void (**)(void)) listener, data); } #define XDG_WM_BASE_DESTROY 0 #define XDG_WM_BASE_CREATE_POSITIONER 1 #define XDG_WM_BASE_GET_XDG_SURFACE 2 #define XDG_WM_BASE_PONG 3 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_PING_SINCE_VERSION 1 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_PONG_SINCE_VERSION 1 /** @ingroup iface_xdg_wm_base */ static inline void xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data); } /** @ingroup iface_xdg_wm_base */ static inline void * xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base); } static inline uint32_t xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base) { return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base); } /** * @ingroup iface_xdg_wm_base * * Destroy this xdg_wm_base object. * * Destroying a bound xdg_wm_base object while there are surfaces * still alive created by this xdg_wm_base object instance is illegal * and will result in a protocol error. */ static inline void xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base) { wl_proxy_marshal((struct wl_proxy *) xdg_wm_base, XDG_WM_BASE_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_wm_base); } /** * @ingroup iface_xdg_wm_base * * Create a positioner object. A positioner object is used to position * surfaces relative to some parent surface. See the interface description * and xdg_surface.get_popup for details. */ static inline struct xdg_positioner * xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base) { struct wl_proxy *id; id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_wm_base, XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, NULL); return (struct xdg_positioner *) id; } /** * @ingroup iface_xdg_wm_base * * This creates an xdg_surface for the given surface. While xdg_surface * itself is not a role, the corresponding surface may only be assigned * a role extending xdg_surface, such as xdg_toplevel or xdg_popup. * * This creates an xdg_surface for the given surface. An xdg_surface is * used as basis to define a role to a given surface, such as xdg_toplevel * or xdg_popup. It also manages functionality shared between xdg_surface * based surface roles. * * See the documentation of xdg_surface for more details about what an * xdg_surface is and how it is used. */ static inline struct xdg_surface * xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface) { struct wl_proxy *id; id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_wm_base, XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, NULL, surface); return (struct xdg_surface *) id; } /** * @ingroup iface_xdg_wm_base * * A client must respond to a ping event with a pong request or * the client may be deemed unresponsive. See xdg_wm_base.ping. */ static inline void xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial) { wl_proxy_marshal((struct wl_proxy *) xdg_wm_base, XDG_WM_BASE_PONG, serial); } #ifndef XDG_POSITIONER_ERROR_ENUM #define XDG_POSITIONER_ERROR_ENUM enum xdg_positioner_error { /** * invalid input provided */ XDG_POSITIONER_ERROR_INVALID_INPUT = 0, }; #endif /* XDG_POSITIONER_ERROR_ENUM */ #ifndef XDG_POSITIONER_ANCHOR_ENUM #define XDG_POSITIONER_ANCHOR_ENUM enum xdg_positioner_anchor { XDG_POSITIONER_ANCHOR_NONE = 0, XDG_POSITIONER_ANCHOR_TOP = 1, XDG_POSITIONER_ANCHOR_BOTTOM = 2, XDG_POSITIONER_ANCHOR_LEFT = 3, XDG_POSITIONER_ANCHOR_RIGHT = 4, XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, }; #endif /* XDG_POSITIONER_ANCHOR_ENUM */ #ifndef XDG_POSITIONER_GRAVITY_ENUM #define XDG_POSITIONER_GRAVITY_ENUM enum xdg_positioner_gravity { XDG_POSITIONER_GRAVITY_NONE = 0, XDG_POSITIONER_GRAVITY_TOP = 1, XDG_POSITIONER_GRAVITY_BOTTOM = 2, XDG_POSITIONER_GRAVITY_LEFT = 3, XDG_POSITIONER_GRAVITY_RIGHT = 4, XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, }; #endif /* XDG_POSITIONER_GRAVITY_ENUM */ #ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM #define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM /** * @ingroup iface_xdg_positioner * vertically resize the surface * * Resize the surface vertically so that it is completely unconstrained. */ enum xdg_positioner_constraint_adjustment { XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, }; #endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ #define XDG_POSITIONER_DESTROY 0 #define XDG_POSITIONER_SET_SIZE 1 #define XDG_POSITIONER_SET_ANCHOR_RECT 2 #define XDG_POSITIONER_SET_ANCHOR 3 #define XDG_POSITIONER_SET_GRAVITY 4 #define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5 #define XDG_POSITIONER_SET_OFFSET 6 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 /** @ingroup iface_xdg_positioner */ static inline void xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data); } /** @ingroup iface_xdg_positioner */ static inline void * xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner); } static inline uint32_t xdg_positioner_get_version(struct xdg_positioner *xdg_positioner) { return wl_proxy_get_version((struct wl_proxy *) xdg_positioner); } /** * @ingroup iface_xdg_positioner * * Notify the compositor that the xdg_positioner will no longer be used. */ static inline void xdg_positioner_destroy(struct xdg_positioner *xdg_positioner) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_positioner); } /** * @ingroup iface_xdg_positioner * * Set the size of the surface that is to be positioned with the positioner * object. The size is in surface-local coordinates and corresponds to the * window geometry. See xdg_surface.set_window_geometry. * * If a zero or negative size is set the invalid_input error is raised. */ static inline void xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_SIZE, width, height); } /** * @ingroup iface_xdg_positioner * * Specify the anchor rectangle within the parent surface that the child * surface will be placed relative to. The rectangle is relative to the * window geometry as defined by xdg_surface.set_window_geometry of the * parent surface. * * When the xdg_positioner object is used to position a child surface, the * anchor rectangle may not extend outside the window geometry of the * positioned child's parent surface. * * If a negative size is set the invalid_input error is raised. */ static inline void xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_ANCHOR_RECT, x, y, width, height); } /** * @ingroup iface_xdg_positioner * * Defines the anchor point for the anchor rectangle. The specified anchor * is used derive an anchor point that the child surface will be * positioned relative to. If a corner anchor is set (e.g. 'top_left' or * 'bottom_right'), the anchor point will be at the specified corner; * otherwise, the derived anchor point will be centered on the specified * edge, or in the center of the anchor rectangle if no edge is specified. */ static inline void xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_ANCHOR, anchor); } /** * @ingroup iface_xdg_positioner * * Defines in what direction a surface should be positioned, relative to * the anchor point of the parent surface. If a corner gravity is * specified (e.g. 'bottom_right' or 'top_left'), then the child surface * will be placed towards the specified gravity; otherwise, the child * surface will be centered over the anchor point on any axis that had no * gravity specified. */ static inline void xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_GRAVITY, gravity); } /** * @ingroup iface_xdg_positioner * * Specify how the window should be positioned if the originally intended * position caused the surface to be constrained, meaning at least * partially outside positioning boundaries set by the compositor. The * adjustment is set by constructing a bitmask describing the adjustment to * be made when the surface is constrained on that axis. * * If no bit for one axis is set, the compositor will assume that the child * surface should not change its position on that axis when constrained. * * If more than one bit for one axis is set, the order of how adjustments * are applied is specified in the corresponding adjustment descriptions. * * The default adjustment is none. */ static inline void xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, constraint_adjustment); } /** * @ingroup iface_xdg_positioner * * Specify the surface position offset relative to the position of the * anchor on the anchor rectangle and the anchor on the surface. For * example if the anchor of the anchor rectangle is at (x, y), the surface * has the gravity bottom|right, and the offset is (ox, oy), the calculated * surface position will be (x + ox, y + oy). The offset position of the * surface is the one used for constraint testing. See * set_constraint_adjustment. * * An example use case is placing a popup menu on top of a user interface * element, while aligning the user interface element of the parent surface * with some user interface element placed somewhere in the popup surface. */ static inline void xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_OFFSET, x, y); } #ifndef XDG_SURFACE_ERROR_ENUM #define XDG_SURFACE_ERROR_ENUM enum xdg_surface_error { XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, }; #endif /* XDG_SURFACE_ERROR_ENUM */ /** * @ingroup iface_xdg_surface * @struct xdg_surface_listener */ struct xdg_surface_listener { /** * suggest a surface change * * The configure event marks the end of a configure sequence. A * configure sequence is a set of one or more events configuring * the state of the xdg_surface, including the final * xdg_surface.configure event. * * Where applicable, xdg_surface surface roles will during a * configure sequence extend this event as a latched state sent as * events before the xdg_surface.configure event. Such events * should be considered to make up a set of atomically applied * configuration states, where the xdg_surface.configure commits * the accumulated state. * * Clients should arrange their surface for the new states, and * then send an ack_configure request with the serial sent in this * configure event at some point before committing the new surface. * * If the client receives multiple configure events before it can * respond to one, it is free to discard all but the last event it * received. * @param serial serial of the configure event */ void (*configure)(void *data, struct xdg_surface *xdg_surface, uint32_t serial); }; /** * @ingroup iface_xdg_surface */ static inline int xdg_surface_add_listener(struct xdg_surface *xdg_surface, const struct xdg_surface_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) xdg_surface, (void (**)(void)) listener, data); } #define XDG_SURFACE_DESTROY 0 #define XDG_SURFACE_GET_TOPLEVEL 1 #define XDG_SURFACE_GET_POPUP 2 #define XDG_SURFACE_SET_WINDOW_GEOMETRY 3 #define XDG_SURFACE_ACK_CONFIGURE 4 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 /** @ingroup iface_xdg_surface */ static inline void xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data); } /** @ingroup iface_xdg_surface */ static inline void * xdg_surface_get_user_data(struct xdg_surface *xdg_surface) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface); } static inline uint32_t xdg_surface_get_version(struct xdg_surface *xdg_surface) { return wl_proxy_get_version((struct wl_proxy *) xdg_surface); } /** * @ingroup iface_xdg_surface * * Destroy the xdg_surface object. An xdg_surface must only be destroyed * after its role object has been destroyed. */ static inline void xdg_surface_destroy(struct xdg_surface *xdg_surface) { wl_proxy_marshal((struct wl_proxy *) xdg_surface, XDG_SURFACE_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_surface); } /** * @ingroup iface_xdg_surface * * This creates an xdg_toplevel object for the given xdg_surface and gives * the associated wl_surface the xdg_toplevel role. * * See the documentation of xdg_toplevel for more details about what an * xdg_toplevel is and how it is used. */ static inline struct xdg_toplevel * xdg_surface_get_toplevel(struct xdg_surface *xdg_surface) { struct wl_proxy *id; id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_surface, XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, NULL); return (struct xdg_toplevel *) id; } /** * @ingroup iface_xdg_surface * * This creates an xdg_popup object for the given xdg_surface and gives * the associated wl_surface the xdg_popup role. * * If null is passed as a parent, a parent surface must be specified using * some other protocol, before committing the initial state. * * See the documentation of xdg_popup for more details about what an * xdg_popup is and how it is used. */ static inline struct xdg_popup * xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner) { struct wl_proxy *id; id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_surface, XDG_SURFACE_GET_POPUP, &xdg_popup_interface, NULL, parent, positioner); return (struct xdg_popup *) id; } /** * @ingroup iface_xdg_surface * * The window geometry of a surface is its "visible bounds" from the * user's perspective. Client-side decorations often have invisible * portions like drop-shadows which should be ignored for the * purposes of aligning, placing and constraining windows. * * The window geometry is double buffered, and will be applied at the * time wl_surface.commit of the corresponding wl_surface is called. * * When maintaining a position, the compositor should treat the (x, y) * coordinate of the window geometry as the top left corner of the window. * A client changing the (x, y) window geometry coordinate should in * general not alter the position of the window. * * Once the window geometry of the surface is set, it is not possible to * unset it, and it will remain the same until set_window_geometry is * called again, even if a new subsurface or buffer is attached. * * If never set, the value is the full bounds of the surface, * including any subsurfaces. This updates dynamically on every * commit. This unset is meant for extremely simple clients. * * The arguments are given in the surface-local coordinate space of * the wl_surface associated with this xdg_surface. * * The width and height must be greater than zero. Setting an invalid size * will raise an error. When applied, the effective window geometry will be * the set window geometry clamped to the bounding rectangle of the * combined geometry of the surface of the xdg_surface and the associated * subsurfaces. */ static inline void xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_surface, XDG_SURFACE_SET_WINDOW_GEOMETRY, x, y, width, height); } /** * @ingroup iface_xdg_surface * * When a configure event is received, if a client commits the * surface in response to the configure event, then the client * must make an ack_configure request sometime before the commit * request, passing along the serial of the configure event. * * For instance, for toplevel surfaces the compositor might use this * information to move a surface to the top left only when the client has * drawn itself for the maximized or fullscreen state. * * If the client receives multiple configure events before it * can respond to one, it only has to ack the last configure event. * * A client is not required to commit immediately after sending * an ack_configure request - it may even ack_configure several times * before its next surface commit. * * A client may send multiple ack_configure requests before committing, but * only the last request sent before a commit indicates which configure * event the client really is responding to. */ static inline void xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial) { wl_proxy_marshal((struct wl_proxy *) xdg_surface, XDG_SURFACE_ACK_CONFIGURE, serial); } #ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM #define XDG_TOPLEVEL_RESIZE_EDGE_ENUM /** * @ingroup iface_xdg_toplevel * edge values for resizing * * These values are used to indicate which edge of a surface * is being dragged in a resize operation. */ enum xdg_toplevel_resize_edge { XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, }; #endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ #ifndef XDG_TOPLEVEL_STATE_ENUM #define XDG_TOPLEVEL_STATE_ENUM /** * @ingroup iface_xdg_toplevel * the surface is tiled * * The window is currently in a tiled layout and the bottom edge is * considered to be adjacent to another part of the tiling grid. */ enum xdg_toplevel_state { /** * the surface is maximized */ XDG_TOPLEVEL_STATE_MAXIMIZED = 1, /** * the surface is fullscreen */ XDG_TOPLEVEL_STATE_FULLSCREEN = 2, /** * the surface is being resized */ XDG_TOPLEVEL_STATE_RESIZING = 3, /** * the surface is now activated */ XDG_TOPLEVEL_STATE_ACTIVATED = 4, /** * @since 2 */ XDG_TOPLEVEL_STATE_TILED_LEFT = 5, /** * @since 2 */ XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, /** * @since 2 */ XDG_TOPLEVEL_STATE_TILED_TOP = 7, /** * @since 2 */ XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, }; /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 #endif /* XDG_TOPLEVEL_STATE_ENUM */ /** * @ingroup iface_xdg_toplevel * @struct xdg_toplevel_listener */ struct xdg_toplevel_listener { /** * suggest a surface change * * This configure event asks the client to resize its toplevel * surface or to change its state. The configured state should not * be applied immediately. See xdg_surface.configure for details. * * The width and height arguments specify a hint to the window * about how its surface should be resized in window geometry * coordinates. See set_window_geometry. * * If the width or height arguments are zero, it means the client * should decide its own window dimension. This may happen when the * compositor needs to configure the state of the surface but * doesn't have any information about any previous or expected * dimension. * * The states listed in the event specify how the width/height * arguments should be interpreted, and possibly how it should be * drawn. * * Clients must send an ack_configure in response to this event. * See xdg_surface.configure and xdg_surface.ack_configure for * details. */ void (*configure)(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states); /** * surface wants to be closed * * The close event is sent by the compositor when the user wants * the surface to be closed. This should be equivalent to the user * clicking the close button in client-side decorations, if your * application has any. * * This is only a request that the user intends to close the * window. The client may choose to ignore this request, or show a * dialog to ask the user to save their data, etc. */ void (*close)(void *data, struct xdg_toplevel *xdg_toplevel); }; /** * @ingroup iface_xdg_toplevel */ static inline int xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel, const struct xdg_toplevel_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel, (void (**)(void)) listener, data); } #define XDG_TOPLEVEL_DESTROY 0 #define XDG_TOPLEVEL_SET_PARENT 1 #define XDG_TOPLEVEL_SET_TITLE 2 #define XDG_TOPLEVEL_SET_APP_ID 3 #define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4 #define XDG_TOPLEVEL_MOVE 5 #define XDG_TOPLEVEL_RESIZE 6 #define XDG_TOPLEVEL_SET_MAX_SIZE 7 #define XDG_TOPLEVEL_SET_MIN_SIZE 8 #define XDG_TOPLEVEL_SET_MAXIMIZED 9 #define XDG_TOPLEVEL_UNSET_MAXIMIZED 10 #define XDG_TOPLEVEL_SET_FULLSCREEN 11 #define XDG_TOPLEVEL_UNSET_FULLSCREEN 12 #define XDG_TOPLEVEL_SET_MINIMIZED 13 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 /** @ingroup iface_xdg_toplevel */ static inline void xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data); } /** @ingroup iface_xdg_toplevel */ static inline void * xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel); } static inline uint32_t xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel) { return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel); } /** * @ingroup iface_xdg_toplevel * * This request destroys the role surface and unmaps the surface; * see "Unmapping" behavior in interface section for details. */ static inline void xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_toplevel); } /** * @ingroup iface_xdg_toplevel * * Set the "parent" of this surface. This surface should be stacked * above the parent surface and all other ancestor surfaces. * * Parent windows should be set on dialogs, toolboxes, or other * "auxiliary" surfaces, so that the parent is raised when the dialog * is raised. * * Setting a null parent for a child window removes any parent-child * relationship for the child. Setting a null parent for a window which * currently has no parent is a no-op. * * If the parent is unmapped then its children are managed as * though the parent of the now-unmapped parent has become the * parent of this surface. If no parent exists for the now-unmapped * parent then the children are managed as though they have no * parent surface. */ static inline void xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_PARENT, parent); } /** * @ingroup iface_xdg_toplevel * * Set a short title for the surface. * * This string may be used to identify the surface in a task bar, * window list, or other user interface elements provided by the * compositor. * * The string must be encoded in UTF-8. */ static inline void xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_TITLE, title); } /** * @ingroup iface_xdg_toplevel * * Set an application identifier for the surface. * * The app ID identifies the general class of applications to which * the surface belongs. The compositor can use this to group multiple * surfaces together, or to determine how to launch a new application. * * For D-Bus activatable applications, the app ID is used as the D-Bus * service name. * * The compositor shell will try to group application surfaces together * by their app ID. As a best practice, it is suggested to select app * ID's that match the basename of the application's .desktop file. * For example, "org.freedesktop.FooViewer" where the .desktop file is * "org.freedesktop.FooViewer.desktop". * * See the desktop-entry specification [0] for more details on * application identifiers and how they relate to well-known D-Bus * names and .desktop files. * * [0] http://standards.freedesktop.org/desktop-entry-spec/ */ static inline void xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_APP_ID, app_id); } /** * @ingroup iface_xdg_toplevel * * Clients implementing client-side decorations might want to show * a context menu when right-clicking on the decorations, giving the * user a menu that they can use to maximize or minimize the window. * * This request asks the compositor to pop up such a window menu at * the given position, relative to the local surface coordinates of * the parent surface. There are no guarantees as to what menu items * the window menu contains. * * This request must be used in response to some sort of user action * like a button press, key press, or touch down event. */ static inline void xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SHOW_WINDOW_MENU, seat, serial, x, y); } /** * @ingroup iface_xdg_toplevel * * Start an interactive, user-driven move of the surface. * * This request must be used in response to some sort of user action * like a button press, key press, or touch down event. The passed * serial is used to determine the type of interactive move (touch, * pointer, etc). * * The server may ignore move requests depending on the state of * the surface (e.g. fullscreen or maximized), or if the passed serial * is no longer valid. * * If triggered, the surface will lose the focus of the device * (wl_pointer, wl_touch, etc) used for the move. It is up to the * compositor to visually indicate that the move is taking place, such as * updating a pointer cursor, during the move. There is no guarantee * that the device focus will return when the move is completed. */ static inline void xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_MOVE, seat, serial); } /** * @ingroup iface_xdg_toplevel * * Start a user-driven, interactive resize of the surface. * * This request must be used in response to some sort of user action * like a button press, key press, or touch down event. The passed * serial is used to determine the type of interactive resize (touch, * pointer, etc). * * The server may ignore resize requests depending on the state of * the surface (e.g. fullscreen or maximized). * * If triggered, the client will receive configure events with the * "resize" state enum value and the expected sizes. See the "resize" * enum value for more details about what is required. The client * must also acknowledge configure events using "ack_configure". After * the resize is completed, the client will receive another "configure" * event without the resize state. * * If triggered, the surface also will lose the focus of the device * (wl_pointer, wl_touch, etc) used for the resize. It is up to the * compositor to visually indicate that the resize is taking place, * such as updating a pointer cursor, during the resize. There is no * guarantee that the device focus will return when the resize is * completed. * * The edges parameter specifies how the surface should be resized, * and is one of the values of the resize_edge enum. The compositor * may use this information to update the surface position for * example when dragging the top left corner. The compositor may also * use this information to adapt its behavior, e.g. choose an * appropriate cursor image. */ static inline void xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_RESIZE, seat, serial, edges); } /** * @ingroup iface_xdg_toplevel * * Set a maximum size for the window. * * The client can specify a maximum size so that the compositor does * not try to configure the window beyond this size. * * The width and height arguments are in window geometry coordinates. * See xdg_surface.set_window_geometry. * * Values set in this way are double-buffered. They will get applied * on the next commit. * * The compositor can use this information to allow or disallow * different states like maximize or fullscreen and draw accurate * animations. * * Similarly, a tiling window manager may use this information to * place and resize client windows in a more effective way. * * The client should not rely on the compositor to obey the maximum * size. The compositor may decide to ignore the values set by the * client and request a larger size. * * If never set, or a value of zero in the request, means that the * client has no expected maximum size in the given dimension. * As a result, a client wishing to reset the maximum size * to an unspecified state can use zero for width and height in the * request. * * Requesting a maximum size to be smaller than the minimum size of * a surface is illegal and will result in a protocol error. * * The width and height must be greater than or equal to zero. Using * strictly negative values for width and height will result in a * protocol error. */ static inline void xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_MAX_SIZE, width, height); } /** * @ingroup iface_xdg_toplevel * * Set a minimum size for the window. * * The client can specify a minimum size so that the compositor does * not try to configure the window below this size. * * The width and height arguments are in window geometry coordinates. * See xdg_surface.set_window_geometry. * * Values set in this way are double-buffered. They will get applied * on the next commit. * * The compositor can use this information to allow or disallow * different states like maximize or fullscreen and draw accurate * animations. * * Similarly, a tiling window manager may use this information to * place and resize client windows in a more effective way. * * The client should not rely on the compositor to obey the minimum * size. The compositor may decide to ignore the values set by the * client and request a smaller size. * * If never set, or a value of zero in the request, means that the * client has no expected minimum size in the given dimension. * As a result, a client wishing to reset the minimum size * to an unspecified state can use zero for width and height in the * request. * * Requesting a minimum size to be larger than the maximum size of * a surface is illegal and will result in a protocol error. * * The width and height must be greater than or equal to zero. Using * strictly negative values for width and height will result in a * protocol error. */ static inline void xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_MIN_SIZE, width, height); } /** * @ingroup iface_xdg_toplevel * * Maximize the surface. * * After requesting that the surface should be maximized, the compositor * will respond by emitting a configure event. Whether this configure * actually sets the window maximized is subject to compositor policies. * The client must then update its content, drawing in the configured * state. The client must also acknowledge the configure when committing * the new content (see ack_configure). * * It is up to the compositor to decide how and where to maximize the * surface, for example which output and what region of the screen should * be used. * * If the surface was already maximized, the compositor will still emit * a configure event with the "maximized" state. * * If the surface is in a fullscreen state, this request has no direct * effect. It may alter the state the surface is returned to when * unmaximized unless overridden by the compositor. */ static inline void xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_MAXIMIZED); } /** * @ingroup iface_xdg_toplevel * * Unmaximize the surface. * * After requesting that the surface should be unmaximized, the compositor * will respond by emitting a configure event. Whether this actually * un-maximizes the window is subject to compositor policies. * If available and applicable, the compositor will include the window * geometry dimensions the window had prior to being maximized in the * configure event. The client must then update its content, drawing it in * the configured state. The client must also acknowledge the configure * when committing the new content (see ack_configure). * * It is up to the compositor to position the surface after it was * unmaximized; usually the position the surface had before maximizing, if * applicable. * * If the surface was already not maximized, the compositor will still * emit a configure event without the "maximized" state. * * If the surface is in a fullscreen state, this request has no direct * effect. It may alter the state the surface is returned to when * unmaximized unless overridden by the compositor. */ static inline void xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_UNSET_MAXIMIZED); } /** * @ingroup iface_xdg_toplevel * * Make the surface fullscreen. * * After requesting that the surface should be fullscreened, the * compositor will respond by emitting a configure event. Whether the * client is actually put into a fullscreen state is subject to compositor * policies. The client must also acknowledge the configure when * committing the new content (see ack_configure). * * The output passed by the request indicates the client's preference as * to which display it should be set fullscreen on. If this value is NULL, * it's up to the compositor to choose which display will be used to map * this surface. * * If the surface doesn't cover the whole output, the compositor will * position the surface in the center of the output and compensate with * with border fill covering the rest of the output. The content of the * border fill is undefined, but should be assumed to be in some way that * attempts to blend into the surrounding area (e.g. solid black). * * If the fullscreened surface is not opaque, the compositor must make * sure that other screen content not part of the same surface tree (made * up of subsurfaces, popups or similarly coupled surfaces) are not * visible below the fullscreened surface. */ static inline void xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_FULLSCREEN, output); } /** * @ingroup iface_xdg_toplevel * * Make the surface no longer fullscreen. * * After requesting that the surface should be unfullscreened, the * compositor will respond by emitting a configure event. * Whether this actually removes the fullscreen state of the client is * subject to compositor policies. * * Making a surface unfullscreen sets states for the surface based on the following: * * the state(s) it may have had before becoming fullscreen * * any state(s) decided by the compositor * * any state(s) requested by the client while the surface was fullscreen * * The compositor may include the previous window geometry dimensions in * the configure event, if applicable. * * The client must also acknowledge the configure when committing the new * content (see ack_configure). */ static inline void xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_UNSET_FULLSCREEN); } /** * @ingroup iface_xdg_toplevel * * Request that the compositor minimize your surface. There is no * way to know if the surface is currently minimized, nor is there * any way to unset minimization on this surface. * * If you are looking to throttle redrawing when minimized, please * instead use the wl_surface.frame event for this, as this will * also work with live previews on windows in Alt-Tab, Expose or * similar compositor features. */ static inline void xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_MINIMIZED); } #ifndef XDG_POPUP_ERROR_ENUM #define XDG_POPUP_ERROR_ENUM enum xdg_popup_error { /** * tried to grab after being mapped */ XDG_POPUP_ERROR_INVALID_GRAB = 0, }; #endif /* XDG_POPUP_ERROR_ENUM */ /** * @ingroup iface_xdg_popup * @struct xdg_popup_listener */ struct xdg_popup_listener { /** * configure the popup surface * * This event asks the popup surface to configure itself given * the configuration. The configured state should not be applied * immediately. See xdg_surface.configure for details. * * The x and y arguments represent the position the popup was * placed at given the xdg_positioner rule, relative to the upper * left corner of the window geometry of the parent surface. * @param x x position relative to parent surface window geometry * @param y y position relative to parent surface window geometry * @param width window geometry width * @param height window geometry height */ void (*configure)(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height); /** * popup interaction is done * * The popup_done event is sent out when a popup is dismissed by * the compositor. The client should destroy the xdg_popup object * at this point. */ void (*popup_done)(void *data, struct xdg_popup *xdg_popup); }; /** * @ingroup iface_xdg_popup */ static inline int xdg_popup_add_listener(struct xdg_popup *xdg_popup, const struct xdg_popup_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) xdg_popup, (void (**)(void)) listener, data); } #define XDG_POPUP_DESTROY 0 #define XDG_POPUP_GRAB 1 /** * @ingroup iface_xdg_popup */ #define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 /** * @ingroup iface_xdg_popup */ #define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 /** * @ingroup iface_xdg_popup */ #define XDG_POPUP_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_popup */ #define XDG_POPUP_GRAB_SINCE_VERSION 1 /** @ingroup iface_xdg_popup */ static inline void xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data); } /** @ingroup iface_xdg_popup */ static inline void * xdg_popup_get_user_data(struct xdg_popup *xdg_popup) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup); } static inline uint32_t xdg_popup_get_version(struct xdg_popup *xdg_popup) { return wl_proxy_get_version((struct wl_proxy *) xdg_popup); } /** * @ingroup iface_xdg_popup * * This destroys the popup. Explicitly destroying the xdg_popup * object will also dismiss the popup, and unmap the surface. * * If this xdg_popup is not the "topmost" popup, a protocol error * will be sent. */ static inline void xdg_popup_destroy(struct xdg_popup *xdg_popup) { wl_proxy_marshal((struct wl_proxy *) xdg_popup, XDG_POPUP_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_popup); } /** * @ingroup iface_xdg_popup * * This request makes the created popup take an explicit grab. An explicit * grab will be dismissed when the user dismisses the popup, or when the * client destroys the xdg_popup. This can be done by the user clicking * outside the surface, using the keyboard, or even locking the screen * through closing the lid or a timeout. * * If the compositor denies the grab, the popup will be immediately * dismissed. * * This request must be used in response to some sort of user action like a * button press, key press, or touch down event. The serial number of the * event should be passed as 'serial'. * * The parent of a grabbing popup must either be an xdg_toplevel surface or * another xdg_popup with an explicit grab. If the parent is another * xdg_popup it means that the popups are nested, with this popup now being * the topmost popup. * * Nested popups must be destroyed in the reverse order they were created * in, e.g. the only popup you are allowed to destroy at all times is the * topmost one. * * When compositors choose to dismiss a popup, they may dismiss every * nested grabbing popup as well. When a compositor dismisses popups, it * will follow the same dismissing order as required from the client. * * The parent of a grabbing popup must either be another xdg_popup with an * active explicit grab, or an xdg_popup or xdg_toplevel, if there are no * explicit grabs already taken. * * If the topmost grabbing popup is destroyed, the grab will be returned to * the parent of the popup, if that parent previously had an explicit grab. * * If the parent is a grabbing popup which has already been dismissed, this * popup will be immediately dismissed. If the parent is a popup that did * not take an explicit grab, an error will be raised. * * During a popup grab, the client owning the grab will receive pointer * and touch events for all their surfaces as normal (similar to an * "owner-events" grab in X11 parlance), while the top most grabbing popup * will always have keyboard focus. */ static inline void xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial) { wl_proxy_marshal((struct wl_proxy *) xdg_popup, XDG_POPUP_GRAB, seat, serial); } #ifdef __cplusplus } #endif #endif imv-4.0.1/src/xdg-shell-protocol.c000066400000000000000000000121151353152213200167650ustar00rootroot00000000000000/* Generated by wayland-scanner 1.17.0 */ /* * Copyright © 2008-2013 Kristian Høgsberg * Copyright © 2013 Rafael Antognolli * Copyright © 2013 Jasper St. Pierre * Copyright © 2010-2013 Intel Corporation * Copyright © 2015-2017 Samsung Electronics Co., Ltd * Copyright © 2015-2017 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include "wayland-util.h" #ifndef __has_attribute # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ #endif #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) #define WL_PRIVATE __attribute__ ((visibility("hidden"))) #else #define WL_PRIVATE #endif extern const struct wl_interface wl_output_interface; extern const struct wl_interface wl_seat_interface; extern const struct wl_interface wl_surface_interface; extern const struct wl_interface xdg_popup_interface; extern const struct wl_interface xdg_positioner_interface; extern const struct wl_interface xdg_surface_interface; extern const struct wl_interface xdg_toplevel_interface; static const struct wl_interface *types[] = { NULL, NULL, NULL, NULL, &xdg_positioner_interface, &xdg_surface_interface, &wl_surface_interface, &xdg_toplevel_interface, &xdg_popup_interface, &xdg_surface_interface, &xdg_positioner_interface, &xdg_toplevel_interface, &wl_seat_interface, NULL, NULL, NULL, &wl_seat_interface, NULL, &wl_seat_interface, NULL, NULL, &wl_output_interface, &wl_seat_interface, NULL, }; static const struct wl_message xdg_wm_base_requests[] = { { "destroy", "", types + 0 }, { "create_positioner", "n", types + 4 }, { "get_xdg_surface", "no", types + 5 }, { "pong", "u", types + 0 }, }; static const struct wl_message xdg_wm_base_events[] = { { "ping", "u", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { "xdg_wm_base", 2, 4, xdg_wm_base_requests, 1, xdg_wm_base_events, }; static const struct wl_message xdg_positioner_requests[] = { { "destroy", "", types + 0 }, { "set_size", "ii", types + 0 }, { "set_anchor_rect", "iiii", types + 0 }, { "set_anchor", "u", types + 0 }, { "set_gravity", "u", types + 0 }, { "set_constraint_adjustment", "u", types + 0 }, { "set_offset", "ii", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_positioner_interface = { "xdg_positioner", 2, 7, xdg_positioner_requests, 0, NULL, }; static const struct wl_message xdg_surface_requests[] = { { "destroy", "", types + 0 }, { "get_toplevel", "n", types + 7 }, { "get_popup", "n?oo", types + 8 }, { "set_window_geometry", "iiii", types + 0 }, { "ack_configure", "u", types + 0 }, }; static const struct wl_message xdg_surface_events[] = { { "configure", "u", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_surface_interface = { "xdg_surface", 2, 5, xdg_surface_requests, 1, xdg_surface_events, }; static const struct wl_message xdg_toplevel_requests[] = { { "destroy", "", types + 0 }, { "set_parent", "?o", types + 11 }, { "set_title", "s", types + 0 }, { "set_app_id", "s", types + 0 }, { "show_window_menu", "ouii", types + 12 }, { "move", "ou", types + 16 }, { "resize", "ouu", types + 18 }, { "set_max_size", "ii", types + 0 }, { "set_min_size", "ii", types + 0 }, { "set_maximized", "", types + 0 }, { "unset_maximized", "", types + 0 }, { "set_fullscreen", "?o", types + 21 }, { "unset_fullscreen", "", types + 0 }, { "set_minimized", "", types + 0 }, }; static const struct wl_message xdg_toplevel_events[] = { { "configure", "iia", types + 0 }, { "close", "", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { "xdg_toplevel", 2, 14, xdg_toplevel_requests, 2, xdg_toplevel_events, }; static const struct wl_message xdg_popup_requests[] = { { "destroy", "", types + 0 }, { "grab", "ou", types + 22 }, }; static const struct wl_message xdg_popup_events[] = { { "configure", "iiii", types + 0 }, { "popup_done", "", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_popup_interface = { "xdg_popup", 2, 2, xdg_popup_requests, 2, xdg_popup_events, }; imv-4.0.1/test/000077500000000000000000000000001353152213200132635ustar00rootroot00000000000000imv-4.0.1/test/list.c000066400000000000000000000023701353152213200144040ustar00rootroot00000000000000#include #include #include #include #include #include "list.h" static void test_split_string(void **state) { (void)state; struct list *list; list = list_from_string("word", ' '); assert_true(list); assert_true(list->len == 1); assert_false(strcmp(list->items[0], "word")); list_deep_free(list); list = list_from_string("hello world this is a test", ' '); assert_true(list); assert_true(list->len == 6); assert_false(strcmp(list->items[0], "hello")); assert_false(strcmp(list->items[1], "world")); assert_false(strcmp(list->items[2], "this")); assert_false(strcmp(list->items[3], "is")); assert_false(strcmp(list->items[4], "a")); assert_false(strcmp(list->items[5], "test")); list_deep_free(list); list = list_from_string(" odd whitespace test ", ' '); assert_true(list); assert_true(list->len == 3); assert_false(strcmp(list->items[0], "odd")); assert_false(strcmp(list->items[1], "whitespace")); assert_false(strcmp(list->items[2], "test")); list_deep_free(list); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_split_string), }; return cmocka_run_group_tests(tests, NULL, NULL); } /* vim:set ts=2 sts=2 sw=2 et: */ imv-4.0.1/test/navigator.c000066400000000000000000000072461353152213200154320ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "navigator.h" #define FILENAME1 "example.file.1" #define FILENAME2 "example.file.2" #define FILENAME3 "example.file.3" #define FILENAME4 "example.file.4" #define FILENAME5 "example.file.5" #define FILENAME6 "example.file.6" static void test_navigator_add_remove(void **state) { (void)state; struct imv_navigator *nav = imv_navigator_create(); /* Check poll_changed */ assert_false(imv_navigator_poll_changed(nav)); /* Add 6 paths, one non-existant should fail */ assert_false(imv_navigator_add(nav, FILENAME1, 0)); assert_false(imv_navigator_add(nav, FILENAME2, 0)); assert_false(imv_navigator_add(nav, FILENAME3, 0)); assert_false(imv_navigator_add(nav, FILENAME4, 0)); assert_false(imv_navigator_add(nav, FILENAME5, 0)); assert_false(imv_navigator_add(nav, FILENAME6, 0)); assert_int_equal(imv_navigator_length(nav), 6); /* Check poll_changed */ assert_true(imv_navigator_poll_changed(nav)); /* Make sure current selection is #1 */ assert_string_equal(imv_navigator_selection(nav), FILENAME1); /* Move right and remove current file (#2); should get to #3 */ imv_navigator_select_rel(nav, 1); assert_string_equal(imv_navigator_selection(nav), FILENAME2); imv_navigator_remove(nav, FILENAME2); assert_int_equal(imv_navigator_length(nav), 5); assert_string_equal(imv_navigator_selection(nav), FILENAME3); /* Move left and remove current file (#1); should get to #6 */ imv_navigator_select_rel(nav, -1); assert_string_equal(imv_navigator_selection(nav), FILENAME1); imv_navigator_remove(nav, FILENAME1); assert_string_equal(imv_navigator_selection(nav), FILENAME6); /* Move left, right, remove current file (#6); should get to #3 */ imv_navigator_select_rel(nav, -1); imv_navigator_select_rel(nav, 1); assert_string_equal(imv_navigator_selection(nav), FILENAME6); imv_navigator_remove(nav, FILENAME6); assert_string_equal(imv_navigator_selection(nav), FILENAME3); /* Remove #4; should not move */ imv_navigator_remove(nav, FILENAME4); assert_string_equal(imv_navigator_selection(nav), FILENAME3); /* Verify that #4 is removed by moving left; should get to #5 */ imv_navigator_select_rel(nav, 1); assert_string_equal(imv_navigator_selection(nav), FILENAME5); imv_navigator_free(nav); } static void test_navigator_file_changed(void **state) { int fd; struct imv_navigator *nav = imv_navigator_create(); struct timespec times[2] = { {0, 0}, {0, 0} }; (void)state; fd = open(FILENAME1, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { imv_navigator_free(nav); (void)unlink(FILENAME1); skip(); } assert_false(futimens(fd, times) == -1); assert_false(imv_navigator_add(nav, FILENAME1, 0)); assert_true(imv_navigator_poll_changed(nav)); assert_false(imv_navigator_poll_changed(nav)); assert_false(sleep(1)); fd = open(FILENAME1, O_RDWR); assert_false(fd == -1); times[0].tv_nsec = UTIME_NOW; times[0].tv_sec = UTIME_NOW; times[1].tv_nsec = UTIME_NOW; times[1].tv_sec = UTIME_NOW; assert_false(futimens(fd, times) == -1); /* sleep to ensure we don't hit the poll rate-limiting */ sleep(1); assert_true(imv_navigator_poll_changed(nav)); (void)close(fd); (void)unlink(FILENAME1); imv_navigator_free(nav); } int main(void) { (void)test_navigator_add_remove; /* skipped for now */ const struct CMUnitTest tests[] = { /* cmocka_unit_test(test_navigator_add_remove), */ cmocka_unit_test(test_navigator_file_changed), }; return cmocka_run_group_tests(tests, NULL, NULL); } /* vim:set ts=2 sts=2 sw=2 et: */