diff --git a/Makefile.am b/Makefile.am
index f4113b1..ef546d4 100644
--- a/Makefile.am
+++ b/Makefile.am
audio/gateway.h audio/gateway.c \
audio/headset.h audio/headset.c \
audio/control.h audio/control.c \
+ audio/avctp.h audio/avctp.c \
audio/device.h audio/device.c \
audio/source.h audio/source.c \
audio/sink.h audio/sink.c \
diff --git a/audio/avctp.c b/audio/avctp.c
new file mode 100644
index 0000000..75875bf
--- /dev/null
+++ b/audio/avctp.c
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * 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 <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 <glib.h>
+
+#include "adapter.h"
+#include "../src/device.h"
+
+#include "log.h"
+#include "error.h"
+#include "uinput.h"
+#include "btio.h"
+#include "manager.h"
+#include "device.h"
+#include "avctp.h"
+
+#define QUIRK_NO_RELEASE 1 << 0
+
+/* 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
+
+#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 avc_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 AVC_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 avc_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 AVC_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;
+ GSList *sessions;
+};
+
+struct avctp {
+ struct avctp_server *server;
+ bdaddr_t dst;
+
+ avctp_state_t state;
+
+ int uinput;
+
+ GIOChannel *io;
+ guint io_id;
+
+ uint16_t mtu;
+
+ uint8_t key_quirks[256];
+};
+
+struct avctp_pdu_handler {
+ uint8_t opcode;
+ avctp_pdu_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+static struct {
+ const char *name;
+ uint8_t avc;
+ uint16_t uinput;
+} key_map[] = {
+ { "PLAY", PLAY_OP, KEY_PLAYCD },
+ { "STOP", STAVC_OP_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 *callbacks = NULL;
+static GSList *servers = NULL;
+static GSList *handlers = NULL;
+
+static void auth_cb(DBusError *derr, void *user_data);
+
+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 size_t handle_panel_passthrough(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ const char *status;
+ int pressed, i;
+
+ if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) {
+ *code = AVC_CTYPE_REJECTED;
+ return 0;
+ }
+
+ if (operand_count == 0)
+ goto done;
+
+ 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].avc)
+ continue;
+
+ DBG("AVRCP: %s %s", key_map[i].name, status);
+
+ key_quirks = session->key_quirks[key_map[i].avc];
+
+ if (key_quirks & QUIRK_NO_RELEASE) {
+ if (!pressed) {
+ DBG("AVRCP: Ignoring release");
+ break;
+ }
+
+ DBG("AVRCP: treating key press as press + release");
+ send_key(session->uinput, key_map[i].uinput, 1);
+ send_key(session->uinput, key_map[i].uinput, 0);
+ break;
+ }
+
+ send_key(session->uinput, key_map[i].uinput, pressed);
+ break;
+ }
+
+ if (key_map[i].name == NULL)
+ DBG("AVRCP: unknown button 0x%02X %s",
+ operands[0] & 0x7F, status);
+
+done:
+ *code = AVC_CTYPE_ACCEPTED;
+ return operand_count;
+}
+
+static size_t handle_unit_info(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ if (*code != AVC_CTYPE_STATUS) {
+ *code = AVC_CTYPE_REJECTED;
+ return 0;
+ }
+
+ *code = AVC_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)
+ operands[0] = 0x07;
+ if (operand_count >= 2)
+ operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+ DBG("reply to AVC_OP_UNITINFO");
+
+ return 0;
+}
+
+static size_t handle_subunit_info(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ if (*code != AVC_CTYPE_STATUS) {
+ *code = AVC_CTYPE_REJECTED;
+ return 0;
+ }
+
+ *code = AVC_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 >= 2)
+ operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+ DBG("reply to AVC_OP_SUBUNITINFO");
+
+ return 0;
+}
+
+static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode)
+{
+ for (; list; list = list->next) {
+ struct avctp_pdu_handler *handler = list->data;
+
+ if (handler->opcode == opcode)
+ return handler;
+ }
+
+ return NULL;
+}
+
+static void avctp_disconnected(struct avctp *session)
+{
+ struct avctp_server *server = session->server;
+
+ if (!session)
+ return;
+
+ if (session->io) {
+ g_io_channel_shutdown(session->io, TRUE, NULL);
+ g_io_channel_unref(session->io);
+ session->io = NULL;
+ }
+
+ if (session->io_id) {
+ g_source_remove(session->io_id);
+ session->io_id = 0;
+
+ if (session->state == AVCTP_STATE_CONNECTING) {
+ struct audio_device *dev;
+
+ dev = manager_get_device(&session->server->src,
+ &session->dst, FALSE);
+ audio_device_cancel_authorization(dev, auth_cb,
+ session);
+ }
+ }
+
+ if (session->uinput >= 0) {
+ char address[18];
+
+ ba2str(&session->dst, address);
+ DBG("AVCTP: closing uinput for %s", address);
+
+ ioctl(session->uinput, UI_DEV_DESTROY);
+ close(session->uinput);
+ session->uinput = -1;
+ }
+
+ server->sessions = g_slist_remove(server->sessions, session);
+ g_free(session);
+}
+
+static void avctp_set_state(struct avctp *session, avctp_state_t new_state)
+{
+ GSList *l;
+ struct audio_device *dev;
+ avctp_state_t old_state = session->state;
+
+ dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+ if (dev == NULL) {
+ error("avdtp_set_state(): no matching audio device");
+ return;
+ }
+
+ session->state = new_state;
+
+ for (l = callbacks; l != NULL; l = l->next) {
+ struct avctp_state_callback *cb = l->data;
+ cb->cb(dev, old_state, new_state, cb->user_data);
+ }
+
+ switch (new_state) {
+ case AVCTP_STATE_DISCONNECTED:
+ DBG("AVCTP Disconnected");
+
+ avctp_disconnected(session);
+
+ if (old_state != AVCTP_STATE_CONNECTED)
+ break;
+
+ 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");
+ break;
+ default:
+ error("Invalid AVCTP state %d", new_state);
+ return;
+ }
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avctp *session = data;
+ uint8_t buf[1024], *operands, code, subunit;
+ struct avctp_header *avctp;
+ struct avc_header *avc;
+ int ret, packet_size, operand_count, sock;
+ struct avctp_pdu_handler *handler;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ goto failed;
+
+ sock = g_io_channel_unix_get_fd(session->io);
+
+ ret = read(sock, buf, sizeof(buf));
+ if (ret <= 0)
+ goto failed;
+
+ DBG("Got %d bytes of data for AVCTP session %p", ret, session);
+
+ if ((unsigned int) ret < sizeof(struct avctp_header)) {
+ error("Too small AVCTP packet");
+ goto failed;
+ }
+
+ 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 avc_header)) {
+ error("Too small AVRCP packet");
+ goto failed;
+ }
+
+ avc = (struct avc_header *) (buf + sizeof(struct avctp_header));
+
+ ret -= sizeof(struct avc_header);
+
+ operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
+ operand_count = ret;
+
+ DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
+ "opcode 0x%02X, %d operands",
+ avctp->cr ? "response" : "command",
+ avc->code, avc->subunit_type, avc->subunit_id,
+ avc->opcode, operand_count);
+
+ if (avctp->cr == AVCTP_RESPONSE)
+ return TRUE;
+
+ packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH;
+ avctp->cr = AVCTP_RESPONSE;
+
+ if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
+ avc->code = AVC_CTYPE_NOT_IMPLEMENTED;
+ goto done;
+ }
+
+ if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+ avctp->ipid = 1;
+ avc->code = AVC_CTYPE_REJECTED;
+ goto done;
+ }
+
+ handler = find_handler(handlers, avc->opcode);
+ if (!handler) {
+ avc->code = AVC_CTYPE_REJECTED;
+ goto done;
+ }
+
+ code = avc->code;
+ subunit = avc->subunit_type;
+
+ packet_size += handler->cb(session, avctp->transaction, &code,
+ &subunit, operands, operand_count,
+ handler->user_data);
+
+ avc->code = code;
+ avc->subunit_type = subunit;
+
+done:
+ ret = write(sock, buf, packet_size);
+ if (ret != packet_size)
+ goto failed;
+
+ return TRUE;
+
+failed:
+ DBG("AVCTP session %p got disconnected", session);
+ avctp_set_state(session, 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 avctp *session)
+{
+ struct audio_device *dev;
+ char address[18], name[248 + 1];
+
+ dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+
+ device_get_name(dev->btd_dev, name, sizeof(name));
+ if (g_str_equal(name, "Nokia CK-20W")) {
+ session->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE;
+ session->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE;
+ session->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE;
+ session->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE;
+ }
+
+ ba2str(&session->dst, address);
+
+ session->uinput = uinput_create(address);
+ if (session->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 avctp *session = data;
+ char address[18];
+ uint16_t imtu;
+ GError *gerr = NULL;
+
+ if (err) {
+ avctp_set_state(session, 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(session, AVCTP_STATE_DISCONNECTED);
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ return;
+ }
+
+ DBG("AVCTP: connected to %s", address);
+
+ if (!session->io)
+ session->io = g_io_channel_ref(chan);
+
+ init_uinput(session);
+
+ avctp_set_state(session, AVCTP_STATE_CONNECTED);
+ session->mtu = imtu;
+ session->io_id = g_io_add_watch(chan,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct avctp *session = user_data;
+ GError *err = NULL;
+
+ if (session->io_id) {
+ g_source_remove(session->io_id);
+ session->io_id = 0;
+ }
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ return;
+ }
+
+ if (!bt_io_accept(session->io, avctp_connect_cb, session,
+ NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ }
+}
+
+static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+ for (; list; list = list->next) {
+ struct avctp_server *server = list->data;
+
+ if (bacmp(&server->src, src) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+static struct avctp *find_session(GSList *list, const bdaddr_t *dst)
+{
+ for (; list != NULL; list = g_slist_next(list)) {
+ struct avctp *s = list->data;
+
+ if (bacmp(dst, &s->dst))
+ continue;
+
+ return s;
+ }
+
+ return NULL;
+}
+
+static struct avctp *avctp_get_internal(const bdaddr_t *src,
+ const bdaddr_t *dst)
+{
+ struct avctp_server *server;
+ struct avctp *session;
+
+ assert(src != NULL);
+ assert(dst != NULL);
+
+ server = find_server(servers, src);
+ if (server == NULL)
+ return NULL;
+
+ session = find_session(server->sessions, dst);
+ if (session)
+ return session;
+
+ session = g_new0(struct avctp, 1);
+
+ session->server = server;
+ bacpy(&session->dst, dst);
+ session->state = AVCTP_STATE_DISCONNECTED;
+
+ server->sessions = g_slist_append(server->sessions, session);
+
+ return session;
+}
+
+static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+ struct avctp *session;
+ 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;
+ }
+
+ DBG("AVCTP: incoming connect from %s", address);
+
+ session = avctp_get_internal(&src, &dst);
+ if (!session)
+ goto drop;
+
+ dev = manager_get_device(&src, &dst, FALSE);
+ if (!dev) {
+ dev = manager_get_device(&src, &dst, TRUE);
+ if (!dev) {
+ error("Unable to get audio device object for %s",
+ address);
+ goto drop;
+ }
+ btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
+ }
+
+ if (session->io) {
+ error("Refusing unexpected connect from %s", address);
+ goto drop;
+ }
+
+ avctp_set_state(session, AVCTP_STATE_CONNECTING);
+ session->io = g_io_channel_ref(chan);
+
+ if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
+ auth_cb, session) < 0)
+ goto drop;
+
+ session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ session_cb, session);
+ return;
+
+drop:
+ if (!session || !session->io)
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ if (session)
+ avctp_set_state(session, 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;
+}
+
+static unsigned int passthrough_id = 0;
+static unsigned int unit_id = 0;
+static unsigned int subunit_id = 0;
+
+int avctp_register(const bdaddr_t *src, gboolean master)
+{
+ struct avctp_server *server;
+
+ server = g_new0(struct avctp_server, 1);
+ if (!server)
+ return -ENOMEM;
+
+ server->io = avctp_server_socket(src, master);
+ if (!server->io) {
+ g_free(server);
+ return -1;
+ }
+
+ bacpy(&server->src, src);
+
+ servers = g_slist_append(servers, server);
+
+ if (!passthrough_id)
+ passthrough_id = avctp_register_pdu_handler(AVC_OP_PASSTHROUGH,
+ handle_panel_passthrough, NULL);
+
+ if (!unit_id)
+ unit_id = avctp_register_pdu_handler(AVC_OP_UNITINFO, handle_unit_info,
+ NULL);
+
+ if (!subunit_id)
+ subunit_id = avctp_register_pdu_handler(AVC_OP_SUBUNITINFO,
+ handle_subunit_info, NULL);
+
+ return 0;
+}
+
+void avctp_unregister(const bdaddr_t *src)
+{
+ struct avctp_server *server;
+
+ server = find_server(servers, src);
+ if (!server)
+ return;
+
+ while (server->sessions)
+ avctp_disconnected(server->sessions->data);
+
+ servers = g_slist_remove(servers, server);
+
+ g_io_channel_shutdown(server->io, TRUE, NULL);
+ g_io_channel_unref(server->io);
+ g_free(server);
+
+ if (servers)
+ return;
+
+ if (passthrough_id)
+ avctp_unregister_pdu_handler(passthrough_id);
+
+ if (unit_id)
+ avctp_unregister_pdu_handler(unit_id);
+
+ if (subunit_id)
+ avctp_unregister_pdu_handler(unit_id);
+}
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op)
+{
+ unsigned char buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + 2];
+ struct avctp_header *avctp = (void *) buf;
+ struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+ uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
+ int sk;
+ static uint8_t transaction = 0;
+
+ if (session->state != AVCTP_STATE_CONNECTED)
+ return -ENOTCONN;
+
+ 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);
+
+ avc->code = AVC_CTYPE_CONTROL;
+ avc->subunit_type = AVC_SUBUNIT_PANEL;
+ avc->opcode = AVC_OP_PASSTHROUGH;
+
+ operands[0] = op & 0x7f;
+ operands[1] = 0;
+
+ sk = g_io_channel_unix_get_fd(session->io);
+
+ 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;
+}
+
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+ uint8_t code, uint8_t subunit,
+ uint8_t *operands, size_t operand_count)
+{
+ uint8_t *buf;
+ struct avctp_header *avctp;
+ struct avc_header *avc;
+ uint8_t *pdu;
+ int sk, err;
+ uint16_t size;
+
+ if (session->state != AVCTP_STATE_CONNECTED)
+ return -ENOTCONN;
+
+ sk = g_io_channel_unix_get_fd(session->io);
+ size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + operand_count;
+ buf = g_malloc0(size);
+
+ avctp = (void *) buf;
+ avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+ pdu = (void *) &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
+
+ avctp->transaction = transaction;
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->cr = AVCTP_RESPONSE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ avc->code = code;
+ avc->subunit_type = subunit;
+ avc->opcode = AVC_OP_VENDORDEP;
+
+ memcpy(pdu, operands, operand_count);
+
+ err = write(sk, buf, size);
+ if (err < 0) {
+ g_free(buf);
+ return -errno;
+ }
+
+ g_free(buf);
+ return 0;
+}
+
+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;
+
+ callbacks = g_slist_append(callbacks, state_cb);
+
+ return state_cb->id;
+}
+
+gboolean avctp_remove_state_cb(unsigned int id)
+{
+ GSList *l;
+
+ for (l = callbacks; l != NULL; l = l->next) {
+ struct avctp_state_callback *cb = l->data;
+ if (cb && cb->id == id) {
+ callbacks = g_slist_remove(callbacks, cb);
+ g_free(cb);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
+ void *user_data)
+{
+ struct avctp_pdu_handler *handler;
+ static unsigned int id = 0;
+
+ handler = find_handler(handlers, opcode);
+ if (handler)
+ return 0;
+
+ handler = g_new(struct avctp_pdu_handler, 1);
+ handler->opcode = opcode;
+ handler->cb = cb;
+ handler->user_data = user_data;
+ handler->id = ++id;
+
+ handlers = g_slist_append(handlers, handler);
+
+ return handler->id;
+}
+
+gboolean avctp_unregister_pdu_handler(unsigned int id)
+{
+ GSList *l;
+
+ for (l = handlers; l != NULL; l = l->next) {
+ struct avctp_pdu_handler *handler = l->data;
+
+ if (handler->id == id) {
+ handlers = g_slist_remove(handlers, handler);
+ g_free(handler);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct avctp *session;
+ GError *err = NULL;
+ GIOChannel *io;
+
+ session = avctp_get_internal(src, dst);
+ if (!session)
+ return NULL;
+
+ if (session->state > AVCTP_STATE_DISCONNECTED)
+ return session;
+
+ avctp_set_state(session, AVCTP_STATE_CONNECTING);
+
+ io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, session, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &session->server->src,
+ BT_IO_OPT_DEST_BDADDR, &session->dst,
+ BT_IO_OPT_PSM, AVCTP_PSM,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ error("%s", err->message);
+ g_error_free(err);
+ return NULL;
+ }
+
+ session->io = io;
+
+ return session;
+}
+
+void avctp_disconnect(struct avctp *session)
+{
+ if (session->io)
+ return;
+
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+}
diff --git a/audio/avctp.h b/audio/avctp.h
new file mode 100644
index 0000000..157ecc6
--- /dev/null
+++ b/audio/avctp.h
+/*
+ *
+ * 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
+ *
+ */
+
+#define AVCTP_PSM 23
+
+/* ctype entries */
+#define AVC_CTYPE_CONTROL 0x0
+#define AVC_CTYPE_STATUS 0x1
+#define AVC_CTYPE_NOTIFY 0x3
+#define AVC_CTYPE_NOT_IMPLEMENTED 0x8
+#define AVC_CTYPE_ACCEPTED 0x9
+#define AVC_CTYPE_REJECTED 0xA
+#define AVC_CTYPE_STABLE 0xC
+#define AVC_CTYPE_CHANGED 0xD
+#define AVC_CTYPE_INTERIM 0xF
+
+/* opcodes */
+#define AVC_OP_VENDORDEP 0x00
+#define AVC_OP_UNITINFO 0x30
+#define AVC_OP_SUBUNITINFO 0x31
+#define AVC_OP_PASSTHROUGH 0x7c
+
+/* subunits of interest */
+#define AVC_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 STAVC_OP_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
+
+struct avctp;
+
+typedef enum {
+ AVCTP_STATE_DISCONNECTED = 0,
+ AVCTP_STATE_CONNECTING,
+ AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
+typedef void (*avctp_state_cb) (struct audio_device *dev,
+ avctp_state_t old_state,
+ avctp_state_t new_state,
+ void *user_data);
+
+typedef size_t (*avctp_pdu_cb) (struct avctp *session, uint8_t transaction,
+ uint8_t *code, uint8_t *subunit,
+ uint8_t *operands, size_t operand_count,
+ void *user_data);
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
+gboolean avctp_remove_state_cb(unsigned int id);
+
+int avctp_register(const bdaddr_t *src, gboolean master);
+void avctp_unregister(const bdaddr_t *src);
+
+struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst);
+void avctp_disconnect(struct avctp *session);
+
+unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
+ void *user_data);
+gboolean avctp_unregister_pdu_handler(unsigned int id);
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op);
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+ uint8_t code, uint8_t subunit,
+ uint8_t *operands, size_t operand_count);
diff --git a/audio/control.c b/audio/control.c
index cafb951..dceb004 100644
--- a/audio/control.c
+++ b/audio/control.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
-#include <netinet/in.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.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 "avctp.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_NOTIFY 0x3
-#define CTYPE_NOT_IMPLEMENTED 0x8
-#define CTYPE_ACCEPTED 0x9
-#define CTYPE_REJECTED 0xA
-#define CTYPE_STABLE 0xC
-#define CTYPE_CHANGED 0xD
-#define CTYPE_INTERIM 0xF
-
-/* opcodes */
-#define OP_VENDORDEP 0x00
-#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
-
/* Company IDs for vendor dependent commands */
#define IEEEID_BTSIG 0x001958
#define CAP_COMPANY_ID 0x02
#define CAP_EVENTS_SUPPORTED 0x03
-#define QUIRK_NO_RELEASE 1 << 0
-
enum player_setting {
PLAYER_SETTING_EQUALIZER = 1,
PLAYER_SETTING_REPEAT = 2,
static DBusConnection *connection = NULL;
static GSList *servers = NULL;
+static unsigned int avctp_id = 0;
#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 avc_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 AVC_HEADER_LENGTH 3
-
struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
#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 avc_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 AVC_HEADER_LENGTH 3
-
struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
#error "Unknown byte order"
#endif
-struct avctp_state_callback {
- avctp_state_cb cb;
- void *user_data;
- unsigned int id;
-};
-
-struct avctp_server {
+struct avrcp_server {
bdaddr_t src;
- GIOChannel *io;
uint32_t tg_record_id;
uint32_t ct_record_id;
};
struct media_info mi;
GTimer *timer;
+ unsigned int handler;
};
struct control {
struct audio_device *dev;
struct media_player *mp;
-
- avctp_state_t state;
-
- int uinput;
-
- GIOChannel *io;
- guint io_id;
-
- uint16_t mtu;
+ struct avctp *session;
gboolean target;
- uint8_t key_quirks[256];
-
uint16_t registered_events;
uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
};
-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 }
-};
-
/* Company IDs supported by this device */
static uint32_t company_ids[] = {
IEEEID_BTSIG,
};
-static GSList *avctp_callbacks = NULL;
-
-static void auth_cb(DBusError *derr, void *user_data);
-
static sdp_record_t *avrcp_ct_record(void)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
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 unsigned int attr_get_max_val(uint8_t attr)
{
switch (attr) {
return NULL;
}
-static int avctp_send_event(struct control *control, uint8_t id, void *data)
+static int avrcp_send_event(struct control *control, uint8_t id, void *data)
{
- uint8_t buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
- AVRCP_HEADER_LENGTH + 9];
- struct avctp_header *avctp = (void *) buf;
- struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
- struct avrcp_header *pdu = (void *) &buf[AVCTP_HEADER_LENGTH +
- AVC_HEADER_LENGTH];
- int sk;
+ uint8_t buf[AVRCP_HEADER_LENGTH + 9];
+ struct avrcp_header *pdu = (void *) buf;
uint16_t size;
+ int err;
- if (control->state != AVCTP_STATE_CONNECTED)
+ if (control->session)
return -ENOTCONN;
if (!(control->registered_events & (1 << id)))
memset(buf, 0, sizeof(buf));
- avctp->transaction = control->transaction_events[id];
- avctp->packet_type = AVCTP_PACKET_SINGLE;
- avctp->cr = AVCTP_RESPONSE;
- avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
-
- avc->code = CTYPE_CHANGED;
- avc->subunit_type = SUBUNIT_PANEL;
- avc->opcode = OP_VENDORDEP;
-
pdu->company_id[0] = IEEEID_BTSIG >> 16;
pdu->company_id[1] = (IEEEID_BTSIG >> 8) & 0xFF;
pdu->company_id[2] = IEEEID_BTSIG & 0xFF;
}
pdu->params_len = htons(size);
- size += AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
- AVRCP_HEADER_LENGTH;
-
- sk = g_io_channel_unix_get_fd(control->io);
- if (write(sk, buf, size) < 0)
- return -errno;
+ err = avctp_send_vendordep(control->session, control->transaction_events[id],
+ AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL,
+ buf, size);
+ if (err < 0)
+ return err;
/* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
control->registered_events ^= 1 << id;
mp->status = status;
- avctp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
+ avrcp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
&status);
}
mi->title, mi->artist, mi->album, mi->genre,
mi->ntracks, mi->track, mi->track_len);
- avctp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
+ avrcp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
}
static uint8_t avrcp_handle_get_capabilities(struct control *control,
pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
pdu->params[1] = G_N_ELEMENTS(company_ids);
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
case CAP_EVENTS_SUPPORTED:
pdu->params_len = htons(4);
pdu->params[1] = 2;
pdu->params[2] = AVRCP_EVENT_PLAYBACK_STATUS_CHANGED;
pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED;
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_list_player_attributes(struct control *control,
if (len != 0) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
if (!mp)
pdu->params[0] = len;
pdu->params_len = htons(len + 1);
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}
static uint8_t avrcp_handle_list_player_values(struct control *control,
pdu->params[0] = len;
pdu->params_len = htons(len + 1);
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_get_element_attributes(struct control *control,
pdu->params[0] = len;
pdu->params_len = htons(pos);
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_get_current_player_value(struct control *control,
pdu->params[0] = len;
pdu->params_len = htons(2 * len + 1);
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}
error("No valid attributes in request");
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_set_player_value(struct control *control,
if (len) {
pdu->params_len = 0;
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_displayable_charset(struct control *control,
if (len < 3) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
/*
* encoding since CT is obliged to support it.
*/
pdu->params_len = 0;
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}
static uint8_t avrcp_handle_ct_battery_status(struct control *control,
DBUS_TYPE_STRING, &valstr);
pdu->params_len = 0;
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_get_play_status(struct control *control,
if (len != 0) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
if (control->mp) {
pdu->params_len = htons(9);
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}
static uint8_t avrcp_handle_register_notification(struct control *control,
pdu->params_len = htons(len);
- return CTYPE_INTERIM;
+ return AVC_CTYPE_INTERIM;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}
static struct pdu_handler {
struct avrcp_header *pdu,
uint8_t transaction);
} handlers[] = {
- { AVRCP_GET_CAPABILITIES, CTYPE_STATUS,
+ { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS,
avrcp_handle_get_capabilities },
- { AVRCP_LIST_PLAYER_ATTRIBUTES, CTYPE_STATUS,
+ { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS,
avrcp_handle_list_player_attributes },
- { AVRCP_LIST_PLAYER_VALUES, CTYPE_STATUS,
+ { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS,
avrcp_handle_list_player_values },
- { AVRCP_GET_ELEMENT_ATTRIBUTES, CTYPE_STATUS,
+ { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS,
avrcp_handle_get_element_attributes },
- { AVRCP_GET_CURRENT_PLAYER_VALUE, CTYPE_STATUS,
+ { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS,
avrcp_handle_get_current_player_value },
- { AVRCP_SET_PLAYER_VALUE, CTYPE_CONTROL,
+ { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL,
avrcp_handle_set_player_value },
- { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, CTYPE_STATUS,
+ { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS,
NULL },
- { AVRCP_GET_PLAYER_VALUE_TEXT, CTYPE_STATUS,
+ { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS,
NULL },
- { AVRCP_DISPLAYABLE_CHARSET, CTYPE_STATUS,
+ { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS,
avrcp_handle_displayable_charset },
- { AVRCP_CT_BATTERY_STATUS, CTYPE_STATUS,
+ { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS,
avrcp_handle_ct_battery_status },
- { AVRCP_GET_PLAY_STATUS, CTYPE_STATUS,
+ { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS,
avrcp_handle_get_play_status },
- { AVRCP_REGISTER_NOTIFICATION, CTYPE_NOTIFY,
+ { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY,
avrcp_handle_register_notification },
{ },
};
/* handle vendordep pdu inside an avctp packet */
-static int handle_vendordep_pdu(struct control *control,
- struct avctp_header *avctp,
- struct avc_header *avc,
- int operand_count)
+static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
+ uint8_t *code, uint8_t *subunit,
+ uint8_t *operands, size_t operand_count,
+ void *user_data)
{
+ struct control *control = user_data;
struct pdu_handler *handler;
- struct avrcp_header *pdu = (void *) avc + AVC_HEADER_LENGTH;
+ struct avrcp_header *pdu = (void *) operands;
uint32_t company_id = (pdu->company_id[0] << 16) |
(pdu->company_id[1] << 8) |
(pdu->company_id[2]);
- if (company_id != IEEEID_BTSIG ||
- pdu->packet_type != AVCTP_PACKET_SINGLE) {
- avc->code = CTYPE_NOT_IMPLEMENTED;
- return AVC_HEADER_LENGTH;
+ if (company_id != IEEEID_BTSIG) {
+ *code = AVC_CTYPE_NOT_IMPLEMENTED;
+ return 0;
}
+ DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X",
+ pdu->pdu_id, company_id, pdu->params_len);
+
pdu->packet_type = 0;
pdu->rsvd = 0;
break;
}
- if (!handler || handler->code != avc->code) {
+ if (!handler || handler->code != *code) {
pdu->params[0] = E_INVALID_COMMAND;
goto err_metadata;
}
goto err_metadata;
}
- avc->code = handler->func(control, pdu, avctp->transaction);
+ *code = handler->func(control, pdu, transaction);
- return AVC_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
- ntohs(pdu->params_len);
+ return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
err_metadata:
pdu->params_len = htons(1);
- avc->code = CTYPE_REJECTED;
+ *code = AVC_CTYPE_REJECTED;
- return AVC_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 1;
+ return AVRCP_HEADER_LENGTH + 1;
}
-static void avctp_disconnected(struct audio_device *dev)
+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;
-
- 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");
+ control->session = NULL;
- avctp_disconnected(control->dev);
+ if (control->mp && control->mp->handler) {
+ avctp_unregister_pdu_handler(control->mp->handler);
+ control->mp->handler = 0;
+ }
if (old_state != AVCTP_STATE_CONNECTED)
break;
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");
+ if (control->session)
+ break;
+
+ control->session = avctp_connect(&dev->src, &dev->dst);
+ if (!control->mp)
+ break;
+
+ control->mp->handler = avctp_register_pdu_handler(
+ AVC_OP_VENDORDEP,
+ handle_vendordep_pdu,
+ control);
break;
case AVCTP_STATE_CONNECTED:
- DBG("AVCTP Connected");
value = TRUE;
- g_dbus_emit_signal(control->dev->conn, control->dev->path,
+ g_dbus_emit_signal(dev->conn, dev->path,
AUDIO_CONTROL_INTERFACE, "Connected",
DBUS_TYPE_INVALID);
- emit_property_changed(control->dev->conn, control->dev->path,
+ emit_property_changed(dev->conn, 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 avc_header *avc;
- 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 avc_header)) {
- error("Too small AVRCP packet");
- goto failed;
- }
-
- avc = (struct avc_header *) (buf + sizeof(struct avctp_header));
-
- ret -= sizeof(struct avc_header);
-
- operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
- operand_count = ret;
-
- DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
- "opcode 0x%02X, %d operands",
- avctp->cr ? "response" : "command",
- avc->code, avc->subunit_type, avc->subunit_id,
- avc->opcode, operand_count);
-
- if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
- avctp->cr = AVCTP_RESPONSE;
- avc->code = CTYPE_NOT_IMPLEMENTED;
- } else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
- avctp->ipid = 1;
- avctp->cr = AVCTP_RESPONSE;
- packet_size = sizeof(*avctp);
- } else if (avctp->cr == AVCTP_COMMAND &&
- avc->code == CTYPE_CONTROL &&
- avc->subunit_type == SUBUNIT_PANEL &&
- avc->opcode == OP_PASSTHROUGH) {
- handle_panel_passthrough(control, operands, operand_count);
- avctp->cr = AVCTP_RESPONSE;
- avc->code = CTYPE_ACCEPTED;
- } else if (avctp->cr == AVCTP_COMMAND &&
- avc->code == CTYPE_STATUS &&
- (avc->opcode == OP_UNITINFO
- || avc->opcode == OP_SUBUNITINFO)) {
- avctp->cr = AVCTP_RESPONSE;
- avc->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 && avc->opcode == OP_UNITINFO)
- operands[0] = 0x07;
- if (operand_count >= 2)
- operands[1] = SUBUNIT_PANEL << 3;
- DBG("reply to %s", avc->opcode == OP_UNITINFO ?
- "OP_UNITINFO" : "OP_SUBUNITINFO");
- } else if (avctp->cr == AVCTP_COMMAND &&
- avc->opcode == OP_VENDORDEP) {
- int r_size;
- operand_count -= 3;
- avctp->cr = AVCTP_RESPONSE;
- r_size = handle_vendordep_pdu(control, avctp, avc,
- operand_count);
- packet_size = AVCTP_HEADER_LENGTH + r_size;
- } else {
- avctp->cr = AVCTP_RESPONSE;
- avc->code = CTYPE_REJECTED;
- }
-
- ret = write(sock, buf, packet_size);
- if (ret != packet_size)
- goto failed;
-
- 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 media_info_init(struct media_info *mi)
mi->elapsed = 0xFFFFFFFF;
}
-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)
+ if (control->session)
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);
+ control->session = avctp_connect(&dev->src, &dev->dst);
+ if (!control->session)
return FALSE;
- }
-
- control->io = io;
return TRUE;
}
{
struct control *control = dev->control;
- if (!(control && control->io))
+ if (!(control && control->session))
return;
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ avctp_disconnect(control->session);
}
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;
+ struct avrcp_server *server;
if (config) {
tmp = g_key_file_get_boolean(config, "General",
master = tmp;
}
- server = g_new0(struct avctp_server, 1);
+ server = g_new0(struct avrcp_server, 1);
if (!server)
return -ENOMEM;
}
server->ct_record_id = record->handle;
- server->io = avctp_server_socket(src, master);
- if (!server->io) {
+ if (avctp_register(src, master) < 0) {
remove_record_from_server(server->ct_record_id);
remove_record_from_server(server->tg_record_id);
g_free(server);
return 0;
}
-static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src)
{
for (; list; list = list->next) {
- struct avctp_server *server = list->data;
+ struct avrcp_server *server = list->data;
if (bacmp(&server->src, src) == 0)
return server;
void avrcp_unregister(const bdaddr_t *src)
{
- struct avctp_server *server;
+ struct avrcp_server *server;
server = find_server(servers, src);
if (!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);
+ avctp_unregister(&server->src);
g_free(server);
if (servers)
return;
+ if (avctp_id)
+ avctp_remove_state_cb(avctp_id);
+
dbus_connection_unref(connection);
connection = NULL;
}
if (!reply)
return NULL;
- connected = (control->state == AVCTP_STATE_CONNECTED);
+ connected = (control->session != NULL);
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 + AVC_HEADER_LENGTH + 2];
- struct avctp_header *avctp = (void *) buf;
- struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
- uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_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);
-
- avc->code = CTYPE_CONTROL;
- avc->subunit_type = SUBUNIT_PANEL;
- avc->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 control *control = device->control;
int err;
- if (control->state != AVCTP_STATE_CONNECTED)
+ if (!control->session)
return btd_error_not_connected(msg);
if (!control->target)
return btd_error_not_supported(msg);
- err = avctp_send_passthrough(control, VOL_UP_OP);
+ err = avctp_send_passthrough(control->session, VOL_UP_OP);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
struct control *control = device->control;
int err;
- if (control->state != AVCTP_STATE_CONNECTED)
+ if (!control->session)
return btd_error_not_connected(msg);
if (!control->target)
return btd_error_not_supported(msg);
- err = avctp_send_passthrough(control, VOL_DOWN_OP);
+ err = avctp_send_passthrough(control->session, VOL_DOWN_OP);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
/* Connected */
- value = (device->control->state == AVCTP_STATE_CONNECTED);
+ value = (device->control->session != NULL);
dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
dbus_message_iter_close_container(&iter, &dict);
DBG("Unregistered interface %s on path %s",
AUDIO_CONTROL_INTERFACE, dev->path);
- if (control->state != AVCTP_STATE_DISCONNECTED)
- avctp_disconnected(dev);
+ if (control->session)
+ avctp_disconnect(control->session);
g_free(control);
dev->control = NULL;
DBG("Unregistered interface %s on path %s",
MEDIA_PLAYER_INTERFACE, dev->path);
+ if (mp->handler)
+ avctp_unregister_pdu_handler(mp->handler);
+
g_timer_destroy(mp->timer);
g_free(mp);
control->mp = NULL;
control = g_new0(struct control, 1);
control->dev = dev;
- control->state = AVCTP_STATE_DISCONNECTED;
- control->uinput = -1;
control_update(control, uuid16, media_player);
+ if (!avctp_id)
+ avctp_id = avctp_add_state_cb(state_changed, NULL);
+
return control;
}
{
struct control *control = dev->control;
- if (control && control->state != AVCTP_STATE_DISCONNECTED)
+ if (control && control->session)
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;
-}
diff --git a/audio/control.h b/audio/control.h
index 77e7595..f5cfef2 100644
--- a/audio/control.h
+++ b/audio/control.h
#define AUDIO_CONTROL_INTERFACE "org.bluez.Control"
#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer"
-typedef enum {
- AVCTP_STATE_DISCONNECTED = 0,
- AVCTP_STATE_CONNECTING,
- AVCTP_STATE_CONNECTED
-} avctp_state_t;
-
-typedef void (*avctp_state_cb) (struct audio_device *dev,
- avctp_state_t old_state,
- avctp_state_t new_state,
- void *user_data);
-
-unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
-gboolean avctp_remove_state_cb(unsigned int id);
-
int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
void avrcp_unregister(const bdaddr_t *src);
diff --git a/audio/device.c b/audio/device.c
index 1d76a3f..a885da0 100644
--- a/audio/device.c
+++ b/audio/device.c
#include "unix.h"
#include "avdtp.h"
#include "control.h"
+#include "avctp.h"
#include "headset.h"
#include "gateway.h"
#include "sink.h"