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 
15 enum list_op {
16 	LIST_OP_DEVICE = 0,
17 	LIST_OP_PCM,
18 	LIST_OP_HELP,
19 };
20 
dump_device(snd_ctl_t *handle, const char *id, const char *name, snd_pcm_stream_t stream ATTRIBUTE_UNUSED, snd_pcm_info_t *info)21 static 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 
dump_devices(snd_ctl_t *handle, const char *id, const char *name, snd_pcm_stream_t direction)53 static 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 
list_devices(snd_pcm_stream_t direction)87 static 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 	}
135 end:
136 	free(info);
137 	return err;
138 }
139 
list_pcms(snd_pcm_stream_t direction)140 static 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 
print_help(void)192 static 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).
decide_operation(int argc, char *const *argv, enum list_op *op)205 static 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 
detect_operation(int argc, char *const *argv, enum list_op *op)233 static 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 
subcmd_list(int argc, char *const *argv, snd_pcm_stream_t direction)254 int 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