diff --git a/include/libsigrok/libsigrok.h b/include/libsigrok/libsigrok.h index 3a2c900a..8292483a 100644 --- a/include/libsigrok/libsigrok.h +++ b/include/libsigrok/libsigrok.h @@ -429,6 +429,20 @@ struct sr_datafeed_analog { float *data; }; +/** Generic option struct used by various subsystems. */ +struct sr_option { + /* Short name suitable for commandline usage, [a-z0-9-]. */ + char *id; + /* Short name suitable for GUI usage, can contain UTF-8. */ + char *name; + /* Description of the option, in a sentence. */ + char *desc; + /* Default value for this option. */ + GVariant *def; + /* List of possible values, if this is an option with few values. */ + GSList *values; +}; + /** Input (file) format struct. */ struct sr_input { /** @@ -501,102 +515,8 @@ struct sr_input_format { int (*loadfile) (struct sr_input *in, const char *filename); }; -/** Output (file) format struct. */ -struct sr_output { - /** A pointer to this output's format. */ - struct sr_output_format *format; - - /** - * The device for which this output module is creating output. This - * can be used by the module to find out channel names and numbers. - */ - const struct sr_dev_inst *sdi; - - /** - * An optional parameter which the frontend can pass in to the - * output module. How the string is interpreted is entirely up to - * the module. - */ - GHashTable *params; - - /** - * A generic pointer which can be used by the module to keep internal - * state between calls into its callback functions. - * - * For example, the module might store a pointer to a chunk of output - * there, and only flush it when it reaches a certain size. - */ - void *internal; -}; - -/** Output (file) format driver. */ -struct sr_output_format { - /** - * A unique ID for this output format. Must not be NULL. - * - * It can be used by frontends to select this output format for use. - * - * For example, calling sigrok-cli with -O hex will - * select the hexadecimal text output format. - */ - char *id; - - /** - * A short description of the output format. Must not be NULL. - * - * This can be displayed by frontends, e.g. when selecting the output - * format for saving a file. - */ - char *description; - - /** - * This function is called once, at the beginning of an output stream. - * - * The device struct will be available in the output struct passed in, - * as well as the param field -- which may be NULL or an empty string, - * if no parameter was passed. - * - * The module can use this to initialize itself, create a struct for - * keeping state and storing it in the internal field. - * - * @param o Pointer to the respective 'struct sr_output'. - * - * @retval SR_OK Success - * @retval other Negative error code. - */ - int (*init) (struct sr_output *o); - - /** - * This function is passed a copy of every packed in the data feed. - * Any output generated by the output module in response to the - * packet should be returned in a newly allocated GString - * out, which will be freed by the caller. - * - * Packets not of interest to the output module can just be ignored, - * and the out parameter set to NULL. - * - * @param o Pointer to the respective 'struct sr_output'. - * @param sdi The device instance that generated the packet. - * @param packet The complete packet. - * @param out A pointer where a GString * should be stored if - * the module generates output, or NULL if not. - * - * @retval SR_OK Success - * @retval other Negative error code. - */ - int (*receive) (struct sr_output *o, - const struct sr_datafeed_packet *packet, GString **out); - - /** - * This function is called after the caller is finished using - * the output module, and can be used to free any internal - * resources the module may keep. - * - * @retval SR_OK Success - * @retval other Negative error code. - */ - int (*cleanup) (struct sr_output *o); -}; +struct sr_output; +struct sr_output_module; /** Constants for channel type. */ enum sr_channeltype { diff --git a/include/libsigrok/proto.h b/include/libsigrok/proto.h index f11efeda..edcd4c97 100644 --- a/include/libsigrok/proto.h +++ b/include/libsigrok/proto.h @@ -128,12 +128,18 @@ SR_API struct sr_input_format **sr_input_list(void); /*--- output/output.c -------------------------------------------------------*/ -SR_API struct sr_output_format **sr_output_list(void); -SR_API struct sr_output *sr_output_new(struct sr_output_format *of, +SR_API const struct sr_output_module **sr_output_list(void); +SR_API const char *sr_output_id_get(const struct sr_output_module *o); +SR_API const char *sr_output_name_get(const struct sr_output_module *o); +SR_API const char *sr_output_description_get(const struct sr_output_module *o); +SR_API const struct sr_output_module *sr_output_find(char *id); +SR_API const struct sr_option *sr_output_options_get(const struct sr_output_module *o); +SR_API void sr_output_options_free(const struct sr_output_module *o); +SR_API const struct sr_output *sr_output_new(const struct sr_output_module *o, GHashTable *params, const struct sr_dev_inst *sdi); -SR_API int sr_output_send(struct sr_output *o, +SR_API int sr_output_send(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out); -SR_API int sr_output_free(struct sr_output *o); +SR_API int sr_output_free(const struct sr_output *o); /*--- trigger.c -------------------------------------------------------------*/ diff --git a/src/backend.c b/src/backend.c index e0e18175..47f352bf 100644 --- a/src/backend.c +++ b/src/backend.c @@ -265,7 +265,7 @@ static int sanity_check_all_input_modules(void) static int sanity_check_all_output_modules(void) { int i, errors, ret = SR_OK; - struct sr_output_format **outputs; + const struct sr_output_module **outputs; const char *d; sr_spew("Sanity-checking all output modules."); @@ -280,7 +280,11 @@ static int sanity_check_all_output_modules(void) sr_err("No ID in module %d ('%s').", i, d); errors++; } - if (!outputs[i]->description) { + if (!outputs[i]->name) { + sr_err("No name in module %d ('%s').", i, d); + errors++; + } + if (!outputs[i]->desc) { sr_err("No description in module '%s'.", d); errors++; } diff --git a/src/libsigrok-internal.h b/src/libsigrok-internal.h index 8c58bd9f..2a4384c4 100644 --- a/src/libsigrok-internal.h +++ b/src/libsigrok-internal.h @@ -159,6 +159,107 @@ struct sr_context { #endif }; +/** Output module instance. */ +struct sr_output { + /** A pointer to this output's module. */ + const struct sr_output_module *module; + + /** + * The device for which this output module is creating output. This + * can be used by the module to find out channel names and numbers. + */ + const struct sr_dev_inst *sdi; + + /** + * A generic pointer which can be used by the module to keep internal + * state between calls into its callback functions. + * + * For example, the module might store a pointer to a chunk of output + * there, and only flush it when it reaches a certain size. + */ + void *internal; +}; + +/** Output module driver. */ +struct sr_output_module { + /** + * A unique ID for this output module, suitable for use in command-line + * clients, [a-z0-9-]. Must not be NULL. + */ + char *id; + + /** + * A unique name for this output module, suitable for use in GUI + * clients, can contain UTF-8. Must not be NULL. + */ + const char *name; + + /** + * A short description of the output module. Must not be NULL. + * + * This can be displayed by frontends, e.g. when selecting the output + * module for saving a file. + */ + char *desc; + + /** + * Returns a NULL-terminated list of options this module can take. + * Can be NULL, if the module has no options. + * + * If cached is TRUE, no new GVariants are created for the def and + * values fields; instead, the current values are returned. + */ + struct sr_option *(*options) (gboolean cached); + + /** + * This function is called once, at the beginning of an output stream. + * + * The device struct will be available in the output struct passed in, + * as well as the param field -- which may be NULL or an empty string, + * if no parameter was passed. + * + * The module can use this to initialize itself, create a struct for + * keeping state and storing it in the internal field. + * + * @param o Pointer to the respective 'struct sr_output'. + * + * @retval SR_OK Success + * @retval other Negative error code. + */ + int (*init) (struct sr_output *o, GHashTable *options); + + /** + * This function is passed a copy of every packed in the data feed. + * Any output generated by the output module in response to the + * packet should be returned in a newly allocated GString + * out, which will be freed by the caller. + * + * Packets not of interest to the output module can just be ignored, + * and the out parameter set to NULL. + * + * @param o Pointer to the respective 'struct sr_output'. + * @param sdi The device instance that generated the packet. + * @param packet The complete packet. + * @param out A pointer where a GString * should be stored if + * the module generates output, or NULL if not. + * + * @retval SR_OK Success + * @retval other Negative error code. + */ + int (*receive) (const struct sr_output *o, + const struct sr_datafeed_packet *packet, GString **out); + + /** + * This function is called after the caller is finished using + * the output module, and can be used to free any internal + * resources the module may keep. + * + * @retval SR_OK Success + * @retval other Negative error code. + */ + int (*cleanup) (struct sr_output *o); +}; + #ifdef HAVE_LIBUSB_1_0 /** USB device instance */ struct sr_usb_dev_inst { diff --git a/src/output/analog.c b/src/output/analog.c index fbc41d88..f81e683c 100644 --- a/src/output/analog.c +++ b/src/output/analog.c @@ -31,13 +31,13 @@ struct context { GPtrArray *channellist; }; -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; struct sr_channel *ch; GSList *l; - sr_spew("Initializing output module."); + (void)options; if (!o || !o->sdi) return SR_ERR_ARG; @@ -215,7 +215,7 @@ static void fancyprint(int unit, int mqflags, float value, GString *out) g_string_append_c(out, '\n'); } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_analog *analog; @@ -268,9 +268,11 @@ static int cleanup(struct sr_output *o) return SR_OK; } -SR_PRIV struct sr_output_format output_analog = { +SR_PRIV struct sr_output_module output_analog = { .id = "analog", - .description = "Analog data", + .name = "Analog", + .desc = "Analog data and types", + .options = NULL, .init = init, .receive = receive, .cleanup = cleanup diff --git a/src/output/ascii.c b/src/output/ascii.c index d0689427..72eb6822 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -44,7 +44,7 @@ struct context { GString *header; }; -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; struct sr_channel *ch; @@ -52,22 +52,27 @@ static int init(struct sr_output *o) GHashTableIter iter; gpointer key, value; unsigned int i, j; - int spl; + uint32_t spl; if (!o || !o->sdi) return SR_ERR_ARG; spl = DEFAULT_SAMPLES_PER_LINE; - g_hash_table_iter_init(&iter, o->params); - while (g_hash_table_iter_next(&iter, &key, &value)) { - if (!strcmp(key, "width")) { - if ((spl = strtoul(value, NULL, 10)) < 1) { - sr_err("Invalid width."); - return SR_ERR_ARG; + if (options) { + g_hash_table_iter_init(&iter, options); + while (g_hash_table_iter_next(&iter, &key, &value)) { + if (!strcmp(key, "width")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32)) { + sr_err("Invalid type for 'width' option."); + return SR_ERR_ARG; + } + if (!(spl = g_variant_get_uint32(value))) { + sr_err("Invalid width."); + return SR_ERR_ARG; + } + } else { + sr_err("Unknown option '%s'.", key); } - } else { - sr_err("Unknown parameter '%s'.", key); - return SR_ERR_ARG; } } @@ -106,7 +111,7 @@ static int init(struct sr_output *o) return SR_OK; } -static GString *gen_header(struct sr_output *o) +static GString *gen_header(const struct sr_output *o) { struct context *ctx; GVariant *gvar; @@ -138,7 +143,7 @@ static GString *gen_header(struct sr_output *o) return header; } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_meta *meta; @@ -250,9 +255,27 @@ static int cleanup(struct sr_output *o) return SR_OK; } -SR_PRIV struct sr_output_format output_ascii = { +static struct sr_option options[] = { + { "width", "Width", "Number of samples per line", NULL, NULL }, + { 0 } +}; + +static struct sr_option *get_options(gboolean cached) +{ + if (cached) + return options; + + options[0].def = g_variant_new_uint32(DEFAULT_SAMPLES_PER_LINE); + g_variant_ref_sink(options[0].def); + + return options; +} + +SR_PRIV struct sr_output_module output_ascii = { .id = "ascii", - .description = "ASCII", + .name = "ASCII", + .desc = "ASCII art", + .options = get_options, .init = init, .receive = receive, .cleanup = cleanup, diff --git a/src/output/binary.c b/src/output/binary.c index 6e2531d1..10f0409f 100644 --- a/src/output/binary.c +++ b/src/output/binary.c @@ -26,7 +26,7 @@ #define LOG_PREFIX "output/binary" -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_logic *logic; @@ -42,8 +42,10 @@ static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, return SR_OK; } -SR_PRIV struct sr_output_format output_binary = { +SR_PRIV struct sr_output_module output_binary = { .id = "binary", - .description = "Raw binary", + .name = "Binary", + .desc = "Raw binary", + .options = NULL, .receive = receive, }; diff --git a/src/output/bits.c b/src/output/bits.c index b44f1057..1035d849 100644 --- a/src/output/bits.c +++ b/src/output/bits.c @@ -39,7 +39,7 @@ struct context { GString **lines; }; -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; struct sr_channel *ch; @@ -47,22 +47,27 @@ static int init(struct sr_output *o) GHashTableIter iter; gpointer key, value; unsigned int i, j; - int spl; + uint32_t spl; if (!o || !o->sdi) return SR_ERR_ARG; spl = DEFAULT_SAMPLES_PER_LINE; - g_hash_table_iter_init(&iter, o->params); - while (g_hash_table_iter_next(&iter, &key, &value)) { - if (!strcmp(key, "width")) { - if ((spl = strtoul(value, NULL, 10)) < 1) { - sr_err("Invalid width."); - return SR_ERR_ARG; + if (options) { + g_hash_table_iter_init(&iter, options); + while (g_hash_table_iter_next(&iter, &key, &value)) { + if (!strcmp(key, "width")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32)) { + sr_err("Invalid type for 'width' option."); + return SR_ERR_ARG; + } + if (!(spl = g_variant_get_uint32(value))) { + sr_err("Invalid width."); + return SR_ERR_ARG; + } + } else { + sr_err("Unknown option '%s'.", key); } - } else { - sr_err("Unknown parameter '%s'.", key); - return SR_ERR_ARG; } } @@ -100,7 +105,7 @@ static int init(struct sr_output *o) return SR_OK; } -static GString *gen_header(struct sr_output *o) +static GString *gen_header(const struct sr_output *o) { struct context *ctx; GVariant *gvar; @@ -132,7 +137,7 @@ static GString *gen_header(struct sr_output *o) return header; } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_meta *meta; @@ -236,9 +241,27 @@ static int cleanup(struct sr_output *o) return SR_OK; } -SR_PRIV struct sr_output_format output_bits = { +static struct sr_option options[] = { + { "width", "Width", "Number of samples per line", NULL, NULL }, + { 0 } +}; + +static struct sr_option *get_options(gboolean cached) +{ + if (cached) + return options; + + options[0].def = g_variant_new_uint32(DEFAULT_SAMPLES_PER_LINE); + g_variant_ref_sink(options[0].def); + + return options; +} + +SR_PRIV struct sr_output_module output_bits = { .id = "bits", - .description = "Bits", + .name = "Bits", + .desc = "0/1 digits", + .options = get_options, .init = init, .receive = receive, .cleanup = cleanup, diff --git a/src/output/chronovu_la8.c b/src/output/chronovu_la8.c index ad0b5d32..4263fc36 100644 --- a/src/output/chronovu_la8.c +++ b/src/output/chronovu_la8.c @@ -75,12 +75,14 @@ static uint8_t samplerate_to_divcount(uint64_t samplerate) return (SR_MHZ(100) / samplerate) - 1; } -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; struct sr_channel *ch; GSList *l; + (void)options; + if (!o || !o->sdi) return SR_ERR_ARG; @@ -101,7 +103,7 @@ static int init(struct sr_output *o) return SR_OK; } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_logic *logic; @@ -181,9 +183,11 @@ static int cleanup(struct sr_output *o) return SR_OK; } -SR_PRIV struct sr_output_format output_chronovu_la8 = { +SR_PRIV struct sr_output_module output_chronovu_la8 = { .id = "chronovu-la8", - .description = "ChronoVu LA8", + .name = "ChronoVu LA8", + .desc = "ChronoVu LA8 native file format", + .options = NULL, .init = init, .receive = receive, .cleanup = cleanup, diff --git a/src/output/csv.c b/src/output/csv.c index d17969e8..6ac7a77f 100644 --- a/src/output/csv.c +++ b/src/output/csv.c @@ -47,13 +47,15 @@ struct context { * - Trigger support. */ -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; struct sr_channel *ch; GSList *l; int i; + (void)options; + if (!o || !o->sdi) return SR_ERR_ARG; @@ -85,7 +87,7 @@ static int init(struct sr_output *o) return SR_OK; } -static GString *gen_header(struct sr_output *o) +static GString *gen_header(const struct sr_output *o) { struct context *ctx; struct sr_channel *ch; @@ -137,7 +139,7 @@ static GString *gen_header(struct sr_output *o) return header; } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_meta *meta; @@ -211,9 +213,11 @@ static int cleanup(struct sr_output *o) return SR_OK; } -SR_PRIV struct sr_output_format output_csv = { +SR_PRIV struct sr_output_module output_csv = { .id = "csv", - .description = "Comma-separated values (CSV)", + .name = "CSV", + .desc = "Comma-separated values", + .options = NULL, .init = init, .receive = receive, .cleanup = cleanup, diff --git a/src/output/gnuplot.c b/src/output/gnuplot.c index a9feec67..7af41740 100644 --- a/src/output/gnuplot.c +++ b/src/output/gnuplot.c @@ -44,13 +44,15 @@ static const char *gnuplot_header2 = "\ # 0\t\tSample counter (for internal gnuplot purposes)\n"; -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; struct sr_channel *ch; GSList *l; unsigned int i; + (void)options; + if (!o || !o->sdi) return SR_ERR_ARG; @@ -84,7 +86,7 @@ static int init(struct sr_output *o) return SR_OK; } -static GString *gen_header(struct sr_output *o) +static GString *gen_header(const struct sr_output *o) { struct context *ctx; struct sr_channel *ch; @@ -130,7 +132,7 @@ static GString *gen_header(struct sr_output *o) return header; } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_meta *meta; @@ -215,9 +217,11 @@ static int cleanup(struct sr_output *o) return SR_OK; } -SR_PRIV struct sr_output_format output_gnuplot = { +SR_PRIV struct sr_output_module output_gnuplot = { .id = "gnuplot", - .description = "Gnuplot", + .name = "Gnuplot", + .desc = "Gnuplot file format", + .options = NULL, .init = init, .receive = receive, .cleanup = cleanup, diff --git a/src/output/hex.c b/src/output/hex.c index 07a430ca..313dace8 100644 --- a/src/output/hex.c +++ b/src/output/hex.c @@ -42,7 +42,7 @@ struct context { GString **lines; }; -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; struct sr_channel *ch; @@ -50,22 +50,28 @@ static int init(struct sr_output *o) GHashTableIter iter; gpointer key, value; unsigned int i, j; - int spl; + uint32_t spl; if (!o || !o->sdi) return SR_ERR_ARG; spl = DEFAULT_SAMPLES_PER_LINE; - g_hash_table_iter_init(&iter, o->params); - while (g_hash_table_iter_next(&iter, &key, &value)) { - if (!strcmp(key, "width")) { - if ((spl = strtoul(value, NULL, 10)) < 1) { - sr_err("Invalid width."); + if (options) { + g_hash_table_iter_init(&iter, options); + while (g_hash_table_iter_next(&iter, &key, &value)) { + if (!strcmp(key, "width")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32)) { + sr_err("Invalid type for 'width' option."); + return SR_ERR_ARG; + } + if (!(spl = g_variant_get_uint32(value))) { + sr_err("Invalid width."); + return SR_ERR_ARG; + } + } else { + sr_err("Unknown option '%s'.", key); return SR_ERR_ARG; } - } else { - sr_err("Unknown parameter '%s'.", key); - return SR_ERR_ARG; } } @@ -105,7 +111,7 @@ static int init(struct sr_output *o) return SR_OK; } -static GString *gen_header(struct sr_output *o) +static GString *gen_header(const struct sr_output *o) { struct context *ctx; GVariant *gvar; @@ -137,7 +143,7 @@ static GString *gen_header(struct sr_output *o) return header; } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_meta *meta; @@ -250,9 +256,27 @@ static int cleanup(struct sr_output *o) return SR_OK; } -SR_PRIV struct sr_output_format output_hex = { +static struct sr_option options[] = { + { "width", "Width", "Number of samples per line", NULL, NULL }, + { 0 } +}; + +static struct sr_option *get_options(gboolean cached) +{ + if (cached) + return options; + + options[0].def = g_variant_new_uint32(DEFAULT_SAMPLES_PER_LINE); + g_variant_ref_sink(options[0].def); + + return options; +} + +SR_PRIV struct sr_output_module output_hex = { .id = "hex", - .description = "Hexadecimal", + .name = "Hexadecimal", + .desc = "Hexadecimal digits", + .options = get_options, .init = init, .receive = receive, .cleanup = cleanup, diff --git a/src/output/ols.c b/src/output/ols.c index 78a41bb0..8c9cca8d 100644 --- a/src/output/ols.c +++ b/src/output/ols.c @@ -38,10 +38,12 @@ struct context { uint64_t num_samples; }; -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; + (void)options; + if (!(ctx = g_try_malloc(sizeof(struct context)))) { sr_err("%s: ctx malloc failed", __func__); return SR_ERR_MALLOC; @@ -88,7 +90,7 @@ static GString *gen_header(const struct sr_dev_inst *sdi, struct context *ctx) return s; } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { struct context *ctx; @@ -148,9 +150,11 @@ static int cleanup(struct sr_output *o) return SR_OK; } -SR_PRIV struct sr_output_format output_ols = { +SR_PRIV struct sr_output_module output_ols = { .id = "ols", - .description = "OpenBench Logic Sniffer", + .name = "OLS", + .desc = "OpenBench Logic Sniffer", + .options = NULL, .init = init, .receive = receive, .cleanup = cleanup diff --git a/src/output/output.c b/src/output/output.c index 4cf78f60..6406fcac 100644 --- a/src/output/output.c +++ b/src/output/output.c @@ -17,23 +17,26 @@ * along with this program. If not, see . */ +#include #include "libsigrok.h" #include "libsigrok-internal.h" +#define LOG_PREFIX "output" + /** * @file * - * Output file/data format handling. + * Output module handling. */ /** - * @defgroup grp_output Output formats + * @defgroup grp_output Output modules * - * Output file/data format handling. + * Output module handling. * - * libsigrok supports several output (file) formats, e.g. binary, VCD, - * gnuplot, and so on. It provides an output API that frontends can use. - * New output formats can be added/implemented in libsigrok without having + * libsigrok supports several output modules for file formats such as binary, + * VCD, gnuplot, and so on. It provides an output API that frontends can use. + * New output modules can be added/implemented in libsigrok without having * to change the frontends at all. * * All output modules are fed data in a stream. Devices that can stream data @@ -47,19 +50,19 @@ */ /** @cond PRIVATE */ -extern SR_PRIV struct sr_output_format output_bits; -extern SR_PRIV struct sr_output_format output_hex; -extern SR_PRIV struct sr_output_format output_ascii; -extern SR_PRIV struct sr_output_format output_binary; -extern SR_PRIV struct sr_output_format output_vcd; -extern SR_PRIV struct sr_output_format output_ols; -extern SR_PRIV struct sr_output_format output_gnuplot; -extern SR_PRIV struct sr_output_format output_chronovu_la8; -extern SR_PRIV struct sr_output_format output_csv; -extern SR_PRIV struct sr_output_format output_analog; +extern SR_PRIV struct sr_output_module output_bits; +extern SR_PRIV struct sr_output_module output_hex; +extern SR_PRIV struct sr_output_module output_ascii; +extern SR_PRIV struct sr_output_module output_binary; +extern SR_PRIV struct sr_output_module output_vcd; +extern SR_PRIV struct sr_output_module output_ols; +extern SR_PRIV struct sr_output_module output_gnuplot; +extern SR_PRIV struct sr_output_module output_chronovu_la8; +extern SR_PRIV struct sr_output_module output_csv; +extern SR_PRIV struct sr_output_module output_analog; /* @endcond */ -static struct sr_output_format *output_module_list[] = { +static const struct sr_output_module *output_module_list[] = { &output_ascii, &output_binary, &output_bits, @@ -73,46 +76,182 @@ static struct sr_output_format *output_module_list[] = { NULL, }; -/** @since 0.1.0 */ -SR_API struct sr_output_format **sr_output_list(void) +/** + * Returns a NULL-terminated list of all the available output modules. + * + * @since 0.4.0 + */ +SR_API const struct sr_output_module **sr_output_list(void) { return output_module_list; } -/** @since 0.3.0 */ -SR_API struct sr_output *sr_output_new(struct sr_output_format *of, - GHashTable *params, const struct sr_dev_inst *sdi) +/** + * Returns the specified output module's ID. + * + * @since 0.4.0 + */ +SR_API const char *sr_output_id_get(const struct sr_output_module *o) { - struct sr_output *o; - - o = g_malloc(sizeof(struct sr_output)); - o->format = of; - o->sdi = sdi; - o->params = params; - if (o->format->init && o->format->init(o) != SR_OK) { - g_free(o); - o = NULL; + if (!o) { + sr_err("Invalid output module NULL!"); + return NULL; } - return o; + return o->id; } -/** @since 0.3.0 */ -SR_API int sr_output_send(struct sr_output *o, +/** + * Returns the specified output module's name. + * + * @since 0.4.0 + */ +SR_API const char *sr_output_name_get(const struct sr_output_module *o) +{ + if (!o) { + sr_err("Invalid output module NULL!"); + return NULL; + } + + return o->name; +} + +/** + * Returns the specified output module's description. + * + * @since 0.4.0 + */ +SR_API const char *sr_output_description_get(const struct sr_output_module *o) +{ + if (!o) { + sr_err("Invalid output module NULL!"); + return NULL; + } + + return o->desc; +} + +/** + * Return the output module with the specified ID, or NULL if no module + * with that id is found. + * + * @since 0.4.0 + */ +SR_API const struct sr_output_module *sr_output_find(char *id) +{ + int i; + + for (i = 0; output_module_list[i]; i++) { + if (!strcmp(output_module_list[i]->id, id)) + return output_module_list[i]; + } + + return NULL; +} + +/** + * Returns a NULL-terminated array of struct sr_option, or NULL if the + * module takes no options. + * + * Each call to this function must be followed by a call to + * sr_output_options_free(). + * + * @since 0.4.0 + */ +SR_API const struct sr_option *sr_output_options_get(const struct sr_output_module *o) +{ + + if (!o || !o->options) + return NULL; + + return o->options(FALSE); +} + +/** + * After a call to sr_output_options_get(), this function cleans up all + * the resources allocated by that call. + * + * @since 0.4.0 + */ +SR_API void sr_output_options_free(const struct sr_output_module *o) +{ + struct sr_option *opt; + + if (!o || !o->options) + return; + + for (opt = o->options(TRUE); opt->id; opt++) { + if (opt->def) { + g_variant_unref(opt->def); + opt->def = NULL; + } + + if (opt->values) { + g_slist_free_full(opt->values, (GDestroyNotify)g_variant_unref); + opt->values = NULL; + } + } +} + +/** + * Create a new output instance using the specified output module. + * + * options is a *HashTable with the keys corresponding with + * the module options' id field. The values should be GVariant + * pointers with sunk * references, of the same GVariantType as the option's + * default value. + * + * The sr_dev_inst passed in can be used by the instance to determine + * channel names, samplerate, and so on. + * + * @since 0.4.0 + */ +SR_API const struct sr_output *sr_output_new(const struct sr_output_module *o, + GHashTable *options, const struct sr_dev_inst *sdi) +{ + struct sr_output *op; + + op = g_malloc(sizeof(struct sr_output)); + op->module = o; + op->sdi = sdi; + if (op->module->init && op->module->init(op, options) != SR_OK) { + g_free(op); + op = NULL; + } + + return op; +} + +/** + * Send a packet to the specified output instance. + * + * The instance's output is returned as a newly allocated GString, + * which must be freed by the caller. + * + * @since 0.4.0 + */ +SR_API int sr_output_send(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { - return o->format->receive(o, packet, out); + return o->module->receive(o, packet, out); } -/** @since 0.3.0 */ -SR_API int sr_output_free(struct sr_output *o) +/** + * Free the specified output instance and all associated resources. + * + * @since 0.4.0 + */ +SR_API int sr_output_free(const struct sr_output *o) { int ret; + if (!o) + return SR_ERR_ARG; + ret = SR_OK; - if (o->format->cleanup) - ret = o->format->cleanup(o); - g_free(o); + if (o->module->cleanup) + ret = o->module->cleanup((struct sr_output *)o); + g_free((gpointer)o); return ret; } diff --git a/src/output/vcd.c b/src/output/vcd.c index 87f80496..479f331c 100644 --- a/src/output/vcd.c +++ b/src/output/vcd.c @@ -42,13 +42,15 @@ struct context { static const char *const vcd_header_comment = "$comment\n Acquisition with %d/%d channels at %s\n$end\n"; -static int init(struct sr_output *o) +static int init(struct sr_output *o, GHashTable *options) { struct context *ctx; struct sr_channel *ch; GSList *l; int num_enabled_channels, i; + (void)options; + num_enabled_channels = 0; for (l = o->sdi->channels; l; l = l->next) { ch = l->data; @@ -81,7 +83,7 @@ static int init(struct sr_output *o) return SR_OK; } -static GString *gen_header(struct sr_output *o) +static GString *gen_header(const struct sr_output *o) { struct context *ctx; struct sr_channel *ch; @@ -154,7 +156,7 @@ static GString *gen_header(struct sr_output *o) return header; } -static int receive(struct sr_output *o, const struct sr_datafeed_packet *packet, +static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { const struct sr_datafeed_meta *meta; @@ -260,9 +262,11 @@ static int cleanup(struct sr_output *o) return SR_OK; } -struct sr_output_format output_vcd = { +struct sr_output_module output_vcd = { .id = "vcd", - .description = "Value Change Dump (VCD)", + .name = "VCD", + .desc = "Value Change Dump", + .options = NULL, .init = init, .receive = receive, .cleanup = cleanup, diff --git a/tests/check_output_all.c b/tests/check_output_all.c index 05b65b76..aa8a2fe2 100644 --- a/tests/check_output_all.c +++ b/tests/check_output_all.c @@ -26,13 +26,76 @@ /* Check whether at least one output module is available. */ START_TEST(test_output_available) { - struct sr_output_format **outputs; + const struct sr_output_module **outputs; outputs = sr_output_list(); fail_unless(outputs != NULL, "No output modules found."); } END_TEST +/* Check whether sr_output_id_get() works. */ +START_TEST(test_output_id) +{ + const struct sr_output_module **outputs; + const char *id; + + outputs = sr_output_list(); + + id = sr_output_id_get(outputs[0]); + fail_unless(id != NULL, "No id found in output module."); +} +END_TEST + +/* Check whether sr_output_name_get() works. */ +START_TEST(test_output_name) +{ + const struct sr_output_module **outputs; + const char *name; + + outputs = sr_output_list(); + + name = sr_output_name_get(outputs[0]); + fail_unless(name != NULL, "No name found in output module."); +} +END_TEST + +/* Check whether sr_output_description_get() works. */ +START_TEST(test_output_desc) +{ + const struct sr_output_module **outputs; + const char *desc; + + outputs = sr_output_list(); + + desc = sr_output_description_get(outputs[0]); + fail_unless(desc != NULL, "No description found in output module."); +} +END_TEST + +/* Check whether sr_output_find() works. */ +START_TEST(test_output_find) +{ + const struct sr_output_module *omod; + const char *id; + + omod = sr_output_find("bits"); + fail_unless(omod != NULL, "Couldn't find the 'bits' output module."); + id = sr_output_id_get(omod); + fail_unless(!strcmp(id, "bits"), "That is not the 'bits' module!"); +} +END_TEST + +/* Check whether sr_output_options_get() works. */ +START_TEST(test_output_options) +{ + const struct sr_option *opt; + + opt = sr_output_options_get(sr_output_find("bits")); + fail_unless(opt != NULL, "Couldn't find 'bits' options."); + fail_unless(!strcmp(opt->id, "width"), "Wrong 'bits' option found!"); +} +END_TEST + Suite *suite_output_all(void) { Suite *s; @@ -42,6 +105,11 @@ Suite *suite_output_all(void) tc = tcase_create("basic"); tcase_add_test(tc, test_output_available); + tcase_add_test(tc, test_output_id); + tcase_add_test(tc, test_output_name); + tcase_add_test(tc, test_output_desc); + tcase_add_test(tc, test_output_find); + tcase_add_test(tc, test_output_options); suite_add_tcase(s, tc); return s; diff --git a/tests/lib.c b/tests/lib.c index aca44deb..f1c3c015 100644 --- a/tests/lib.c +++ b/tests/lib.c @@ -64,25 +64,6 @@ struct sr_input_format *srtest_input_get(const char *id) return input; } -/* Get a libsigrok output format by ID. */ -struct sr_output_format *srtest_output_get(const char *id) -{ - struct sr_output_format **outputs, *output = NULL; - int i; - - outputs = sr_output_list(); - fail_unless(outputs != NULL, "No output modules found."); - - for (i = 0; outputs[i]; i++) { - if (strcmp(outputs[i]->id, id)) - continue; - output = outputs[i]; - } - fail_unless(output != NULL, "Output module '%s' not found.", id); - - return output; -} - /* Initialize a libsigrok driver. */ void srtest_driver_init(struct sr_context *sr_ctx, struct sr_dev_driver *driver) { diff --git a/tests/lib.h b/tests/lib.h index 8f8e3b02..daed18da 100644 --- a/tests/lib.h +++ b/tests/lib.h @@ -25,7 +25,6 @@ struct sr_dev_driver *srtest_driver_get(const char *drivername); struct sr_input_format *srtest_input_get(const char *id); -struct sr_output_format *srtest_output_get(const char *id); void srtest_driver_init(struct sr_context *sr_ctx, struct sr_dev_driver *driver); void srtest_driver_init_all(struct sr_context *sr_ctx);