From: "Felipe F. Tonello" <eu@xxxxxxxxxxxxxxxxx> This module is used to change a card profile when a voice call is received from oFono. It also provides a functionality to avoid suspension of sink/source that module-suspend-on-idle does. This is usefull since most of those pcms are dummy just to change the codec route to the modem in the kernel driver. If you use this functinoality, please load this module before loading module-suspend-on-idle. Signed-off-by: Felipe F. Tonello <eu at felipetonello.com> --- src/Makefile.am | 12 +- src/modules/module-ofono-switch-on-voicecall.c | 540 +++++++++++++++++++++++++ 2 files changed, 550 insertions(+), 2 deletions(-) create mode 100644 src/modules/module-ofono-switch-on-voicecall.c diff --git a/src/Makefile.am b/src/Makefile.am index 99d76ce..a3b1473 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1322,7 +1322,8 @@ endif if HAVE_DBUS modlibexec_LTLIBRARIES += \ module-rygel-media-server.la \ - module-dbus-protocol.la + module-dbus-protocol.la \ + module-ofono-switch-on-voicecall.la endif if HAVE_BLUEZ @@ -1453,7 +1454,8 @@ SYMDEF_FILES = \ module-switch-on-connect-symdef.h \ module-switch-on-port-available-symdef.h \ module-filter-apply-symdef.h \ - module-filter-heuristics-symdef.h + module-filter-heuristics-symdef.h \ + module-ofono-switch-on-voicecall-symdef.h if HAVE_ESOUND SYMDEF_FILES += \ @@ -2099,6 +2101,12 @@ 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) +# oFono VoiceCall switcher +module_ofono_switch_on_voicecall_la_SOURCES = modules/module-ofono-switch-on-voicecall.c +module_ofono_switch_on_voicecall_la_LDFLAGS = $(MODULE_LDFLAGS) +module_ofono_switch_on_voicecall_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) +module_ofono_switch_on_voicecall_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) + ################################### # Some minor stuff # ################################### diff --git a/src/modules/module-ofono-switch-on-voicecall.c b/src/modules/module-ofono-switch-on-voicecall.c new file mode 100644 index 0000000..627371e --- /dev/null +++ b/src/modules/module-ofono-switch-on-voicecall.c @@ -0,0 +1,540 @@ +/*** + This file is part of PulseAudio. + + Copyright 2013 Felipe F. Tonello <eu at felipetonello.com> + + 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 <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> +#include <pulsecore/dbus-shared.h> +#include <pulsecore/strlist.h> +#include <pulsecore/hashmap.h> + +#include "module-ofono-switch-on-voicecall-symdef.h" + +PA_MODULE_AUTHOR("Felipe F. Tonello"); +PA_MODULE_DESCRIPTION("Card profile switcher while a call detected by oFono"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(false); +PA_MODULE_USAGE("card=<card to switch profile> profile=<profile to set during a call> sink=<sink to" + " disable suspend> source=<source to disable suspend>"); + +static const char* const valid_modargs[] = { + "card", + "profile", + "sink", + "source", + NULL, +}; + +struct userdata { + char *sink_name; + char *source_name; + pa_card *card; + pa_card_profile *profile; + pa_card_profile *old_profile; + pa_hook_slot *sink_new_slot; + pa_hook_slot *source_new_slot; + pa_dbus_connection *bus; + bool filter_added; + pa_hashmap *modems_hash; + char *voicecall_path; +}; + +static void modems_hash_free_value(void *p) +{ + pa_strlist_free((pa_strlist *)p); +} + +static const char *check_variant_property(DBusMessageIter *i) +{ + const char *key; + + pa_assert(i); + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { + pa_log("Property name not a string."); + return NULL; + } + + dbus_message_iter_get_basic(i, &key); + + if (!dbus_message_iter_next(i)) { + pa_log("Property value missing"); + return NULL; + } + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { + pa_log("Property value not a variant."); + return NULL; + } + + return key; +} + +static inline bool update_profile(const struct userdata *u, pa_card_profile *p) +{ + pa_assert(u); + pa_assert(p); + + /* only change profile if the new profile is different */ + if (u->card->active_profile == p) + return false; + + if (pa_card_set_profile(u->card, p, false) < 0) { + pa_log_error("Couldn't set profile '%s' for card '%s'", p->name, u->card->name); + return false; + } + + pa_log_error("Set profile '%s' for card '%s'", p->name, u->card->name); + + return true; +} + +static bool switch_profile(const struct userdata *u) +{ + return update_profile(u, u->profile); +} + +static bool switch_back_profile(const struct userdata *u) +{ + return update_profile(u, u->old_profile); +} + +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) +{ + struct userdata *u = userdata; + DBusError error; + + pa_assert(bus); + pa_assert(message); + pa_assert(u); + + dbus_error_init(&error); + + pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", + dbus_message_get_interface(message), + dbus_message_get_path(message), + dbus_message_get_member(message)); + + if (dbus_message_is_signal(message, "org.ofono.Manager", "ModemAdded")) { + /* create new listener for the modem */ + + const char *path; + pa_strlist *filter_list = NULL; + char *call_added, *call_removed; + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log_error("Failed to parse ModemAdded message: %s: %s", error.name, error.message); + goto finish; + } + + pa_log_debug("ModemAdded(object path, dict properties): path=%s", path); + + if (pa_hashmap_get(u->modems_hash, path)) { + pa_log_info("Modem already loaded: %s", path); + goto finish; + } + + call_added = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCallManager'," + "path='%s',member='CallAdded'", path); + call_removed = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCallManager'," + "path='%s',member='CallRemoved'", path); + + if (pa_dbus_add_matches(pa_dbus_connection_get(u->bus), &error, + call_added, call_removed, NULL) < 0) { + pa_log_error("Unable to subscribe to oFono VoiceCallManager signals: %s: %s", + error.name, error.message); + pa_xfree(call_added); + pa_xfree(call_removed); + goto finish; + } + + filter_list = pa_strlist_prepend(filter_list, call_added); + filter_list = pa_strlist_prepend(filter_list, call_removed); + pa_hashmap_put(u->modems_hash, (char *)path, filter_list); + + } else if (dbus_message_is_signal(message, "org.ofono.Manager", "ModemRemoved")) { + /* remove that listener */ + const char *path; + pa_strlist *filter_list = NULL, *l; + char *s; + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log_error("Failed to parse ModemAdded message: %s: %s", error.name, error.message); + goto finish; + } + + pa_log("ModemRemoved(object path): path=%s", path); + + if (!(filter_list = pa_hashmap_remove(u->modems_hash, path))) { + pa_log_info("Modem '%s' not loaded", path); + goto finish; + } + + l = filter_list; + while (l) { + l = pa_strlist_pop(l, &s); + pa_dbus_remove_matches(pa_dbus_connection_get(u->bus), s, NULL); + pa_xfree(s); + } + + } else if (dbus_message_is_signal(message, "org.ofono.VoiceCallManager", "CallAdded")) { + /* check call properties if "state" is "dialing" or "incoming" */ + DBusMessageIter arg_i; + + if (u->voicecall_path) { + pa_log_error("VoiceCall was already loaded: '%s'", u->voicecall_path); + goto finish; + } + + if (!dbus_message_iter_init(message, &arg_i)) { + pa_log_error("Failed to parse ModemAdded: %s: %s", error.name, error.message); + goto finish; + } + + while (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_INVALID) { + const char *path; + + switch (dbus_message_iter_get_arg_type(&arg_i)) { + case DBUS_TYPE_OBJECT_PATH: { + dbus_message_iter_get_basic(&arg_i, &path); + break; + } + + case DBUS_TYPE_ARRAY: { + DBusMessageIter array_i; + dbus_message_iter_recurse(&arg_i, &array_i); + while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) { + DBusMessageIter dict_i; + + dbus_message_iter_recurse(&array_i, &dict_i); + while (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_INVALID) { + const char *key; + + key = check_variant_property(&dict_i); + if (key) { + DBusMessageIter variant_i; + + dbus_message_iter_recurse(&dict_i, &variant_i); + + if (dbus_message_iter_get_arg_type(&variant_i) == DBUS_TYPE_STRING && + pa_streq(key, "State")) { + const char *state; + dbus_message_iter_get_basic(&variant_i, &state); + + if (pa_streq(state, "dialing") || pa_streq(state, "incoming")) { + char *filter; + + pa_log_debug("CallAdded(object path, dict properties): path=%s " + "state=%s", path, state); + + u->voicecall_path = pa_xstrdup(path); + filter = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCall'," + "path='%s',member='PropertyChanged'", + u->voicecall_path); + + if (pa_dbus_add_matches(pa_dbus_connection_get(u->bus), &error, + filter, NULL) < 0) { + pa_log_error("Unable to subscribe to oFono VoiceCallManager signals: %s: %s", + error.name, error.message); + pa_xfree(filter); + goto finish; + } + pa_xfree(filter); + } + } + } + dbus_message_iter_next(&dict_i); + } + + dbus_message_iter_next(&array_i); + } + break; + } + } + dbus_message_iter_next(&arg_i); + } + } else if (dbus_message_is_signal(message, "org.ofono.VoiceCallManager", "CallRemoved")) { + const char *path; + char *filter; + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { + pa_log_error("Failed to parse ModemAdded message: %s: %s", error.name, error.message); + goto finish; + } + + pa_log_debug("CallRemoved(object path, dict properties): path=%s", path); + + if (!(u->voicecall_path && pa_streq(path, u->voicecall_path))) { + pa_log_error("VoiceCall '%s' not loaded", path); + goto finish; + } + + switch_back_profile(u); + + filter = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCall'," + "path='%s',member='PropertyChanged'", + u->voicecall_path); + + pa_dbus_remove_matches(pa_dbus_connection_get(u->bus), filter, NULL); + + pa_xfree(filter); + pa_xfree(u->voicecall_path); + u->voicecall_path = NULL; + + } else if (dbus_message_is_signal(message, "org.ofono.VoiceCall", "PropertyChanged")) { + const char *path, *key; + DBusMessageIter arg_i, variant_i; + + path = dbus_message_get_path(message); + + if (!(u->voicecall_path && pa_streq(path, u->voicecall_path))) { + pa_log_error("VoiceCall '%s' not loaded", path); + goto finish; + } + + if (!dbus_message_iter_init(message, &arg_i)) { + pa_log_error("Failed to parse PropertyChange: %s: %s", error.name, error.message); + goto finish; + } + + key = check_variant_property(&arg_i); + if (!key) + goto finish; + + dbus_message_iter_recurse(&arg_i, &variant_i); + + if (dbus_message_iter_get_arg_type(&variant_i) == DBUS_TYPE_STRING && + pa_streq(key, "State")) { + const char *state; + + dbus_message_iter_get_basic(&variant_i, &state); + if (pa_streq(state, "alerting") || pa_streq(state, "active")) { + + pa_log_debug("CallAdded(object path, dict properties): path=%s " + "state=%s", path, state); + + switch_profile(u); + } + } + } + +finish: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) +{ + if (pa_streq(new_data->name, u->source_name)) { + /* We found our source */ + pa_log_info("Setting '%s' source's property module-suspend-on-idle.timeout to -1", u->source_name); + /* Set module-suspend-on-idle.timeout to -1 which means that module-suspend-on-idle will + not suspend this source */ + pa_proplist_sets(new_data->proplist, "module-suspend-on-idle.timeout", "-1"); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) +{ + if (pa_streq(new_data->name, u->sink_name)) { + /* We found our sink */ + pa_log_info("Setting '%s' sink's property module-suspend-on-idle.timeout to -1", u->sink_name); + /* Set module-suspend-on-idle.timeout to -1 which means that module-suspend-on-idle will + not suspend this sink */ + pa_proplist_sets(new_data->proplist, "module-suspend-on-idle.timeout", "-1"); + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module *m) +{ + pa_modargs *ma = NULL; + struct userdata *u = NULL; + char *card_name = NULL; + char *profile_name = NULL; + pa_card *card = NULL; + pa_card_profile *profile = NULL; + void *state; + DBusError error; + + pa_assert(m); + + dbus_error_init(&error); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log_error("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->modems_hash = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, + NULL, modems_hash_free_value); + + if (!(card_name = pa_xstrdup(pa_modargs_get_value(ma, "card", NULL)))) { + pa_log_error("Missing card name"); + goto fail; + } + + if (!(profile_name = pa_xstrdup(pa_modargs_get_value(ma, "profile", NULL)))) { + pa_log_error("Missing profile name"); + goto fail; + } + + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL)); + + if (!(card = pa_namereg_get(m->core, card_name, PA_NAMEREG_CARD))) { + pa_log_error("No such card (%s)", card_name); + goto fail; + } + + u->card = card; + + PA_HASHMAP_FOREACH(profile, card->profiles, state) + if (profile && pa_streq(profile->name, profile_name)) { + u->profile = profile; + break; + } + + if (!profile) { + pa_log_error("No such profile (%s) associated with card (%s)", profile_name, card_name); + goto fail; + } + + u->old_profile = card->active_profile; + + if (u->sink_name) + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW], + PA_HOOK_NORMAL, (pa_hook_cb_t) sink_new_hook_callback, u); + + if (u->source_name) + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], + PA_HOOK_NORMAL, (pa_hook_cb_t) source_new_hook_callback, u); + + if (!(u->bus = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error))) { + pa_log_error("Failed to get system bus connection: %s: %s", error.name, error.message); + goto fail; + } + + if (!dbus_connection_add_filter(pa_dbus_connection_get(u->bus), filter_cb, u, NULL)) { + pa_log_error("Failed to add D-Bus filter function"); + goto fail; + } + + u->filter_added = true; + + if (pa_dbus_add_matches(pa_dbus_connection_get(u->bus), &error, + "type='signal',sender='org.ofono'," + "interface='org.ofono.Manager'," + "path='/'", + NULL) < 0) { + pa_log_error("Unable to subscribe to oFono signals: %s: %s", error.name, error.message); + goto fail; + } + + pa_modargs_free(ma); + pa_xfree(card_name); + pa_xfree(profile_name); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa_xfree(card_name); + pa_xfree(profile_name); + + /* Make sure we don't change anything in pa__done() */ + if (u) { + u->card = NULL; + u->old_profile = NULL; + } + + dbus_error_free(&error); + + pa__done(m); + return -1; +} + +void pa__done(pa_module *m) +{ + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->modems_hash) + pa_hashmap_free(u->modems_hash); + + if (u->card && u->old_profile) + pa_card_set_profile(u->card, u->old_profile, true); + + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + + if (u->source_new_slot) + pa_hook_slot_free(u->source_new_slot); + + if (u->bus) { + + if (u->voicecall_path) { + char *filter = pa_sprintf_malloc("type='signal',sender='org.ofono'," + "interface='org.ofono.VoiceCall'," + "path='%s',member='PropertyChanged'", + u->voicecall_path); + + pa_dbus_remove_matches(pa_dbus_connection_get(u->bus), filter, NULL); + pa_xfree(filter); + pa_xfree(u->voicecall_path); + } + + pa_dbus_remove_matches(pa_dbus_connection_get(u->bus), + "type='signal',sender='org.ofono'," + "interface='org.ofono.Manager'," + "path='/'", + NULL); + + if (u->filter_added) + dbus_connection_remove_filter(pa_dbus_connection_get(u->bus), filter_cb, u); + + pa_dbus_connection_unref(u->bus); + } + + pa_xfree(u->sink_name); + pa_xfree(u->source_name); + pa_xfree(u); +} -- 1.8.5.3