summaryrefslogtreecommitdiff
path: root/libs/ardouralsautil/request_device.c
blob: 3fff33bf137b64c7b6c29ccb2996ab296a74dca7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/* alsa/ardour dbus device request tool
 *
 * Copyright (C) 2014 Robin Gareus <robin@gareus.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

// NB generate man-page with
// help2man -N -n "alsa/ardour dbus device request tool" -o ardour-request-device.1 ./build/libs/ardouralsautil/ardour-request-device

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

#include "ardouralsautil/reserve.h"

#ifndef ARD_PROG_NAME
#define ARD_PROG_NAME "alsa_request_device"
#endif
#ifndef ARD_APPL_NAME
#define ARD_APPL_NAME "ALSA User"
#endif
#ifndef VERSION
#define VERSION "v0.3"
#endif

static int run = 1;
static int release_wait_for_signal = 0;
static pid_t parent_pid = 0;

static void wearedone(int sig) {
	(void) sig; // skip 'unused variable' compiler warning;
	fprintf(stderr, "caught signal - shutting down.\n");
	run=0;
}

static int stdin_available(void) {
	errno = 0;
	if (fcntl(STDIN_FILENO, F_GETFD) == 1) return 0;
	return errno != EBADF;
}

static void print_version(int status) {
	printf (ARD_PROG_NAME " " VERSION "\n\n");
	printf (
		"Copyright (C) 2014 Robin Gareus <robin@gareus.org>\n"
		"This is free software; see the source for copying conditions.  There is NO\n"
		"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"
		);
	exit (status);
}

static void usage(int status) {
	printf (ARD_PROG_NAME " - DBus Audio Reservation Utility.\n");
	printf ("Usage: " ARD_PROG_NAME " [ OPTIONS ] <Audio-Device-ID>\n");
	printf ("Options:\n\
      -h, --help                 display this help and exit\n\
      -p, --priority <int>       reservation priority (default: int32_max)\n\
      -P, --pid <int>            process-id to watch (default 0: none)\n\
      -n, --name <string>        application name to use for registration\n\
      -V, --version              print version information and exit\n\
      -w, --releasewait          wait for signal on yield-release\n\
");

	printf ("\n\
This tool issues a dbus request to reserve an ALSA Audio-device.\n\
If successful other users of the device (e.g. pulseaudio) will\n\
release the device.\n\
\n\
" ARD_PROG_NAME " by default announces itself as \"" ARD_APPL_NAME "\"\n\
and uses the maximum possible priority for requesting the device.\n\
These settings can be overridden using the -n and -p options respectively.\n\
\n\
If a PID is given the tool will watch the process and if that is not running\n\
release the device and exit.  Otherwise " ARD_PROG_NAME " runs until\n\
either stdin is closed, a SIGINT or SIGTERM is received or some other\n\
application requests the device with a higher priority.\n\
\n\
Without the -w option, " ARD_PROG_NAME " yields the device after 500ms to\n\
any higher-priority request. With the -w option this tool waits until it\n\
for SIGINT or SIGTERM - but at most 4 sec to acknowledge before releasing.\n\
\n\
The audio-device-id is a string e.g. 'Audio1'\n\
\n\
Examples:\n\
" ARD_PROG_NAME " Audio0\n\
\n");

	printf ("Report bugs to Robin Gareus <robin@gareus.org>\n");
	exit (status);
}

static struct option const long_options[] =
{
	{"help", no_argument, 0, 'h'},
	{"name", required_argument, 0, 'n'},
	{"pid", required_argument, 0, 'P'},
	{"priority", required_argument, 0, 'p'},
	{"version", no_argument, 0, 'V'},
	{"releasewait", no_argument, 0, 'w'},
	{NULL, 0, NULL, 0}
};

static int request_cb(rd_device *d, int forced) {
	(void) d; // skip 'unused variable' compiler warning;
	(void) forced; // skip 'unused variable' compiler warning;
	fprintf(stdout, "Received higher priority request - releasing device.\n");
	fflush(stdout);
	if(!release_wait_for_signal) {
		usleep (500000);
		run = 0;
	} else if (run) {
		int timeout = 4000;
		fprintf(stdout, "Waiting for acknowledge signal to release.\n");
		while (release_wait_for_signal && run && --timeout) {
			if (!stdin_available()) {
				break;
			}
			if (parent_pid > 0 && kill (parent_pid, 0)) {
				break;
			}
			usleep (1000);
		}
		run = 0;
	}
	return 1; // OK
}

int main(int argc, char **argv) {
	DBusConnection* dbus_connection = NULL;
	rd_device * reserved_device = NULL;
	DBusError error;
	int ret, c;

	int32_t priority = INT32_MAX;
	char *name = strdup(ARD_APPL_NAME);

	while ((c = getopt_long (argc, argv,
					"h"  /* help */
					"n:" /* name */
					"P:" /* pid */
					"p:" /* priority */
					"V"  /* version */
					"w", /* release wait for signal */
					long_options, (int *) 0)) != EOF)
	{
		switch (c) {
			case 'h':
				free(name);
				usage(EXIT_SUCCESS);
				break;
			case 'n':
				free(name);
				name = strdup(optarg);
				break;
			case 'p':
				priority = atoi (optarg);
				if (priority < 0) priority = 0;
				break;
			case 'P':
				parent_pid = atoi (optarg);
				break;
			case 'V':
				free(name);
				print_version(EXIT_SUCCESS);
				break;
			case 'w':
				release_wait_for_signal = 1;
				break;
			default:
				free(name);
				usage(EXIT_FAILURE);
				break;
		}
	}

	if (optind + 1 != argc) {
		free(name);
		usage(EXIT_FAILURE);
	}
	const char *device_name = argv[optind];

	if (parent_pid > 0 && kill (parent_pid, 0)) {
		fprintf(stderr, "Given PID to watch is not running.\n");
		free(name);
		return EXIT_FAILURE;
	}

	dbus_error_init(&error);

	if (!(dbus_connection = dbus_bus_get (DBUS_BUS_SESSION, &error))) {
		fprintf(stderr, "Failed to connect to session bus for device reservation: %s\n", error.message ? error.message : "unknown error.");
		dbus_error_free(&error);
		free(name);
		return EXIT_FAILURE;
	}

	if ((ret = rd_acquire (
					&reserved_device,
					dbus_connection,
					device_name,
					name,
					priority,
					request_cb,
					&error)) < 0)
	{
		fprintf(stderr, "Failed to acquire device: '%s'\n%s\n", device_name, (error.message ? error.message : strerror(-ret)));
		dbus_error_free(&error);
		dbus_connection_unref(dbus_connection);
		free(name);
		return EXIT_FAILURE;
	}

	fprintf(stdout, "Acquired audio-card '%s'\n", device_name);
	fprintf(stdout, "Press Ctrl+C or close stdin to release the device.\n");
	fflush(stdout);

	signal(SIGTERM, wearedone);
	signal(SIGINT, wearedone);

	while (run && dbus_connection_read_write_dispatch (dbus_connection, 200)) {
		if (!stdin_available()) {
			fprintf(stderr, "stdin closed - releasing device.\n");
			break;
		}
		if (parent_pid > 0 && kill (parent_pid, 0)) {
			fprintf(stderr, "watched PID no longer exists - releasing device.\n");
			break;
		}
	}

	rd_release (reserved_device);
	fprintf(stdout, "Released audio-card '%s'\n", device_name);

	dbus_connection_unref(dbus_connection);
	dbus_error_free(&error);
	free(name);
	return EXIT_SUCCESS;
}