1 /*
2  * connect / disconnect two subscriber ports
3  *   ver.0.1.3
4  *
5  * Copyright (C) 1999 Takashi Iwai
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  */
17 
18 #include <stdio.h>
19 #include <ctype.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <getopt.h>
25 #include <stdarg.h>
26 #include <locale.h>
27 #include <sys/ioctl.h>
28 #include <alsa/asoundlib.h>
29 #include "aconfig.h"
30 #include "gettext.h"
31 
error_handler(const char * file,int line,const char * function,int err,const char * fmt,...)32 static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
33 {
34 	va_list arg;
35 
36 	if (err == ENOENT)	/* Ignore those misleading "warnings" */
37 		return;
38 	va_start(arg, fmt);
39 	fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function);
40 	vfprintf(stderr, fmt, arg);
41 	if (err)
42 		fprintf(stderr, ": %s", snd_strerror(err));
43 	putc('\n', stderr);
44 	va_end(arg);
45 }
46 
usage(void)47 static void usage(void)
48 {
49 	printf(_("aconnect - ALSA sequencer connection manager\n"));
50 	printf(_("Copyright (C) 1999-2000 Takashi Iwai\n"));
51 	printf(_("Usage:\n"));
52 	printf(_(" * Connection/disconnection between two ports\n"));
53 	printf(_("   aconnect [-options] sender receiver\n"));
54 	printf(_("     sender, receiver = client:port pair\n"));
55 	printf(_("     -d,--disconnect     disconnect\n"));
56 	printf(_("     -e,--exclusive      exclusive connection\n"));
57 	printf(_("     -r,--real #         convert real-time-stamp on queue\n"));
58 	printf(_("     -t,--tick #         convert tick-time-stamp on queue\n"));
59 	printf(_(" * List connected ports (no subscription action)\n"));
60 	printf(_("   aconnect -i|-o [-options]\n"));
61 	printf(_("     -i,--input          list input (readable) ports\n"));
62 	printf(_("     -o,--output         list output (writable) ports\n"));
63 	printf(_("     -l,--list           list current connections of each port\n"));
64 	printf(_(" * Remove all exported connections\n"));
65 	printf(_("     -x, --removeall\n"));
66 }
67 
68 /*
69  * check permission (capability) of specified port
70  */
71 
72 #define LIST_INPUT	1
73 #define LIST_OUTPUT	2
74 
75 #define perm_ok(pinfo,bits) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
76 
check_permission(snd_seq_port_info_t * pinfo,int perm)77 static int check_permission(snd_seq_port_info_t *pinfo, int perm)
78 {
79 	if (perm) {
80 		if (perm & LIST_INPUT) {
81 			if (perm_ok(pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))
82 				goto __ok;
83 		}
84 		if (perm & LIST_OUTPUT) {
85 			if (perm_ok(pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
86 				goto __ok;
87 		}
88 		return 0;
89 	}
90  __ok:
91 	if (snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_NO_EXPORT)
92 		return 0;
93 	return 1;
94 }
95 
96 /*
97  * list subscribers of specified type
98  */
list_each_subs(snd_seq_t * seq,snd_seq_query_subscribe_t * subs,int type,const char * msg)99 static void list_each_subs(snd_seq_t *seq, snd_seq_query_subscribe_t *subs, int type, const char *msg)
100 {
101 	int count = 0;
102 	snd_seq_query_subscribe_set_type(subs, type);
103 	snd_seq_query_subscribe_set_index(subs, 0);
104 	while (snd_seq_query_port_subscribers(seq, subs) >= 0) {
105 		const snd_seq_addr_t *addr;
106 		if (count++ == 0)
107 			printf("\t%s: ", msg);
108 		else
109 			printf(", ");
110 		addr = snd_seq_query_subscribe_get_addr(subs);
111 		printf("%d:%d", addr->client, addr->port);
112 		if (snd_seq_query_subscribe_get_exclusive(subs))
113 			printf("[ex]");
114 		if (snd_seq_query_subscribe_get_time_update(subs))
115 			printf("[%s:%d]",
116 			       (snd_seq_query_subscribe_get_time_real(subs) ? "real" : "tick"),
117 			       snd_seq_query_subscribe_get_queue(subs));
118 		snd_seq_query_subscribe_set_index(subs, snd_seq_query_subscribe_get_index(subs) + 1);
119 	}
120 	if (count > 0)
121 		printf("\n");
122 }
123 
124 /*
125  * list subscribers
126  */
list_subscribers(snd_seq_t * seq,const snd_seq_addr_t * addr)127 static void list_subscribers(snd_seq_t *seq, const snd_seq_addr_t *addr)
128 {
129 	snd_seq_query_subscribe_t *subs;
130 	snd_seq_query_subscribe_alloca(&subs);
131 	snd_seq_query_subscribe_set_root(subs, addr);
132 	list_each_subs(seq, subs, SND_SEQ_QUERY_SUBS_READ, _("Connecting To"));
133 	list_each_subs(seq, subs, SND_SEQ_QUERY_SUBS_WRITE, _("Connected From"));
134 }
135 
136 /*
137  * search all ports
138  */
139 typedef void (*action_func_t)(snd_seq_t *seq, snd_seq_client_info_t *cinfo, snd_seq_port_info_t *pinfo, int count);
140 
do_search_port(snd_seq_t * seq,int perm,action_func_t do_action)141 static void do_search_port(snd_seq_t *seq, int perm, action_func_t do_action)
142 {
143 	snd_seq_client_info_t *cinfo;
144 	snd_seq_port_info_t *pinfo;
145 	int count;
146 
147 	snd_seq_client_info_alloca(&cinfo);
148 	snd_seq_port_info_alloca(&pinfo);
149 	snd_seq_client_info_set_client(cinfo, -1);
150 	while (snd_seq_query_next_client(seq, cinfo) >= 0) {
151 		/* reset query info */
152 		snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
153 		snd_seq_port_info_set_port(pinfo, -1);
154 		count = 0;
155 		while (snd_seq_query_next_port(seq, pinfo) >= 0) {
156 			if (check_permission(pinfo, perm)) {
157 				do_action(seq, cinfo, pinfo, count);
158 				count++;
159 			}
160 		}
161 	}
162 }
163 
164 
print_port(snd_seq_t * seq,snd_seq_client_info_t * cinfo,snd_seq_port_info_t * pinfo,int count)165 static void print_port(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
166 		       snd_seq_port_info_t *pinfo, int count)
167 {
168 	if (! count) {
169 		int card = -1, pid = -1;
170 
171 		printf(_("client %d: '%s' [type=%s"),
172 		       snd_seq_client_info_get_client(cinfo),
173 		       snd_seq_client_info_get_name(cinfo),
174 		       (snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ?
175 			_("user") : _("kernel")));
176 
177 #ifdef HAVE_SEQ_CLIENT_INFO_GET_CARD
178 		card = snd_seq_client_info_get_card(cinfo);
179 #endif
180 		if (card != -1)
181 			printf(",card=%d", card);
182 
183 #ifdef HAVE_SEQ_CLIENT_INFO_GET_PID
184 		pid = snd_seq_client_info_get_pid(cinfo);
185 #endif
186 		if (pid != -1)
187 			printf(",pid=%d", pid);
188 		printf("]\n");
189 	}
190 	printf("  %3d '%-16s'\n",
191 	       snd_seq_port_info_get_port(pinfo),
192 	       snd_seq_port_info_get_name(pinfo));
193 }
194 
print_port_and_subs(snd_seq_t * seq,snd_seq_client_info_t * cinfo,snd_seq_port_info_t * pinfo,int count)195 static void print_port_and_subs(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
196 				snd_seq_port_info_t *pinfo, int count)
197 {
198 	print_port(seq, cinfo, pinfo, count);
199 	list_subscribers(seq, snd_seq_port_info_get_addr(pinfo));
200 }
201 
202 
203 /*
204  * remove all (exported) connections
205  */
remove_connection(snd_seq_t * seq,snd_seq_client_info_t * cinfo,snd_seq_port_info_t * pinfo,int count)206 static void remove_connection(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
207 			      snd_seq_port_info_t *pinfo, int count)
208 {
209 	snd_seq_query_subscribe_t *query;
210 	snd_seq_port_info_t *port;
211 	snd_seq_port_subscribe_t *subs;
212 
213 	snd_seq_query_subscribe_alloca(&query);
214 	snd_seq_query_subscribe_set_root(query, snd_seq_port_info_get_addr(pinfo));
215 	snd_seq_query_subscribe_set_type(query, SND_SEQ_QUERY_SUBS_READ);
216 	snd_seq_query_subscribe_set_index(query, 0);
217 
218 	snd_seq_port_info_alloca(&port);
219 	snd_seq_port_subscribe_alloca(&subs);
220 
221 	while (snd_seq_query_port_subscribers(seq, query) >= 0) {
222 		const snd_seq_addr_t *sender = snd_seq_query_subscribe_get_root(query);
223 		const snd_seq_addr_t *dest = snd_seq_query_subscribe_get_addr(query);
224 
225 		if (snd_seq_get_any_port_info(seq, dest->client, dest->port, port) < 0 ||
226 		    !(snd_seq_port_info_get_capability(port) & SND_SEQ_PORT_CAP_SUBS_WRITE) ||
227 		    (snd_seq_port_info_get_capability(port) & SND_SEQ_PORT_CAP_NO_EXPORT)) {
228 			snd_seq_query_subscribe_set_index(query, snd_seq_query_subscribe_get_index(query) + 1);
229 			continue;
230 		}
231 		snd_seq_port_subscribe_set_queue(subs, snd_seq_query_subscribe_get_queue(query));
232 		snd_seq_port_subscribe_set_sender(subs, sender);
233 		snd_seq_port_subscribe_set_dest(subs, dest);
234 		if (snd_seq_unsubscribe_port(seq, subs) < 0) {
235 			snd_seq_query_subscribe_set_index(query, snd_seq_query_subscribe_get_index(query) + 1);
236 		}
237 	}
238 }
239 
remove_all_connections(snd_seq_t * seq)240 static void remove_all_connections(snd_seq_t *seq)
241 {
242 	do_search_port(seq, 0, remove_connection);
243 }
244 
245 
246 /*
247  * main..
248  */
249 
250 enum {
251 	SUBSCRIBE, UNSUBSCRIBE, LIST, REMOVE_ALL
252 };
253 
254 static const struct option long_option[] = {
255 	{"disconnect", 0, NULL, 'd'},
256 	{"input", 0, NULL, 'i'},
257 	{"output", 0, NULL, 'o'},
258 	{"real", 1, NULL, 'r'},
259 	{"tick", 1, NULL, 't'},
260 	{"exclusive", 0, NULL, 'e'},
261 	{"list", 0, NULL, 'l'},
262 	{"removeall", 0, NULL, 'x'},
263 	{NULL, 0, NULL, 0},
264 };
265 
main(int argc,char ** argv)266 int main(int argc, char **argv)
267 {
268 	int c;
269 	snd_seq_t *seq;
270 	int queue = 0, convert_time = 0, convert_real = 0, exclusive = 0;
271 	int command = SUBSCRIBE;
272 	int list_perm = 0;
273 	int client;
274 	int list_subs = 0;
275 	snd_seq_port_subscribe_t *subs;
276 	snd_seq_addr_t sender, dest;
277 
278 #ifdef ENABLE_NLS
279 	setlocale(LC_ALL, "");
280 	textdomain(PACKAGE);
281 #endif
282 
283 	while ((c = getopt_long(argc, argv, "dior:t:elx", long_option, NULL)) != -1) {
284 		switch (c) {
285 		case 'd':
286 			command = UNSUBSCRIBE;
287 			break;
288 		case 'i':
289 			command = LIST;
290 			list_perm |= LIST_INPUT;
291 			break;
292 		case 'o':
293 			command = LIST;
294 			list_perm |= LIST_OUTPUT;
295 			break;
296 		case 'e':
297 			exclusive = 1;
298 			break;
299 		case 'r':
300 			queue = atoi(optarg);
301 			convert_time = 1;
302 			convert_real = 1;
303 			break;
304 		case 't':
305 			queue = atoi(optarg);
306 			convert_time = 1;
307 			convert_real = 0;
308 			break;
309 		case 'l':
310 			command = LIST;
311 			list_subs = 1;
312 			break;
313 		case 'x':
314 			command = REMOVE_ALL;
315 			break;
316 		default:
317 			usage();
318 			exit(1);
319 		}
320 	}
321 
322 	if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
323 		fprintf(stderr, _("can't open sequencer\n"));
324 		return 1;
325 	}
326 
327 	snd_lib_error_set_handler(error_handler);
328 
329 	switch (command) {
330 	case LIST:
331 		do_search_port(seq, list_perm,
332 			       list_subs ? print_port_and_subs : print_port);
333 		snd_seq_close(seq);
334 		return 0;
335 	case REMOVE_ALL:
336 		remove_all_connections(seq);
337 		snd_seq_close(seq);
338 		return 0;
339 	}
340 
341 	/* connection or disconnection */
342 
343 	if (optind + 2 > argc) {
344 		snd_seq_close(seq);
345 		usage();
346 		exit(1);
347 	}
348 
349 	if ((client = snd_seq_client_id(seq)) < 0) {
350 		snd_seq_close(seq);
351 		fprintf(stderr, _("can't get client id\n"));
352 		return 1;
353 	}
354 
355 	/* set client info */
356 	if (snd_seq_set_client_name(seq, "ALSA Connector") < 0) {
357 		snd_seq_close(seq);
358 		fprintf(stderr, _("can't set client info\n"));
359 		return 1;
360 	}
361 
362 	/* set subscription */
363 	if (snd_seq_parse_address(seq, &sender, argv[optind]) < 0) {
364 		snd_seq_close(seq);
365 		fprintf(stderr, _("invalid sender address %s\n"), argv[optind]);
366 		return 1;
367 	}
368 	if (snd_seq_parse_address(seq, &dest, argv[optind + 1]) < 0) {
369 		snd_seq_close(seq);
370 		fprintf(stderr, _("invalid destination address %s\n"), argv[optind + 1]);
371 		return 1;
372 	}
373 	snd_seq_port_subscribe_alloca(&subs);
374 	snd_seq_port_subscribe_set_sender(subs, &sender);
375 	snd_seq_port_subscribe_set_dest(subs, &dest);
376 	snd_seq_port_subscribe_set_queue(subs, queue);
377 	snd_seq_port_subscribe_set_exclusive(subs, exclusive);
378 	snd_seq_port_subscribe_set_time_update(subs, convert_time);
379 	snd_seq_port_subscribe_set_time_real(subs, convert_real);
380 
381 	if (command == UNSUBSCRIBE) {
382 		if (snd_seq_get_port_subscription(seq, subs) < 0) {
383 			snd_seq_close(seq);
384 			fprintf(stderr, _("No subscription is found\n"));
385 			return 1;
386 		}
387 		if (snd_seq_unsubscribe_port(seq, subs) < 0) {
388 			snd_seq_close(seq);
389 			fprintf(stderr, _("Disconnection failed (%s)\n"), snd_strerror(errno));
390 			return 1;
391 		}
392 	} else {
393 		if (snd_seq_get_port_subscription(seq, subs) == 0) {
394 			snd_seq_close(seq);
395 			fprintf(stderr, _("Connection is already subscribed\n"));
396 			return 1;
397 		}
398 		if (snd_seq_subscribe_port(seq, subs) < 0) {
399 			snd_seq_close(seq);
400 			fprintf(stderr, _("Connection failed (%s)\n"), snd_strerror(errno));
401 			return 1;
402 		}
403 	}
404 
405 	snd_seq_close(seq);
406 
407 	return 0;
408 }
409