diff options
Diffstat (limited to 'audio/control.c')
-rw-r--r-- | audio/control.c | 1193 |
1 files changed, 1193 insertions, 0 deletions
diff --git a/audio/control.c b/audio/control.c new file mode 100644 index 0000000..95c0406 --- /dev/null +++ b/audio/control.c @@ -0,0 +1,1193 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 <config.h> +#endif + +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <netinet/in.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "log.h" +#include "error.h" +#include "uinput.h" +#include "adapter.h" +#include "../src/device.h" +#include "device.h" +#include "manager.h" +#include "avdtp.h" +#include "control.h" +#include "sdpd.h" +#include "glib-helper.h" +#include "btio.h" +#include "dbus-common.h" + +#define AVCTP_PSM 23 + +/* Message types */ +#define AVCTP_COMMAND 0 +#define AVCTP_RESPONSE 1 + +/* Packet types */ +#define AVCTP_PACKET_SINGLE 0 +#define AVCTP_PACKET_START 1 +#define AVCTP_PACKET_CONTINUE 2 +#define AVCTP_PACKET_END 3 + +/* ctype entries */ +#define CTYPE_CONTROL 0x0 +#define CTYPE_STATUS 0x1 +#define CTYPE_NOT_IMPLEMENTED 0x8 +#define CTYPE_ACCEPTED 0x9 +#define CTYPE_REJECTED 0xA +#define CTYPE_STABLE 0xC + +/* opcodes */ +#define OP_UNITINFO 0x30 +#define OP_SUBUNITINFO 0x31 +#define OP_PASSTHROUGH 0x7c + +/* subunits of interest */ +#define SUBUNIT_PANEL 0x09 + +/* operands in passthrough commands */ +#define VOL_UP_OP 0x41 +#define VOL_DOWN_OP 0x42 +#define MUTE_OP 0x43 +#define PLAY_OP 0x44 +#define STOP_OP 0x45 +#define PAUSE_OP 0x46 +#define RECORD_OP 0x47 +#define REWIND_OP 0x48 +#define FAST_FORWARD_OP 0x49 +#define EJECT_OP 0x4a +#define FORWARD_OP 0x4b +#define BACKWARD_OP 0x4c + +#define QUIRK_NO_RELEASE 1 << 0 + +static DBusConnection *connection = NULL; + +static GSList *servers = NULL; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avctp_header { + uint8_t ipid:1; + uint8_t cr:1; + uint8_t packet_type:2; + uint8_t transaction:4; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_LENGTH 3 + +struct avrcp_header { + uint8_t code:4; + uint8_t _hdr0:4; + uint8_t subunit_id:3; + uint8_t subunit_type:5; + uint8_t opcode; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 3 + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avctp_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t cr:1; + uint8_t ipid:1; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_LENGTH 3 + +struct avrcp_header { + uint8_t _hdr0:4; + uint8_t code:4; + uint8_t subunit_type:5; + uint8_t subunit_id:3; + uint8_t opcode; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 3 + +#else +#error "Unknown byte order" +#endif + +struct avctp_state_callback { + avctp_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct avctp_server { + bdaddr_t src; + GIOChannel *io; + uint32_t tg_record_id; + uint32_t ct_record_id; +}; + +struct control { + struct audio_device *dev; + + avctp_state_t state; + + int uinput; + + GIOChannel *io; + guint io_id; + + uint16_t mtu; + + gboolean target; + + uint8_t key_quirks[256]; +}; + +static struct { + const char *name; + uint8_t avrcp; + uint16_t uinput; +} key_map[] = { + { "PLAY", PLAY_OP, KEY_PLAYCD }, + { "STOP", STOP_OP, KEY_STOPCD }, + { "PAUSE", PAUSE_OP, KEY_PAUSECD }, + { "FORWARD", FORWARD_OP, KEY_NEXTSONG }, + { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG }, + { "REWIND", REWIND_OP, KEY_REWIND }, + { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD }, + { NULL } +}; + +static GSList *avctp_callbacks = NULL; + +static void auth_cb(DBusError *derr, void *user_data); + +static sdp_record_t *avrcp_ct_record() +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrct; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVCTP_PSM; + uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); + svclass_id = sdp_list_append(0, &avrct); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(0, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = avrcp_ver; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP CT", 0, 0); + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static sdp_record_t *avrcp_tg_record() +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrtg; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVCTP_PSM; + uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); + svclass_id = sdp_list_append(0, &avrtg); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(0, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = avrcp_ver; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP TG", 0, 0); + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static int send_event(int fd, uint16_t type, uint16_t code, int32_t value) +{ + struct uinput_event event; + + memset(&event, 0, sizeof(event)); + event.type = type; + event.code = code; + event.value = value; + + return write(fd, &event, sizeof(event)); +} + +static void send_key(int fd, uint16_t key, int pressed) +{ + if (fd < 0) + return; + + send_event(fd, EV_KEY, key, pressed); + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static void handle_panel_passthrough(struct control *control, + const unsigned char *operands, + int operand_count) +{ + const char *status; + int pressed, i; + + if (operand_count == 0) + return; + + if (operands[0] & 0x80) { + status = "released"; + pressed = 0; + } else { + status = "pressed"; + pressed = 1; + } + + for (i = 0; key_map[i].name != NULL; i++) { + uint8_t key_quirks; + + if ((operands[0] & 0x7F) != key_map[i].avrcp) + continue; + + DBG("AVRCP: %s %s", key_map[i].name, status); + + key_quirks = control->key_quirks[key_map[i].avrcp]; + + if (key_quirks & QUIRK_NO_RELEASE) { + if (!pressed) { + DBG("AVRCP: Ignoring release"); + break; + } + + DBG("AVRCP: treating key press as press + release"); + send_key(control->uinput, key_map[i].uinput, 1); + send_key(control->uinput, key_map[i].uinput, 0); + break; + } + + send_key(control->uinput, key_map[i].uinput, pressed); + break; + } + + if (key_map[i].name == NULL) + DBG("AVRCP: unknown button 0x%02X %s", + operands[0] & 0x7F, status); +} + +static void avctp_disconnected(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (!control) + return; + + if (control->io) { + g_io_channel_shutdown(control->io, TRUE, NULL); + g_io_channel_unref(control->io); + control->io = NULL; + } + + if (control->io_id) { + g_source_remove(control->io_id); + control->io_id = 0; + + if (control->state == AVCTP_STATE_CONNECTING) + audio_device_cancel_authorization(dev, auth_cb, + control); + } + + if (control->uinput >= 0) { + char address[18]; + + ba2str(&dev->dst, address); + DBG("AVRCP: closing uinput for %s", address); + + ioctl(control->uinput, UI_DEV_DESTROY); + close(control->uinput); + control->uinput = -1; + } +} + +static void avctp_set_state(struct control *control, avctp_state_t new_state) +{ + GSList *l; + struct audio_device *dev = control->dev; + avctp_state_t old_state = control->state; + gboolean value; + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + DBG("AVCTP Disconnected"); + + avctp_disconnected(control->dev); + + 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); + + if (!audio_device_is_active(dev, NULL)) + audio_device_set_authorized(dev, FALSE); + + break; + case AVCTP_STATE_CONNECTING: + DBG("AVCTP Connecting"); + break; + case AVCTP_STATE_CONNECTED: + DBG("AVCTP Connected"); + value = TRUE; + g_dbus_emit_signal(control->dev->conn, control->dev->path, + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_INVALID); + emit_property_changed(control->dev->conn, control->dev->path, + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); + break; + default: + error("Invalid AVCTP state %d", new_state); + return; + } + + control->state = new_state; + + for (l = avctp_callbacks; l != NULL; l = l->next) { + struct avctp_state_callback *cb = l->data; + cb->cb(control->dev, old_state, new_state, cb->user_data); + } +} + +static gboolean control_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct control *control = data; + unsigned char buf[1024], *operands; + struct avctp_header *avctp; + struct avrcp_header *avrcp; + int ret, packet_size, operand_count, sock; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + goto failed; + + sock = g_io_channel_unix_get_fd(control->io); + + ret = read(sock, buf, sizeof(buf)); + if (ret <= 0) + goto failed; + + DBG("Got %d bytes of data for AVCTP session %p", ret, control); + + if ((unsigned int) ret < sizeof(struct avctp_header)) { + error("Too small AVCTP packet"); + goto failed; + } + + packet_size = ret; + + avctp = (struct avctp_header *) buf; + + DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, " + "PID 0x%04X", + avctp->transaction, avctp->packet_type, + avctp->cr, avctp->ipid, ntohs(avctp->pid)); + + ret -= sizeof(struct avctp_header); + if ((unsigned int) ret < sizeof(struct avrcp_header)) { + error("Too small AVRCP packet"); + goto failed; + } + + avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header)); + + ret -= sizeof(struct avrcp_header); + + operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header); + operand_count = ret; + + DBG("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, " + "opcode 0x%02X, %d operands", + avctp->cr ? "response" : "command", + avrcp->code, avrcp->subunit_type, avrcp->subunit_id, + avrcp->opcode, operand_count); + + if (avctp->packet_type != AVCTP_PACKET_SINGLE) { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_NOT_IMPLEMENTED; + } else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) { + avctp->ipid = 1; + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_REJECTED; + } else if (avctp->cr == AVCTP_COMMAND && + avrcp->code == CTYPE_CONTROL && + avrcp->subunit_type == SUBUNIT_PANEL && + avrcp->opcode == OP_PASSTHROUGH) { + handle_panel_passthrough(control, operands, operand_count); + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_ACCEPTED; + } else if (avctp->cr == AVCTP_COMMAND && + avrcp->code == CTYPE_STATUS && + (avrcp->opcode == OP_UNITINFO + || avrcp->opcode == OP_SUBUNITINFO)) { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_STABLE; + /* The first operand should be 0x07 for the UNITINFO response. + * Neither AVRCP (section 22.1, page 117) nor AVC Digital + * Interface Command Set (section 9.2.1, page 45) specs + * explain this value but both use it */ + if (operand_count >= 1 && avrcp->opcode == OP_UNITINFO) + operands[0] = 0x07; + if (operand_count >= 2) + operands[1] = SUBUNIT_PANEL << 3; + DBG("reply to %s", avrcp->opcode == OP_UNITINFO ? + "OP_UNITINFO" : "OP_SUBUNITINFO"); + } else { + avctp->cr = AVCTP_RESPONSE; + avrcp->code = CTYPE_REJECTED; + } + ret = write(sock, buf, packet_size); + + return TRUE; + +failed: + DBG("AVCTP session %p got disconnected", control); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + return FALSE; +} + +static int uinput_create(char *name) +{ + struct uinput_dev dev; + int fd, err, i; + + fd = open("/dev/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/input/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/misc/uinput", O_RDWR); + if (fd < 0) { + err = errno; + error("Can't open input device: %s (%d)", + strerror(err), err); + return -err; + } + } + } + + memset(&dev, 0, sizeof(dev)); + if (name) + strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1); + + dev.id.bustype = BUS_BLUETOOTH; + dev.id.vendor = 0x0000; + dev.id.product = 0x0000; + dev.id.version = 0x0000; + + if (write(fd, &dev, sizeof(dev)) < 0) { + err = errno; + error("Can't write device information: %s (%d)", + strerror(err), err); + close(fd); + errno = err; + return -err; + } + + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_EVBIT, EV_REL); + ioctl(fd, UI_SET_EVBIT, EV_REP); + ioctl(fd, UI_SET_EVBIT, EV_SYN); + + for (i = 0; key_map[i].name != NULL; i++) + ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput); + + if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) { + err = errno; + error("Can't create uinput device: %s (%d)", + strerror(err), err); + close(fd); + errno = err; + return -err; + } + + return fd; +} + +static void init_uinput(struct control *control) +{ + struct audio_device *dev = control->dev; + char address[18], name[248 + 1]; + + device_get_name(dev->btd_dev, name, sizeof(name)); + if (g_str_equal(name, "Nokia CK-20W")) { + control->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE; + control->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE; + control->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE; + control->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE; + } + + ba2str(&dev->dst, address); + + control->uinput = uinput_create(address); + if (control->uinput < 0) + error("AVRCP: failed to init uinput for %s", address); + else + DBG("AVRCP: uinput initialized for %s", address); +} + +static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data) +{ + struct control *control = data; + char address[18]; + uint16_t imtu; + GError *gerr = NULL; + + if (err) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", err->message); + return; + } + + bt_io_get(chan, BT_IO_L2CAP, &gerr, + BT_IO_OPT_DEST, &address, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID); + if (gerr) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", gerr->message); + g_error_free(gerr); + return; + } + + DBG("AVCTP: connected to %s", address); + + if (!control->io) + control->io = g_io_channel_ref(chan); + + init_uinput(control); + + avctp_set_state(control, AVCTP_STATE_CONNECTED); + control->mtu = imtu; + control->io_id = g_io_add_watch(chan, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) control_cb, control); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct control *control = user_data; + GError *err = NULL; + + if (control->io_id) { + g_source_remove(control->io_id); + control->io_id = 0; + } + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + return; + } + + if (!bt_io_accept(control->io, avctp_connect_cb, control, + NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + } +} + +static void avctp_confirm_cb(GIOChannel *chan, gpointer data) +{ + struct control *control = NULL; + struct audio_device *dev; + char address[18]; + bdaddr_t src, dst; + GError *err = NULL; + + bt_io_get(chan, BT_IO_L2CAP, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + dev = manager_get_device(&src, &dst, TRUE); + if (!dev) { + error("Unable to get audio device object for %s", address); + goto drop; + } + + if (!dev->control) { + btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID); + if (!dev->control) + goto drop; + } + + control = dev->control; + + if (control->io) { + error("Refusing unexpected connect from %s", address); + goto drop; + } + + avctp_set_state(control, AVCTP_STATE_CONNECTING); + control->io = g_io_channel_ref(chan); + + if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID, + auth_cb, dev->control) < 0) + goto drop; + + control->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + control_cb, control); + return; + +drop: + if (!control || !control->io) + g_io_channel_shutdown(chan, TRUE, NULL); + if (control) + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); +} + +static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master) +{ + GError *err = NULL; + GIOChannel *io; + + io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, AVCTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + } + + return io; +} + +gboolean avrcp_connect(struct audio_device *dev) +{ + struct control *control = dev->control; + GError *err = NULL; + GIOChannel *io; + + if (control->state > AVCTP_STATE_DISCONNECTED) + return TRUE; + + avctp_set_state(control, AVCTP_STATE_CONNECTING); + + io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &dev->src, + BT_IO_OPT_DEST_BDADDR, &dev->dst, + BT_IO_OPT_PSM, AVCTP_PSM, + BT_IO_OPT_INVALID); + if (err) { + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); + error("%s", err->message); + g_error_free(err); + return FALSE; + } + + control->io = io; + + return TRUE; +} + +void avrcp_disconnect(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (!(control && control->io)) + return; + + avctp_set_state(control, AVCTP_STATE_DISCONNECTED); +} + +int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) +{ + sdp_record_t *record; + gboolean tmp, master = TRUE; + GError *err = NULL; + struct avctp_server *server; + + if (config) { + tmp = g_key_file_get_boolean(config, "General", + "Master", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_error_free(err); + } else + master = tmp; + } + + server = g_new0(struct avctp_server, 1); + if (!server) + return -ENOMEM; + + if (!connection) + connection = dbus_connection_ref(conn); + + record = avrcp_tg_record(); + if (!record) { + error("Unable to allocate new service record"); + g_free(server); + return -1; + } + + if (add_record_to_server(src, record) < 0) { + error("Unable to register AVRCP target service record"); + g_free(server); + sdp_record_free(record); + return -1; + } + server->tg_record_id = record->handle; + + record = avrcp_ct_record(); + if (!record) { + error("Unable to allocate new service record"); + g_free(server); + return -1; + } + + if (add_record_to_server(src, record) < 0) { + error("Unable to register AVRCP controller service record"); + sdp_record_free(record); + g_free(server); + return -1; + } + server->ct_record_id = record->handle; + + server->io = avctp_server_socket(src, master); + if (!server->io) { + remove_record_from_server(server->ct_record_id); + remove_record_from_server(server->tg_record_id); + g_free(server); + return -1; + } + + bacpy(&server->src, src); + + servers = g_slist_append(servers, server); + + return 0; +} + +static struct avctp_server *find_server(GSList *list, const bdaddr_t *src) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct avctp_server *server = l->data; + + if (bacmp(&server->src, src) == 0) + return server; + } + + return NULL; +} + +void avrcp_unregister(const bdaddr_t *src) +{ + struct avctp_server *server; + + server = find_server(servers, src); + if (!server) + return; + + servers = g_slist_remove(servers, server); + + remove_record_from_server(server->ct_record_id); + remove_record_from_server(server->tg_record_id); + + g_io_channel_shutdown(server->io, TRUE, NULL); + g_io_channel_unref(server->io); + g_free(server); + + if (servers) + return; + + dbus_connection_unref(connection); + connection = NULL; +} + +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->state == AVCTP_STATE_CONNECTED); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + return reply; +} + +static int avctp_send_passthrough(struct control *control, uint8_t op) +{ + unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2]; + struct avctp_header *avctp = (void *) buf; + struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH]; + uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH]; + int sk = g_io_channel_unix_get_fd(control->io); + static uint8_t transaction = 0; + + memset(buf, 0, sizeof(buf)); + + avctp->transaction = transaction++; + avctp->packet_type = AVCTP_PACKET_SINGLE; + avctp->cr = AVCTP_COMMAND; + avctp->pid = htons(AV_REMOTE_SVCLASS_ID); + + avrcp->code = CTYPE_CONTROL; + avrcp->subunit_type = SUBUNIT_PANEL; + avrcp->opcode = OP_PASSTHROUGH; + + operands[0] = op & 0x7f; + operands[1] = 0; + + if (write(sk, buf, sizeof(buf)) < 0) + return -errno; + + /* Button release */ + avctp->transaction = transaction++; + operands[0] |= 0x80; + + if (write(sk, buf, sizeof(buf)) < 0) + return -errno; + + return 0; +} + +static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + DBusMessage *reply; + int err; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (control->state != AVCTP_STATE_CONNECTED) + return btd_error_not_connected(msg); + + if (!control->target) + return btd_error_not_supported(msg); + + err = avctp_send_passthrough(control, 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; + DBusMessage *reply; + int err; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (control->state != AVCTP_STATE_CONNECTED) + return btd_error_not_connected(msg); + + if (!control->target) + return btd_error_not_supported(msg); + + err = avctp_send_passthrough(control, 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->state == AVCTP_STATE_CONNECTED); + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable control_methods[] = { + { "IsConnected", "", "b", control_is_connected, + G_DBUS_METHOD_FLAG_DEPRECATED }, + { "GetProperties", "", "a{sv}",control_get_properties }, + { "VolumeUp", "", "", volume_up }, + { "VolumeDown", "", "", volume_down }, + { NULL, NULL, NULL, NULL } +}; + +static GDBusSignalTable control_signals[] = { + { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED}, + { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED}, + { "PropertyChanged", "sv" }, + { NULL, NULL } +}; + +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->state != AVCTP_STATE_DISCONNECTED) + avctp_disconnected(dev); + + 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 audio_device *dev, uint16_t uuid16) +{ + struct control *control = dev->control; + + 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->state = AVCTP_STATE_DISCONNECTED; + control->uinput = -1; + + if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) + control->target = TRUE; + + return control; +} + +gboolean control_is_active(struct audio_device *dev) +{ + struct control *control = dev->control; + + if (control && control->state != AVCTP_STATE_DISCONNECTED) + return TRUE; + + return FALSE; +} + +unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data) +{ + struct avctp_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct avctp_state_callback, 1); + state_cb->cb = cb; + state_cb->user_data = user_data; + state_cb->id = ++id; + + avctp_callbacks = g_slist_append(avctp_callbacks, state_cb); + + return state_cb->id; +} + +gboolean avctp_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = avctp_callbacks; l != NULL; l = l->next) { + struct avctp_state_callback *cb = l->data; + if (cb && cb->id == id) { + avctp_callbacks = g_slist_remove(avctp_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} |