[pulseaudio-discuss] [PATCH] Run a user specified program on suspend/resume.

David Henningsson david.henningsson at canonical.com
Mon Dec 7 01:36:20 PST 2015


Hi!

Sorry for the late reply.

When starting child processes, there is a helper function in 
src/pulsecore/start-child.c that you can use. It should cover the 
platform and security questions you're having. (Or at least put the ball 
back into our ballpark. :-) )

That said, about the general concept of launching an executable on 
suspend/resume...it seems useful to me, but should such an executable be 
blocking or non-blocking (i e, resume/suspend continuing in parallel, or 
waiting for the executable to finish)?
What do others think?

On 2015-08-21 22:29, Joonas Govenius wrote:
> 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
>    ?

That seems to be the right place IMO.

>
> ---
>   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);
>   }
>

-- 
David Henningsson, Canonical Ltd.
https://launchpad.net/~diwic


More information about the pulseaudio-discuss mailing list