diff --git a/Makefile b/Makefile index 4aff30b..bb516b7 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ include config.mk -SRC = draw.c dunst.c +SRC = draw.c dunst.c ini.c OBJ = ${SRC:.c=.o} all: doc options dunst @@ -20,9 +20,9 @@ options: ${OBJ}: config.mk -dunst: draw.o dunst.o +dunst: draw.o dunst.o ini.o @echo CC -o $@ - @${CC} ${CFLAGS} -o $@ dunst.o draw.o ${LDFLAGS} + @${CC} ${CFLAGS} -o $@ dunst.o draw.o ini.o ${LDFLAGS} clean: @echo cleaning diff --git a/config.mk b/config.mk index bb62a74..a1a860e 100644 --- a/config.mk +++ b/config.mk @@ -13,12 +13,15 @@ XFTLIBS = -lXft -lXrender -lfreetype -lz -lfontconfig XINERAMALIBS = -lXinerama XINERAMAFLAGS = -DXINERAMA +# inih flags +INIFLAGS = -DINI_ALLOW_MULTILINE=0 + # includes and libs INCS = -I${X11INC} -I/usr/lib/dbus-1.0/include -I/usr/include/dbus-1.0 ${XFTINC} LIBS = -L${X11LIB} -lX11 -lXext -lXss -ldbus-1 ${XFTLIBS} -lpthread -liniparser -lrt ${XINERAMALIBS} # flags -CPPFLAGS = -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +CPPFLAGS = -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} ${INIFLAGS} CFLAGS = -g -ansi -pedantic -Wall -Os ${INCS} ${CPPFLAGS} LDFLAGS = ${LIBS} diff --git a/dunst.c b/dunst.c index fb44b33..22b363d 100644 --- a/dunst.c +++ b/dunst.c @@ -15,10 +15,9 @@ #endif #include -#include - #include "dunst.h" #include "draw.h" +#include "ini.h" #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) #define LENGTH(X) (sizeof X / sizeof X[0]) @@ -882,35 +881,190 @@ parse_cmdline(int argc, char *argv[]) { } } -char * -create_iniparser_key(char *section, char *key) { - char *new_key; +static int +dunst_ini_get_boolean(const char *value) { - new_key = malloc(sizeof(char) * (strlen(section) - + strlen(key) - + strlen(":") - + 1 /* \0 */ )); + switch (value[0]) + { + case 'y': + case 'Y': + case 't': + case 'T': + case '1': + return True; + case 'n': + case 'N': + case 'f': + case 'F': + case '0': + return False; + default: + return False; + } +} - sprintf(new_key, "%s:%s", section, key); - return new_key; +static rule_t * +dunst_rules_find_or_create(const char *section) { + + rule_t *current_rule = rules, *last_rule; + + while (current_rule && strcmp(current_rule->name, section) != 0) { + current_rule = current_rule->next; + } + + if (current_rule) { + return current_rule; + } + + dunst_printf(DEBUG, "adding rule %s\n", section); + + current_rule = initrule(); + current_rule->name = strdup(section); + current_rule->appname = NULL; + current_rule->summary = NULL; + current_rule->body = NULL; + current_rule->icon = NULL; + current_rule->timeout = -1; + current_rule->urgency = -1; + current_rule->fg = NULL; + current_rule->bg = NULL; + current_rule->format = NULL; + + last_rule = rules; + while (last_rule && last_rule->next) { + last_rule = last_rule->next; + } + + if (last_rule == NULL) { + last_rule = current_rule; + rules = last_rule; + } else { + last_rule->next = current_rule; + } + + return current_rule; +} + +static char * +dunst_ini_get_string(const char *value) { + + char *s; + + if (value[0] == '"') + s = strdup(value + 1); + else + s = strdup(value); + + if (s[strlen(s) - 1] == '"') + s[strlen(s) - 1] = '\0'; + + return s; +} + +static int +dunst_ini_handle(void *user_data, const char *section, + const char *name, const char *value) { + + if (strcmp(section, "global") == 0) { + if (strcmp(name, "font") == 0) + font = dunst_ini_get_string(value); + else if (strcmp(name, "format") == 0) + format = dunst_ini_get_string(value); + else if (strcmp(name, "sort") == 0) + sort = dunst_ini_get_boolean(value); + else if (strcmp(name, "indicate_hidden") == 0) + indicate_hidden = dunst_ini_get_boolean(value); + else if (strcmp(name, "key") == 0) + key_string = dunst_ini_get_string(value); + else if (strcmp(name, "history_key") == 0) + history_key_string = dunst_ini_get_string(value); + else if (strcmp(name, "geometry") == 0) { + geom = dunst_ini_get_string(value); + geometry.mask = XParseGeometry(geom, + &geometry.x, &geometry.y, + &geometry.w, &geometry.h); + } else if (strcmp(name, "modifier") == 0) { + char *mod = dunst_ini_get_string(value); + + if (mod == NULL) { + mask = 0; + } else if (!strcmp(mod, "ctrl")) { + mask = ControlMask; + } else if (!strcmp(mod, "mod4")) { + mask = Mod4Mask; + } else if (!strcmp(mod, "mod3")) { + mask = Mod3Mask; + } else if (!strcmp(mod, "mod2")) { + mask = Mod2Mask; + } else if (!strcmp(mod, "mod1")) { + mask = Mod1Mask; + } else { + mask = 0; + } + free (mod); + } + } else if (strcmp(section, "urgency_low") == 0) { + if (strcmp(name, "background") == 0) + lowbgcolor = dunst_ini_get_string(value); + else if (strcmp(name, "foreground") == 0) + lowfgcolor = dunst_ini_get_string(value); + else if (strcmp(name, "timeout") == 0) + timeouts[LOW] = atoi(value); + } else if (strcmp(section, "urgency_normal") == 0) { + if (strcmp(name, "background") == 0) + normbgcolor = dunst_ini_get_string(value); + else if (strcmp(name, "foreground") == 0) + normfgcolor = dunst_ini_get_string(value); + else if (strcmp(name, "timeout") == 0) + timeouts[NORM] = atoi(value); + } else if (strcmp(section, "urgency_critical") == 0) { + if (strcmp(name, "background") == 0) + critbgcolor = dunst_ini_get_string(value); + else if (strcmp(name, "foreground") == 0) + critfgcolor = dunst_ini_get_string(value); + else if (strcmp(name, "timeout") == 0) + timeouts[CRIT] = atoi(value); + } else { + + rule_t *current_rule = dunst_rules_find_or_create(section); + + if (strcmp(name, "appname") == 0) + current_rule->appname = dunst_ini_get_string(value); + else if (strcmp(name, "summary") == 0) + current_rule->summary = dunst_ini_get_string(value); + else if (strcmp(name, "body") == 0) + current_rule->body = dunst_ini_get_string(value); + else if (strcmp(name, "icon") == 0) + current_rule->icon = dunst_ini_get_string(value); + else if (strcmp(name, "timeout") == 0) + current_rule->timeout = atoi(value); + else if (strcmp(name, "urgency") == 0) { + const char *urg = value; + + if (strcmp(urg, "low") == 0) + current_rule->urgency = LOW; + else if (strcmp(urg, "normal") == 0) + current_rule->urgency = NORM; + else if (strcmp(urg, "critical") == 0) + current_rule->urgency = CRIT; + else + fprintf(stderr, "unknown urgency: %s, ignoring\n", urg); + } else if (strcmp(name, "foreground") == 0) + current_rule->fg = dunst_ini_get_string(value); + else if (strcmp(name, "background") == 0) + current_rule->bg = dunst_ini_get_string(value); + else if (strcmp(name, "format") == 0) + current_rule->format = dunst_ini_get_string(value); + } + + return 1; } void parse_dunstrc(void) { - char *mod = NULL; - char *urg = NULL; - char *secname = NULL; - rule_t *last_rule; - rule_t *new_rule; - char *key; char *config_path = NULL; - int nsec = 0; - int i; - - dictionary *ini; - dunst_printf(DEBUG, "Begin parsing of dunstrc\n"); if (config_file == NULL) { @@ -931,130 +1085,12 @@ parse_dunstrc(void) { } dunst_printf(DEBUG, "Reading %s\n", config_file); - ini = iniparser_load(config_file); - if (ini == NULL) { + + if (ini_parse(config_file, dunst_ini_handle, NULL) < 0) { puts("no dunstrc found -> skipping\n"); } - font = iniparser_getstring(ini, "global:font", font); - format = iniparser_getstring(ini, "global:format", format); - sort = iniparser_getboolean(ini, "global:sort", sort); - indicate_hidden = iniparser_getboolean(ini, "global:indicate_hidden", indicate_hidden); - idle_threshold = iniparser_getint(ini, "global:idle_threshold", idle_threshold); - key_string = iniparser_getstring(ini, "global:key", key_string); - history_key_string = iniparser_getstring(ini, "global:history_key", history_key_string); - - geom = iniparser_getstring(ini, "global:geometry", geom); - geometry.mask = XParseGeometry(geom, - &geometry.x, &geometry.y, - &geometry.w, &geometry.h); - - mod = iniparser_getstring(ini, "global:modifier", mod); - - if (mod == NULL) { - mask = 0; - } else if (!strcmp(mod, "ctrl")) { - mask = ControlMask; - } else if (!strcmp(mod, "mod4")) { - mask = Mod4Mask; - } else if (!strcmp(mod, "mod3")) { - mask = Mod3Mask; - } else if (!strcmp(mod, "mod2")) { - mask = Mod2Mask; - } else if (!strcmp(mod, "mod1")) { - mask = Mod1Mask; - } else { - mask = 0; - } - - lowbgcolor = iniparser_getstring(ini, "urgency_low:background", lowbgcolor); - lowfgcolor = iniparser_getstring(ini, "urgency_low:foreground", lowfgcolor); - timeouts[LOW] = iniparser_getint(ini, "urgency_low:timeout", timeouts[LOW]); - normbgcolor = iniparser_getstring(ini, "urgency_normal:background", normbgcolor); - normfgcolor = iniparser_getstring(ini, "urgency_normal:foreground", normfgcolor); - timeouts[NORM] = iniparser_getint(ini, "urgency_normal:timeout", timeouts[NORM]); - critbgcolor = iniparser_getstring(ini, "urgency_critical:background", critbgcolor); - critfgcolor = iniparser_getstring(ini, "urgency_critical:foreground", critfgcolor); - timeouts[CRIT] = iniparser_getint(ini, "urgency_critical:timeout", timeouts[CRIT]); - - /* parse rules */ - nsec = iniparser_getnsec(ini); - - /* init last_rule */ - last_rule = rules; - while (last_rule && last_rule->next) { - last_rule = last_rule->next; - } - - for (i = 0; i < nsec; i++) { - secname = iniparser_getsecname(ini, i); - - /* every section not handled above is considered a rule */ - if (strcmp(secname, "global") == 0 - || strcmp(secname, "urgency_low") == 0 - || strcmp(secname, "urgency_normal") == 0 - || strcmp(secname, "urgency_critical") == 0) { - continue; - } - - dunst_printf(DEBUG, "adding rule %s\n", secname); - - new_rule = initrule(); - - new_rule->name = secname; - key = create_iniparser_key(secname, "appname"); - new_rule->appname = iniparser_getstring(ini, key, NULL); - free(key); - key = create_iniparser_key(secname, "summary"); - new_rule->summary= iniparser_getstring(ini, key, NULL); - free(key); - key = create_iniparser_key(secname, "body"); - new_rule->body= iniparser_getstring(ini, key, NULL); - free(key); - key = create_iniparser_key(secname, "icon"); - new_rule->icon= iniparser_getstring(ini, key, NULL); - free(key); - key = create_iniparser_key(secname, "timeout"); - new_rule->timeout= iniparser_getint(ini, key, -1); - free(key); - - key = create_iniparser_key(secname, "urgency"); - urg = iniparser_getstring(ini, key, NULL); - free(key); - if (urg == NULL) { - new_rule->urgency = -1; - } else if (!strcmp(urg, "low")) { - new_rule->urgency = LOW; - } else if (!strcmp(urg, "normal")) { - new_rule->urgency = NORM; - } else if (!strcmp(urg, "critical")) { - new_rule->urgency = CRIT; - } else { - fprintf(stderr, "unknown urgency: %s, ignoring\n", urg); - } - - key = create_iniparser_key(secname, "foreground"); - new_rule->fg= iniparser_getstring(ini, key, NULL); - free(key); - key = create_iniparser_key(secname, "background"); - new_rule->bg= iniparser_getstring(ini, key, NULL); - free(key); - key = create_iniparser_key(secname, "format"); - new_rule->format= iniparser_getstring(ini, key, NULL); - free(key); - - if (last_rule == NULL) { - last_rule = new_rule; - rules = last_rule; - } else { - last_rule->next = new_rule; - last_rule = last_rule->next; - } - print_rule(last_rule); - } - print_rules(); - } diff --git a/ini.c b/ini.c new file mode 100644 index 0000000..5edbdab --- /dev/null +++ b/ini.c @@ -0,0 +1,149 @@ +/* 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: + +http://code.google.com/p/inih/ + +*/ + +#include +#include +#include + +#include "ini.h" + +#define MAX_LINE 200 +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* 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(*--p)) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace(*s)) + s++; + return (char*)s; +} + +/* Return pointer to first char c or ';' comment in given string, or pointer to + null at end of string if neither found. ';' must be prefixed by a whitespace + character to register as a comment. */ +static char* find_char_or_comment(const char* s, char c) +{ + int was_whitespace = 0; + while (*s && *s != c && !(was_whitespace && *s == ';')) { + was_whitespace = isspace(*s); + s++; + } + 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_file(FILE* file, + int (*handler)(void*, const char*, const char*, + const char*), + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ + char line[MAX_LINE]; + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + + /* Scan through file line by line */ + while (fgets(line, sizeof(line), file) != NULL) { + lineno++; + start = lskip(rstrip(line)); + + if (*start == ';' || *start == '#') { + /* Per Python ConfigParser, allow '#' comments at start of line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-black 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_char_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 && *start != ';') { + /* Not a comment, must be a name[=:]value pair */ + end = find_char_or_comment(start, '='); + if (*end != '=') { + end = find_char_or_comment(start, ':'); + } + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); + end = find_char_or_comment(value, '\0'); + if (*end == ';') + *end = '\0'; + 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; + } + } + } + + return error; +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, + int (*handler)(void*, const char*, const char*, const char*), + 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; +} diff --git a/ini.h b/ini.h new file mode 100644 index 0000000..07eed55 --- /dev/null +++ b/ini.h @@ -0,0 +1,55 @@ +/* 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: + +http://code.google.com/p/inih/ + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* 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), or -1 on file open error. +*/ +int ini_parse(const char* filename, + int (*handler)(void* user, const char* section, + const char* name, const char* value), + 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, + int (*handler)(void* user, const char* section, + const char* name, const char* value), + 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 + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */