This allows calling a user-specified external program whenever a source or sink is suspended or resumed (by specifying external_suspend_handler="<path_to_executable>" as an option to module-suspend-on-idle). The external executable is called with "--suspended" or "--resumed" followed by "--source <source_name>" or "--sink <sink_name>". My use case for this feature is to cut the power to my active speakers in order to eliminate hissing. I've been using the patch (applied to pulseaudio 4.0) on Ubuntu 14.04 since February. I have not tested it with later versions, except to check that it compiles. I could do some further testing if the patch is otherwise acceptable/useful enough. Some things I'm not sure about: * What happens on Windows? Does fork() work and if not, what does it return? Maybe some of the code should be wrapped with "#ifndef OS_IS_WIN32". * Security considerations? This might provide a sneaky way to run malicious code repeatedly, but only if you have write access to the config file. In that case you are probably screwed in a multitude of ways already... * What would be the correct place to document the new option. Maybe http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#index62h3 ? --- src/modules/module-suspend-on-idle.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index f7620db..f21c7cc 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -21,6 +21,8 @@ #include <config.h> #endif +#include <unistd.h> + #include <pulse/xmalloc.h> #include <pulse/timeval.h> #include <pulse/rtclock.h> @@ -38,10 +40,11 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); -PA_MODULE_USAGE("timeout=<timeout>"); +PA_MODULE_USAGE("timeout=<timeout>, external_suspend_handler=<executable called whenever a sink/source is suspended/resumed>"); static const char* const valid_modargs[] = { "timeout", + "external_suspend_handler", NULL, }; @@ -49,6 +52,7 @@ struct userdata { pa_core *core; pa_usec_t timeout; pa_hashmap *device_infos; + const char *external_suspend_handler; }; struct device_info { @@ -60,6 +64,24 @@ struct device_info { pa_usec_t timeout; }; +static void call_external_suspend_handler(bool is_suspended, struct device_info *d) { + /* is_suspended --> whether suspending or resuming */ + if (d->userdata->external_suspend_handler) { + const char* c = d->userdata->external_suspend_handler; + + pa_log_debug("Calling external %s handler: %s", is_suspended ? "suspend" : "resume", c); + if (fork() == 0) { + execl(c, c, is_suspended ? "--suspended" : "--resumed", (d->sink) ? "--sink" : "--source", + (d->sink) ? d->sink->name : d->source->name, NULL); + + pa_log_debug("Failed to execute external suspend/resume handler " + "(Is the following a valid path to an executable file?): %s", c); + /* This is normal if the user-specified path is invalid, so terminate the child process quietly. */ + _exit(3); + } + } +} + static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) { struct device_info *d = userdata; @@ -69,12 +91,14 @@ static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && !(d->sink->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name); + call_external_suspend_handler(true, d); pa_sink_suspend(d->sink, true, PA_SUSPEND_IDLE); pa_core_maybe_vacuum(d->userdata->core); } if (d->source && pa_source_check_suspend(d->source) <= 0 && !(d->source->suspend_cause & PA_SUSPEND_IDLE)) { pa_log_info("Source %s idle for too long, suspending ...", d->source->name); + call_external_suspend_handler(true, d); pa_source_suspend(d->source, true, PA_SUSPEND_IDLE); pa_core_maybe_vacuum(d->userdata->core); } @@ -102,11 +126,13 @@ static void resume(struct device_info *d) { if (d->sink) { pa_log_debug("Sink %s becomes busy, resuming.", d->sink->name); + call_external_suspend_handler(false, d); pa_sink_suspend(d->sink, false, PA_SUSPEND_IDLE); } if (d->source) { pa_log_debug("Source %s becomes busy, resuming.", d->source->name); + call_external_suspend_handler(false, d); pa_source_suspend(d->source, false, PA_SUSPEND_IDLE); } } @@ -439,6 +465,10 @@ int pa__init(pa_module*m) { u->timeout = timeout * PA_USEC_PER_SEC; u->device_infos = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) device_info_free); + u->external_suspend_handler = pa_modargs_get_value(ma, "external_suspend_handler", NULL); + if (u->external_suspend_handler) + u->external_suspend_handler = pa_xstrdup(u->external_suspend_handler); + PA_IDXSET_FOREACH(sink, m->core->sinks, idx) device_new_hook_cb(m->core, PA_OBJECT(sink), u); @@ -486,5 +516,8 @@ void pa__done(pa_module*m) { pa_hashmap_free(u->device_infos); + if (u->external_suspend_handler) + pa_xfree((char *) u->external_suspend_handler); + pa_xfree(u); } -- 1.9.1