/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include #include #include #include "audio_thread.h" #include "cras_bt_player.h" #include "cras_dbus.h" #include "cras_dbus_control.h" #include "cras_dbus_util.h" #include "cras_hfp_ag_profile.h" #include "cras_iodev_list.h" #include "cras_main_thread_log.h" #include "cras_observer.h" #include "cras_system_state.h" #include "cras_utf8.h" #include "cras_util.h" #include "utlist.h" #define CRAS_CONTROL_INTERFACE "org.chromium.cras.Control" #define CRAS_ROOT_OBJECT_PATH "/org/chromium/cras" #define CONTROL_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "\n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ "\n" struct cras_dbus_control { DBusConnection *conn; struct cras_observer_client *observer; }; static struct cras_dbus_control dbus_control; /* helper to extract a single argument from a DBus message. */ static int get_single_arg(DBusMessage *message, int dbus_type, void *arg) { DBusError dbus_error; dbus_error_init(&dbus_error); if (!dbus_message_get_args(message, &dbus_error, dbus_type, arg, DBUS_TYPE_INVALID)) { syslog(LOG_WARNING, "Bad method received: %s", dbus_error.message); dbus_error_free(&dbus_error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } return 0; } static bool get_string_metadata(DBusMessageIter *iter, const char **dst) { if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(iter, dst); return TRUE; } static bool get_int64_metadata(DBusMessageIter *iter, dbus_int64_t *dst) { if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INT64) return FALSE; dbus_message_iter_get_basic(iter, dst); return TRUE; } static bool get_metadata(DBusMessage *message, const char **title, const char **artist, const char **album, dbus_int64_t *length) { DBusError dbus_error; DBusMessageIter iter, dict; dbus_error_init(&dbus_error); dbus_message_iter_init(message, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(&iter, &dict); while (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) { DBusMessageIter entry, var; const char *key; if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY) return FALSE; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) return FALSE; dbus_message_iter_recurse(&entry, &var); if (strcasecmp(key, "title") == 0) { if (!get_string_metadata(&var, title)) return FALSE; } else if (strcasecmp(key, "artist") == 0) { if (!get_string_metadata(&var, artist)) return FALSE; } else if (strcasecmp(key, "album") == 0) { if (!get_string_metadata(&var, album)) return FALSE; } else if (strcasecmp(key, "length") == 0) { if (!get_int64_metadata(&var, length)) return FALSE; } else syslog(LOG_WARNING, "%s not supported, ignoring", key); dbus_message_iter_next(&dict); } return TRUE; } /* Helper to send an empty reply. */ static void send_empty_reply(DBusConnection *conn, DBusMessage *message) { DBusMessage *reply; dbus_uint32_t serial = 0; reply = dbus_message_new_method_return(message); if (!reply) return; dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); } /* Helper to send an int32 reply. */ static void send_int32_reply(DBusConnection *conn, DBusMessage *message, dbus_int32_t value) { DBusMessage *reply; dbus_uint32_t serial = 0; reply = dbus_message_new_method_return(message); if (!reply) return; dbus_message_append_args(reply, DBUS_TYPE_INT32, &value, DBUS_TYPE_INVALID); dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); } /* Handlers for exported DBus method calls. */ static DBusHandlerResult handle_set_output_volume(DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_int32_t new_vol; rc = get_single_arg(message, DBUS_TYPE_INT32, &new_vol); if (rc) return rc; cras_system_set_volume(new_vol); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_output_node_volume(DBusConnection *conn, DBusMessage *message, void *arg) { dbus_int32_t new_vol; cras_node_id_t id; DBusError dbus_error; dbus_error_init(&dbus_error); if (!dbus_message_get_args(message, &dbus_error, DBUS_TYPE_UINT64, &id, DBUS_TYPE_INT32, &new_vol, DBUS_TYPE_INVALID)) { syslog(LOG_WARNING, "Bad method received: %s", dbus_error.message); dbus_error_free(&dbus_error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } cras_iodev_list_set_node_attr(id, IONODE_ATTR_VOLUME, new_vol); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_swap_left_right(DBusConnection *conn, DBusMessage *message, void *arg) { cras_node_id_t id; dbus_bool_t swap; DBusError dbus_error; dbus_error_init(&dbus_error); if (!dbus_message_get_args(message, &dbus_error, DBUS_TYPE_UINT64, &id, DBUS_TYPE_BOOLEAN, &swap, DBUS_TYPE_INVALID)) { syslog(LOG_WARNING, "Bad method received: %s", dbus_error.message); dbus_error_free(&dbus_error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } cras_iodev_list_set_node_attr(id, IONODE_ATTR_SWAP_LEFT_RIGHT, swap); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_output_mute(DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_bool_t new_mute; rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &new_mute); if (rc) return rc; cras_system_set_mute(new_mute); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_output_user_mute(DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_bool_t new_mute; rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &new_mute); if (rc) return rc; cras_system_set_user_mute(new_mute); MAINLOG(main_log, MAIN_THREAD_SET_OUTPUT_USER_MUTE, new_mute, 0, 0); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_suspend_audio(DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_bool_t suspend; rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &suspend); if (rc) return rc; cras_system_set_suspended(suspend); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_input_node_gain(DBusConnection *conn, DBusMessage *message, void *arg) { dbus_int32_t new_gain; cras_node_id_t id; DBusError dbus_error; dbus_error_init(&dbus_error); if (!dbus_message_get_args(message, &dbus_error, DBUS_TYPE_UINT64, &id, DBUS_TYPE_INT32, &new_gain, DBUS_TYPE_INVALID)) { syslog(LOG_WARNING, "Bad method received: %s", dbus_error.message); dbus_error_free(&dbus_error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } cras_iodev_list_set_node_attr(id, IONODE_ATTR_CAPTURE_GAIN, new_gain); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_input_mute(DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_bool_t new_mute; rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &new_mute); if (rc) return rc; cras_system_set_capture_mute(new_mute); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_get_volume_state(DBusConnection *conn, DBusMessage *message, void *arg) { DBusMessage *reply; dbus_uint32_t serial = 0; dbus_int32_t volume; dbus_bool_t system_muted; dbus_bool_t user_muted; dbus_bool_t capture_muted; reply = dbus_message_new_method_return(message); volume = cras_system_get_volume(); system_muted = cras_system_get_system_mute(); user_muted = cras_system_get_user_mute(); capture_muted = cras_system_get_capture_mute(); dbus_message_append_args(reply, DBUS_TYPE_INT32, &volume, DBUS_TYPE_BOOLEAN, &system_muted, DBUS_TYPE_BOOLEAN, &capture_muted, DBUS_TYPE_BOOLEAN, &user_muted, DBUS_TYPE_INVALID); dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_get_default_output_buffer_size(DBusConnection *conn, DBusMessage *message, void *arg) { DBusMessage *reply; dbus_uint32_t serial = 0; dbus_int32_t buffer_size; reply = dbus_message_new_method_return(message); buffer_size = cras_system_get_default_output_buffer_size(); dbus_message_append_args(reply, DBUS_TYPE_INT32, &buffer_size, DBUS_TYPE_INVALID); dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } /* Appends the information about a node to the dbus message. Returns * false if not enough memory. */ static dbus_bool_t append_node_dict(DBusMessageIter *iter, const struct cras_iodev_info *dev, const struct cras_ionode_info *node, enum CRAS_STREAM_DIRECTION direction) { DBusMessageIter dict; dbus_bool_t is_input; dbus_uint64_t id; const char *dev_name = dev->name; dbus_uint64_t stable_dev_id = node->stable_id; const char *node_type = node->type; const char *node_name = node->name; dbus_bool_t active; dbus_uint64_t plugged_time = node->plugged_time.tv_sec * 1000000ULL + node->plugged_time.tv_usec; dbus_uint64_t node_volume = node->volume; dbus_int64_t node_capture_gain = node->capture_gain; char *models, *empty_models = ""; is_input = (direction == CRAS_STREAM_INPUT); id = node->iodev_idx; id = (id << 32) | node->ionode_idx; active = !!node->active; // If dev_name is not utf8, libdbus may abort cras. if (!is_utf8_string(dev_name)) { syslog(LOG_ERR, "Non-utf8 device name '%s' cannot be sent via dbus", dev_name); dev_name = ""; } if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict)) return FALSE; if (!append_key_value(&dict, "IsInput", DBUS_TYPE_BOOLEAN, DBUS_TYPE_BOOLEAN_AS_STRING, &is_input)) return FALSE; if (!append_key_value(&dict, "Id", DBUS_TYPE_UINT64, DBUS_TYPE_UINT64_AS_STRING, &id)) return FALSE; if (!append_key_value(&dict, "DeviceName", DBUS_TYPE_STRING, DBUS_TYPE_STRING_AS_STRING, &dev_name)) return FALSE; /* * If stable id migration is needed, use key 'StableDeviceIdNew' * together with 'StableDeviceId'. */ if (!append_key_value(&dict, "StableDeviceId", DBUS_TYPE_UINT64, DBUS_TYPE_UINT64_AS_STRING, &stable_dev_id)) return FALSE; if (!append_key_value(&dict, "Type", DBUS_TYPE_STRING, DBUS_TYPE_STRING_AS_STRING, &node_type)) return FALSE; if (!append_key_value(&dict, "Name", DBUS_TYPE_STRING, DBUS_TYPE_STRING_AS_STRING, &node_name)) return FALSE; if (!append_key_value(&dict, "Active", DBUS_TYPE_BOOLEAN, DBUS_TYPE_BOOLEAN_AS_STRING, &active)) return FALSE; if (!append_key_value(&dict, "PluggedTime", DBUS_TYPE_UINT64, DBUS_TYPE_UINT64_AS_STRING, &plugged_time)) return FALSE; if (!append_key_value(&dict, "NodeVolume", DBUS_TYPE_UINT64, DBUS_TYPE_UINT64_AS_STRING, &node_volume)) return FALSE; if (!append_key_value(&dict, "NodeCaptureGain", DBUS_TYPE_INT64, DBUS_TYPE_INT64_AS_STRING, &node_capture_gain)) return FALSE; models = cras_iodev_list_get_hotword_models(id); if (!append_key_value(&dict, "HotwordModels", DBUS_TYPE_STRING, DBUS_TYPE_STRING_AS_STRING, models ? &models : &empty_models)) { free(models); return FALSE; } free(models); if (!dbus_message_iter_close_container(iter, &dict)) return FALSE; return TRUE; } /* Appends the information about all nodes in a given direction. Returns false * if not enough memory. */ static dbus_bool_t append_nodes(enum CRAS_STREAM_DIRECTION direction, DBusMessageIter *array) { const struct cras_iodev_info *devs; const struct cras_ionode_info *nodes; int ndevs, nnodes; int i, j; if (direction == CRAS_STREAM_OUTPUT) { ndevs = cras_system_state_get_output_devs(&devs); nnodes = cras_system_state_get_output_nodes(&nodes); } else { ndevs = cras_system_state_get_input_devs(&devs); nnodes = cras_system_state_get_input_nodes(&nodes); } for (i = 0; i < nnodes; i++) { /* Don't reply unplugged nodes. */ if (!nodes[i].plugged) continue; /* Find the device for this node. */ for (j = 0; j < ndevs; j++) if (devs[j].idx == nodes[i].iodev_idx) break; if (j == ndevs) continue; /* Send information about this node. */ if (!append_node_dict(array, &devs[j], &nodes[i], direction)) return FALSE; } return TRUE; } static DBusHandlerResult handle_get_nodes(DBusConnection *conn, DBusMessage *message, void *arg) { DBusMessage *reply; DBusMessageIter array; dbus_uint32_t serial = 0; reply = dbus_message_new_method_return(message); dbus_message_iter_init_append(reply, &array); if (!append_nodes(CRAS_STREAM_OUTPUT, &array)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!append_nodes(CRAS_STREAM_INPUT, &array)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_get_system_aec_supported(DBusConnection *conn, DBusMessage *message, void *arg) { DBusMessage *reply; dbus_uint32_t serial = 0; dbus_bool_t system_aec_supported; reply = dbus_message_new_method_return(message); system_aec_supported = cras_system_get_aec_supported(); dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &system_aec_supported, DBUS_TYPE_INVALID); dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_get_system_aec_group_id(DBusConnection *conn, DBusMessage *message, void *arg) { DBusMessage *reply; dbus_uint32_t serial = 0; dbus_int32_t system_aec_group_id; reply = dbus_message_new_method_return(message); system_aec_group_id = cras_system_get_aec_group_id(); dbus_message_append_args(reply, DBUS_TYPE_INT32, &system_aec_group_id, DBUS_TYPE_INVALID); dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_get_deprioritize_bt_wbs_mic(DBusConnection *conn, DBusMessage *message, void *arg) { DBusMessage *reply; dbus_uint32_t serial = 0; dbus_bool_t deprioritized; reply = dbus_message_new_method_return(message); deprioritized = cras_system_get_deprioritize_bt_wbs_mic(); dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &deprioritized, DBUS_TYPE_INVALID); dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_active_node(DBusConnection *conn, DBusMessage *message, void *arg, enum CRAS_STREAM_DIRECTION direction) { int rc; cras_node_id_t id; rc = get_single_arg(message, DBUS_TYPE_UINT64, &id); if (rc) return rc; cras_iodev_list_select_node(direction, id); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_add_active_node(DBusConnection *conn, DBusMessage *message, void *arg, enum CRAS_STREAM_DIRECTION direction) { int rc; cras_node_id_t id; rc = get_single_arg(message, DBUS_TYPE_UINT64, &id); if (rc) return rc; cras_iodev_list_add_active_node(direction, id); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_rm_active_node(DBusConnection *conn, DBusMessage *message, void *arg, enum CRAS_STREAM_DIRECTION direction) { int rc; cras_node_id_t id; rc = get_single_arg(message, DBUS_TYPE_UINT64, &id); if (rc) return rc; cras_iodev_list_rm_active_node(direction, id); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_fix_a2dp_packet_size(DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_bool_t enabled = FALSE; rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &enabled); if (rc) return rc; cras_system_set_bt_fix_a2dp_packet_size_enabled(enabled); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_get_num_active_streams(DBusConnection *conn, DBusMessage *message, void *arg) { send_int32_reply(conn, message, cras_system_state_get_active_streams()); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_get_num_active_streams_use_input_hw(DBusConnection *conn, DBusMessage *message, void *arg) { dbus_int32_t num = 0; unsigned i; for (i = 0; i < CRAS_NUM_DIRECTIONS; i++) { if (cras_stream_uses_input_hw(i)) num += cras_system_state_get_active_streams_by_direction( i); } send_int32_reply(conn, message, num); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_get_num_active_streams_use_output_hw(DBusConnection *conn, DBusMessage *message, void *arg) { dbus_int32_t num = 0; unsigned i; for (i = 0; i < CRAS_NUM_DIRECTIONS; i++) { if (cras_stream_uses_output_hw(i)) num += cras_system_state_get_active_streams_by_direction( i); } send_int32_reply(conn, message, num); return DBUS_HANDLER_RESULT_HANDLED; } static bool append_num_input_streams_with_permission( DBusMessage *message, uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]) { DBusMessageIter array; DBusMessageIter dict; unsigned type; dbus_message_iter_init_append(message, &array); for (type = 0; type < CRAS_NUM_CLIENT_TYPE; ++type) { const char *client_type_str = cras_client_type_str(type); if (!is_utf8_string(client_type_str)) { syslog(LOG_ERR, "Non-utf8 clinet_type_str '%s' cannot be sent " "via dbus", client_type_str); client_type_str = ""; } if (!dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY, "{sv}", &dict)) return false; if (!append_key_value(&dict, "ClientType", DBUS_TYPE_STRING, DBUS_TYPE_STRING_AS_STRING, &client_type_str)) return false; if (!append_key_value(&dict, "NumStreamsWithPermission", DBUS_TYPE_UINT32, DBUS_TYPE_UINT32_AS_STRING, &num_input_streams[type])) return false; if (!dbus_message_iter_close_container(&array, &dict)) return false; } return true; } static DBusHandlerResult handle_get_num_input_streams_with_permission(DBusConnection *conn, DBusMessage *message, void *arg) { DBusMessage *reply; dbus_uint32_t serial = 0; uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE] = {}; reply = dbus_message_new_method_return(message); cras_system_state_get_input_streams_with_permission(num_input_streams); if (!append_num_input_streams_with_permission(reply, num_input_streams)) goto error; dbus_connection_send(conn, reply, &serial); dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; error: dbus_message_unref(reply); return DBUS_HANDLER_RESULT_NEED_MEMORY; } static DBusHandlerResult handle_set_global_output_channel_remix(DBusConnection *conn, DBusMessage *message, void *arg) { dbus_int32_t num_channels; double *coeff_array; dbus_int32_t count; DBusError dbus_error; float *coefficient; int i; dbus_error_init(&dbus_error); if (!dbus_message_get_args(message, &dbus_error, DBUS_TYPE_INT32, &num_channels, DBUS_TYPE_ARRAY, DBUS_TYPE_DOUBLE, &coeff_array, &count, DBUS_TYPE_INVALID)) { syslog(LOG_WARNING, "Set global output channel remix error: %s", dbus_error.message); dbus_error_free(&dbus_error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } coefficient = (float *)calloc(count, sizeof(*coefficient)); if (!coefficient) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; for (i = 0; i < count; i++) coefficient[i] = coeff_array[i]; audio_thread_config_global_remix(cras_iodev_list_get_audio_thread(), num_channels, coefficient); send_empty_reply(conn, message); free(coefficient); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_hotword_model(DBusConnection *conn, DBusMessage *message, void *arg) { cras_node_id_t id; const char *model_name; DBusError dbus_error; dbus_int32_t ret; dbus_error_init(&dbus_error); if (!dbus_message_get_args(message, &dbus_error, DBUS_TYPE_UINT64, &id, DBUS_TYPE_STRING, &model_name, DBUS_TYPE_INVALID)) { syslog(LOG_WARNING, "Bad method received: %s", dbus_error.message); dbus_error_free(&dbus_error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } ret = cras_iodev_list_set_hotword_model(id, model_name); send_int32_reply(conn, message, ret); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_is_audio_active(DBusConnection *conn, DBusMessage *message, void *arg) { dbus_int32_t active = cras_system_state_get_non_empty_status(); send_int32_reply(conn, message, active); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_wbs_enabled(DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_bool_t enabled; rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &enabled); if (rc) return rc; cras_system_set_bt_wbs_enabled(enabled); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_noise_cancellation_enabled(DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_bool_t enabled; rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &enabled); if (rc) return rc; cras_system_set_noise_cancellation_enabled(enabled); send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_player_playback_status(DBusConnection *conn, DBusMessage *message, void *arg) { char *status; DBusError dbus_error; int rc; dbus_error_init(&dbus_error); rc = get_single_arg(message, DBUS_TYPE_STRING, &status); if (rc) return rc; rc = cras_bt_player_update_playback_status(conn, status); if (rc) { syslog(LOG_WARNING, "CRAS failed to update BT Player Status: %d", rc); } send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_player_identity(DBusConnection *conn, DBusMessage *message, void *arg) { char *identity; DBusError dbus_error; int rc; dbus_error_init(&dbus_error); rc = get_single_arg(message, DBUS_TYPE_STRING, &identity); if (rc) return rc; rc = cras_bt_player_update_identity(conn, identity); if (rc) { syslog(LOG_WARNING, "CRAS failed to update BT Player Identity: %d", rc); } send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_player_position(DBusConnection *conn, DBusMessage *message, void *arg) { dbus_int64_t position; DBusError dbus_error; int rc; dbus_error_init(&dbus_error); rc = get_single_arg(message, DBUS_TYPE_INT64, &position); if (rc) return rc; rc = cras_bt_player_update_position(conn, position); if (rc) { syslog(LOG_WARNING, "CRAS failed to update BT Player Position: %d", rc); } send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult handle_set_player_metadata(DBusConnection *conn, DBusMessage *message, void *arg) { DBusError dbus_error; int rc; dbus_error_init(&dbus_error); const char *title = NULL, *artist = NULL, *album = NULL; dbus_int64_t length = 0; if (!get_metadata(message, &title, &artist, &album, &length)) return -EINVAL; rc = cras_bt_player_update_metadata(conn, title, artist, album, length); if (rc) { syslog(LOG_WARNING, "CRAS failed to update BT Metadata: %d", rc); } send_empty_reply(conn, message); return DBUS_HANDLER_RESULT_HANDLED; } /* Handle incoming messages. */ static DBusHandlerResult handle_control_message(DBusConnection *conn, DBusMessage *message, void *arg) { syslog(LOG_DEBUG, "Control message: %s %s %s", dbus_message_get_path(message), dbus_message_get_interface(message), dbus_message_get_member(message)); if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { DBusMessage *reply; const char *xml = CONTROL_INTROSPECT_XML; reply = dbus_message_new_method_return(message); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, reply, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetOutputVolume")) { return handle_set_output_volume(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetOutputNodeVolume")) { return handle_set_output_node_volume(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SwapLeftRight")) { return handle_swap_left_right(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetOutputMute")) { return handle_set_output_mute(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetOutputUserMute")) { return handle_set_output_user_mute(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetSuspendAudio")) { return handle_set_suspend_audio(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetInputNodeGain")) { return handle_set_input_node_gain(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetInputMute")) { return handle_set_input_mute(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "GetVolumeState")) { return handle_get_volume_state(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "GetDefaultOutputBufferSize")) { return handle_get_default_output_buffer_size(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "GetNodes")) { return handle_get_nodes(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "GetSystemAecSupported")) { return handle_get_system_aec_supported(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "GetSystemAecGroupId")) { return handle_get_system_aec_group_id(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "GetDeprioritizeBtWbsMic")) { return handle_get_deprioritize_bt_wbs_mic(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetActiveOutputNode")) { return handle_set_active_node(conn, message, arg, CRAS_STREAM_OUTPUT); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetActiveInputNode")) { return handle_set_active_node(conn, message, arg, CRAS_STREAM_INPUT); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "AddActiveInputNode")) { return handle_add_active_node(conn, message, arg, CRAS_STREAM_INPUT); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "AddActiveOutputNode")) { return handle_add_active_node(conn, message, arg, CRAS_STREAM_OUTPUT); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "RemoveActiveInputNode")) { return handle_rm_active_node(conn, message, arg, CRAS_STREAM_INPUT); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "RemoveActiveOutputNode")) { return handle_rm_active_node(conn, message, arg, CRAS_STREAM_OUTPUT); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetFixA2dpPacketSize")) { return handle_set_fix_a2dp_packet_size(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "GetNumberOfActiveStreams")) { return handle_get_num_active_streams(conn, message, arg); } else if (dbus_message_is_method_call( message, CRAS_CONTROL_INTERFACE, "GetNumberOfActiveInputStreams")) { return handle_get_num_active_streams_use_input_hw(conn, message, arg); } else if (dbus_message_is_method_call( message, CRAS_CONTROL_INTERFACE, "GetNumberOfInputStreamsWithPermission")) { return handle_get_num_input_streams_with_permission( conn, message, arg); } else if (dbus_message_is_method_call( message, CRAS_CONTROL_INTERFACE, "GetNumberOfActiveOutputStreams")) { return handle_get_num_active_streams_use_output_hw( conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetGlobalOutputChannelRemix")) { return handle_set_global_output_channel_remix(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetHotwordModel")) { return handle_set_hotword_model(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "IsAudioOutputActive")) { return handle_is_audio_active(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetWbsEnabled")) { return handle_set_wbs_enabled(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetNoiseCancellationEnabled")) { return handle_set_noise_cancellation_enabled(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetPlayerPlaybackStatus")) { return handle_set_player_playback_status(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetPlayerIdentity")) { return handle_set_player_identity(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetPlayerPosition")) { return handle_set_player_position(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetPlayerMetadata")) { return handle_set_player_metadata(conn, message, arg); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /* Creates a new DBus message, must be freed with dbus_message_unref. */ static DBusMessage *create_dbus_message(const char *name) { DBusMessage *msg; msg = dbus_message_new_signal(CRAS_ROOT_OBJECT_PATH, CRAS_CONTROL_INTERFACE, name); if (!msg) syslog(LOG_ERR, "Failed to create signal"); return msg; } /* Handlers for system updates that generate DBus signals. */ static void signal_output_volume(void *context, int32_t volume) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("OutputVolumeChanged"); if (!msg) return; volume = cras_system_get_volume(); dbus_message_append_args(msg, DBUS_TYPE_INT32, &volume, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_output_mute(void *context, int muted, int user_muted, int mute_locked) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("OutputMuteChanged"); if (!msg) return; muted = cras_system_get_system_mute(); user_muted = cras_system_get_user_mute(); dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &muted, DBUS_TYPE_BOOLEAN, &user_muted, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_capture_gain(void *context, int32_t gain) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("InputGainChanged"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_INT32, &gain, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_capture_mute(void *context, int muted, int mute_locked) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("InputMuteChanged"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &muted, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_nodes_changed(void *context) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("NodesChanged"); if (!msg) return; dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_active_node_changed(void *context, enum CRAS_STREAM_DIRECTION dir, cras_node_id_t node_id) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; DBusMessage *msg; dbus_uint32_t serial = 0; msg = create_dbus_message((dir == CRAS_STREAM_OUTPUT) ? "ActiveOutputNodeChanged" : "ActiveInputNodeChanged"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_UINT64, &node_id, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } /* Called by iodev_list when a node volume changes. */ static void signal_node_volume_changed(void *context, cras_node_id_t node_id, int32_t volume) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("OutputNodeVolumeChanged"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_UINT64, &node_id, DBUS_TYPE_INT32, &volume, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_node_capture_gain_changed(void *context, cras_node_id_t node_id, int capture_gain) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("InputNodeGainChanged"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_UINT64, &node_id, DBUS_TYPE_INT32, &capture_gain, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_node_left_right_swapped_changed(void *context, cras_node_id_t node_id, int swapped) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("NodeLeftRightSwappedChanged"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_UINT64, &node_id, DBUS_TYPE_BOOLEAN, &swapped, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_num_active_streams_changed(void *context, enum CRAS_STREAM_DIRECTION dir, uint32_t num_active_streams) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; dbus_int32_t num; msg = create_dbus_message("NumberOfActiveStreamsChanged"); if (!msg) return; num = cras_system_state_get_active_streams(); dbus_message_append_args(msg, DBUS_TYPE_INT32, &num, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_num_input_streams_with_permission_changed( void *context, uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("NumberOfInputStreamsWithPermissionChanged"); if (!msg) return; if (!append_num_input_streams_with_permission(msg, num_input_streams)) goto error; dbus_connection_send(control->conn, msg, &serial); error: dbus_message_unref(msg); } static void signal_hotword_triggered(void *context, int64_t tv_sec, int64_t tv_nsec) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("HotwordTriggered"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_INT64, &tv_sec, DBUS_TYPE_INT64, &tv_nsec, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } static void signal_non_empty_audio_state_changed(void *context, int non_empty) { struct cras_dbus_control *control = (struct cras_dbus_control *)context; dbus_uint32_t serial = 0; DBusMessage *msg; msg = create_dbus_message("AudioOutputActiveStateChanged"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &non_empty, DBUS_TYPE_INVALID); dbus_connection_send(control->conn, msg, &serial); dbus_message_unref(msg); } /* Exported Interface */ void cras_dbus_control_start(DBusConnection *conn) { static const DBusObjectPathVTable control_vtable = { .message_function = handle_control_message, }; DBusError dbus_error; struct cras_observer_ops observer_ops; dbus_control.conn = conn; dbus_connection_ref(dbus_control.conn); if (!dbus_connection_register_object_path(conn, CRAS_ROOT_OBJECT_PATH, &control_vtable, &dbus_error)) { syslog(LOG_WARNING, "Couldn't register CRAS control: %s: %s", CRAS_ROOT_OBJECT_PATH, dbus_error.message); dbus_error_free(&dbus_error); return; } memset(&observer_ops, 0, sizeof(observer_ops)); observer_ops.output_volume_changed = signal_output_volume; observer_ops.output_mute_changed = signal_output_mute; observer_ops.capture_gain_changed = signal_capture_gain; observer_ops.capture_mute_changed = signal_capture_mute; observer_ops.num_active_streams_changed = signal_num_active_streams_changed; observer_ops.num_input_streams_with_permission_changed = signal_num_input_streams_with_permission_changed; observer_ops.nodes_changed = signal_nodes_changed; observer_ops.active_node_changed = signal_active_node_changed; observer_ops.input_node_gain_changed = signal_node_capture_gain_changed; observer_ops.output_node_volume_changed = signal_node_volume_changed; observer_ops.node_left_right_swapped_changed = signal_node_left_right_swapped_changed; observer_ops.hotword_triggered = signal_hotword_triggered; observer_ops.non_empty_audio_state_changed = signal_non_empty_audio_state_changed; dbus_control.observer = cras_observer_add(&observer_ops, &dbus_control); } void cras_dbus_control_stop() { if (!dbus_control.conn) return; dbus_connection_unregister_object_path(dbus_control.conn, CRAS_ROOT_OBJECT_PATH); dbus_connection_unref(dbus_control.conn); dbus_control.conn = NULL; cras_observer_remove(dbus_control.observer); dbus_control.observer = NULL; }