/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2011 Texas Instruments, Inc. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "error.h" #include "device.h" #include "manager.h" #include "avctp.h" #include "control.h" #include "sdpd.h" #include "glib-helper.h" #include "dbus-common.h" static unsigned int avctp_id = 0; struct control { struct audio_device *dev; struct avctp *session; gboolean target; }; static void state_changed(struct audio_device *dev, avctp_state_t old_state, avctp_state_t new_state, void *user_data) { struct control *control = dev->control; gboolean value; switch (new_state) { case AVCTP_STATE_DISCONNECTED: control->session = NULL; if (old_state != AVCTP_STATE_CONNECTED) break; value = FALSE; g_dbus_emit_signal(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE, "Disconnected", DBUS_TYPE_INVALID); emit_property_changed(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE, "Connected", DBUS_TYPE_BOOLEAN, &value); break; case AVCTP_STATE_CONNECTING: if (control->session) break; control->session = avctp_get(&dev->src, &dev->dst); break; case AVCTP_STATE_CONNECTED: value = TRUE; g_dbus_emit_signal(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE, "Connected", DBUS_TYPE_INVALID); emit_property_changed(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE, "Connected", DBUS_TYPE_BOOLEAN, &value); break; default: return; } } static DBusMessage *control_is_connected(DBusConnection *conn, DBusMessage *msg, void *data) { struct audio_device *device = data; struct control *control = device->control; DBusMessage *reply; dbus_bool_t connected; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; connected = (control->session != NULL); dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, DBUS_TYPE_INVALID); return reply; } static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg, void *data) { struct audio_device *device = data; struct control *control = device->control; int err; if (!control->session) return btd_error_not_connected(msg); if (!control->target) return btd_error_not_supported(msg); err = avctp_send_passthrough(control->session, VOL_UP_OP); if (err < 0) return btd_error_failed(msg, strerror(-err)); return dbus_message_new_method_return(msg); } static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg, void *data) { struct audio_device *device = data; struct control *control = device->control; int err; if (!control->session) return btd_error_not_connected(msg); if (!control->target) return btd_error_not_supported(msg); err = avctp_send_passthrough(control->session, VOL_DOWN_OP); if (err < 0) return btd_error_failed(msg, strerror(-err)); return dbus_message_new_method_return(msg); } static DBusMessage *control_get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { struct audio_device *device = data; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; gboolean value; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); /* Connected */ value = (device->control->session != NULL); dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); dbus_message_iter_close_container(&iter, &dict); return reply; } static const GDBusMethodTable control_methods[] = { { GDBUS_ASYNC_METHOD("IsConnected", NULL, GDBUS_ARGS({ "connected", "b" }), control_is_connected) }, { GDBUS_METHOD("GetProperties", NULL, GDBUS_ARGS({ "properties", "a{sv}" }), control_get_properties) }, { GDBUS_METHOD("VolumeUp", NULL, NULL, volume_up) }, { GDBUS_METHOD("VolumeDown", NULL, NULL, volume_down) }, { } }; static const GDBusSignalTable control_signals[] = { { GDBUS_DEPRECATED_SIGNAL("Connected", NULL) }, { GDBUS_DEPRECATED_SIGNAL("Disconnected", NULL) }, { GDBUS_SIGNAL("PropertyChanged", GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, { } }; static void path_unregister(void *data) { struct audio_device *dev = data; struct control *control = dev->control; DBG("Unregistered interface %s on path %s", AUDIO_CONTROL_INTERFACE, dev->path); if (control->session) avctp_disconnect(control->session); g_free(control); dev->control = NULL; } void control_unregister(struct audio_device *dev) { g_dbus_unregister_interface(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE); } void control_update(struct control *control, uint16_t uuid16) { if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) control->target = TRUE; } struct control *control_init(struct audio_device *dev, uint16_t uuid16) { struct control *control; if (!g_dbus_register_interface(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE, control_methods, control_signals, NULL, dev, path_unregister)) return NULL; DBG("Registered interface %s on path %s", AUDIO_CONTROL_INTERFACE, dev->path); control = g_new0(struct control, 1); control->dev = dev; control_update(control, uuid16); if (!avctp_id) avctp_id = avctp_add_state_cb(state_changed, NULL); return control; } gboolean control_is_active(struct audio_device *dev) { struct control *control = dev->control; if (control && control->session) return TRUE; return FALSE; }