| /* 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 <dbus/dbus.h> |
| #include <errno.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| |
| #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 \ |
| "<node>\n" \ |
| " <interface name=\""CRAS_CONTROL_INTERFACE"\">\n" \ |
| " <method name=\"SetOutputVolume\">\n" \ |
| " <arg name=\"volume\" type=\"i\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetOutputNodeVolume\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " <arg name=\"volume\" type=\"i\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SwapLeftRight\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " <arg name=\"swap\" type=\"b\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetOutputMute\">\n" \ |
| " <arg name=\"mute_on\" type=\"b\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetOutputUserMute\">\n" \ |
| " <arg name=\"mute_on\" type=\"b\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetSuspendAudio\">\n" \ |
| " <arg name=\"suspend\" type=\"b\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetInputGain\">\n" \ |
| " <arg name=\"gain\" type=\"i\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetInputNodeGain\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " <arg name=\"gain\" type=\"i\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetInputMute\">\n" \ |
| " <arg name=\"mute_on\" type=\"b\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetVolumeState\">\n" \ |
| " <arg name=\"output_volume\" type=\"i\" direction=\"out\"/>\n"\ |
| " <arg name=\"output_mute\" type=\"b\" direction=\"out\"/>\n" \ |
| " <arg name=\"input_gain\" type=\"i\" direction=\"out\"/>\n" \ |
| " <arg name=\"input_mute\" type=\"b\" direction=\"out\"/>\n" \ |
| " <arg name=\"output_user_mute\" type=\"b\" direction=\"out\"/>\n"\ |
| " </method>\n" \ |
| " <method name=\"GetNodes\">\n" \ |
| " <arg name=\"nodes\" type=\"a{sv}\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetActiveOutputNode\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetActiveInputNode\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"AddActiveInputNode\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"AddActiveOutputNode\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"RemoveActiveInputNode\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"RemoveActiveOutputNode\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetNumberOfActiveStreams\">\n" \ |
| " <arg name=\"num\" type=\"i\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetNumberOfActiveOutputStreams\">\n" \ |
| " <arg name=\"num\" type=\"i\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetNumberOfActiveInputStreams\">\n" \ |
| " <arg name=\"num\" type=\"i\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetGlobalOutputChannelRemix\">\n" \ |
| " <arg name=\"num_channels\" type=\"i\" direction=\"in\"/>\n" \ |
| " <arg name=\"coefficient\" type=\"ad\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetHotwordModel\">\n" \ |
| " <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \ |
| " <arg name=\"model_name\" type=\"s\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " </interface>\n" \ |
| " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" \ |
| " <method name=\"Introspect\">\n" \ |
| " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " </interface>\n" \ |
| "</node>\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; |
| } |
| |
| /* 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_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; |
| } |
| |
| /* 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, |
| "GetNodes")) { |
| return handle_get_nodes(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); |
| } |
| |
| |
| 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); |
| } |
| |
| /* 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; |
| |
| 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; |
| } |