diff options
author | Jaska Uimonen <jaska.uimonen@intel.com> | 2012-10-11 10:09:15 +0300 |
---|---|---|
committer | Jaska Uimonen <jaska.uimonen@intel.com> | 2013-02-15 09:39:56 +0200 |
commit | 8e7012c1c0dd94a5e446f01afdd34902c0e66c47 (patch) | |
tree | 1aacfeab37c3b717614fed61b96d91b4a605c7f2 | |
parent | 010e461aa0245baf188c126d4c7f789745353bc9 (diff) | |
download | pulseaudio-panda-8e7012c1c0dd94a5e446f01afdd34902c0e66c47.tar.gz pulseaudio-panda-8e7012c1c0dd94a5e446f01afdd34902c0e66c47.tar.bz2 pulseaudio-panda-8e7012c1c0dd94a5e446f01afdd34902c0e66c47.zip |
add Samsung policy module - Samsung
-rw-r--r-- | configure.ac | 17 | ||||
-rw-r--r-- | src/Makefile.am | 30 | ||||
-rw-r--r-- | src/map-file | 2 | ||||
-rw-r--r-- | src/modules/module-policy.c | 709 | ||||
-rw-r--r-- | src/pulse/ext-policy.c | 131 | ||||
-rw-r--r-- | src/pulse/ext-policy.h | 54 | ||||
-rw-r--r-- | src/pulse/proplist.h | 3 |
7 files changed, 946 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac index a3c11f4f..fb162d3c 100644 --- a/configure.ac +++ b/configure.ac @@ -1271,6 +1271,21 @@ if test "x$HAVE_PMAPI" = "xyes"; then fi AM_CONDITIONAL(HAVE_PMAPI, test "x$HAVE_PMAPI" = "xyes") +AC_ARG_ENABLE(spolicy, AC_HELP_STRING([--enable-spolicy], [using Samsung policy module]), +[ + case "${enableval}" in + yes) HAVE_SPOLICY=yes ;; + no) HAVE_SPOLICY=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-spolicy) ;; + esac + ],[HAVE_SPOLICY=no]) + +if test "x$HAVE_SPOLICY" = "xyes"; then + PKG_CHECK_MODULES(VCONF, vconf) + AC_SUBST(VCONF_CFLAGS) + AC_SUBST(VCONF_LIBS) +fi +AM_CONDITIONAL(HAVE_SPOLICY, test "x$HAVE_SPOLICY" = "xyes") ################################### # Output # @@ -1426,6 +1441,7 @@ AS_IF([test "x$HAVE_SPEEX" = "x1"], ENABLE_SPEEX=yes, ENABLE_SPEEX=no) AS_IF([test "x$HAVE_WEBRTC" = "x1"], ENABLE_WEBRTC=yes, ENABLE_WEBRTC=no) AS_IF([test "x$HAVE_DLOG" = "xyes"], ENABLE_DLOG=yes, ENABLE_DLOG=no) AS_IF([test "x$HAVE_PMAPI" = "xyes"], ENABLE_PMAPI=yes, ENABLE_PMAPI=no) +AS_IF([test "x$HAVE_SPOLICY" = "xyes"], ENABLE_SPOLICY=yes, ENABLE_SPOLICY=no) AS_IF([test "x$HAVE_TDB" = "x1"], ENABLE_TDB=yes, ENABLE_TDB=no) AS_IF([test "x$HAVE_GDBM" = "x1"], ENABLE_GDBM=yes, ENABLE_GDBM=no) AS_IF([test "x$HAVE_SIMPLEDB" = "x1"], ENABLE_SIMPLEDB=yes, ENABLE_SIMPLEDB=no) @@ -1481,6 +1497,7 @@ echo " Enable WebRTC echo canceller: ${ENABLE_WEBRTC} Enable DLOG: ${ENABLE_DLOG} Enable PMAPI: ${ENABLE_PMAPI} + Enable Samsung policy: ${ENABLE_SPOLICY} Database tdb: ${ENABLE_TDB} gdbm: ${ENABLE_GDBM} diff --git a/src/Makefile.am b/src/Makefile.am index 7332aa3e..60804c10 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -698,6 +698,12 @@ lib_LTLIBRARIES = \ libpulse.la \ libpulse-simple.la + +if HAVE_SPOLICY +pulseinclude_HEADERS += \ + pulse/ext-policy.h +endif + if HAVE_GLIB20 pulseinclude_HEADERS += \ pulse/glib-mainloop.h @@ -738,6 +744,11 @@ libpulse_la_SOURCES = \ pulse/volume.c pulse/volume.h \ pulse/xmalloc.c pulse/xmalloc.h +if HAVE_SPOLICY +libpulse_la_SOURCES += \ + pulse/ext-policy.c pulse/ext-policy.h +endif + libpulse_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBJSON_LIBS) libpulsecommon-@PA_MAJORMINOR@.la libpulse_la_LDFLAGS = $(AM_LDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_VERSION_INFO) @@ -984,6 +995,13 @@ modlibexec_LTLIBRARIES += \ module-console-kit.la endif +if HAVE_DBUS +if HAVE_SPOLICY +modlibexec_LTLIBRARIES += \ + module-policy.la +endif +endif + modlibexec_LTLIBRARIES += \ module-cli.la \ module-cli-protocol-tcp.la \ @@ -1358,6 +1376,11 @@ SYMDEF_FILES += \ module-esound-sink-symdef.h endif +if HAVE_SPOLICY +SYMDEF_FILES += \ + module-policy-symdef.h +endif + EXTRA_DIST += $(SYMDEF_FILES) BUILT_SOURCES += $(SYMDEF_FILES) builddirs @@ -1979,6 +2002,13 @@ module_rygel_media_server_la_LDFLAGS = $(MODULE_LDFLAGS) module_rygel_media_server_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libprotocol-http.la module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +if HAVE_SPOLICY +module_policy_la_SOURCES = modules/module-policy.c +module_policy_la_LDFLAGS = $(MODULE_LDFLAGS) +module_policy_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) $(VCONF_LIBS) libprotocol-native.la libpulsecore-@PA_MAJORMINOR@.la libpulsecommon-@PA_MAJORMINOR@.la libpulse.la +module_policy_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(VCONF_CFLAGS) +endif + ################################### # Some minor stuff # ################################### diff --git a/src/map-file b/src/map-file index 1df7b3c2..8a005fc4 100644 --- a/src/map-file +++ b/src/map-file @@ -166,6 +166,8 @@ pa_ext_stream_restore_set_subscribe_cb; pa_ext_stream_restore_subscribe; pa_ext_stream_restore_test; pa_ext_stream_restore_write; +pa_ext_policy_test; +pa_ext_policy_set_mono; pa_format_info_copy; pa_format_info_free; pa_format_info_free2; diff --git a/src/modules/module-policy.c b/src/modules/module-policy.c new file mode 100644 index 00000000..5e24fbb1 --- /dev/null +++ b/src/modules/module-policy.c @@ -0,0 +1,709 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/core.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/core-rtclock.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <stdbool.h> +#include <strings.h> + +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-error.h> + +#include <pulsecore/protocol-native.h> +#include <pulsecore/pstream-util.h> +#include <vconf.h> // for mono + +#include "module-policy-symdef.h" + +PA_MODULE_AUTHOR("Seungbae Shin"); +PA_MODULE_DESCRIPTION("Media Policy module"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "on_hotplug=<When new device becomes available, recheck streams?> "); + +static const char* const valid_modargs[] = { + "on_hotplug", + NULL +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_hook_slot *sink_input_new_hook_slot,*sink_put_hook_slot; + + pa_hook_slot *sink_input_unlink_slot,*sink_unlink_slot; + pa_hook_slot *sink_input_unlink_post_slot, *sink_unlink_post_slot; + pa_hook_slot *sink_input_move_start_slot,*sink_input_move_finish_slot; + pa_subscription *subscription; + + pa_bool_t on_hotplug:1; + int bt_off_idx; + + int is_mono; + pa_module* module_mono_bt; + pa_module* module_combined; + pa_module* module_mono_combined; + pa_native_protocol *protocol; +}; + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_MONO, +}; + +/* DEFINEs */ +#define SINK_ALSA "alsa_output.0.analog-stereo" +#define SINK_MONO_ALSA "mono_alsa" +#define SINK_MONO_BT "mono_bt" +#define SINK_COMBINED "combined" +#define SINK_MONO_COMBINED "mono_combined" +#define POLICY_AUTO "auto" +#define POLICY_PHONE "phone" +#define POLICY_ALL "all" +#define BLUEZ_API "bluez" +#define MONO_KEY "db/setting/accessibility/mono_audio" + +/* check if this sink is bluez */ +static pa_bool_t policy_is_bluez (pa_sink* sink) +{ + const char* api_name = NULL; + + if (sink == NULL) { + pa_log_warn ("input param sink is null"); + return FALSE; + } + + api_name = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_API); + if (api_name) { + if (pa_streq (api_name, BLUEZ_API)) { +#ifdef DEBUG_DETAIL + pa_log_debug("[POLICY][%s] [%s] exists and it is [%s]...TRUE !!", __func__, PA_PROP_DEVICE_API, api_name); +#endif + return TRUE; + } else { +#ifdef DEBUG_DETAIL + pa_log_debug("[POLICY][%s] [%s] exists, but not bluez...FALSE !!", __func__, PA_PROP_DEVICE_API); +#endif + } + } else { +#ifdef DEBUG_DETAIL + pa_log_debug("[POLICY][%s] No [%s] exists...FALSE!!", __func__, PA_PROP_DEVICE_API); +#endif + } + + return FALSE; +} + +/* Get sink by name */ +static pa_sink* policy_get_sink_by_name (pa_core *c, const char* sink_name) +{ + pa_sink *s = NULL; + uint32_t idx; + + if (c == NULL || sink_name == NULL) { + pa_log_warn ("input param is null"); + return NULL; + } + + PA_IDXSET_FOREACH(s, c->sinks, idx) { + if (pa_streq (s->name, sink_name)) { + pa_log_debug ("[POLICY][%s] return [%p] for [%s]\n", __func__, s, sink_name); + return s; + } + } + return NULL; +} + +/* Get bt sink if available */ +static pa_sink* policy_get_bt_sink (pa_core *c) +{ + pa_sink *s = NULL; + uint32_t idx; + + if (c == NULL) { + pa_log_warn ("input param is null"); + return NULL; + } + + PA_IDXSET_FOREACH(s, c->sinks, idx) { + if (policy_is_bluez (s)) { + pa_log_debug ("[POLICY][%s] return [%p] for [%s]\n", __func__, s, s->name); + return s; + } + } + return NULL; +} + +/* Select sink for given condition */ +static pa_sink* policy_select_proper_sink (pa_core *c, const char* policy, int is_mono) +{ + pa_sink* sink = NULL; + pa_sink* bt_sink = NULL; + pa_sink* def = NULL; + + if (c == NULL || policy == NULL) { + pa_log_warn ("input param is null"); + return NULL; + } + + pa_assert (c); + + bt_sink = policy_get_bt_sink(c); + def = pa_namereg_get_default_sink(c); + pa_log_debug ("[POLICY][%s] policy[%s], is_mono[%d], current default[%s], bt sink[%s]\n", + __func__, policy, is_mono, def->name, (bt_sink)? bt_sink->name:"null"); + + /* Select sink to */ + if (pa_streq(policy, POLICY_ALL)) { + /* all */ + if (bt_sink) { + sink = policy_get_sink_by_name(c, (is_mono)? SINK_MONO_COMBINED : SINK_COMBINED); + } else { + sink = policy_get_sink_by_name (c, (is_mono)? SINK_MONO_ALSA : SINK_ALSA); + } + + } else if (pa_streq(policy, POLICY_PHONE)) { + /* phone */ + sink = policy_get_sink_by_name (c, (is_mono)? SINK_MONO_ALSA : SINK_ALSA); + } else { + /* auto */ + if (pa_streq (def->name, SINK_ALSA)) { + sink = (is_mono)? policy_get_sink_by_name (c, SINK_MONO_ALSA) : def; + } else { + sink = (is_mono)? policy_get_sink_by_name (c, SINK_MONO_BT) : def; + } + } + + pa_log_debug ("[POLICY][%s] selected sink : [%s]\n", __func__, (sink)? sink->name : "null"); + return sink; +} + +static pa_bool_t policy_is_filter (pa_sink_input* si) +{ + const char* role = NULL; + + if (si == NULL) { + pa_log_warn ("input param sink-input is null"); + return FALSE; + } + + if ((role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE))) { +#ifdef DEBUG_DETAIL + pa_log_debug("[POLICY][%s] Role of sink input [%d] = %s", __func__, si->index, role); +#endif + if (pa_streq(role, "filter")) { +#ifdef DEBUG_DETAIL + pa_log_debug("[POLICY] no need to change of sink for %s", role); +#endif + return TRUE; + } + } + + return FALSE; +} + + + +#define EXT_VERSION 1 + +static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) { + struct userdata *u = NULL; + uint32_t command; + pa_tagstruct *reply = NULL; + + pa_sink_input *si = NULL; + uint32_t idx; + pa_sink* sink_to_move = NULL; + + pa_assert(p); + pa_assert(m); + pa_assert(c); + pa_assert(t); + + u = m->userdata; + + if (pa_tagstruct_getu32(t, &command) < 0) + goto fail; + + reply = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(reply, PA_COMMAND_REPLY); + pa_tagstruct_putu32(reply, tag); + + switch (command) { + case SUBCOMMAND_TEST: { + if (!pa_tagstruct_eof(t)) + goto fail; + + pa_tagstruct_putu32(reply, EXT_VERSION); + break; + } + + case SUBCOMMAND_MONO: { + + pa_bool_t enable; + + if (pa_tagstruct_get_boolean(t, &enable) < 0) + goto fail; + + pa_log_debug ("[POLICY][%s] new mono value = %d\n", __func__, enable); + if (enable == u->is_mono) { + pa_log_debug ("[POLICY][%s] No changes in mono value = %d", __func__, u->is_mono); + break; + } + + u->is_mono = enable; + + /* Move current sink-input to proper mono sink */ + PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) { + const char *policy = NULL; + + /* Skip this if it is already in the process of being moved + * anyway */ + if (!si->sink) + continue; + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + continue; + + /* Get role (if role is filter, skip it) */ + if (policy_is_filter(si)) + continue; + + /* Check policy, if no policy exists, treat as AUTO */ + if (!(policy = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_POLICY))) { + pa_log_debug("[POLICY] set policy of sink-input[%d] from [%s] to [auto]", si->index, "null"); + policy = POLICY_AUTO; + } + pa_log_debug("[POLICY] Policy of sink input [%d] = %s", si->index, policy); + + /* Select sink to move and move to it */ + sink_to_move = policy_select_proper_sink (u->core, policy, u->is_mono); + if (sink_to_move) { + pa_log_debug("[POLICY][%s] Moving sink-input[%d] from [%s] to [%s]", __func__, si->index, si->sink->name, sink_to_move->name); + pa_sink_input_move_to(si, sink_to_move, FALSE); + } else { + pa_log_debug("[POLICY][%s] Can't move sink-input....", __func__); + } + } + break; + } + + default: + goto fail; + } + + pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply); + return 0; + + fail: + + if (reply) + pa_tagstruct_free(reply); + + return -1; +} + +/* Called when new sink-input is creating */ +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) +{ + const char *policy = NULL; + + pa_assert(c); + pa_assert(new_data); + pa_assert(u); + + if (!new_data->proplist) { + pa_log_debug("[POLICY] New stream lacks property data."); + return PA_HOOK_OK; + } + + /* If sink-input has already sink, skip */ + if (new_data->sink) { + /* sink-input with filter role will be also here because sink is already set */ +#ifdef DEBUG_DETAIL + pa_log_debug("[POLICY] Not setting device for stream [%s], because already set.", + pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); +#endif + return PA_HOOK_OK; + } + + /* If no policy exists, skip */ + if (!(policy = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_POLICY))) { + pa_log_debug("[POLICY][%s] Not setting device for stream [%s], because it lacks policy.", + __func__, pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME))); + return PA_HOOK_OK; + } + pa_log_debug("[POLICY][%s] Policy for stream [%s] = [%s]", + __func__, pa_strnull(pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_NAME)), policy); + + /* Set proper sink to sink-input */ + new_data->save_sink = FALSE; + new_data->sink = policy_select_proper_sink (c, policy, u->is_mono); + pa_log_debug("[POLICY][%s] set sink of sink-input to [%s]", __func__, (new_data->sink)? new_data->sink->name : "null"); + + return PA_HOOK_OK; +} + +/* Called when new sink is added while sink-input is existing */ +static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, struct userdata *u) +{ + pa_sink_input *si; + pa_sink *sink_to_move; + uint32_t idx; + char *args = NULL; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + pa_assert(u->on_hotplug); + + /* If connected sink is BLUETOOTH, set as default */ + /* we are checking with device.api property */ + if (policy_is_bluez(sink)) { + pa_log_debug("[POLICY][%s] set default sink to sink[%s][%d]", __func__, sink->name, sink->index); + pa_namereg_set_default_sink (c,sink); + } else { + pa_log_debug("[POLICY][%s] this sink [%s][%d] is not a bluez....return", __func__, sink->name, sink->index); + return PA_HOOK_OK; + } + + /* Load mono_bt sink */ + args = pa_sprintf_malloc("sink_name=%s master=%s channels=1", SINK_MONO_BT, sink->name); + u->module_mono_bt = pa_module_load(u->module->core, "module-remap-sink", args); + pa_xfree(args); + + /* load combine sink */ + args = pa_sprintf_malloc("sink_name=%s slaves=\"%s,%s\"", SINK_COMBINED, sink->name, SINK_ALSA); + u->module_combined = pa_module_load(u->module->core, "module-combine", args); + pa_xfree(args); + + /* load mono_combine sink */ + args = pa_sprintf_malloc("sink_name=%s master=%s channels=1", SINK_MONO_COMBINED, SINK_COMBINED); + u->module_mono_combined = pa_module_load(u->module->core, "module-remap-sink", args); + pa_xfree(args); + + + /* Iterate each sink inputs to decide whether we should move to new sink */ + PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { + const char *policy = NULL; + + if (si->sink == sink) + continue; + + /* Skip this if it is already in the process of being moved + * anyway */ + if (!si->sink) + continue; + + /* It might happen that a stream and a sink are set up at the + same time, in which case we want to make sure we don't + interfere with that */ + if (!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(si))) + continue; + + /* Get role (if role is filter, skip it) */ + if (policy_is_filter(si)) + continue; + + /* Check policy */ + if (!(policy = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_POLICY))) { + /* No policy exists, this means auto */ + pa_log_debug("[POLICY][%s] set policy of sink-input[%d] from [%s] to [auto]", __func__, si->index, "null"); + policy = POLICY_AUTO; + } + + sink_to_move = policy_select_proper_sink (c, policy, u->is_mono); + if (sink_to_move) { + pa_log_debug("[POLICY][%s] Moving sink-input[%d] from [%s] to [%s]", __func__, si->index, si->sink->name, sink_to_move->name); + pa_sink_input_move_to(si, sink_to_move, FALSE); + } else { + pa_log_debug("[POLICY][%s] Can't move sink-input....",__func__); + } + } + + return PA_HOOK_OK; +} + +static void subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) +{ + struct userdata *u = userdata; + pa_sink *def; + pa_sink_input *si; + uint32_t idx2; + pa_sink *sink_to_move = NULL; + pa_assert(u); + + pa_log_debug("[POLICY][%s] subscribe_cb() t=[0x%x], idx=[%d]", __func__, t, idx); + + /* We only handle server changes */ + if (t == (PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE)) { + + def = pa_namereg_get_default_sink(c); + pa_log_debug("[POLICY][%s] trying to move stream to current default sink = [%s]", __func__, def->name); + + /* Iterate each sink inputs to decide whether we should move to new DEFAULT sink */ + PA_IDXSET_FOREACH(si, c->sink_inputs, idx2) { + const char *policy = NULL; + + if (!si->sink) + continue; + + /* Get role (if role is filter, skip it) */ + if (policy_is_filter(si)) + continue; + + /* Get policy */ + if (!(policy = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_POLICY))) { + /* No policy exists, this means auto */ + pa_log_debug("[POLICY][%s] set policy of sink-input[%d] from [%s] to [auto]", __func__, si->index, "null"); + policy = POLICY_AUTO; + } + + sink_to_move = policy_select_proper_sink (c, policy, u->is_mono); + if (sink_to_move) { + /* Move sink-input to new DEFAULT sink */ + pa_log_debug("[POLICY][%s] Moving sink-input[%d] from [%s] to [%s]", __func__, si->index, si->sink->name, sink_to_move->name); + pa_sink_input_move_to(si, sink_to_move, FALSE); + } + } + } +} + +static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + struct userdata *u = userdata; + uint32_t idx; + pa_sink *sink_to_move; + pa_sink_input *si; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + /* if unloading sink is not bt, just return */ + if (!policy_is_bluez (sink)) { + pa_log_debug("[POLICY][%s] sink[%s][%d] unlinked but not a bluez....return\n", __func__, sink->name, sink->index); + return PA_HOOK_OK; + } + + pa_log_debug ("[POLICY][%s] SINK unlinked ================================ sink [%s][%d], bt_off_idx was [%d]", + __func__, sink->name, sink->index,u->bt_off_idx); + + u->bt_off_idx = sink->index; + pa_log_debug ("[POLICY][%s] bt_off_idx is set to [%d]", __func__, u->bt_off_idx); + + /* BT sink is unloading, move sink-input to proper sink */ + PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { + + if (!si->sink) + continue; + + /* Get role (if role is filter, skip it) */ + if (policy_is_filter(si)) + continue; + + /* Find who were using bt sink or bt related sink and move them to proper sink (alsa/mono_alsa) */ + if (pa_streq (si->sink->name, SINK_MONO_BT) || + pa_streq (si->sink->name, SINK_MONO_COMBINED) || + pa_streq (si->sink->name, SINK_COMBINED) || + policy_is_bluez (si->sink)) { + + /* Move sink-input to proper sink : only alsa related sink is available now */ + sink_to_move = policy_get_sink_by_name (c, (u->is_mono)? SINK_MONO_ALSA : SINK_ALSA); + pa_log_debug("[POLICY][%s] Moving sink-input[%d] from [%s] to [%s]", __func__, si->index, si->sink->name, sink_to_move->name); + pa_sink_input_move_to(si, sink_to_move, FALSE); + } + } + + pa_log_debug ("[POLICY][%s] unload sink in dependencies", __func__); + + /* Unload mono_combine sink */ + if (u->module_mono_combined) { + pa_module_unload(u->module->core, u->module_mono_combined, TRUE); + u->module_mono_combined = NULL; + } + + /* Unload combine sink */ + if (u->module_combined) { + pa_module_unload(u->module->core, u->module_combined, TRUE); + u->module_combined = NULL; + } + + /* Unload mono_bt sink */ + if (u->module_mono_bt) { + pa_module_unload(u->module->core, u->module_mono_bt, TRUE); + u->module_mono_bt = NULL; + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_post_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + struct userdata *u = userdata; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + + pa_log_debug("[POLICY][%s] SINK unlinked POST ================================ sink [%s][%d]", __func__, sink->name, sink->index); + + /* There's no point in doing anything if the core is shut down anyway */ + if (c->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + /* if unloading sink is not bt, just return */ + if (!policy_is_bluez (sink)) { + pa_log_debug("[POLICY][%s] not a bluez....return\n", __func__); + return PA_HOOK_OK; + } + + u->bt_off_idx = -1; + pa_log_debug ("[POLICY][%s] bt_off_idx is cleared to [%d]", __func__, u->bt_off_idx); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_move_start_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + /* There's no point in doing anything if the core is shut down anyway */ + if (core->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + pa_log_debug ("[POLICY][%s] sink_input_move_start_cb -------------------------------------- sink-input [%d] was sink [%s][%d] : Trying to mute!!!", + __func__, i->index, i->sink->name, i->sink->index); + pa_sink_input_set_mute(i, TRUE, FALSE); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) { + pa_core_assert_ref(core); + pa_sink_input_assert_ref(i); + + /* There's no point in doing anything if the core is shut down anyway */ + if (core->state == PA_CORE_SHUTDOWN) + return PA_HOOK_OK; + + pa_log_debug("[POLICY][%s] sink_input_move_finish_cb -------------------------------------- sink-input [%d], sink [%s][%d], bt_off_idx [%d] : %s", + __func__, i->index, i->sink->name, i->sink->index, u->bt_off_idx, + (u->bt_off_idx == -1)? "Trying to un-mute!!!!" : "skip un-mute..."); + + /* If sink input move is caused by bt sink unlink, then skip un-mute operation */ + if (u->bt_off_idx == -1) { + pa_sink_input_set_mute(i, FALSE, FALSE); + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module *m) +{ + pa_modargs *ma = NULL; + struct userdata *u; + pa_bool_t on_hotplug = TRUE, on_rescue = TRUE; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "on_hotplug", &on_hotplug) < 0 || + pa_modargs_get_value_boolean(ma, "on_rescue", &on_rescue) < 0) { + pa_log("on_hotplug= and on_rescue= expect boolean arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->on_hotplug = on_hotplug; + + + /* A little bit later than module-stream-restore */ + u->sink_input_new_hook_slot = + pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+10, (pa_hook_cb_t) sink_input_new_hook_callback, u); + + if (on_hotplug) { + /* A little bit later than module-stream-restore */ + u->sink_put_hook_slot = + pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+10, (pa_hook_cb_t) sink_put_hook_callback, u); + } + + /* sink unlink comes before sink-input unlink */ + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_hook_callback, u); + u->sink_unlink_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_post_hook_callback, u); + + u->sink_input_move_start_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_start_cb, u); + u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u); + + u->subscription = pa_subscription_new(u->core, PA_SUBSCRIPTION_MASK_SERVER, subscribe_cb, u); + + + u->bt_off_idx = -1; /* initial bt off sink index */ + + u->module_mono_bt = NULL; + u->module_combined = NULL; + u->module_mono_combined = NULL; + + u->protocol = pa_native_protocol_get(m->core); + pa_native_protocol_install_ext(u->protocol, m, extension_cb); + + /* Get mono key value for init */ + vconf_get_bool(MONO_KEY, &u->is_mono); + + pa_log_info("policy module is loaded\n"); + + return 0; + +fail: + pa__done(m); + + return -1; +} + +void pa__done(pa_module *m) +{ + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input_new_hook_slot) + pa_hook_slot_free(u->sink_input_new_hook_slot); + if (u->sink_put_hook_slot) + pa_hook_slot_free(u->sink_put_hook_slot); + if (u->subscription) + pa_subscription_free(u->subscription); + if (u->protocol) { + pa_native_protocol_remove_ext(u->protocol, m); + pa_native_protocol_unref(u->protocol); + } + + pa_xfree(u); + + + pa_log_info("policy module is unloaded\n"); +} diff --git a/src/pulse/ext-policy.c b/src/pulse/ext-policy.c new file mode 100644 index 00000000..b65fc921 --- /dev/null +++ b/src/pulse/ext-policy.c @@ -0,0 +1,131 @@ +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/context.h> +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" +#include "operation.h" +#include "fork-detect.h" + +#include "ext-policy.h" + +enum { + SUBCOMMAND_TEST, + SUBCOMMAND_MONO, +}; + +static void ext_policy_test_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t version = PA_INVALID_INDEX; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t, FALSE) < 0) + goto finish; + + } else if (pa_tagstruct_getu32(t, &version) < 0 || + !pa_tagstruct_eof(t)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_ext_policy_test_cb_t cb = (pa_ext_policy_test_cb_t) o->callback; + cb(o->context, version, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation *pa_ext_policy_test( + pa_context *c, + pa_ext_device_manager_test_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-policy"); + pa_tagstruct_putu32(t, SUBCOMMAND_TEST); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, ext_policy_test_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation *pa_ext_policy_set_mono ( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata) { + + uint32_t tag; + pa_operation *o = NULL; + pa_tagstruct *t = NULL; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 14, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXTENSION, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, "module-policy"); + pa_tagstruct_putu32(t, SUBCOMMAND_MONO); + pa_tagstruct_put_boolean(t, !!enable); + + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} diff --git a/src/pulse/ext-policy.h b/src/pulse/ext-policy.h new file mode 100644 index 00000000..16dda07e --- /dev/null +++ b/src/pulse/ext-policy.h @@ -0,0 +1,54 @@ +#ifndef foopulseextpolicyhfoo +#define foopulseextpolicyhfoo + +/*** + This file is part of PulseAudio. + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulse/context.h> +#include <pulse/version.h> + +/** \file + * + * Routines for controlling module-policy + */ + +PA_C_DECL_BEGIN + +/** Callback prototype for pa_ext_policy_test(). \since 0.9.21 */ +typedef void (*pa_ext_policy_test_cb_t)( + pa_context *c, + uint32_t version, + void *userdata); + +/** Test if this extension module is available in the server. \since 0.9.21 */ +pa_operation *pa_ext_policy_test( + pa_context *c, + pa_ext_policy_test_cb_t cb, + void *userdata); + +/** Enable the mono mode. \since 0.9.21 */ +pa_operation *pa_ext_policy_set_mono ( + pa_context *c, + int enable, + pa_context_success_cb_t cb, + void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h index 46a9a61a..54e93561 100644 --- a/src/pulse/proplist.h +++ b/src/pulse/proplist.h @@ -65,6 +65,9 @@ PA_C_DECL_BEGIN /** For streams: logic role of this media. One of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y", "test" */ #define PA_PROP_MEDIA_ROLE "media.role" +/** For streams: logic role of this media. One of the strings "auto", "phone" */ +#define PA_PROP_MEDIA_POLICY "media.policy" + /** For streams: the name of a filter that is desired, e.g. "echo-cancel" or "equalizer-sink". PulseAudio may choose to not apply the filter if it does not make sense (for example, applying echo-cancellation on a Bluetooth headset probably does not make sense. \since 1.0 */ #define PA_PROP_FILTER_WANT "filter.want" |