alsa-utils/axfer/main.c
Takashi Sakamoto a4782feac3 axfer: print help text of command entry
This commit adds help text of command entry. This is printed when
help subcommand is given, or a valid subcommand or a valid direction
are not given.

Unfortunately, at present, execution of alias (aplay/arecord) with help
options prints the added help text. It should print help options in
transfer subcommand. This bug will fixed in future commits.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-12-07 10:56:10 +01:00

287 lines
6.1 KiB
C

// SPDX-License-Identifier: GPL-2.0
// main.c - an entry point for this program.
//
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
//
// Originally written as 'aplay', by Michael Beck and Jaroslav Kysela.
//
// Licensed under the terms of the GNU General Public License, version 2.
#include "subcmd.h"
#include "misc.h"
#include "version.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
enum subcmds {
SUBCMD_TRANSFER = 0,
SUBCMD_LIST,
SUBCMD_HELP,
SUBCMD_VERSION,
};
char *arg_duplicate_string(const char *str, int *err)
{
char *ptr;
// For safe.
if (strlen(str) > 1024) {
*err = -EINVAL;
return NULL;
}
ptr = strdup(str);
if (ptr == NULL)
*err = -ENOMEM;
return ptr;
}
long arg_parse_decimal_num(const char *str, int *err)
{
long val;
char *endptr;
errno = 0;
val = strtol(str, &endptr, 0);
if (errno > 0) {
*err = -errno;
return 0;
}
if (*endptr != '\0') {
*err = -EINVAL;
return 0;
}
return val;
}
static void print_version(const char *const cmdname)
{
printf("%s: version %s\n", cmdname, SND_UTIL_VERSION_STR);
}
static void print_help(void)
{
printf(
"Usage:\n"
" axfer transfer DIRECTION OPTIONS\n"
" axfer list DIRECTION OPTIONS\n"
" axfer version\n"
" axfer help\n"
"\n"
" where:\n"
" DIRECTION = capture | playback\n"
" OPTIONS = -h | --help | (subcommand specific)\n"
);
}
// Backward compatibility to aplay(1).
static bool decide_subcmd(int argc, char *const *argv, enum subcmds *subcmd)
{
static const struct {
const char *const name;
enum subcmds subcmd;
} long_opts[] = {
{"--list-devices", SUBCMD_LIST},
{"--list-pcms", SUBCMD_LIST},
{"--help", SUBCMD_HELP},
{"--version", SUBCMD_VERSION},
};
static const struct {
unsigned char c;
enum subcmds subcmd;
} short_opts[] = {
{'l', SUBCMD_LIST},
{'L', SUBCMD_LIST},
{'h', SUBCMD_HELP},
};
char *pos;
int i, j;
if (argc == 1)
return false;
// Original command system. For long options.
for (i = 0; i < ARRAY_SIZE(long_opts); ++i) {
for (j = 0; j < argc; ++j) {
if (!strcmp(long_opts[i].name, argv[j])) {
*subcmd = long_opts[i].subcmd;
return true;
}
}
}
// Original command system. For short options.
for (i = 1; i < argc; ++i) {
// Pick up short options only.
if (argv[i][0] != '-' || argv[i][0] == '\0' ||
argv[i][1] == '-' || argv[i][1] == '\0')
continue;
for (pos = argv[i]; *pos != '\0'; ++pos) {
for (j = 0; j < ARRAY_SIZE(short_opts); ++j) {
if (*pos == short_opts[j].c) {
*subcmd = short_opts[j].subcmd;
return true;
}
}
}
}
return false;
}
// Backward compatibility to aplay(1).
static bool decide_direction(int argc, char *const *argv,
snd_pcm_stream_t *direction)
{
static const struct {
const char *const name;
snd_pcm_stream_t direction;
} long_opts[] = {
{"--capture", SND_PCM_STREAM_CAPTURE},
{"--playback", SND_PCM_STREAM_PLAYBACK},
};
static const struct {
unsigned char c;
snd_pcm_stream_t direction;
} short_opts[] = {
{'C', SND_PCM_STREAM_CAPTURE},
{'P', SND_PCM_STREAM_PLAYBACK},
};
static const char *const aliases[] = {
[SND_PCM_STREAM_CAPTURE] = "arecord",
[SND_PCM_STREAM_PLAYBACK] = "aplay",
};
int i, j;
char *pos;
// Original command system. For long options.
for (i = 0; i < ARRAY_SIZE(long_opts); ++i) {
for (j = 0; j < argc; ++j) {
if (!strcmp(long_opts[i].name, argv[j])) {
*direction = long_opts[i].direction;
return true;
}
}
}
// Original command system. For short options.
for (i = 1; i < argc; ++i) {
// Pick up short options only.
if (argv[i][0] != '-' || argv[i][0] == '\0' ||
argv[i][1] == '-' || argv[i][1] == '\0')
continue;
for (pos = argv[i]; *pos != '\0'; ++pos) {
for (j = 0; j < ARRAY_SIZE(short_opts); ++j) {
if (*pos == short_opts[j].c) {
*direction = short_opts[j].direction;
return true;
}
}
}
}
// If not decided yet, judge according to command name.
for (i = 0; i < ARRAY_SIZE(aliases); ++i) {
for (pos = argv[0] + strlen(argv[0]); pos != argv[0]; --pos) {
if (strstr(pos, aliases[i]) != NULL) {
*direction = i;
return true;
}
}
}
return false;
}
static bool detect_subcmd(int argc, char *const *argv, enum subcmds *subcmd)
{
static const char *const subcmds[] = {
[SUBCMD_TRANSFER] = "transfer",
[SUBCMD_LIST] = "list",
[SUBCMD_HELP] = "help",
[SUBCMD_VERSION] = "version",
};
int i;
if (argc < 2)
return false;
for (i = 0; i < ARRAY_SIZE(subcmds); ++i) {
if (!strcmp(argv[1], subcmds[i])) {
*subcmd = i;
return true;
}
}
return false;
}
static bool detect_direction(int argc, char *const *argv,
snd_pcm_stream_t *direction)
{
if (argc < 3)
return false;
if (!strcmp(argv[2], "capture")) {
*direction = SND_PCM_STREAM_CAPTURE;
return true;
}
if (!strcmp(argv[2], "playback")) {
*direction = SND_PCM_STREAM_PLAYBACK;
return true;
}
return false;
}
int main(int argc, char *const *argv)
{
snd_pcm_stream_t direction;
enum subcmds subcmd;
int err = 0;
// For compatibility to aplay(1) implementation.
if (strstr(argv[0], "arecord") == argv[0] + strlen(argv[0]) - 7 ||
strstr(argv[0], "aplay") == argv[0] + strlen(argv[0]) - 5) {
if (!decide_direction(argc, argv, &direction))
direction = SND_PCM_STREAM_PLAYBACK;
if (!decide_subcmd(argc, argv, &subcmd))
subcmd = SUBCMD_TRANSFER;
} else {
// The first option should be one of subcommands.
if (!detect_subcmd(argc, argv, &subcmd))
subcmd = SUBCMD_HELP;
// The second option should be either 'capture' or 'direction'
// if subcommand is neither 'version' nor 'help'.
if (subcmd != SUBCMD_VERSION && subcmd != SUBCMD_HELP) {
if (!detect_direction(argc, argv, &direction)) {
subcmd = SUBCMD_HELP;
} else {
// argv[0] is needed for unparsed option to use
// getopt_long(3).
argc -= 2;
argv += 2;
}
}
}
if (subcmd == SUBCMD_TRANSFER)
err = subcmd_transfer(argc, argv, direction);
else if (subcmd == SUBCMD_LIST)
err = subcmd_list(argc, argv, direction);
else if (subcmd == SUBCMD_VERSION)
print_version(argv[0]);
else
print_help();
if (err < 0)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}