aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/binds.c16
-rw-r--r--src/imv.c186
-rw-r--r--src/ini.c398
-rw-r--r--src/ini.h111
4 files changed, 458 insertions, 253 deletions
diff --git a/src/binds.c b/src/binds.c
index 9de63d7..e0426e6 100644
--- a/src/binds.c
+++ b/src/binds.c
@@ -189,15 +189,18 @@ static int print_event(char *buf, size_t len, const SDL_Event *event)
/* Try plain old character input */
const char *keyname = SDL_GetKeyName(kevent->keysym.sym);
- /* Because '<' and '>' have special meaning in our syntax, and '=' is
- * restricted within ini files, we rename these.
- */
+ /* Because '<' and '>' have special meaning in our syntax, and '=', '[', and
+ * ']' are restricted within ini files, we rename these. */
if(!strcmp(keyname, "<")) {
keyname = "Less";
} else if(!strcmp(keyname, ">")) {
keyname = "Greater";
} else if(!strcmp(keyname, "=")) {
keyname = "Equals";
+ } else if(!strcmp(keyname, "[")) {
+ keyname = "LeftSquareBracket";
+ } else if(!strcmp(keyname, "]")) {
+ keyname = "RightSquareBracket";
}
return snprintf(buf, len, "%s%s", prefix, keyname);
@@ -263,6 +266,13 @@ struct list *imv_bind_parse_keys(const char *keys)
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;
}
}
diff --git a/src/imv.c b/src/imv.c
index 642a060..efad1f7 100644
--- a/src/imv.c
+++ b/src/imv.c
@@ -86,11 +86,6 @@ struct imv {
} current_image;
};
-enum config_section {
- CFG_OPTIONS,
- CFG_BINDS
-};
-
void command_quit(struct list *args, const char *argstr, void *data);
void command_pan(struct list *args, const char *argstr, void *data);
void command_select_rel(struct list *args, const char *argstr, void *data);
@@ -111,14 +106,26 @@ static bool setup_window(struct imv *imv);
static void handle_event(struct imv *imv, SDL_Event *event);
static void render_window(struct imv *imv);
-static void add_bind(struct imv *imv, const char *keys, const char *command)
+static const char *add_bind(struct imv *imv, const char *keys, const char *command)
{
struct list *list = imv_bind_parse_keys(keys);
if(!list) {
- return;
+ return "Invalid key combination";
+ }
+
+ enum bind_result result = imv_binds_add(imv->binds, list, command);
+
+ if (result == BIND_SUCCESS) {
+ return NULL;
+ } else if (result == BIND_INVALID_KEYS) {
+ return "Invalid keys to bind to";
+ } else if (result == BIND_INVALID_COMMAND) {
+ return "No command given to bind to";
+ } else if (result == BIND_CONFLICTS) {
+ return "Key combination conflicts with existing bind";
}
- imv_binds_add(imv->binds, list, command);
+ return NULL;
}
struct imv *imv_create(void)
@@ -860,80 +867,115 @@ static bool parse_bool(const char *str)
);
}
-bool imv_load_config(struct imv *imv)
+static int handle_ini_value(void *user, const char *section, const char *name,
+ const char *value)
{
- const char *path = get_config_path();
- if(!path) {
- return true;
- }
+ struct imv *imv = user;
- FILE *f = fopen(path, "r");
- if (!f) {
- fprintf(stderr, "Unable to open config file: %s\n", path);
- return false;
+ if (!strcmp(section, "binds")) {
+ const char *err = add_bind(imv, name, value);
+ if (err) {
+ fprintf(stderr, "Config error: %s\n", err);
+ return 0;
+ }
+ return 1;
}
- enum config_section sect = CFG_OPTIONS;
- int type;
- do {
- char key[128], value[128];
- type = parse_ini_file(f, &key[0], sizeof(key), &value[0], sizeof(value));
+ if (!strcmp(section, "options")) {
- if(type == INI_SECTION) {
- if(!strcmp(key, "binds")) {
- sect = CFG_BINDS;
- } else {
- fprintf(stderr, "Unknown config section: %s\n", key);
+ if(!strcmp(name, "fullscreen")) {
+ imv->fullscreen = parse_bool(value);
+ return 1;
+ }
+
+ if(!strcmp(name, "overlay")) {
+ imv->overlay_enabled = parse_bool(value);
+ return 1;
+ }
+
+ if(!strcmp(name, "sampling")) {
+ imv->nearest_neighbour = !strcmp(value, "nearest_neighbour");
+ return 1;
+ }
+
+ if(!strcmp(name, "recursive")) {
+ imv->recursive_load = parse_bool(value);
+ return 1;
+ }
+
+ if(!strcmp(name, "cycle_input")) {
+ imv->cycle_input = parse_bool(value);
+ return 1;
+ }
+
+ if(!strcmp(name, "list_at_exit")) {
+ imv->list_at_exit = parse_bool(value);
+ return 1;
+ }
+
+ if(!strcmp(name, "scaling")) {
+ if(!strcmp(value, "none")) {
+ imv->scaling_mode = SCALING_NONE;
+ } else if(!strcmp(value, "shrink")) {
+ imv->scaling_mode = SCALING_DOWN;
+ } else if(!strcmp(value, "full")) {
+ imv->scaling_mode = SCALING_FULL;
+ }
+ return 1;
+ }
+
+ if(!strcmp(name, "background")) {
+ if(!parse_bg(imv, value)) {
return false;
}
- } else if(type == INI_VALUE) {
- if(sect == CFG_OPTIONS) {
- if(!strcmp(key, "fullscreen")) {
- imv->fullscreen = parse_bool(value);
- } else if(!strcmp(key, "overlay")) {
- imv->overlay_enabled = parse_bool(value);
- } else if(!strcmp(key, "sampling")) {
- imv->nearest_neighbour = !strcmp(value, "nearest_neighbour");
- } else if(!strcmp(key, "recursive")) {
- imv->recursive_load = parse_bool(value);
- } else if(!strcmp(key, "cycle_input")) {
- imv->cycle_input = parse_bool(value);
- } else if(!strcmp(key, "list_at_exit")) {
- imv->list_at_exit = parse_bool(value);
- } else if(!strcmp(key, "scaling")) {
- if(!strcmp(value, "none")) {
- imv->scaling_mode = SCALING_NONE;
- } else if(!strcmp(value, "shrink")) {
- imv->scaling_mode = SCALING_DOWN;
- } else if(!strcmp(value, "full")) {
- imv->scaling_mode = SCALING_FULL;
- }
- } else if(!strcmp(key, "background")) {
- if(!parse_bg(imv, value)) {
- return false;
- }
- } else if(!strcmp(key, "slideshow")) {
- if(!parse_slideshow_duration(imv, value)) {
- return false;
- }
- } else if(!strcmp(key, "overlay_font")) {
- free(imv->font_name);
- imv->font_name = strdup(value);
- } else if(!strcmp(key, "default_binds")) {
- const bool default_binds = parse_bool(value);
- if(!default_binds) {
- /* clear out any default binds if requested */
- imv_binds_clear(imv->binds);
- }
- } else {
- fprintf(stderr, "Ignoring unknown option: %s\n", key);
- }
- } else if(sect == CFG_BINDS) {
- add_bind(imv, key, value);
+ return 1;
+ }
+
+ if(!strcmp(name, "slideshow")) {
+ if(!parse_slideshow_duration(imv, value)) {
+ return false;
}
+ return 1;
+ }
+
+ if(!strcmp(name, "overlay_font")) {
+ free(imv->font_name);
+ imv->font_name = strdup(value);
+ return 1;
}
- } while(type);
+ if(!strcmp(name, "default_binds")) {
+ const bool default_binds = parse_bool(value);
+ if(!default_binds) {
+ /* clear out any default binds if requested */
+ imv_binds_clear(imv->binds);
+ }
+ return 1;
+ }
+
+ /* No matches so far */
+ fprintf(stderr, "Ignoring unknown option: %s\n", name);
+ return 1;
+ }
+ return 0;
+}
+
+bool imv_load_config(struct imv *imv)
+{
+ const char *path = get_config_path();
+ if(!path) {
+ /* no config, no problem - we have defaults */
+ return true;
+ }
+
+ const int err = ini_parse(path, handle_ini_value, imv);
+ if (err == -1) {
+ fprintf(stderr, "Unable to open config file: %s\n", path);
+ return false;
+ } else if (err > 0) {
+ fprintf(stderr, "Error in config file: %s:%d\n", path, err);
+ return false;
+ }
return true;
}
diff --git a/src/ini.c b/src/ini.c
index e4e734d..f9dba36 100644
--- a/src/ini.c
+++ b/src/ini.c
@@ -1,188 +1,244 @@
-#include "ini.h"
+/* 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 <stdio.h>
#include <ctype.h>
-#include <stdlib.h>
#include <string.h>
-enum parse_state {
- START,
- READING_KEY,
- SEEKING_EQ,
- SEEKING_VALUE,
- READING_VALUE,
- READING_SECTION_KEY,
- SEEKING_SECTION_VALUE,
- SEEKING_END_SECTION,
- READING_SECTION_VALUE,
- ACCEPT,
- REJECT
-};
-
-int parse_ini_file(FILE* f, char *out_key, size_t key_size, char *out_value, size_t value_size)
+#include "ini.h"
+
+#if !INI_USE_STACK
+#include <stdlib.h>
+#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 buf[512];
- while(fgets(&buf[0], sizeof(buf), f)) {
- int type = parse_ini_str(&buf[0], out_key, key_size, out_value, value_size);
- if(type != 0) {
- return type;
+ 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++;
}
- }
- return 0;
+#endif
+ return (char*)s;
}
-int parse_ini_str(const char* str, char *out_key, size_t key_size, char *out_value, size_t value_size)
+/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
+static char* strncpy0(char* dest, const char* src, size_t size)
{
- /* ignore comments */
- if(*str == ';') {
- out_key[0] = 0;
- out_value[0] = 0;
- return INI_UNKNOWN;
- }
-
- const char *key_start = 0;
- size_t key_len = 0;
- const char *value_start = 0;
- size_t value_len = 0;
-
- int type = INI_UNKNOWN;
- enum parse_state state = START;
- char quote = 0;
- char c = 0;
-
- do {
- c = *str;
- switch(state) {
- case START:
- if(c == '[') {
- type = INI_SECTION;
- state = READING_SECTION_KEY;
- } else if(c != '=') {
- type = INI_VALUE;
- state = READING_KEY;
- key_start = str;
- ++key_len;
- } else {
- state = REJECT;
- }
- break;
- case READING_KEY:
- if(isspace(c)) {
- state = SEEKING_EQ;
- } else if(c == '=') {
- state = SEEKING_VALUE;
- } else {
- ++key_len;
- }
- break;
- case SEEKING_EQ:
- if(c == '=') {
- state = SEEKING_VALUE;
- } else if(!isspace(c)) {
- state = REJECT;
- }
- break;
- case SEEKING_VALUE:
- if(c == '"' || c == '\'') {
- if(!value_start) {
- quote = c;
- state = READING_VALUE;
- } else {
- state = REJECT;
- }
- } else if(!isspace(c)) {
- value_start = str;
- ++value_len;
- state = READING_VALUE;
- }
- break;
- case READING_VALUE:
- if(quote == 0 && isspace(c)) {
- state = ACCEPT;
- } else if(quote && c == quote) {
- state = ACCEPT;
- } else if(c == 0) {
- state = ACCEPT;
- } else if(c) {
- if(!value_start) {
- value_start = str;
- }
- ++value_len;
- } else {
- state = REJECT;
- }
- break;
- case READING_SECTION_KEY:
- if(isspace(c)) {
- state = SEEKING_SECTION_VALUE;
- } else if(c == ']') {
- state = ACCEPT;
- } else {
- if(!key_start) {
- key_start = str;
- }
- ++key_len;
+ 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;
}
- break;
- case SEEKING_SECTION_VALUE:
- if(c == '"' || c == '\'') {
- quote = c;
- state = READING_SECTION_VALUE;
- } else if(c == ']') {
- state = ACCEPT;
- } else if(!isspace(c)) {
- state = REJECT;
+#endif
+ start = lskip(rstrip(start));
+
+ if (*start == ';' || *start == '#') {
+ /* Per Python configparser, allow both ; and # comments at the
+ start of a line */
}
- break;
- case READING_SECTION_VALUE:
- if(quote == 0 && isspace(c)) {
- state = SEEKING_END_SECTION;
- } else if(quote && c == quote) {
- state = SEEKING_END_SECTION;
- } else if(c == ']') {
- state = ACCEPT;
- } else if(c) {
- if(!value_start) {
- value_start = str;
- }
- ++value_len;
- } else {
- state = REJECT;
+#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;
}
- break;
- case SEEKING_END_SECTION:
- if(c == ']') {
- state = ACCEPT;
- } else if(!isspace(c)) {
- state = REJECT;
+#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;
+ }
}
- break;
- case ACCEPT:
- if(c != 0 && !isspace(c)) {
- state = REJECT;
+ 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;
+ }
}
- break;
- case REJECT:
- return 0;
- break;
- }
- ++str;
- } while(c != 0 && state != REJECT);
- if(state == ACCEPT) {
- if(key_len >= key_size) {
- key_len = key_size - 1;
+#if INI_STOP_ON_FIRST_ERROR
+ if (error)
+ break;
+#endif
}
- if(value_len >= value_size) {
- value_len = value_size - 1;
+
+#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--;
}
- memcpy(out_key, key_start, key_len);
- memcpy(out_value, value_start, value_len);
- out_key[key_len] = 0;
- out_value[value_len] = 0;
- return type;
- } else {
- out_key[0] = 0;
- out_value[0] = 0;
- }
-
- return INI_UNKNOWN;
+
+ *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);
}
diff --git a/src/ini.h b/src/ini.h
index 12f4228..569dfdb 100644
--- a/src/ini.h
+++ b/src/ini.h
@@ -1,13 +1,110 @@
-#ifndef INI_H
-#define INI_H
+/* 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 <stdio.h>
-#define INI_UNKNOWN 0
-#define INI_VALUE 1
-#define INI_SECTION 2
+/* 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);
-int parse_ini_file(FILE* f, char *out_key, size_t key_size, char *out_value, size_t value_size);
-int parse_ini_str(const char* str, char *out_key, size_t key_size, char *out_value, size_t value_size);
+/* 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__ */