[PATCH 17/30] sink, source: Add PA_CORE_HOOK_DEVICE_SET_PORT

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This hook allows router modules to "intercept" port change requests.
This will be used at least by the default router to detach streams
from the device before the port switch happens and reattach them when
the switch is completed. The reason why the default router should do
that is that in the future streams that are playing to a sink during
a port switch get killed, unless a router module detaches the streams
first.

The reason for killing the streams is to keep the node based routing
infrastructure simpler. When a node becomes unavailable (in this case
a port becomes inactive), connections to other nodes should be
removed, and if there's no routing module that decides otherwise, then
the core infrastructure will implement the connection removal by
killing the stream. Doing anything else would be complicated.

The default router could also do something else than to just reattach
the streams to the same device. If, for example, a sink has ports for
headphones and speakers, and headphones are unplugged, then the
speaker port should get activated, but it's not obvious that the
streams should be moved to the speakers. Perhaps there's another pair
of headphones on a different card that should be preferred over the
speakers. For now, however, the default router doesn't have this kind
of device prioritization capabilities, so it will just reimplement
what has so far been done in the core (keep streams playing to the
same sink).
---
 src/modules/dbus/iface-device.c               |  4 +--
 src/modules/module-switch-on-port-available.c |  8 +++---
 src/pulsecore/cli-command.c                   |  4 +--
 src/pulsecore/core.h                          | 39 +++++++++++++++++++++++++++
 src/pulsecore/protocol-native.c               |  4 +--
 src/pulsecore/sink.c                          | 18 ++++++++++++-
 src/pulsecore/sink.h                          | 11 +++++++-
 src/pulsecore/source.c                        | 18 ++++++++++++-
 src/pulsecore/source.h                        |  2 +-
 9 files changed, 94 insertions(+), 14 deletions(-)

