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

Joonas Govenius joonas.govenius at gmail.com
Fri Aug 21 13:29:11 PDT 2015


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



More information about the pulseaudio-discuss mailing list