From e223dd74bf9ffd4dee0bcbf5f3cee07643406579 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Sat, 17 Feb 2018 18:20:53 -0800 Subject: settings: introduce abstract settings Settings will be used to express configurable parameters in the rendering modules and fb backends. The goal is to address both commandline argument setting of parameters, automatic use of defaults, as well as interactive configuration including the outputting of the resulting settings in a form usable as a commandline for future reuse. Since settings can be numerous and highly varied from one module or backend to another, a form similar to the Linux kernel's cmdline or QEMU's approach has been adopted. For example, a complete DRM backend, card selection and config would be: rototiller --video=drm,dev=/dev/dri/card0,connector=LVDS-1,mode=1024x768@60 If any of the above were omitted, then the missing settings would be interactively configured. If you added --defaults, then any omissions would be automatically filled in with the defaults. i.e. rototiller --video=drm,dev=/dev/dri/card4 --defaults would use the preferred connector and mode for that card. rototiller --video=drm --defaults would do the same but also default to the /dev/dri/card0 path. for brevity, I omitted rendering modules from above, but the same approach applies simply to --module=: rototiller --module=sparkler --video=drm --defaults If you ran rototiller without any arguments, then a fully interactive setup would ensue for module and video. If you ran rototiller with just --defaults, then everything is defaulted for you. A default rendering module will be used (the original roto renderer, probably). Note that this commit only adds scaffolding to make this possible, none of this is wired up yet. --- src/Makefile.am | 2 +- src/settings.c | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 36 +++++++ 3 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 src/settings.c create mode 100644 src/settings.h diff --git a/src/Makefile.am b/src/Makefile.am index 59ba58c..9ee9d88 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ SUBDIRS = modules bin_PROGRAMS = rototiller -rototiller_SOURCES = drmsetup.c drmsetup.h drm_fb.c drm_fb.h fb.c fb.h fps.c fps.h rototiller.c rototiller.h threads.c threads.h util.c util.h +rototiller_SOURCES = drmsetup.c drmsetup.h drm_fb.c drm_fb.h fb.c fb.h fps.c fps.h rototiller.c rototiller.h settings.h settings.c threads.c threads.h util.c util.h rototiller_LDADD = @ROTOTILLER_LIBS@ -lm modules/julia/libjulia.a modules/plasma/libplasma.a modules/ray/libray.a modules/roto/libroto.a modules/sparkler/libsparkler.a modules/stars/libstars.a rototiller_CPPFLAGS = @ROTOTILLER_CFLAGS@ diff --git a/src/settings.c b/src/settings.c new file mode 100644 index 0000000..e958b43 --- /dev/null +++ b/src/settings.c @@ -0,0 +1,329 @@ +#include +#include +#include +#include +#include + +#include "settings.h" + +/* Split form of key=value[,key=value...] settings string */ +typedef struct settings_t { + unsigned num; + const char **keys; + const char **values; +} settings_t; + +typedef enum settings_fsm_state_t { + SETTINGS_FSM_STATE_KEY, + SETTINGS_FSM_STATE_EQUAL, + SETTINGS_FSM_STATE_VALUE, + SETTINGS_FSM_STATE_COMMA, +} settings_fsm_state_t; + + +static int add_value(settings_t *settings, const char *key, const char *value) +{ + assert(settings); + + settings->num++; + /* TODO errors */ + settings->keys = realloc(settings->keys, settings->num * sizeof(const char **)); + settings->values = realloc(settings->values, settings->num * sizeof(const char **)); + settings->keys[settings->num - 1] = key; + settings->values[settings->num - 1] = value; + + return 0; +} + + +/* split settings_string into a data structure */ +settings_t * settings_new(const char *settings_string) +{ + settings_fsm_state_t state = SETTINGS_FSM_STATE_KEY; + const char *p, *token; + settings_t *settings; + + settings = calloc(1, sizeof(settings_t)); + if (!settings) + return NULL; + + if (!settings_string) + return settings; + + /* TODO: unescaping? */ + for (token = p = settings_string; ;p++) { + + switch (state) { + case SETTINGS_FSM_STATE_COMMA: + token = p; + state = SETTINGS_FSM_STATE_KEY; + break; + + case SETTINGS_FSM_STATE_KEY: + if (*p == '=' || *p == ',' || *p == '\0') { + add_value(settings, strndup(token, p - token), NULL); + + if (*p == '=') + state = SETTINGS_FSM_STATE_EQUAL; + else if (*p == ',') + state = SETTINGS_FSM_STATE_COMMA; + } + break; + + case SETTINGS_FSM_STATE_EQUAL: + token = p; + state = SETTINGS_FSM_STATE_VALUE; + break; + + case SETTINGS_FSM_STATE_VALUE: + if (*p == ',' || *p == '\0') { + settings->values[settings->num - 1] = strndup(token, p - token); + state = SETTINGS_FSM_STATE_COMMA; + } + break; + + default: + assert(0); + } + + if (*p == '\0') + break; + } + + /* FIXME: this should probably never leave a value or key entry NULL */ + + return settings; +} + + +/* free structure attained via settings_new() */ +void settings_free(settings_t *settings) +{ + unsigned i; + + assert(settings); + + for (i = 0; i < settings->num; i++) { + free((void *)settings->keys[i]); + free((void *)settings->values[i]); + } + + free((void *)settings->keys); + free((void *)settings->values); + free(settings); +} + + +/* find key= in settings, return dup of value side or NULL if missing */ +const char * settings_get_value(settings_t *settings, const char *key) +{ + int i; + + assert(settings); + assert(key); + + for (i = 0; i < settings->num; i++) { + if (!strcmp(key, settings->keys[i])) + return settings->values[i]; + } + + return NULL; +} + + +/* return positional key from settings */ +const char * settings_get_key(settings_t *settings, unsigned pos) +{ + assert(settings); + + if (pos < settings->num) + return settings->keys[pos]; + + return NULL; +} + + +/* add key=value to the settings, + * or just key if value is NULL. + */ +/* returns < 0 on error */ +int settings_add_value(settings_t *settings, const char *key, const char *value) +{ + assert(settings); + assert(key); + + return add_value(settings, strdup(key), value ? strdup(value) : NULL); +} + + +/* apply the supplied setting description generators to the supplied settings */ +/* returns 0 when input settings are complete */ +/* returns 1 when input settings are incomplete, storing the next setting's description needed in *next_setting */ +/* returns -errno on error */ +int settings_apply_desc_generators(settings_t *settings, const setting_desc_generator_t generators[], unsigned n_generators, void *setup_context, setting_desc_t **next_setting) +{ + unsigned i; + setting_desc_t *next; + + assert(settings); + assert(generators); + assert(n_generators > 0); + assert(next_setting); + + for (i = 0; i < n_generators; i++) { + const setting_desc_generator_t *g = &generators[i]; + const char *value; + + value = settings_get_value(settings, g->key); + if (value) { + if (g->value_ptr) + *g->value_ptr = value; + + continue; + } + + next = g->func(setup_context); + if (!next) + return -ENOMEM; + + *next_setting = next; + + return 1; + } + + return 0; +} + + +/* convenience helper for creating a new setting description */ +/* copies of everything supplied are made in newly allocated memory */ +setting_desc_t * setting_desc_new(const char *name, const char *key, const char *regex, const char *preferred, const char *values[], const char *annotations[]) +{ + setting_desc_t *desc; + + assert(name); + assert(preferred); /* XXX: require a preferred default? */ + assert(!annotations || values); + + desc = calloc(1, sizeof(setting_desc_t)); + if (!desc) + return NULL; + + desc->name = strdup(name); + if (key) /* This is inappropriately subtle, but when key is NULL, the value will be the key, and there will be no value side at all. */ + desc->key = strdup(key); + if (regex) + desc->regex = strdup(regex); + + desc->preferred = strdup(preferred); + + if (values) { + unsigned i; + + for (i = 0; values[i]; i++); + + desc->values = calloc(i + 1, sizeof(*values)); + + if (annotations) + desc->annotations = calloc(i + 1, sizeof(*annotations)); + + for (i = 0; values[i]; i++) { + desc->values[i] = strdup(values[i]); + + if (annotations) { + assert(annotations[i]); + desc->annotations[i] = strdup(annotations[i]); + } + } + } + + /* TODO: handle allocation errors above... */ + + return desc; +} + + +void setting_desc_free(setting_desc_t *desc) +{ + free((void *)desc->name); + free((void *)desc->key); + free((void *)desc->regex); + free((void *)desc->preferred); + + if (desc->values) { + unsigned i; + + for (i = 0; desc->values[i]; i++) { + free((void *)desc->values[i]); + + if (desc->annotations) + free((void *)desc->annotations[i]); + } + + free((void *)desc->values); + free((void *)desc->annotations); + } + + free(desc); +} + + +/* wrapper around sprintf for convenient buffer size computation */ +/* supply NULL buf when computing size, size and offset are ignored. + * supply non-NULL for actual writing into buf of size bytes @ offset. + * return value is number of bytes (potentially if !buf) written + */ +static int snpf(char *buf, size_t size, off_t offset, const char *format, ...) +{ + size_t avail = 0; + va_list ap; + int r; + + if (buf) { + assert(size > offset); + + avail = size - offset; + buf += offset; + } + + va_start(ap, format); + r = vsnprintf(buf, avail, format, ap); + va_end(ap); + + return r; +} + + +char * settings_as_arg(const settings_t *settings) +{ + char *buf = NULL; + size_t off, size; + + /* intentionally avoided open_memstream for portability reasons */ + for (;;) { + unsigned i; + + for (i = off = 0; i < settings->num; i++) { + if (i > 0) + off += snpf(buf, size, off, ","); + + off += snpf(buf, size, off, "%s", settings->keys[i]); + + if (settings->values[i]) + off += snpf(buf, size, off, "=%s", settings->values[i]); + } + + if (!buf) { + size = off + 1; + buf = calloc(size, sizeof(char)); + if (!buf) + return NULL; + + continue; + } + + break; + } + + return buf; +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..6334faa --- /dev/null +++ b/src/settings.h @@ -0,0 +1,36 @@ +#ifndef _SETTINGS_H +#define _SETTINGS_H + +#include + +/* Individual setting description */ +typedef struct setting_desc_t { + const char *name; /* long-form/human name for setting */ + const char *key; /* short-form/key for setting, used as left side of =value in settings string */ + const char *regex; /* value must conform to this regex */ + const char *preferred; /* if there's a default, this is it */ + const char **values; /* if a set of values is provided, listed here */ + const char **annotations; /* if a set of values is provided, annotations for those values may be listed here */ +} setting_desc_t; + +/* For conveniently representing setting description generators */ +typedef struct setting_desc_generator_t { + const char *key; /* key this generator applies to */ + const char **value_ptr; /* where to put the value */ + setting_desc_t *(*func)(void *setup_context); +} setting_desc_generator_t; + +typedef struct settings_t settings_t; + +settings_t * settings_new(const char *settings); +void settings_free(settings_t *settings); +const char * settings_get_value(settings_t *settings, const char *key); +const char * settings_get_key(settings_t *settings, unsigned pos); +int settings_add_value(settings_t *settings, const char *key, const char *value); +char * settings_as_arg(const settings_t *settings); +int settings_apply_desc_generators(settings_t *settings, const setting_desc_generator_t generators[], unsigned n_generators, void *setup_context, setting_desc_t **next_setting); + +setting_desc_t * setting_desc_new(const char *name, const char *key, const char *regex, const char *preferred, const char *values[], const char *annotations[]); +void setting_desc_free(setting_desc_t *desc); + +#endif -- cgit v1.2.3