diff --git a/src/modules/dbus/iface-device.c b/src/modules/dbus/iface-device.c
index c2e1fd7..cf45ea1 100644
--- a/src/modules/dbus/iface-device.c
+++ b/src/modules/dbus/iface-device.c
@@ -753,13 +753,13 @@ static void handle_set_active_port(DBusConnection *conn, DBusMessage *msg, DBusM
     }
 
     if (d->type == PA_DEVICE_TYPE_SINK) {
-        if ((r = pa_sink_set_port(d->sink, pa_dbusiface_device_port_get_port(new_active), true)) < 0) {
+        if ((r = pa_sink_set_port(d->sink, pa_dbusiface_device_port_get_port(new_active), true, false)) < 0) {
             pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
                                "Internal error in PulseAudio: pa_sink_set_port() failed with error code %i.", r);
             return;
         }
     } else {
-        if ((r = pa_source_set_port(d->source, pa_dbusiface_device_port_get_port(new_active), true)) < 0) {
+        if ((r = pa_source_set_port(d->source, pa_dbusiface_device_port_get_port(new_active), true, false)) < 0) {
             pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED,
                                "Internal error in PulseAudio: pa_source_set_port() failed with error code %i.", r);
             return;
diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c
index 05d88e6..bf333d1 100644
--- a/src/modules/module-switch-on-port-available.c
+++ b/src/modules/module-switch-on-port-available.c
@@ -196,9 +196,9 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
         }
 
         if (source)
-            pa_source_set_port(source, port, false);
+            pa_source_set_port(source, port, false, false);
         if (sink)
-            pa_sink_set_port(sink, port, false);
+            pa_sink_set_port(sink, port, false, false);
     }
 
     if (port->available == PA_AVAILABLE_NO) {
@@ -206,7 +206,7 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
             pa_device_port *p2 = find_best_port(sink->ports);
 
             if (p2 && p2->available != PA_AVAILABLE_NO)
-                pa_sink_set_port(sink, p2, false);
+                pa_sink_set_port(sink, p2, false, false);
             else {
                 /* Maybe try to switch to another profile? */
             }
@@ -216,7 +216,7 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
             pa_device_port *p2 = find_best_port(source->ports);
 
             if (p2 && p2->available != PA_AVAILABLE_NO)
-                pa_source_set_port(source, p2, false);
+                pa_source_set_port(source, p2, false, false);
             else {
                 /* Maybe try to switch to another profile? */
             }
diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
index 03512b7..67f592d 100644
--- a/src/pulsecore/cli-command.c
+++ b/src/pulsecore/cli-command.c
@@ -1699,7 +1699,7 @@ static int pa_cli_command_sink_port(pa_core *c, pa_tokenizer *t, pa_strbuf *buf,
         return -1;
     }
 
-    if (pa_sink_set_port(sink, port, true) < 0) {
+    if (pa_sink_set_port(sink, port, true, false) < 0) {
         pa_strbuf_printf(buf, "Failed to set sink port to '%s'.\n", p);
         return -1;
     }
@@ -1737,7 +1737,7 @@ static int pa_cli_command_source_port(pa_core *c, pa_tokenizer *t, pa_strbuf *bu
         return -1;
     }
 
-    if (pa_source_set_port(source, port, true) < 0) {
+    if (pa_source_set_port(source, port, true, false) < 0) {
         pa_strbuf_printf(buf, "Failed to set source port to '%s'.\n", p);
         return -1;
     }
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 443af48..bce6a6d 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -85,6 +85,45 @@ typedef enum pa_core_hook {
     PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED,
     PA_CORE_HOOK_SOURCE_PORT_CHANGED,
     PA_CORE_HOOK_SOURCE_FLAGS_CHANGED,
+
+    /* Fired when pa_sink/source_set_port() is called with bypass_router=false.
+     * Router modules can use this hook and PA_SOURCE_SET_PORT to intercept
+     * device port switch requests. Call data: pa_device_set_port_hook_data,
+     * details below:
+     *
+     *     struct pa_device_set_port_hook_data {
+     *         pa_device_port *port;
+     *         bool save;
+     *         int ret;
+     *         bool ret_valid;
+     *     }
+     *
+     * What does "intercepting port switch requests" mean? Router modules may
+     * want to control whether to accept the port switch, or they may want to
+     * do something before and after the switch.
+     *
+     * The ret_valid field of the data struct controls an important aspect of
+     * pa_sink/source_set_port(): if ret_valid is true after the hook returns,
+     * it is assumed that a router module took care of the port switch, and
+     * pa_sink/source_set_port() will just return the value of the ret field
+     * and not continue executing the port switch.
+     *
+     * Denying a port switch request can be done by setting the ret field to
+     * an appropriate negative error code and the ret_valid field to true.
+     *
+     * Doing something before and after a port switch can be achieved by first
+     * doing the "before" thing in the hook callback, then calling
+     * pa_sink/source_set_port() with bypass_router=true (so that the hook
+     * won't be fired again), and then doing the "after" thing. ret_valid must
+     * be set to true, because the router module already executed the port
+     * switch. In this case ret should probably be the return value of the
+     * "inner" pa_sink/source_set_port() call.
+     *
+     * The hook callback can access the port and save parameters of the
+     * pa_sink/source_set_port() call via the port and save fields of the data
+     * struct. */
+    PA_CORE_HOOK_DEVICE_SET_PORT,
+
     PA_CORE_HOOK_SINK_INPUT_NEW,
     PA_CORE_HOOK_SINK_INPUT_FIXATE,
     PA_CORE_HOOK_SINK_INPUT_PUT,
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index 379c4b3..f41f2f3 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -4779,7 +4779,7 @@ static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command,
         port = pa_hashmap_get(sink->ports, port_name);
         CHECK_VALIDITY(c->pstream, port, tag, PA_ERR_NOENTITY);
 
-        if ((ret = pa_sink_set_port(sink, port, true)) < 0) {
+        if ((ret = pa_sink_set_port(sink, port, true, false)) < 0) {
             pa_pstream_send_error(c->pstream, tag, -ret);
             return;
         }
@@ -4797,7 +4797,7 @@ static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command,
         port = pa_hashmap_get(source->ports, port_name);
         CHECK_VALIDITY(c->pstream, port, tag, PA_ERR_NOENTITY);
 
-        if ((ret = pa_source_set_port(source, port, true)) < 0) {
+        if ((ret = pa_source_set_port(source, port, true, false)) < 0) {
             pa_pstream_send_error(c->pstream, tag, -ret);
             return;
         }
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 45dc618..a4baf0f 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -3424,7 +3424,7 @@ size_t pa_sink_get_max_request(pa_sink *s) {
 }
 
 /* Called from main context */
-int pa_sink_set_port(pa_sink *s, pa_device_port *port, bool save) {
+int pa_sink_set_port(pa_sink *s, pa_device_port *port, bool save, bool bypass_router) {
     pa_device_port *old_port;
     int ret;
 
@@ -3444,6 +3444,22 @@ int pa_sink_set_port(pa_sink *s, pa_device_port *port, bool save) {
         return 0;
     }
 
+    /* See the documentation of PA_CORE_HOOK_DEVICE_SET_PORT to understand what
+     * this is all about. */
+    if (!bypass_router) {
+        pa_device_set_port_hook_data data = {
+            .port = port,
+            .save = save,
+            .ret = 0,
+            .ret_valid = false
+        };
+
+        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_DEVICE_SET_PORT], &data);
+
+        if (data.ret_valid)
+            return data.ret;
+    }
+
     pa_node_active_changed(old_port->node, false);
 
     if (s->flags & PA_SINK_DEFERRED_VOLUME) {
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index a3bd24b..df75fc7 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -366,6 +366,15 @@ typedef struct pa_sink_new_data {
     pa_node_new_data node_data;
 } pa_sink_new_data;
 
+/* See the documentation of PA_CORE_HOOK_DEVICE_SET_PORT to understand this
+ * struct. */
+typedef struct {
+    pa_device_port *port;
+    bool save;
+    int ret;
+    bool ret_valid;
+} pa_device_set_port_hook_data;
+
 pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data);
 void pa_sink_new_data_set_name(pa_sink_new_data *data, const char *name);
 void pa_sink_new_data_set_sample_spec(pa_sink_new_data *data, const pa_sample_spec *spec);
@@ -461,7 +470,7 @@ bool pa_sink_get_mute(pa_sink *sink, bool force_refresh);
 
 bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p);
 
-int pa_sink_set_port(pa_sink *s, pa_device_port *port, bool save);
+int pa_sink_set_port(pa_sink *s, pa_device_port *port, bool save, bool bypass_router);
 void pa_sink_set_mixer_dirty(pa_sink *s, bool is_dirty);
 
 unsigned pa_sink_linked_by(pa_sink *s); /* Number of connected streams */
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index d836ae2..c676868 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -2717,7 +2717,7 @@ size_t pa_source_get_max_rewind(pa_source *s) {
 }
 
 /* Called from main context */
-int pa_source_set_port(pa_source *s, pa_device_port *port, bool save) {
+int pa_source_set_port(pa_source *s, pa_device_port *port, bool save, bool bypass_router) {
     pa_device_port *old_port;
     int ret;
 
@@ -2737,6 +2737,22 @@ int pa_source_set_port(pa_source *s, pa_device_port *port, bool save) {
         return 0;
     }
 
+    /* See the documentation of PA_CORE_HOOK_DEVICE_SET_PORT to understand what
+     * this is all about. */
+    if (!bypass_router) {
+        pa_device_set_port_hook_data data = {
+            .port = port,
+            .save = save,
+            .ret = 0,
+            .ret_valid = false
+        };
+
+        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_DEVICE_SET_PORT], &data);
+
+        if (data.ret_valid)
+            return data.ret;
+    }
+
     pa_node_active_changed(old_port->node, false);
 
     if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index cb8f900..cdc8100 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -395,7 +395,7 @@ bool pa_source_get_mute(pa_source *source, bool force_refresh);
 
 bool pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p);
 
-int pa_source_set_port(pa_source *s, pa_device_port *port, bool save);
+int pa_source_set_port(pa_source *s, pa_device_port *port, bool save, bool bypass_router);
 void pa_source_set_mixer_dirty(pa_source *s, bool is_dirty);
 
 int pa_source_update_rate(pa_source *s, uint32_t rate, bool passthrough);
-- 
1.8.3.1



[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux