when calculating average slave/master delta, use absolute value.
[ardour.git] / libs / ardouralsautil / request_device.c
1 /* alsa/ardour dbus device request tool
2  *
3  * Copyright (C) 2014 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20 // NB generate man-page with
21 // help2man -N -n "alsa/ardour dbus device request tool" -o ardour-request-device.1 ./build/libs/ardouralsautil/ardour-request-device
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <signal.h>
28 #include <string.h>
29 #include <fcntl.h>
30 #include <errno.h>
31
32 #include "ardouralsautil/reserve.h"
33
34 #ifndef ARD_PROG_NAME
35 #define ARD_PROG_NAME "alsa_request_device"
36 #endif
37 #ifndef ARD_APPL_NAME
38 #define ARD_APPL_NAME "ALSA User"
39 #endif
40 #ifndef VERSION
41 #define VERSION "v0.3"
42 #endif
43
44 static int run = 1;
45 static int release_wait_for_signal = 0;
46 static pid_t parent_pid = 0;
47
48 static void wearedone(int sig) {
49         (void) sig; // skip 'unused variable' compiler warning;
50         fprintf(stderr, "caught signal - shutting down.\n");
51         run=0;
52 }
53
54 static int stdin_available(void) {
55         errno = 0;
56         if (fcntl(STDIN_FILENO, F_GETFD) == 1) return 0;
57         return errno != EBADF;
58 }
59
60 static void print_version(int status) {
61         printf (ARD_PROG_NAME " " VERSION "\n\n");
62         printf (
63                 "Copyright (C) 2014 Robin Gareus <robin@gareus.org>\n"
64                 "This is free software; see the source for copying conditions.  There is NO\n"
65                 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"
66                 );
67         exit (status);
68 }
69
70 static void usage(int status) {
71         printf (ARD_PROG_NAME " - DBus Audio Reservation Utility.\n");
72         printf ("Usage: " ARD_PROG_NAME " [ OPTIONS ] <Audio-Device-ID>\n");
73         printf ("Options:\n\
74       -h, --help                 display this help and exit\n\
75       -p, --priority <int>       reservation priority (default: int32_max)\n\
76       -P, --pid <int>            process-id to watch (default 0: none)\n\
77       -n, --name <string>        application name to use for registration\n\
78       -V, --version              print version information and exit\n\
79       -w, --releasewait          wait for signal on yield-release\n\
80 ");
81
82         printf ("\n\
83 This tool issues a dbus request to reserve an ALSA Audio-device.\n\
84 If successful other users of the device (e.g. pulseaudio) will\n\
85 release the device.\n\
86 \n\
87 " ARD_PROG_NAME " by default announces itself as \"" ARD_APPL_NAME "\"\n\
88 and uses the maximum possible priority for requesting the device.\n\
89 These settings can be overridden using the -n and -p options respectively.\n\
90 \n\
91 If a PID is given the tool will watch the process and if that is not running\n\
92 release the device and exit.  Otherwise " ARD_PROG_NAME " runs until\n\
93 either stdin is closed, a SIGINT or SIGTERM is received or some other\n\
94 application requests the device with a higher priority.\n\
95 \n\
96 Without the -w option, " ARD_PROG_NAME " yields the device after 500ms to\n\
97 any higher-priority request. With the -w option this tool waits until it\n\
98 for SIGINT or SIGTERM - but at most 4 sec to acknowledge before releasing.\n\
99 \n\
100 The audio-device-id is a string e.g. 'Audio1'\n\
101 \n\
102 Examples:\n\
103 " ARD_PROG_NAME " Audio0\n\
104 \n");
105
106         printf ("Report bugs to Robin Gareus <robin@gareus.org>\n");
107         exit (status);
108 }
109
110 static struct option const long_options[] =
111 {
112         {"help", no_argument, 0, 'h'},
113         {"name", required_argument, 0, 'n'},
114         {"pid", required_argument, 0, 'P'},
115         {"priority", required_argument, 0, 'p'},
116         {"version", no_argument, 0, 'V'},
117         {"releasewait", no_argument, 0, 'w'},
118         {NULL, 0, NULL, 0}
119 };
120
121 static int request_cb(rd_device *d, int forced) {
122         (void) d; // skip 'unused variable' compiler warning;
123         (void) forced; // skip 'unused variable' compiler warning;
124         fprintf(stdout, "Received higher priority request - releasing device.\n");
125         fflush(stdout);
126         if(!release_wait_for_signal) {
127                 usleep (500000);
128                 run = 0;
129         } else if (run) {
130                 int timeout = 4000;
131                 fprintf(stdout, "Waiting for acknowledge signal to release.\n");
132                 while (release_wait_for_signal && run && --timeout) {
133                         if (!stdin_available()) {
134                                 break;
135                         }
136                         if (parent_pid > 0 && kill (parent_pid, 0)) {
137                                 break;
138                         }
139                         usleep (1000);
140                 }
141                 run = 0;
142         }
143         return 1; // OK
144 }
145
146 int main(int argc, char **argv) {
147         DBusConnection* dbus_connection = NULL;
148         rd_device * reserved_device = NULL;
149         DBusError error;
150         int ret, c;
151
152         int32_t priority = INT32_MAX;
153         char *name = strdup(ARD_APPL_NAME);
154
155         while ((c = getopt_long (argc, argv,
156                                         "h"  /* help */
157                                         "n:" /* name */
158                                         "P:" /* pid */
159                                         "p:" /* priority */
160                                         "V"  /* version */
161                                         "w", /* release wait for signal */
162                                         long_options, (int *) 0)) != EOF)
163         {
164                 switch (c) {
165                         case 'h':
166                                 free(name);
167                                 usage(EXIT_SUCCESS);
168                                 break;
169                         case 'n':
170                                 free(name);
171                                 name = strdup(optarg);
172                                 break;
173                         case 'p':
174                                 priority = atoi (optarg);
175                                 if (priority < 0) priority = 0;
176                                 break;
177                         case 'P':
178                                 parent_pid = atoi (optarg);
179                                 break;
180                         case 'V':
181                                 free(name);
182                                 print_version(EXIT_SUCCESS);
183                                 break;
184                         case 'w':
185                                 release_wait_for_signal = 1;
186                                 break;
187                         default:
188                                 free(name);
189                                 usage(EXIT_FAILURE);
190                                 break;
191                 }
192         }
193
194         if (optind + 1 != argc) {
195                 free(name);
196                 usage(EXIT_FAILURE);
197         }
198         const char *device_name = argv[optind];
199
200         if (parent_pid > 0 && kill (parent_pid, 0)) {
201                 fprintf(stderr, "Given PID to watch is not running.\n");
202                 free(name);
203                 return EXIT_FAILURE;
204         }
205
206         dbus_error_init(&error);
207
208         if (!(dbus_connection = dbus_bus_get (DBUS_BUS_SESSION, &error))) {
209                 fprintf(stderr, "Failed to connect to session bus for device reservation: %s\n", error.message ? error.message : "unknown error.");
210                 dbus_error_free(&error);
211                 free(name);
212                 return EXIT_FAILURE;
213         }
214
215         if ((ret = rd_acquire (
216                                         &reserved_device,
217                                         dbus_connection,
218                                         device_name,
219                                         name,
220                                         priority,
221                                         request_cb,
222                                         &error)) < 0)
223         {
224                 fprintf(stderr, "Failed to acquire device: '%s'\n%s\n", device_name, (error.message ? error.message : strerror(-ret)));
225                 dbus_error_free(&error);
226                 dbus_connection_unref(dbus_connection);
227                 free(name);
228                 return EXIT_FAILURE;
229         }
230
231         fprintf(stdout, "Acquired audio-card '%s'\n", device_name);
232         fprintf(stdout, "Press Ctrl+C or close stdin to release the device.\n");
233         fflush(stdout);
234
235         signal(SIGTERM, wearedone);
236         signal(SIGINT, wearedone);
237
238         while (run && dbus_connection_read_write_dispatch (dbus_connection, 200)) {
239                 if (!stdin_available()) {
240                         fprintf(stderr, "stdin closed - releasing device.\n");
241                         break;
242                 }
243                 if (parent_pid > 0 && kill (parent_pid, 0)) {
244                         fprintf(stderr, "watched PID no longer exists - releasing device.\n");
245                         break;
246                 }
247         }
248
249         rd_release (reserved_device);
250         fprintf(stdout, "Released audio-card '%s'\n", device_name);
251
252         dbus_connection_unref(dbus_connection);
253         dbus_error_free(&error);
254         free(name);
255         return EXIT_SUCCESS;
256 }