diff options
author | Marcel Holtmann <marcel@holtmann.org> | 2008-07-26 19:00:53 +0200 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2008-07-26 19:00:53 +0200 |
commit | d6ae1c3f777832f8e32702f81fe64e33a1396928 (patch) | |
tree | 159a1e59f3929c9d795dbd1f3edd84d9dccba048 /audio/control.c | |
parent | b8e5fea8d31fbcd3d1c044385f8217dbf39892bb (diff) | |
parent | 3382af9114a9b2e657c7ddd0a5511edda6a37a90 (diff) |
Import bluez-utils-3.36 revision history
Diffstat (limited to 'audio/control.c')
-rw-r--r-- | audio/control.c | 949 |
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; +} |