/* 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_dbus.h" #include "cras_dbus_control.h" #include "cras_dbus_util.h" #include "cras_iodev_list.h" #include "cras_observer.h" #include "cras_system_state.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" 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; } /* 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); 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_gain( DBusConnection *conn, DBusMessage *message, void *arg) { int rc; dbus_int32_t new_gain; rc = get_single_arg(message, DBUS_TYPE_INT32, &new_gain); if (rc) return rc; cras_system_set_capture_gain(new_gain); 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_int32_t capture_gain; 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_gain = cras_system_get_capture_gain(); capture_muted = cras_system_get_capture_mute(); dbus_message_append_args(reply, DBUS_TYPE_INT32, &volume, DBUS_TYPE_BOOLEAN, &system_muted, DBUS_TYPE_INT32, &capture_gain, 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; dbus_uint64_t stable_dev_id_new = node->stable_id_new; const char *node_type = node->type; const char *node_name = node->name; const char *mic_positions = node->mic_positions; 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 (!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 (!append_key_value(&dict, "StableDeviceId", DBUS_TYPE_UINT64, DBUS_TYPE_UINT64_AS_STRING, &stable_dev_id)) return FALSE; if (!append_key_value(&dict, "StableDeviceIdNew", DBUS_TYPE_UINT64, DBUS_TYPE_UINT64_AS_STRING, &stable_dev_id_new)) 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, "MicPositions", DBUS_TYPE_STRING, DBUS_TYPE_STRING_AS_STRING, &mic_positions)) 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_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_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 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; } /* 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, "SetInputGain")) { return handle_set_input_gain(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, "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, "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, "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); } 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_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.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; }