8000 imv/src/imv.c at master · somini/imv · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
1988 lines (1687 loc) · 54.1 KB

imv.c

File metadata and controls

1988 lines (1687 loc) · 54.1 KB
#include "imv.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <wordexp.h>
#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 color_rgb {
unsigned char r, g, b;
};
struct internal_event {
enum internal_event_type type;
union {
struct {
struct imv_image *image;
int frametime;
bool is_new_image;
} new_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;
/* overlay */
struct {
/* display some textual info onscreen */
bool enabled;
/* the user-specified format strings for the overlay*/
char *text;
struct color_rgb text_color;
unsigned char text_alpha;
struct color_rgb background_color;
unsigned char background_alpha;
/* overlay position */
bool position_at_bottom;
/* overlay font */
struct {
char *name;
int size;
} font;
} overlay;
/* method for scaling up images: interpolate or nearest neighbour */
enum upscaling_method upscaling_method;
/* dirty state flags */
bool need_redraw;
bool need_rescale;
bool cache_invalidated;
/* 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;
/* FILE to use when reading paths from stdin */
FILE *stdin_pipe;
/* 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 color_rgb 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;
/* 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;
/* 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_rotate(struct list *args, const char *argstr, void *data);
static void command_flip(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 = "<Shift+";
const size_t prefix_len = strlen(prefix);
if (strncmp(keys, prefix, prefix_len)) {
return false;
}
/* Has a Shift prefix. Check for single lower case character. */
if (islower(keys[prefix_len]) && keys[prefix_len+1] == '>') {
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"
"<Shift+n> would now be <Shift+N>.\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 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;
}
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.description)) {
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)) {
7438 imv_console_activate(imv->console);
imv->need_redraw = true;
return;
}
/* Keys such as Shift on their own come through as '', we should skip them
* since they'll turn up later as 'Shift+W', etc.
*/
if (*event->data.keyboard.description != '\0') {
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 bool hex_value_to_color_rgb(const char* hex, struct color_rgb* color)
{
if (*hex == '#')
hex++;
char *ep;
uint32_t n = strtoul(hex, &ep, 16);
if (*ep != '\0' || ep - hex != 6 || n > 0xFFFFFF) {
imv_log(IMV_ERROR, "Invalid hex color: '%s'\n", hex);
return false;
}
color->b = n & 0xFF;
color->g = (n >> 8) & 0xFF;
color->r = (n >> 16);
return true;
}
static bool hex_value_to_alpha(const char* hex, unsigned char *alpha)
{
if (*hex == '#')
hex++;
char *ep;
uint32_t n = strtoul(hex, &ep, 16);
if (*ep != '\0' || ep - hex != 2 || n > 0xFF) {
imv_log(IMV_ERROR, "Invalid hex color: '%s'\n", hex);
return false;
}
*alpha = n & 0xFF;
return true;
}
static void log_to_stderr(enum imv_log_level level, const char *text, void *data)
{
(void)data;
if (level >= IMV_INFO) {
fputs(text, stderr);
}
}
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->overlay.font.name = strdup("Monospace");
imv->overlay.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();
if (imv->ipc) {
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->overlay.text_color.r = 255;
imv->overlay.text_color.g = 255;
imv->overlay.text_color.b = 255;
imv->overlay.text_alpha = 255;
imv->overlay.background_color.r = 0;
imv->overlay.background_color.g = 0;
imv->overlay.background_color.b = 0;
imv->overlay.background_alpha = 195;
imv->overlay.position_at_bottom = false;
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, "rotate", &command_rotate);
imv_command_register(imv->commands, "flip", &command_flip);
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 <v4 */
imv_command_alias(imv->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, "<Left>", "prev");
add_bind(imv, "<bracketleft>", "prev");
add_bind(imv, "<Right>", "next");
add_bind(imv, "<bracketright>", "next");
add_bind(imv, "gg", "goto 0");
add_bind(imv, "<Shift+G>", "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, "<Up>", "zoom 1");
add_bind(imv, "<Shift+plus>", "zoom 1");
add_bind(imv, "i", "zoom 1");
add_bind(imv, "<Down>", "zoom -1");
add_bind(imv, "<minus>", "zoom -1");
add_bind(imv, "o", "zoom -1");
add_bind(imv, "c", "center");
add_bind(imv, "s", "scaling next");
add_bind(imv, "<Shift+S>", "upscaling next");
add_bind(imv, "a", "zoom actual");
add_bind(imv, "r", "reset");
add_bind(imv, "<period>", "next_frame");
add_bind(imv, "<space>", "toggle_playing");
add_bind(imv, "t", "slideshow +1");
add_bind(imv, "<Shift+T>", "slideshow -1");
return imv;
}
void imv_free(struct imv *imv)
{
free(imv->overlay.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_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;
return hex_value_to_color_rgb(bg, &imv->background.color);
}
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 *pipe_stdin(void *data)
{
int *fd = data;
while (1) {
char buf[PIPE_BUF];
ssize_t err = read(STDIN_FILENO, buf, PIPE_BUF);
if (err > 0) {
/* writes up to PIPE_BUF are atomic */
write(*fd, buf, err);
} else if (err == 0 || errno != EINTR) {
/* break if EOF or actual read error */
break;
}
}
return NULL;
}
static void *load_paths_from_stdin(void *data)
{
struct imv *imv = data;
imv_log(IMV_INFO, "Reading paths from stdin...\n");
char buf[PATH_MAX];
while (fgets(buf, sizeof(buf), imv->stdin_pipe) != 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 */
int *stdin_pipe_fds = NULL;
pthread_t pipe_stdin_thread;
pthread_t load_paths_thread;
if (imv->paths_from_stdin) {
/* this array is allocated on the heap because it's passed to pthread_create */
stdin_pipe_fds = calloc(2, sizeof *stdin_pipe_fds);
if (pipe(stdin_pipe_fds)) {
/* if pipe creation fails, we should exit */
free(stdin_pipe_fds);
return 1;
}
imv->stdin_pipe = fdopen(stdin_pipe_fds[0], "re");
if (pthread_create(&load_paths_thread, NULL, load_paths_from_stdin, imv)
|| pthread_create(&pipe_stdin_thread, NULL, pipe_stdin, stdin_pipe_fds + 1)) {
return 1;
}
}
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;
0