1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2004-2006 Lennart Poettering 5 6 PulseAudio is free software; you can redistribute it and/or modify 7 it under the terms of the GNU Lesser General Public License as published 8 by the Free Software Foundation; either version 2.1 of the License, 9 or (at your option) any later version. 10 11 PulseAudio is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public License 17 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 18***/ 19 20#ifdef HAVE_CONFIG_H 21#include <config.h> 22#endif 23 24#include <sys/types.h> 25#include <sys/wait.h> 26 27#include <signal.h> 28#include <string.h> 29#include <errno.h> 30#include <unistd.h> 31#include <assert.h> 32#include <stdio.h> 33#include <stdlib.h> 34#include <getopt.h> 35#include <locale.h> 36 37#ifdef __linux__ 38#include <sys/prctl.h> 39#endif 40 41#include <pulse/pulseaudio.h> 42 43#include <pulsecore/i18n.h> 44#include <pulsecore/macro.h> 45 46static pa_context *context = NULL; 47static pa_mainloop_api *mainloop_api = NULL; 48static char **child_argv = NULL; 49static int child_argc = 0; 50static pid_t child_pid = (pid_t) -1; 51static int child_ret = 0; 52static int dead = 1; 53static int fork_failed = 0; 54 55static void quit(int ret) { 56 pa_assert(mainloop_api); 57 mainloop_api->quit(mainloop_api, ret); 58} 59 60static void context_drain_complete(pa_context *c, void *userdata) { 61 pa_context_disconnect(c); 62} 63 64static void drain(void) { 65 pa_operation *o; 66 67 if (context) { 68 if (!(o = pa_context_drain(context, context_drain_complete, NULL))) 69 pa_context_disconnect(context); 70 else 71 pa_operation_unref(o); 72 } else 73 quit(0); 74} 75 76static int start_child(void) { 77 78 if ((child_pid = fork()) < 0) { 79 fprintf(stderr, _("fork(): %s\n"), strerror(errno)); 80 fork_failed = 1; 81 82 return -1; 83 84 } else if (child_pid == 0) { 85 /* Child */ 86 87#ifdef __linux__ 88 prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0); 89#endif 90 91 if (execvp(child_argv[0], child_argv) < 0) 92 fprintf(stderr, _("execvp(): %s\n"), strerror(errno)); 93 94 _exit(1); 95 96 } else { 97 98 /* parent */ 99 dead = 0; 100 } 101 102 return 0; 103} 104 105static void resume_complete(pa_context *c, int success, void *userdata) { 106 static int n = 0; 107 108 n++; 109 110 if (!success) { 111 fprintf(stderr, _("Failure to resume: %s\n"), pa_strerror(pa_context_errno(c))); 112 quit(1); 113 return; 114 } 115 116 if (n >= 2) 117 drain(); /* drain and quit */ 118} 119 120static void resume(void) { 121 static int n = 0; 122 123 n++; 124 125 if (n > 1) 126 return; 127 128 if (context) { 129 if (pa_context_is_local(context)) { 130 pa_operation_unref(pa_context_suspend_sink_by_index(context, PA_INVALID_INDEX, 0, resume_complete, NULL)); 131 pa_operation_unref(pa_context_suspend_source_by_index(context, PA_INVALID_INDEX, 0, resume_complete, NULL)); 132 } else 133 drain(); 134 } else { 135 quit(0); 136 } 137} 138 139static void suspend_complete(pa_context *c, int success, void *userdata) { 140 static int n = 0; 141 142 n++; 143 144 if (!success) { 145 fprintf(stderr, _("Failure to suspend: %s\n"), pa_strerror(pa_context_errno(c))); 146 quit(1); 147 return; 148 } 149 150 if (n >= 2) { 151 if (start_child() < 0) 152 resume(); 153 } 154} 155 156static void context_state_callback(pa_context *c, void *userdata) { 157 pa_assert(c); 158 159 switch (pa_context_get_state(c)) { 160 case PA_CONTEXT_CONNECTING: 161 case PA_CONTEXT_AUTHORIZING: 162 case PA_CONTEXT_SETTING_NAME: 163 break; 164 165 case PA_CONTEXT_READY: 166 if (pa_context_is_local(c)) { 167 pa_operation_unref(pa_context_suspend_sink_by_index(c, PA_INVALID_INDEX, 1, suspend_complete, NULL)); 168 pa_operation_unref(pa_context_suspend_source_by_index(c, PA_INVALID_INDEX, 1, suspend_complete, NULL)); 169 } else { 170 fprintf(stderr, _("WARNING: Sound server is not local, not suspending.\n")); 171 if (start_child() < 0) 172 drain(); 173 } 174 175 break; 176 177 case PA_CONTEXT_TERMINATED: 178 quit(0); 179 break; 180 181 case PA_CONTEXT_FAILED: 182 default: 183 fprintf(stderr, _("Connection failure: %s\n"), pa_strerror(pa_context_errno(c))); 184 185 pa_context_unref(context); 186 context = NULL; 187 188 if (child_pid == (pid_t) -1) { 189 /* not started yet, then we do it now */ 190 if (start_child() < 0) 191 quit(1); 192 } else if (dead) 193 /* already started, and dead, so let's quit */ 194 quit(1); 195 196 break; 197 } 198} 199 200static void sigint_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { 201 fprintf(stderr, _("Got SIGINT, exiting.\n")); 202 resume(); 203} 204 205static void sigchld_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { 206 int status = 0; 207 pid_t p; 208 209 p = waitpid(-1, &status, WNOHANG); 210 211 if (p != child_pid) 212 return; 213 214 dead = 1; 215 216 if (WIFEXITED(status)) 217 child_ret = WEXITSTATUS(status); 218 else if (WIFSIGNALED(status)) { 219 fprintf(stderr, _("WARNING: Child process terminated by signal %u\n"), WTERMSIG(status)); 220 child_ret = 1; 221 } 222 223 resume(); 224} 225 226static void help(const char *argv0) { 227 228 printf(_("%s [options] -- PROGRAM [ARGUMENTS ...]\n\n" 229 "Temporarily suspend PulseAudio while PROGRAM runs.\n\n" 230 " -h, --help Show this help\n" 231 " --version Show version\n" 232 " -s, --server=SERVER The name of the server to connect to\n\n"), 233 argv0); 234} 235 236enum { 237 ARG_VERSION = 256 238}; 239 240int main(int argc, char *argv[]) { 241 pa_mainloop* m = NULL; 242 int c, ret = 1; 243 char *server = NULL, *bn; 244 245 static const struct option long_options[] = { 246 {"server", 1, NULL, 's'}, 247 {"version", 0, NULL, ARG_VERSION}, 248 {"help", 0, NULL, 'h'}, 249 {NULL, 0, NULL, 0} 250 }; 251 252 setlocale(LC_ALL, ""); 253#ifdef ENABLE_NLS 254 bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); 255#endif 256 257 bn = pa_path_get_filename(argv[0]); 258 259 while ((c = getopt_long(argc, argv, "s:h", long_options, NULL)) != -1) { 260 switch (c) { 261 case 'h' : 262 help(bn); 263 ret = 0; 264 goto quit; 265 266 case ARG_VERSION: 267 printf(_("pasuspender %s\n" 268 "Compiled with libpulse %s\n" 269 "Linked with libpulse %s\n"), 270 PACKAGE_VERSION, 271 pa_get_headers_version(), 272 pa_get_library_version()); 273 ret = 0; 274 goto quit; 275 276 case 's': 277 pa_xfree(server); 278 server = pa_xstrdup(optarg); 279 break; 280 281 default: 282 goto quit; 283 } 284 } 285 286 child_argv = argv + optind; 287 child_argc = argc - optind; 288 289 if (child_argc <= 0) { 290 help(bn); 291 ret = 0; 292 goto quit; 293 } 294 295 if (!(m = pa_mainloop_new())) { 296 fprintf(stderr, _("pa_mainloop_new() failed.\n")); 297 goto quit; 298 } 299 300 pa_assert_se(mainloop_api = pa_mainloop_get_api(m)); 301 pa_assert_se(pa_signal_init(mainloop_api) == 0); 302 pa_signal_new(SIGINT, sigint_callback, NULL); 303 pa_signal_new(SIGCHLD, sigchld_callback, NULL); 304#ifdef SIGPIPE 305 signal(SIGPIPE, SIG_IGN); 306#endif 307 308 if (!(context = pa_context_new(mainloop_api, bn))) { 309 fprintf(stderr, _("pa_context_new() failed.\n")); 310 goto quit; 311 } 312 313 pa_context_set_state_callback(context, context_state_callback, NULL); 314 315 if (pa_context_connect(context, server, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) { 316 fprintf(stderr, "pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(context))); 317 goto quit; 318 } 319 320 if (pa_mainloop_run(m, &ret) < 0) { 321 fprintf(stderr, _("pa_mainloop_run() failed.\n")); 322 goto quit; 323 } 324 325 if (ret == 0 && fork_failed) 326 ret = 1; 327 328quit: 329 if (context) 330 pa_context_unref(context); 331 332 if (m) { 333 pa_signal_done(); 334 pa_mainloop_free(m); 335 } 336 337 pa_xfree(server); 338 339 if (!dead) 340 kill(child_pid, SIGTERM); 341 342 return ret == 0 ? child_ret : ret; 343} 344