summaryrefslogtreecommitdiffstats
path: root/audio/control.c
diff options
context:
space:
mode:
authorMarcel Holtmann <marcel@holtmann.org>2008-07-26 19:00:53 +0200
committerMarcel Holtmann <marcel@holtmann.org>2008-07-26 19:00:53 +0200
commitd6ae1c3f777832f8e32702f81fe64e33a1396928 (patch)
tree159a1e59f3929c9d795dbd1f3edd84d9dccba048 /audio/control.c
parentb8e5fea8d31fbcd3d1c044385f8217dbf39892bb (diff)
parent3382af9114a9b2e657c7ddd0a5511edda6a37a90 (diff)
Import bluez-utils-3.36 revision history
Diffstat (limited to 'audio/control.c')
-rw-r--r--audio/control.c949
1 files changed, 949 insertions, 0 deletions
diff --git a/audio/control.c b/audio/control.c
new file mode 100644
index 00000000..9139213b
--- /dev/null
+++ b/audio/control.c
@@ -0,0 +1,949 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <bluetooth/l2cap.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "dbus-service.h"
+#include "logging.h"
+#include "uinput.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "control.h"
+#include "sdpd.h"
+#include "glib-helper.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_ACCEPTED 0x9
+#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 VOLUP_OP 0x41
+#define VOLDOWN_OP 0x42
+#define MUTE_OP 0x43
+
+#define PLAY_OP 0x44
+#define STOP_OP 0x45
+#define PAUSE_OP 0x46
+#define REWIND_OP 0x48
+#define FAST_FORWARD_OP 0x49
+#define NEXT_OP 0x4b
+#define PREV_OP 0x4c
+
+static DBusConnection *connection = NULL;
+
+static uint32_t tg_record_id = 0;
+static uint32_t ct_record_id = 0;
+
+static GIOChannel *avctp_server = NULL;
+
+static GSList *sessions = NULL;
+
+typedef enum {
+ AVCTP_STATE_DISCONNECTED = 0,
+ AVCTP_STATE_CONNECTING,
+ AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
+#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));
+
+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));
+
+#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));
+
+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));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp {
+ struct audio_device *dev;
+
+ avctp_state_t state;
+
+ bdaddr_t src;
+ bdaddr_t dst;
+
+ int uinput;
+
+ int sock;
+
+ guint io;
+
+ uint16_t mtu;
+};
+
+struct control {
+ struct avctp *session;
+};
+
+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, 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, &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 = 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, 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, &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 = 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 struct avctp *find_session(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ GSList *l;
+
+ for (l = sessions; l != NULL; l = g_slist_next(l)) {
+ struct avctp *s = l->data;
+
+ if (bacmp(src, &s->src) || bacmp(dst, &s->dst))
+ continue;
+
+ return s;
+ }
+
+ return NULL;
+}
+
+static struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct avctp *session;
+
+ assert(src != NULL);
+ assert(dst != NULL);
+
+ session = find_session(src, dst);
+ if (session)
+ return session;
+
+ session = g_new0(struct avctp, 1);
+
+ session->uinput = -1;
+ session->sock = -1;
+ bacpy(&session->src, src);
+ bacpy(&session->dst, dst);
+
+ sessions = g_slist_append(sessions, session);
+
+ return session;
+}
+
+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 avctp *session,
+ const unsigned char *operands,
+ int operand_count)
+{
+ const char *status;
+ int pressed;
+
+ if (operand_count == 0)
+ return;
+
+ if (operands[0] & 0x80) {
+ status = "released";
+ pressed = 0;
+ } else {
+ status = "pressed";
+ pressed = 1;
+ }
+
+ switch (operands[0] & 0x7F) {
+ case PLAY_OP:
+ debug("AVRCP: PLAY %s", status);
+ send_key(session->uinput, KEY_PLAYPAUSE, pressed);
+ break;
+ case STOP_OP:
+ debug("AVRCP: STOP %s", status);
+ send_key(session->uinput, KEY_STOP, pressed);
+ break;
+ case PAUSE_OP:
+ debug("AVRCP: PAUSE %s", status);
+ send_key(session->uinput, KEY_PLAYPAUSE, pressed);
+ break;
+ case NEXT_OP:
+ debug("AVRCP: NEXT %s", status);
+ send_key(session->uinput, KEY_NEXTSONG, pressed);
+ break;
+ case PREV_OP:
+ debug("AVRCP: PREV %s", status);
+ send_key(session->uinput, KEY_PREVIOUSSONG, pressed);
+ break;
+ case REWIND_OP:
+ debug("AVRCP: REWIND %s", status);
+ send_key(session->uinput, KEY_REWIND, pressed);
+ break;
+ case FAST_FORWARD_OP:
+ debug("AVRCP: FAST FORWARD %s", status);
+ send_key(session->uinput, KEY_FORWARD, pressed);
+ break;
+ default:
+ debug("AVRCP: unknown button 0x%02X %s", operands[0] & 0x7F, status);
+ break;
+ }
+}
+
+static void avctp_unref(struct avctp *session)
+{
+ sessions = g_slist_remove(sessions, session);
+
+ if (session->state == AVCTP_STATE_CONNECTED)
+ g_dbus_emit_signal(session->dev->conn,
+ session->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
+ if (session->sock >= 0)
+ close(session->sock);
+ if (session->io)
+ g_source_remove(session->io);
+
+ if (session->dev)
+ session->dev->control->session = NULL;
+
+ if (session->uinput >= 0) {
+ ioctl(session->uinput, UI_DEV_DESTROY);
+ close(session->uinput);
+ }
+
+ g_free(session);
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avctp *session = data;
+ unsigned char buf[1024], *operands;
+ struct avctp_header *avctp;
+ struct avrcp_header *avrcp;
+ int ret, packet_size, operand_count;
+
+ if (!(cond | G_IO_IN))
+ goto failed;
+
+ ret = read(session->sock, buf, sizeof(buf));
+ if (ret <= 0)
+ goto failed;
+
+ debug("Got %d bytes of data for AVCTP session %p", ret, session);
+
+ if (ret < sizeof(struct avctp_header)) {
+ error("Too small AVCTP packet");
+ goto failed;
+ }
+
+ packet_size = ret;
+
+ avctp = (struct avctp_header *) buf;
+
+ debug("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 (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;
+
+ debug("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_COMMAND &&
+ avctp->pid == htons(AV_REMOTE_SVCLASS_ID) &&
+ avrcp->code == CTYPE_CONTROL &&
+ avrcp->subunit_type == SUBUNIT_PANEL &&
+ avrcp->opcode == OP_PASSTHROUGH) {
+ handle_panel_passthrough(session, operands, operand_count);
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_ACCEPTED;
+ ret = write(session->sock, buf, packet_size);
+ }
+
+ if (avctp->packet_type == AVCTP_PACKET_SINGLE &&
+ avctp->cr == AVCTP_COMMAND &&
+ avctp->pid == htons(AV_REMOTE_SVCLASS_ID) &&
+ avrcp->code == CTYPE_STATUS &&
+ (avrcp->opcode == OP_UNITINFO
+ || avrcp->opcode == OP_SUBUNITINFO)) {
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_STABLE;
+ debug("reply to %s", avrcp->opcode == OP_UNITINFO ?
+ "OP_UNITINFO" : "OP_SUBUNITINFO");
+ ret = write(session->sock, buf, packet_size);
+ }
+
+ return TRUE;
+
+failed:
+ debug("AVCTP session %p got disconnected", session);
+ avctp_unref(session);
+ return FALSE;
+}
+
+static int uinput_create(char *name)
+{
+ struct uinput_dev dev;
+ int fd, err;
+
+ 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);
+
+ 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);
+
+ ioctl(fd, UI_SET_KEYBIT, KEY_PLAYPAUSE);
+ ioctl(fd, UI_SET_KEYBIT, KEY_STOP);
+ ioctl(fd, UI_SET_KEYBIT, KEY_NEXTSONG);
+ ioctl(fd, UI_SET_KEYBIT, KEY_PREVIOUSSONG);
+ ioctl(fd, UI_SET_KEYBIT, KEY_REWIND);
+ ioctl(fd, UI_SET_KEYBIT, KEY_FORWARD);
+
+ 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)
+{
+ char address[18], *name;
+
+ ba2str(&session->dst, address);
+
+ name = session->dev->name ? session->dev->name : address;
+
+ session->uinput = uinput_create(name);
+ if (session->uinput < 0)
+ error("AVRCP: failed to init uinput for %s", name);
+ else
+ debug("AVRCP: uinput initialized for %s", name);
+}
+
+static void avctp_connect_session(struct avctp *session)
+{
+ GIOChannel *io;
+
+ session->state = AVCTP_STATE_CONNECTED;
+ session->dev = manager_device_connected(&session->dst,
+ AVRCP_TARGET_UUID);
+ session->dev->control->session = session;
+
+ init_uinput(session);
+
+ g_dbus_emit_signal(session->dev->conn, session->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_INVALID);
+
+ if (session->io)
+ g_source_remove(session->io);
+
+ io = g_io_channel_unix_new(session->sock);
+ session->io = g_io_add_watch(io,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+ g_io_channel_unref(io);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct avctp *session = user_data;
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ if (dbus_error_has_name(derr, DBUS_ERROR_NO_REPLY)) {
+ debug("Canceling authorization request");
+ service_cancel_auth(&session->src, &session->dst);
+ }
+
+ avctp_unref(session);
+ return;
+ }
+
+ avctp_connect_session(session);
+}
+
+static void avctp_server_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer data)
+{
+ socklen_t size;
+ struct l2cap_options l2o;
+ struct avctp *session;
+ GIOCondition flags = G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+ char address[18];
+
+ if (err < 0) {
+ error("AVCTP server socket: %s (%d)", strerror(-err), -err);
+ return;
+ }
+
+ session = avctp_get(src, dst);
+
+ if (!session) {
+ error("Unable to create new AVCTP session");
+ goto drop;
+ }
+
+ if (session->sock >= 0) {
+ error("Refusing unexpected connect from %s", address);
+ goto drop;
+ }
+
+ session->state = AVCTP_STATE_CONNECTING;
+ session->sock = g_io_channel_unix_get_fd(chan);
+
+ memset(&l2o, 0, sizeof(l2o));
+ size = sizeof(l2o);
+ if (getsockopt(session->sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &size) < 0) {
+ err = errno;
+ error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err),
+ err);
+ avctp_unref(session);
+ goto drop;
+ }
+
+ session->mtu = l2o.imtu;
+
+ session->io = g_io_add_watch(chan, flags, (GIOFunc) session_cb,
+ session);
+ g_io_channel_unref(chan);
+
+ if (avdtp_is_connected(src, dst))
+ goto proceed;
+
+ if (service_req_auth(src, dst, AVRCP_TARGET_UUID, auth_cb, session) < 0)
+ goto drop;
+
+ return;
+
+proceed:
+ avctp_connect_session(session);
+
+ return;
+
+drop:
+ close(session->sock);
+}
+
+static GIOChannel *avctp_server_socket(gboolean master)
+{
+ int lm;
+ GIOChannel *io;
+
+ lm = L2CAP_LM_SECURE;
+
+ if (master)
+ lm |= L2CAP_LM_MASTER;
+
+ io = bt_l2cap_listen(BDADDR_ANY, AVCTP_PSM, 0, lm, avctp_server_cb,
+ NULL);
+ if (!io) {
+ error("Unable to allocate new io channel");
+ return NULL;
+ }
+
+ return io;
+}
+
+static void avctp_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer data)
+{
+ struct avctp *session = data;
+ struct l2cap_options l2o;
+ socklen_t len;
+ int sk;
+ char address[18];
+
+ if (err < 0) {
+ avctp_unref(session);
+ error("AVCTP connect(%s): %s (%d)", address, strerror(-err),
+ -err);
+ return;
+ }
+
+ ba2str(&session->dst, address);
+ debug("AVCTP: connected to %s", address);
+
+ g_io_channel_set_close_on_unref(chan, FALSE);
+ sk = g_io_channel_unix_get_fd(chan);
+ session->sock = sk;
+
+ memset(&l2o, 0, sizeof(l2o));
+ len = sizeof(l2o);
+ if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) {
+ err = errno;
+ avctp_unref(session);
+ error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err),
+ err);
+ return;
+ }
+
+ init_uinput(session);
+
+ g_dbus_emit_signal(session->dev->conn, session->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_INVALID);
+
+ session->state = AVCTP_STATE_CONNECTED;
+ session->mtu = l2o.imtu;
+ session->io = g_io_add_watch(chan,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+}
+
+gboolean avrcp_connect(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+ struct avctp *session;
+ int err;
+
+ if (control->session)
+ return TRUE;
+
+ session = avctp_get(&dev->src, &dev->dst);
+ if (!session) {
+ error("Unable to create new AVCTP session");
+ return FALSE;
+ }
+
+ session->dev = dev;
+ session->state = AVCTP_STATE_CONNECTING;
+
+ err = bt_l2cap_connect(&dev->src, &dev->dst, AVCTP_PSM, 0,
+ avctp_connect_cb, session);
+ if (err < 0) {
+ avctp_unref(session);
+ error("Connect failed. %s(%d)", strerror(-err), -err);
+ return FALSE;
+ }
+
+ control->session = session;
+
+ return TRUE;
+}
+
+void avrcp_disconnect(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+ struct avctp *session = control->session;
+
+ if (!session)
+ return;
+
+ avctp_unref(session);
+ control->session = NULL;
+}
+
+int avrcp_init(DBusConnection *conn, GKeyFile *config)
+{
+ sdp_record_t *record;
+ gboolean tmp, master = TRUE;
+ GError *err = NULL;
+
+ if (avctp_server)
+ return 0;
+
+ if (config) {
+ tmp = g_key_file_get_boolean(config, "General",
+ "Master", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_error_free(err);
+ } else
+ master = tmp;
+ }
+
+ connection = dbus_connection_ref(conn);
+
+ record = avrcp_tg_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ return -1;
+ }
+
+ if (add_record_to_server(BDADDR_ANY, record) < 0) {
+ error("Unable to register AVRCP target service record");
+ sdp_record_free(record);
+ return -1;
+ }
+ tg_record_id = record->handle;
+
+ record = avrcp_ct_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ return -1;
+ }
+
+ if (add_record_to_server(BDADDR_ANY, record) < 0) {
+ error("Unable to register AVRCP controller service record");
+ sdp_record_free(record);
+ return -1;
+ }
+ ct_record_id = record->handle;
+
+ avctp_server = avctp_server_socket(master);
+ if (!avctp_server)
+ return -1;
+
+ return 0;
+}
+
+void avrcp_exit(void)
+{
+ if (!avctp_server)
+ return;
+
+ g_io_channel_close(avctp_server);
+ g_io_channel_unref(avctp_server);
+ avctp_server = NULL;
+
+ remove_record_from_server(ct_record_id);
+ ct_record_id = 0;
+
+ remove_record_from_server(tg_record_id);
+ tg_record_id = 0;
+
+ 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->session != NULL);
+
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static GDBusMethodTable control_methods[] = {
+ { "IsConnected", "", "b", control_is_connected },
+ { NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable control_signals[] = {
+ { "Connected", "" },
+ { "Disconnected", "" },
+ { NULL, NULL }
+};
+
+struct control *control_init(struct audio_device *dev)
+{
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ control_methods, control_signals, NULL,
+ dev, NULL))
+ return NULL;
+
+ return g_new0(struct control, 1);
+}
+
+void control_free(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+
+ if (control->session)
+ avctp_unref(control->session);
+
+ g_free(control);
+ dev->control = NULL;
+}
+
+gboolean control_is_active(struct audio_device *dev)
+{
+ struct control *control = dev->control;
+
+ if (control->session &&
+ control->session->state != AVCTP_STATE_DISCONNECTED)
+ return TRUE;
+
+ return FALSE;
+}