1// SPDX-License-Identifier: GPL-2.0
2//
3// subcmd-list.c - operations for list sub command.
4//
5// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
6//
7// Licensed under the terms of the GNU General Public License, version 2.
8
9#include "subcmd.h"
10#include "misc.h"
11
12#include <getopt.h>
13#include <stdbool.h>
14
15enum list_op {
16	LIST_OP_DEVICE = 0,
17	LIST_OP_PCM,
18	LIST_OP_HELP,
19};
20
21static int dump_device(snd_ctl_t *handle, const char *id, const char *name,
22		       snd_pcm_stream_t stream ATTRIBUTE_UNUSED, snd_pcm_info_t *info)
23{
24	unsigned int i, count;
25	int err;
26
27	printf("card %i: %s [%s], device %i: %s [%s]\n",
28	       snd_pcm_info_get_card(info), id, name,
29	       snd_pcm_info_get_device(info), snd_pcm_info_get_id(info),
30	       snd_pcm_info_get_name(info));
31
32	count = snd_pcm_info_get_subdevices_count(info);
33	printf("  Subdevices: %i/%u\n",
34	       snd_pcm_info_get_subdevices_avail(info), count);
35
36	for (i = 0; i < count; ++i) {
37		snd_pcm_info_set_subdevice(info, i);
38
39		err = snd_ctl_pcm_info(handle, info);
40		if (err < 0) {
41			printf("control digital audio playback info (%i): %s",
42			       snd_pcm_info_get_card(info), snd_strerror(err));
43			continue;
44		}
45
46		printf("  Subdevice #%u: %s\n",
47		       i, snd_pcm_info_get_subdevice_name(info));
48	}
49
50	return 0;
51}
52
53static int dump_devices(snd_ctl_t *handle, const char *id, const char *name,
54			snd_pcm_stream_t direction)
55{
56	snd_pcm_info_t *info;
57	int device = -1;
58	int err;
59
60	err = snd_pcm_info_malloc(&info);
61	if (err < 0)
62		return err;
63
64	while (1) {
65		err = snd_ctl_pcm_next_device(handle, &device);
66		if (err < 0)
67			break;
68		if (device < 0)
69			break;
70
71		snd_pcm_info_set_device(info, device);
72		snd_pcm_info_set_subdevice(info, 0);
73		snd_pcm_info_set_stream(info, direction);
74		err = snd_ctl_pcm_info(handle, info);
75		if (err < 0)
76			continue;
77
78		err = dump_device(handle, id, name, direction, info);
79		if (err < 0)
80			break;
81	}
82
83	free(info);
84	return err;
85}
86
87static int list_devices(snd_pcm_stream_t direction)
88{
89	int card = -1;
90	char name[32];
91	snd_ctl_t *handle;
92	snd_ctl_card_info_t *info;
93	int err;
94
95	err = snd_ctl_card_info_malloc(&info);
96	if (err < 0)
97		return err;
98
99	// Not found.
100	if (snd_card_next(&card) < 0 || card < 0)
101		goto end;
102
103	printf("**** List of %s Hardware Devices ****\n",
104	       snd_pcm_stream_name(direction));
105
106	while (card >= 0) {
107		sprintf(name, "hw:%d", card);
108		err = snd_ctl_open(&handle, name, 0);
109		if (err < 0) {
110			printf("control open (%i): %s",
111			       card, snd_strerror(err));
112		} else {
113			err = snd_ctl_card_info(handle, info);
114			if (err < 0) {
115				printf("control hardware info (%i): %s",
116				       card, snd_strerror(err));
117			} else {
118				err = dump_devices(handle,
119					snd_ctl_card_info_get_id(info),
120					snd_ctl_card_info_get_name(info),
121					direction);
122			}
123			snd_ctl_close(handle);
124		}
125
126		if (err < 0)
127			break;
128
129		// Go to next.
130		if (snd_card_next(&card) < 0) {
131			printf("snd_card_next");
132			break;
133		}
134	}
135end:
136	free(info);
137	return err;
138}
139
140static int list_pcms(snd_pcm_stream_t direction)
141{
142	static const char *const filters[] = {
143		[SND_PCM_STREAM_CAPTURE]	= "Input",
144		[SND_PCM_STREAM_PLAYBACK]	= "Output",
145	};
146	const char *filter;
147	void **hints;
148	void **n;
149	char *io;
150	char *name;
151	char *desc;
152
153	if (snd_device_name_hint(-1, "pcm", &hints) < 0)
154		return -EINVAL;
155
156	filter = filters[direction];
157
158	for (n = hints; *n != NULL; ++n) {
159		io = snd_device_name_get_hint(*n, "IOID");
160		if (io != NULL && strcmp(io, filter) != 0) {
161			free(io);
162			continue;
163		}
164
165		name = snd_device_name_get_hint(*n, "NAME");
166		desc = snd_device_name_get_hint(*n, "DESC");
167
168		printf("%s\n", name);
169		if (desc == NULL) {
170			free(name);
171			free(desc);
172			continue;
173		}
174
175
176		printf("    ");
177		while (*desc) {
178			if (*desc == '\n')
179				printf("\n    ");
180			else
181				putchar(*desc);
182			desc++;
183		}
184		putchar('\n');
185	}
186
187	snd_device_name_free_hint(hints);
188
189	return 0;
190}
191
192static void print_help(void)
193{
194	printf(
195"Usage:\n"
196"  axfer list DIRECTION TARGET\n"
197"\n"
198"  where:\n"
199"    DIRECTION = capture | playback\n"
200"    TARGET = device | pcm\n"
201	);
202}
203
204// Backward compatibility to aplay(1).
205static bool decide_operation(int argc, char *const *argv, enum list_op *op)
206{
207	static const char *s_opts = "hlL";
208	static const struct option l_opts[] = {
209		{"list-devices",	0, NULL, 'l'},
210		{"list-pcms",		0, NULL, 'L'},
211		{NULL,			0, NULL, 0}
212	};
213
214	optind = 0;
215	opterr = 0;
216	while (1) {
217		int c = getopt_long(argc, argv, s_opts, l_opts, NULL);
218		if (c < 0)
219			break;
220		if (c == 'l') {
221			*op = LIST_OP_DEVICE;
222			return true;
223		}
224		if (c == 'L') {
225			*op = LIST_OP_PCM;
226			return true;
227		}
228	}
229
230	return false;
231}
232
233static int detect_operation(int argc, char *const *argv, enum list_op *op)
234{
235	static const char *const ops[] = {
236		[LIST_OP_DEVICE] = "device",
237		[LIST_OP_PCM] = "pcm",
238	};
239	int i;
240
241	if (argc < 2)
242		return false;
243
244	for (i = 0; i < (int)ARRAY_SIZE(ops); ++i) {
245		if (!strcmp(argv[1], ops[i])) {
246			*op = i;
247			return true;
248		}
249	}
250
251	return false;
252}
253
254int subcmd_list(int argc, char *const *argv, snd_pcm_stream_t direction)
255{
256	enum list_op op = LIST_OP_HELP;
257	int err = 0;
258
259	// Renewed command system.
260	if (!detect_operation(argc, argv, &op) &&
261	    !decide_operation(argc, argv, &op))
262			err = -EINVAL;
263
264	if (op == LIST_OP_DEVICE)
265		err = list_devices(direction);
266	else if (op == LIST_OP_PCM)
267		err = list_pcms(direction);
268	else
269		print_help();
270
271	return err;
272}
273