summaryrefslogtreecommitdiffstats
path: root/spa/plugins/alsa/acp-tool.c
diff options
context:
space:
mode:
Diffstat (limited to 'spa/plugins/alsa/acp-tool.c')
-rw-r--r--spa/plugins/alsa/acp-tool.c786
1 files changed, 786 insertions, 0 deletions
diff --git a/spa/plugins/alsa/acp-tool.c b/spa/plugins/alsa/acp-tool.c
new file mode 100644
index 0000000..0fc92a4
--- /dev/null
+++ b/spa/plugins/alsa/acp-tool.c
@@ -0,0 +1,786 @@
+/* ALSA card profile test
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <stdbool.h>
+#include <getopt.h>
+
+#include <spa/utils/string.h>
+
+#include <acp/acp.h>
+
+#define WHITESPACE "\n\r\t "
+
+struct data {
+ int verbose;
+ int card_index;
+ char *properties;
+ struct acp_card *card;
+ bool quit;
+};
+
+static void acp_debug_dict(struct acp_dict *dict, int indent)
+{
+ const struct acp_dict_item *it;
+ fprintf(stderr, "%*sproperties: (%d)\n", indent, "", dict->n_items);
+ acp_dict_for_each(it, dict) {
+ fprintf(stderr, "%*s%s = \"%s\"\n", indent+4, "", it->key, it->value);
+ }
+}
+
+static char *split_walk(char *str, const char *delimiter, size_t *len, char **state)
+{
+ char *s = *state ? *state : str;
+
+ if (*s == '\0')
+ return NULL;
+
+ *len = strcspn(s, delimiter);
+ *state = s + *len;
+ *state += strspn(*state, delimiter);
+ return s;
+}
+
+static int split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[])
+{
+ char *state = NULL, *s;
+ size_t len;
+ int n = 0;
+
+ while (true) {
+ if ((s = split_walk(str, delimiter, &len, &state)) == NULL)
+ break;
+ tokens[n++] = s;
+ if (n >= max_tokens)
+ break;
+ s[len] = '\0';
+ }
+ return n;
+}
+
+
+static void card_props_changed(void *data)
+{
+ struct data *d = data;
+ struct acp_card *card = d->card;
+ fprintf(stderr, "*** properties changed:\n");
+ acp_debug_dict(&card->props, 4);
+ fprintf(stderr, "***\n");
+}
+
+static void card_profile_changed(void *data, uint32_t old_index, uint32_t new_index)
+{
+ struct data *d = data;
+ struct acp_card *card = d->card;
+ struct acp_card_profile *op = card->profiles[old_index];
+ struct acp_card_profile *np = card->profiles[new_index];
+ fprintf(stderr, "*** profile changed from %s to %s\n", op->name, np->name);
+}
+
+static void card_profile_available(void *data, uint32_t index,
+ enum acp_available old, enum acp_available available)
+{
+ struct data *d = data;
+ struct acp_card *card = d->card;
+ struct acp_card_profile *p = card->profiles[index];
+ fprintf(stderr, "*** profile %s available %s\n", p->name, acp_available_str(available));
+}
+
+static void card_port_available(void *data, uint32_t index,
+ enum acp_available old, enum acp_available available)
+{
+ struct data *d = data;
+ struct acp_card *card = d->card;
+ struct acp_port *p = card->ports[index];
+ fprintf(stderr, "*** port %s available %s\n", p->name, acp_available_str(available));
+}
+
+static void on_volume_changed(void *data, struct acp_device *dev)
+{
+ float vol;
+ acp_device_get_volume(dev, &vol, 1);
+ fprintf(stderr, "*** volume %s changed to %f\n", dev->name, vol);
+}
+
+static void on_mute_changed(void *data, struct acp_device *dev)
+{
+ bool mute;
+ acp_device_get_mute(dev, &mute);
+ fprintf(stderr, "*** mute %s changed to %d\n", dev->name, mute);
+}
+
+static const struct acp_card_events card_events = {
+ ACP_VERSION_CARD_EVENTS,
+ .props_changed = card_props_changed,
+ .profile_changed = card_profile_changed,
+ .profile_available = card_profile_available,
+ .port_available = card_port_available,
+ .volume_changed = on_volume_changed,
+ .mute_changed = on_mute_changed,
+};
+
+static ACP_PRINTF_FUNC(6,0) void log_func(void *data,
+ int level, const char *file, int line, const char *func,
+ const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+ fprintf(stderr, "\n");
+}
+
+static void show_prompt(struct data *data)
+{
+ fprintf(stderr, ">>>");
+}
+
+struct command {
+ const char *name;
+ const char *args;
+ const char *alias;
+ const char *description;
+ int (*func) (struct data *data, const struct command *cmd, int argc, char *argv[]);
+ void *extra;
+};
+
+static int cmd_help(struct data *data, const struct command *cmd, int argc, char *argv[]);
+
+static int cmd_quit(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ data->quit = true;
+ return 0;
+}
+
+static void print_profile(struct data *data, struct acp_card_profile *p, int indent, int level);
+static void print_device(struct data *data, struct acp_device *d, int indent, int level);
+
+static void print_port(struct data *data, struct acp_port *p, int indent, int level)
+{
+ uint32_t i;
+
+ fprintf(stderr, "%*s %c port %u: name:\"%s\" direction:%s prio:%d (available: %s)\n",
+ indent, "", p->flags & ACP_PORT_ACTIVE ? '*' : ' ', p->index,
+ p->name, acp_direction_str(p->direction), p->priority,
+ acp_available_str(p->available));
+ if (level > 0) {
+ acp_debug_dict(&p->props, indent + 8);
+ }
+ if (level > 1) {
+ fprintf(stderr, "%*sprofiles: (%d)\n", indent+8, "", p->n_profiles);
+ for (i = 0; i < p->n_profiles; i++) {
+ struct acp_card_profile *pr = p->profiles[i];
+ print_profile(data, pr, indent + 8, 0);
+ }
+ fprintf(stderr, "%*sdevices: (%d)\n", indent+8, "", p->n_devices);
+ for (i = 0; i < p->n_devices; i++) {
+ struct acp_device *d = p->devices[i];
+ print_device(data, d, indent + 8, 0);
+ }
+ }
+}
+
+static void print_device(struct data *data, struct acp_device *d, int indent, int level)
+{
+ const char **s;
+ uint32_t i;
+
+ fprintf(stderr, "%*s %c device %u: direction:%s name:\"%s\" prio:%d flags:%08x devices: ",
+ indent, "", d->flags & ACP_DEVICE_ACTIVE ? '*' : ' ', d->index,
+ acp_direction_str(d->direction), d->name, d->priority, d->flags);
+ for (s = d->device_strings; *s; s++)
+ fprintf(stderr, "\"%s\" ", *s);
+ fprintf(stderr, "\n");
+ if (level > 0) {
+ fprintf(stderr, "%*srate:%d channels:%d\n", indent+8, "",
+ d->format.rate_mask, d->format.channels);
+ acp_debug_dict(&d->props, indent + 8);
+ }
+ if (level > 1) {
+ fprintf(stderr, "%*sports: (%d)\n", indent+8, "", d->n_ports);
+ for (i = 0; i < d->n_ports; i++) {
+ struct acp_port *p = d->ports[i];
+ print_port(data, p, indent + 8, 0);
+ }
+ }
+}
+
+static void print_profile(struct data *data, struct acp_card_profile *p, int indent, int level)
+{
+ uint32_t i;
+
+ fprintf(stderr, "%*s %c profile %u: name:\"%s\" prio:%d (available: %s)\n",
+ indent, "", p->flags & ACP_PROFILE_ACTIVE ? '*' : ' ', p->index,
+ p->name, p->priority, acp_available_str(p->available));
+ if (level > 0) {
+ fprintf(stderr, "%*sdescription:\"%s\"\n",
+ indent+8, "", p->description);
+ }
+ if (level > 1) {
+ fprintf(stderr, "%*sdevices: (%d)\n", indent+8, "", p->n_devices);
+ for (i = 0; i < p->n_devices; i++) {
+ struct acp_device *d = p->devices[i];
+ print_device(data, d, indent + 8, 0);
+ }
+ }
+}
+
+static void print_card(struct data *data, struct acp_card *card, int indent, int level)
+{
+ fprintf(stderr, "%*scard %d: profiles:%d devices:%d ports:%d\n", indent, "",
+ card->index, card->n_profiles, card->n_devices, card->n_ports);
+ if (level > 0) {
+ acp_debug_dict(&card->props, 4);
+ }
+}
+
+static int cmd_info(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ print_card(data, card, 0, 2);
+ return 0;
+}
+
+static int cmd_card(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ if (argc < 2) {
+ fprintf(stderr, "arguments: <card_index> missing\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int cmd_list(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ uint32_t i;
+ int level = 0;
+
+ if (spa_streq(cmd->name, "list-verbose"))
+ level = 2;
+
+ print_card(data, card, 0, level);
+ for (i = 0; i < card->n_profiles; i++)
+ print_profile(data, card->profiles[i], 0, level);
+
+ for (i = 0; i < card->n_ports; i++)
+ print_port(data, card->ports[i], 0, level);
+
+ for (i = 0; i < card->n_devices; i++)
+ print_device(data, card->devices[i], 0, level);
+
+ return 0;
+}
+
+static int cmd_list_profiles(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ uint32_t i;
+ struct acp_card *card = data->card;
+
+ if (argc > 1) {
+ i = atoi(argv[1]);
+ if (i >= card->n_profiles)
+ return -EINVAL;
+ print_profile(data, card->profiles[i], 0, 2);
+ } else {
+ for (i = 0; i < card->n_profiles; i++)
+ print_profile(data, card->profiles[i], 0, 0);
+ }
+ return 0;
+}
+
+static int cmd_set_profile(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ uint32_t index;
+
+ if (argc > 1)
+ index = atoi(argv[1]);
+ else
+ index = card->active_profile_index;
+
+ return acp_card_set_profile(card, index, 0);
+}
+
+static int cmd_list_ports(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ uint32_t i;
+ struct acp_card *card = data->card;
+
+ if (argc > 1) {
+ i = atoi(argv[1]);
+ if (i >= card->n_ports)
+ return -EINVAL;
+ print_port(data, card->ports[i], 0, 2);
+ } else {
+ for (i = 0; i < card->n_ports; i++)
+ print_port(data, card->ports[i], 0, 0);
+ }
+ return 0;
+}
+
+static int cmd_set_port(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ uint32_t dev_id, port_id;
+
+ if (argc < 3) {
+ fprintf(stderr, "arguments: <device_id> <port_id> missing\n");
+ return -EINVAL;
+ }
+ dev_id = atoi(argv[1]);
+ port_id = atoi(argv[2]);
+
+ if (dev_id >= card->n_devices)
+ return -EINVAL;
+
+ return acp_device_set_port(card->devices[dev_id], port_id, 0);
+}
+
+static int cmd_list_devices(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ uint32_t i;
+ struct acp_card *card = data->card;
+
+ if (argc > 1) {
+ i = atoi(argv[1]);
+ if (i >= card->n_devices)
+ return -EINVAL;
+ print_device(data, card->devices[i], 0, 2);
+ } else {
+ for (i = 0; i < card->n_devices; i++)
+ print_device(data, card->devices[i], 0, 0);
+ }
+ return 0;
+}
+
+static int cmd_get_volume(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ uint32_t dev_id;
+ float vol;
+
+ if (argc < 2) {
+ fprintf(stderr, "arguments: <device_id> missing\n");
+ return -EINVAL;
+ }
+ dev_id = atoi(argv[1]);
+ if (dev_id >= card->n_devices)
+ return -EINVAL;
+
+ acp_device_get_volume(card->devices[dev_id], &vol, 1);
+
+ fprintf(stderr, "volume: %f\n", vol);
+ return 0;
+}
+
+static int cmd_set_volume(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ uint32_t dev_id;
+ float vol;
+
+ if (argc < 3) {
+ fprintf(stderr, "arguments: <device_id> <volume> missing\n");
+ return -EINVAL;
+ }
+ dev_id = atoi(argv[1]);
+ vol = atof(argv[2]);
+
+ if (dev_id >= card->n_devices)
+ return -EINVAL;
+
+ return acp_device_set_volume(card->devices[dev_id], &vol, 1);
+}
+
+static int adjust_volume(struct data *data, const struct command *cmd, int argc, char *argv[], float adjust)
+{
+ struct acp_card *card = data->card;
+ uint32_t dev_id;
+ float vol;
+
+ if (argc < 2) {
+ fprintf(stderr, "arguments: <device_id> missing\n");
+ return -EINVAL;
+ }
+ dev_id = atoi(argv[1]);
+ if (dev_id >= card->n_devices)
+ return -EINVAL;
+ acp_device_get_volume(card->devices[dev_id], &vol, 1);
+ vol += adjust;
+ acp_device_set_volume(card->devices[dev_id], &vol, 1);
+ fprintf(stderr, "volume: %f\n", vol);
+ return 0;
+}
+
+static int cmd_inc_volume(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ return adjust_volume(data, cmd, argc, argv, 0.2);
+}
+
+static int cmd_dec_volume(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ return adjust_volume(data, cmd, argc, argv, -0.2);
+}
+
+static int cmd_get_mute(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ uint32_t dev_id;
+ bool mute;
+
+ if (argc < 2) {
+ fprintf(stderr, "arguments: <device_id> missing\n");
+ return -EINVAL;
+ }
+ dev_id = atoi(argv[1]);
+ if (dev_id >= card->n_devices)
+ return -EINVAL;
+
+ acp_device_get_mute(card->devices[dev_id], &mute);
+
+ fprintf(stderr, "muted: %s\n", mute ? "yes" : "no");
+ return 0;
+}
+
+static int cmd_set_mute(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ uint32_t dev_id;
+ bool mute;
+
+ if (argc < 3) {
+ fprintf(stderr, "arguments: <device_id> <mute> missing\n");
+ return -EINVAL;
+ }
+ dev_id = atoi(argv[1]);
+ mute = atoi(argv[2]);
+ if (dev_id >= card->n_devices)
+ return -EINVAL;
+
+ acp_device_set_mute(card->devices[dev_id], mute);
+ fprintf(stderr, "muted: %s\n", mute ? "yes" : "no");
+ return 0;
+}
+
+static int cmd_toggle_mute(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ struct acp_card *card = data->card;
+ uint32_t dev_id;
+ bool mute;
+
+ if (argc < 2) {
+ fprintf(stderr, "arguments: <device_id> missing\n");
+ return -EINVAL;
+ }
+ dev_id = atoi(argv[1]);
+ if (dev_id >= card->n_devices)
+ return -EINVAL;
+ acp_device_get_mute(card->devices[dev_id], &mute);
+ mute = !mute;
+ acp_device_set_mute(card->devices[dev_id], mute);
+ fprintf(stderr, "muted: %s\n", mute ? "yes" : "no");
+ return 0;
+}
+
+static const struct command command_list[] = {
+ { "help", "", "h", "Show available commands", cmd_help },
+ { "quit", "", "q", "Quit", cmd_quit },
+ { "card", "<id>", "c", "Probe card", cmd_card },
+ { "info", "", "i", "List card info", cmd_info },
+ { "list", "", "l", "List all objects", cmd_list },
+ { "list-verbose", "", "lv", "List all data", cmd_list },
+ { "list-profiles", "[id]", "lpr", "List profiles", cmd_list_profiles },
+ { "set-profile", "<id>", "spr", "Activate a profile", cmd_set_profile },
+ { "list-ports", "[id]", "lp", "List ports", cmd_list_ports },
+ { "set-port", "<id>", "sp", "Activate a port", cmd_set_port },
+ { "list-devices", "[id]", "ld", "List available devices", cmd_list_devices },
+ { "get-volume", "<id>", "gv", "Get volume from device", cmd_get_volume },
+ { "set-volume", "<id> <vol>", "v", "Set volume on device", cmd_set_volume },
+ { "inc-volume", "<id>", "v+", "Increase volume on device", cmd_inc_volume },
+ { "dec-volume", "<id>", "v-", "Decrease volume on device", cmd_dec_volume },
+ { "get-mute", "<id>", "gm", "Get mute state from device", cmd_get_mute },
+ { "set-mute", "<id> <val>", "sm", "Set mute on device", cmd_set_mute },
+ { "toggle-mute", "<id>", "m", "Toggle mute on device", cmd_toggle_mute },
+};
+#define N_COMMANDS sizeof(command_list)/sizeof(command_list[0])
+
+static const struct command *find_command(struct data *data, const char *cmd)
+{
+ size_t i;
+ for (i = 0; i < N_COMMANDS; i++) {
+ if (spa_streq(command_list[i].name, cmd) ||
+ spa_streq(command_list[i].alias, cmd))
+ return &command_list[i];
+ }
+ return NULL;
+}
+
+static int cmd_help(struct data *data, const struct command *cmd, int argc, char *argv[])
+{
+ size_t i;
+ fprintf(stderr, "Available commands:\n");
+ for (i = 0; i < N_COMMANDS; i++) {
+ fprintf(stdout, "\t%-15.15s %-10.10s\t%s (%s)\n",
+ command_list[i].name,
+ command_list[i].args,
+ command_list[i].description,
+ command_list[i].alias);
+ }
+ return 0;
+}
+
+static int run_command(struct data *data, int argc, char *argv[64])
+{
+ const struct command *command;
+ int res;
+
+ command = find_command(data, argv[0]);
+ if (command == NULL) {
+ fprintf(stderr, "unknown command %s\n", argv[0]);
+ cmd_help(data, NULL, argc, argv);
+ res = -EINVAL;
+ } else if (command->func) {
+ res = command->func(data, command, argc, argv);
+ if (res < 0) {
+ fprintf(stderr, "error: %s\n", strerror(-res));
+ }
+ } else {
+ res = -ENOTSUP;
+ }
+ return res;
+}
+
+static int handle_input(struct data *data)
+{
+ char buf[4096] = { 0, }, *p, *argv[64];
+ ssize_t r;
+ int res, argc;
+
+ if ((r = read(STDIN_FILENO, buf, sizeof(buf)-1)) < 0)
+ return -errno;
+ buf[r] = 0;
+
+ if (r == 0)
+ return -EPIPE;
+
+ if ((p = strchr(buf, '#')))
+ *p = '\0';
+
+ argc = split_ip(buf, WHITESPACE, 64, argv);
+ if (argc < 1)
+ return -EINVAL;
+
+ res = run_command(data, argc, argv);
+
+ if (!data->quit)
+ show_prompt(data);
+
+ return res;
+}
+
+static int do_probe(struct data *data)
+{
+ uint32_t n_items = 0;
+ struct acp_dict_item items[64];
+ struct acp_dict props;
+
+ acp_set_log_func(log_func, data);
+ acp_set_log_level(data->verbose);
+
+ items[n_items++] = ACP_DICT_ITEM_INIT("use-ucm", "true");
+ items[n_items++] = ACP_DICT_ITEM_INIT("verbose", data->verbose ? "true" : "false");
+ if (data->properties != NULL) {
+ char *p = data->properties, *e, f;
+
+ while (*p) {
+ const char *k, *v;
+
+ if ((e = strchr(p, '=')) == NULL)
+ break;
+ *e = '\0';
+ k = p;
+ p = e+1;
+
+ if (*p == '\"') {
+ p++;
+ f = '\"';
+ } else {
+ f = ' ';
+ }
+ if ((e = strchr(p, f)) == NULL &&
+ (e = strchr(p, '\0')) == NULL)
+ break;
+ *e = '\0';
+ v = p;
+ p = e+1;
+ items[n_items++] = ACP_DICT_ITEM_INIT(k, v);
+ if (n_items == 64)
+ break;
+ }
+ }
+ props = ACP_DICT_INIT(items, n_items);
+
+ data->card = acp_card_new(data->card_index, &props);
+ if (data->card == NULL)
+ return -errno;
+ return 0;
+}
+
+static int do_prompt(struct data *data)
+{
+ struct pollfd *pfds;
+ int err, count;
+
+ acp_card_add_listener(data->card, &card_events, data);
+
+ count = acp_card_poll_descriptors_count(data->card);
+ if (count == 0)
+ fprintf(stderr, "card has no events\n");
+
+ count++;
+ pfds = alloca(sizeof(struct pollfd) * count);
+ pfds[0].fd = STDIN_FILENO;
+ pfds[0].events = POLLIN;
+
+ print_card(data, data->card, 0, 0);
+
+ fprintf(stderr, "type 'help' for usage.\n");
+ show_prompt(data);
+
+ while (!data->quit) {
+ unsigned short revents;
+
+ err = acp_card_poll_descriptors(data->card, &pfds[1], count-1);
+ if (err < 0)
+ return err;
+
+ err = poll(pfds, (unsigned int) count, -1);
+ if (err < 0)
+ return -errno;
+
+ if (pfds[0].revents & POLLIN) {
+ if ((err = handle_input(data)) < 0) {
+ if (err == -EPIPE)
+ break;
+ }
+ }
+
+ if (count < 2)
+ continue;
+
+ err = acp_card_poll_descriptors_revents(data->card, &pfds[1], count-1, &revents);
+ if (err < 0)
+ return err;
+
+ if (revents)
+ acp_card_handle_events(data->card);
+ }
+ return 0;
+}
+
+#define OPTIONS "hvc:p:"
+static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h'},
+ { "verbose", no_argument, NULL, 'v'},
+
+ { "card", required_argument, NULL, 'c' },
+ { "properties", required_argument, NULL, 'p' },
+
+ { NULL, 0, NULL, 0 }
+};
+
+static void show_usage(struct data *data, const char *name, bool is_error)
+{
+ FILE *fp;
+
+ fp = is_error ? stderr : stdout;
+
+ fprintf(fp, "%s [options] [COMMAND]\n", name);
+ fprintf(fp,
+ " -h, --help Show this help\n"
+ " -v --verbose Be verbose\n"
+ " -c --card Card number\n"
+ " -p --properties Extra properties:\n"
+ " 'key=value ... '\n"
+ "\n");
+ cmd_help(data, NULL, 0, NULL);
+}
+
+int main(int argc, char *argv[])
+{
+ int c, res;
+ int longopt_index = 0, ret;
+ struct data data = { 0, };
+
+ data.verbose = 1;
+
+ while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) {
+ switch (c) {
+ case 'h':
+ show_usage(&data, argv[0], false);
+ return EXIT_SUCCESS;
+ case 'v':
+ data.verbose++;
+ break;
+ case 'c':
+ ret = atoi(optarg);
+ if (ret < 0) {
+ fprintf(stderr, "error: bad card %s\n", optarg);
+ goto error_usage;
+ }
+ data.card_index = ret;
+ break;
+ case 'p':
+ data.properties = strdup(optarg);
+ break;
+ default:
+ fprintf(stderr, "error: unknown option '%c'\n", c);
+ goto error_usage;
+ }
+ }
+
+ if ((res = do_probe(&data)) < 0) {
+ fprintf(stderr, "failed to probe card: %s\n", strerror(-res));
+ return res;
+ }
+
+ if (optind < argc)
+ run_command(&data, argc - optind, &argv[optind]);
+ else
+ do_prompt(&data);
+
+ if (data.card)
+ acp_card_destroy(data.card);
+
+ free(data.properties);
+
+ return 0;
+
+error_usage:
+ show_usage(&data, argv[0], true);
+ return EXIT_FAILURE;
+}