| /* 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_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 \ |
| "<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=\"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_mute\" type=\"b\" direction=\"out\"/>\n" \ |
| " <arg name=\"output_user_mute\" type=\"b\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetDefaultOutputBufferSize\">\n" \ |
| " <arg name=\"buffer_size\" type=\"i\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetNodes\">\n" \ |
| " <arg name=\"nodes\" type=\"a{sv}\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetSystemAecSupported\">\n" \ |
| " <arg name=\"supported\" type=\"b\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetSystemAecGroupId\">\n" \ |
| " <arg name=\"group_id\" type=\"i\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"GetDeprioritizeBtWbsMic\">\n" \ |
| " <arg name=\"deprioritized\" type=\"b\" 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=\"SetFixA2dpPacketSize\">\n" \ |
| " <arg name=\"toggle\" type=\"b\" 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=\"GetNumberOfInputStreamsWithPermission\">\n" \ |
| " <arg name=\"num\" type=\"a{sv}\" 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" \ |
| " <method name=\"IsAudioOutputActive\">\n" \ |
| " <arg name=\"active\" type=\"b\" direction=\"out\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetWbsEnabled\">\n" \ |
| " <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetPlayerPlaybackStatus\">\n" \ |
| " <arg name=\"status\" type=\"s\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetPlayerIdentity\">\n" \ |
| " <arg name=\"identity\" type=\"s\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetPlayerPosition\">\n" \ |
| " <arg name=\"position\" type=\"x\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"SetPlayerMetadata\">\n" \ |
| " <arg name=\"metadata\" type=\"a{sv}\" direction=\"in\"/>\n" \ |
| " </method>\n" \ |
| " <method name=\"ResendBluetoothBattery\">\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; |
| } |
| |
| 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_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; |
| } |
| |
| static DBusHandlerResult handle_resend_bluetooth_battery(DBusConnection *conn, |
| DBusMessage *message, |
| void *arg) |
| { |
| cras_hfp_ag_resend_device_battery_level(); |
| |
| 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, |
| "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); |
| } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, |
| "ResendBluetoothBattery")) { |
| return handle_resend_bluetooth_battery(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); |
| } |
| |
| static void signal_bt_battery_changed(void *context, const char *address, |
| uint32_t level) |
| { |
| struct cras_dbus_control *control = (struct cras_dbus_control *)context; |
| dbus_uint32_t serial = 0; |
| DBusMessage *msg; |
| |
| msg = create_dbus_message("BluetoothBatteryChanged"); |
| if (!msg) |
| return; |
| |
| dbus_message_append_args(msg, DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID); |
| dbus_message_append_args(msg, DBUS_TYPE_UINT32, &level, |
| 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; |
| observer_ops.bt_battery_changed = signal_bt_battery_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; |
| } |