/*** This file is part of PulseAudio. Copyright 2008 Lennart Poettering Copyright 2009 Tanu Kaskinen Copyright 2013 XXX Author: Arun Raghavan 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 #endif #include #include #include #include #include #include #include #include #include #include #include "module-b2g-policy-symdef.h" #define DEFAULT_ROLE_PROPERTY PA_PROP_MEDIA_ROLE #define VOLUME_FACTOR_KEY "b2g-role-volume" PA_MODULE_AUTHOR("Arun Raghavan"); PA_MODULE_DESCRIPTION("Policy module for Mozilla Firefox OS"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(true); PA_MODULE_USAGE( "role_property= "); static const char* const valid_modargs[] = { "role_property", NULL }; struct userdata { pa_core *core; char *role_property; pa_hashmap *role_volumes; pa_hook_slot *sink_input_fixate_slot; }; enum { SUBCOMMAND_TEST, SUBCOMMAND_GET_ROLE_VOLUME, SUBCOMMAND_SET_ROLE_VOLUME, }; static void apply_volumes(pa_core *c, const char *role, const pa_cvolume *volume) { pa_sink_input *i; uint32_t idx; PA_IDXSET_FOREACH(i, c->sink_inputs, idx) { const char *si_role = pa_proplist_gets(i->proplist, DEFAULT_ROLE_PROPERTY); if (pa_safe_streq(role, si_role)) { /* Remove previous setting, if any */ pa_sink_input_remove_volume_factor(i, VOLUME_FACTOR_KEY); /* Now add the new volume factor in */ pa_sink_input_add_volume_factor(i, VOLUME_FACTOR_KEY, volume); } } } #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; uint32_t command; pa_tagstruct *reply = 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)) { pa_log_warn("Bad arguments to subcommand test"); goto fail; } pa_tagstruct_putu32(reply, EXT_VERSION); break; } case SUBCOMMAND_GET_ROLE_VOLUME: { const char *role; pa_cvolume *vol, invalid; if (pa_tagstruct_gets(t, &role) < 0 || !pa_tagstruct_eof(t)) { pa_log_warn("Bad arguments to subcommand get-role-volume"); goto fail; } vol = pa_hashmap_get(u->role_volumes, role); if (!vol) { /* We could return 1.0 here, but returning "invalid" lets the * client know that there isn't an explicit setting. */ pa_cvolume_init(&invalid); vol = &invalid; } pa_tagstruct_put_cvolume(reply, vol); break; } case SUBCOMMAND_SET_ROLE_VOLUME: { const char *role; pa_cvolume vol, *entry; if (pa_tagstruct_gets(t, &role) < 0 || pa_tagstruct_get_cvolume(t, &vol) < 0 || !pa_cvolume_valid(&vol) || !pa_tagstruct_eof(t)) { pa_log_warn("Bad arguments to subcommand set-role-volume"); goto fail; } /* Valid volume, add this entry */ entry = pa_hashmap_get(u->role_volumes, role); if (entry) { pa_log_debug("Updating role volume for '%s'", role); *entry = vol; } else { pa_log_debug("Storing role volume for '%s'", role); entry = pa_xnewdup(pa_cvolume, &vol, 1); pa_hashmap_put(u->role_volumes, pa_xstrdup(role), entry); } apply_volumes(u->core, role, &vol); break; } } pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply); return 0; fail: if (reply) pa_tagstruct_free(reply); return -1; } static pa_hook_result_t sink_input_fixate_cb(pa_core *core, pa_sink_input_new_data *new_data, struct userdata *u) { const pa_cvolume *vol; const char *role; pa_core_assert_ref(core); pa_assert(u); pa_assert(new_data); role = pa_proplist_gets(new_data->proplist, u->role_property); if (role) { vol = pa_hashmap_get(u->role_volumes, role); if (vol) { /* We have a stored volume factor, apply */ pa_log_debug("Setting volume factor setting for new '%s' stream", role); pa_sink_input_new_data_add_volume_factor(new_data, VOLUME_FACTOR_KEY, vol); } } return PA_HOOK_OK; } int pa__init(pa_module *m) { pa_modargs *ma = NULL; pa_native_protocol *p; struct userdata *u; pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); goto fail; } m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; u->role_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, pa_xfree); u->role_property = pa_xstrdup(pa_modargs_get_value(ma, "role_property", DEFAULT_ROLE_PROPERTY)); p = pa_native_protocol_get(u->core); pa_native_protocol_install_ext(p, m, extension_cb); u->sink_input_fixate_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_fixate_cb, u); pa_modargs_free(ma); return 0; fail: pa__done(m); if (ma) pa_modargs_free(ma); return -1; } void pa__done(pa_module *m) { struct userdata* u; pa_assert(m); if (!(u = m->userdata)) return; if (u->role_volumes) pa_hashmap_free(u->role_volumes); if (u->role_property) pa_xfree(u->role_property); if (u->sink_input_fixate_slot) pa_hook_slot_free(u->sink_input_fixate_slot); pa_xfree(u); }