This hook allows router modules to "intercept" profile change requests. A router might want, for example, to move streams elsewhere when the current sink or source disappears due to a profile switch. For more explanation, see the documentation that I added to core.h for this hook. --- src/modules/bluetooth/module-bluetooth-device.c | 8 +++---- src/modules/bluetooth/module-bluetooth-policy.c | 2 +- src/modules/dbus/iface-card.c | 2 +- src/modules/module-card-restore.c | 2 +- src/modules/module-switch-on-port-available.c | 2 +- src/pulsecore/card.c | 18 +++++++++++++- src/pulsecore/card.h | 11 ++++++++- src/pulsecore/cli-command.c | 2 +- src/pulsecore/core.h | 31 +++++++++++++++++++++++++ src/pulsecore/protocol-native.c | 2 +- 10 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 34745fd..5562f6d 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -537,7 +537,7 @@ static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t o pa_log_debug("Switching the profile to off due to IO thread failure."); - pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); + pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false, false) >= 0); break; } } @@ -1809,7 +1809,7 @@ static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa pa_assert(u); if (t == u->transport && t->state == PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) - pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); + pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false, false) >= 0); if (t->device == u->device) handle_transport_state_change(u, t); @@ -2054,7 +2054,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { off: stop_thread(u); - pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); + pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false, false) >= 0); return -PA_ERR_IO; } @@ -2561,7 +2561,7 @@ int pa__init(pa_module *m) { off: stop_thread(u); - pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); + pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false, false) >= 0); return 0; diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c index a5e9985..a7733e0 100644 --- a/src/modules/bluetooth/module-bluetooth-policy.c +++ b/src/modules/bluetooth/module-bluetooth-policy.c @@ -195,7 +195,7 @@ static pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_prof pa_log_debug("Setting card '%s' to profile '%s'", card->name, selected_profile->name); - if (pa_card_set_profile(card, selected_profile, false) != 0) + if (pa_card_set_profile(card, selected_profile, false, false) != 0) pa_log_warn("Could not set profile '%s'", selected_profile->name); return PA_HOOK_OK; diff --git a/src/modules/dbus/iface-card.c b/src/modules/dbus/iface-card.c index b77a5e4..d4b080b 100644 --- a/src/modules/dbus/iface-card.c +++ b/src/modules/dbus/iface-card.c @@ -336,7 +336,7 @@ static void handle_set_active_profile(DBusConnection *conn, DBusMessage *msg, DB return; } - if ((r = pa_card_set_profile(c->card, pa_dbusiface_card_profile_get_profile(new_active), true)) < 0) { + if ((r = pa_card_set_profile(c->card, pa_dbusiface_card_profile_get_profile(new_active), true, false)) < 0) { pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Internal error in PulseAudio: pa_card_set_profile() failed with error code %i.", r); return; diff --git a/src/modules/module-card-restore.c b/src/modules/module-card-restore.c index 3134067..cc55008 100644 --- a/src/modules/module-card-restore.c +++ b/src/modules/module-card-restore.c @@ -413,7 +413,7 @@ static pa_hook_result_t card_profile_added_callback(pa_core *c, pa_card_profile return PA_HOOK_OK; if (pa_safe_streq(entry->profile, profile->name)) { - if (pa_card_set_profile(profile->card, profile, true) >= 0) + if (pa_card_set_profile(profile->card, profile, true, false) >= 0) pa_log_info("Restored profile '%s' for card %s.", profile->name, profile->card->name); } diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c index 2c7ad17..2ba5397 100644 --- a/src/modules/module-switch-on-port-available.c +++ b/src/modules/module-switch-on-port-available.c @@ -121,7 +121,7 @@ static int try_to_switch_profile(pa_device_port *port) { return -1; } - if (pa_card_set_profile(port->card, best_profile, false) != 0) { + if (pa_card_set_profile(port->card, best_profile, false, false) != 0) { pa_log_debug("Could not set profile %s", best_profile->name); return -1; } diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c index 6e1f77a..e930a43 100644 --- a/src/pulsecore/card.c +++ b/src/pulsecore/card.c @@ -250,7 +250,7 @@ void pa_card_add_profile(pa_card *c, pa_card_profile *profile) { pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], profile); } -int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save) { +int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save, bool bypass_router) { int r; pa_assert(c); @@ -267,6 +267,22 @@ int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save) { return 0; } + /* See the documentation of PA_CORE_HOOK_CARD_SET_PROFILE to understand + * what this is all about. */ + if (!bypass_router) { + pa_card_set_profile_hook_data data = { + .profile = profile, + .save = save, + .ret = 0, + .ret_valid = false + }; + + pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_SET_PROFILE], &data); + + if (data.ret_valid) + return data.ret; + } + if ((r = c->set_profile(c, profile)) < 0) return r; diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h index 5318150..7d59f59 100644 --- a/src/pulsecore/card.h +++ b/src/pulsecore/card.h @@ -99,6 +99,15 @@ typedef struct pa_card_new_data { bool save_profile:1; } pa_card_new_data; +/* See the documentation of PA_CORE_HOOK_CARD_SET_PROFILE to understand this + * struct. */ +typedef struct { + pa_card_profile *profile; + bool save; + int ret; + bool ret_valid; +} pa_card_set_profile_hook_data; + pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra); void pa_card_profile_free(pa_card_profile *c); @@ -115,7 +124,7 @@ void pa_card_free(pa_card *c); void pa_card_add_profile(pa_card *c, pa_card_profile *profile); -int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save); +int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save, bool bypass_router); int pa_card_suspend(pa_card *c, bool suspend, pa_suspend_cause_t cause); diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c index 44b5d84..d390a74 100644 --- a/src/pulsecore/cli-command.c +++ b/src/pulsecore/cli-command.c @@ -1661,7 +1661,7 @@ static int pa_cli_command_card_profile(pa_core *c, pa_tokenizer *t, pa_strbuf *b return -1; } - if (pa_card_set_profile(card, profile, true) < 0) { + if (pa_card_set_profile(card, profile, true, false) < 0) { pa_strbuf_printf(buf, "Failed to set card profile to '%s'.\n", p); return -1; } diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index 8b4bf05..f8863ae 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -115,6 +115,37 @@ typedef enum pa_core_hook { PA_CORE_HOOK_CARD_NEW, PA_CORE_HOOK_CARD_PUT, PA_CORE_HOOK_CARD_UNLINK, + + /* Fired when pa_card_set_profile() is called with bypass_router=false. + * Router modules can use this to intercept card profile switch requests. + * Call data: pa_card_set_profile_hook_data. + * + * What does "intercepting profile switch requests" mean? Router modules + * may want to control whether to accept the profile switch, or they may + * want to do something before and after the switch. + * + * The ret_valid field of pa_card_set_profile_hook_data controls an + * important aspect of pa_card_set_profile(): if ret_valid is true after + * the hook returns, it is assumed that a router module took care of the + * profile switch, and pa_card_set_profile() will just return the value of + * the ret field and not continue executing the profile switch. + * + * Denying a profile 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 profile switch can be achieved by + * first doing the "before" thing in the hook callback, then calling + * pa_card_set_profile() 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 profile + * switch. In this case ret should probably be the return value of the + * "inner" pa_card_set_profile() call. + * + * The hook callback can access the profile and save parameters of the + * pa_card_set_profile() call via the provile and save fields of + * pa_card_set_profile_hook_data. */ + PA_CORE_HOOK_CARD_SET_PROFILE, + PA_CORE_HOOK_CARD_PROFILE_CHANGED, PA_CORE_HOOK_CARD_PROFILE_ADDED, PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED, diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index e195ba8..dd38cc4 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -4736,7 +4736,7 @@ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_ CHECK_VALIDITY(c->pstream, profile, tag, PA_ERR_NOENTITY); - if ((ret = pa_card_set_profile(card, profile, true)) < 0) { + if ((ret = pa_card_set_profile(card, profile, true, false)) < 0) { pa_pstream_send_error(c->pstream, tag, -ret); return; } -- 1.8.3.1