| /* |
| * |
| * 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; |
| } |