bluez/audio/control.c

953 lines
21 KiB
C
Raw Normal View History

2007-10-22 22:11:04 +08:00
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
2007-10-24 01:17:47 +08:00
* Copyright (C) 2006-2007 Nokia Corporation
2008-02-02 11:37:05 +08:00
* Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
2007-10-24 01:17:47 +08:00
*
2007-10-22 22:11:04 +08:00
*
* 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>
2007-10-25 18:38:47 +08:00
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
2007-10-22 22:11:04 +08:00
#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"
2007-10-22 22:11:04 +08:00
#include "logging.h"
2007-10-25 18:38:47 +08:00
#include "uinput.h"
2007-10-22 22:11:04 +08:00
#include "device.h"
#include "manager.h"
#include "avdtp.h"
#include "control.h"
2008-03-28 07:07:19 +08:00
#include "sdpd.h"
#include "glib-helper.h"
2007-10-22 22:11:04 +08:00
#define AVCTP_PSM 23
/* Message types */
#define AVCTP_COMMAND 0
#define AVCTP_RESPONSE 1
/* Packet types */
2007-10-25 01:25:56 +08:00
#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
2007-10-25 18:38:47 +08:00
#define REWIND_OP 0x48
#define FAST_FORWARD_OP 0x49
#define NEXT_OP 0x4b
#define PREV_OP 0x4c
2007-10-22 22:11:04 +08:00
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;
2007-10-22 22:11:04 +08:00
#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;
2007-10-22 22:11:04 +08:00
bdaddr_t src;
bdaddr_t dst;
2007-10-25 18:38:47 +08:00
int uinput;
2007-10-22 22:11:04 +08:00
int sock;
guint io;
uint16_t mtu;
};
struct control {
struct avctp *session;
};
2008-03-28 07:07:19 +08:00
static sdp_record_t *avrcp_ct_record()
2007-10-22 22:11:04 +08:00
{
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];
2008-03-28 07:07:19 +08:00
sdp_record_t *record;
2007-10-22 22:11:04 +08:00
sdp_data_t *psm, *version, *features;
uint16_t lp = AVCTP_PSM, ver = 0x0103, feat = 0x000f;
2008-03-28 07:07:19 +08:00
record = sdp_record_alloc();
if (!record)
return NULL;
2007-10-22 22:11:04 +08:00
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(0, &root_uuid);
2008-03-28 07:07:19 +08:00
sdp_set_browse_groups(record, root);
2007-10-22 22:11:04 +08:00
/* Service Class ID List */
sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
svclass_id = sdp_list_append(0, &avrct);
2008-03-28 07:07:19 +08:00
sdp_set_service_classes(record, svclass_id);
2007-10-22 22:11:04 +08:00
/* 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);
2008-03-28 07:07:19 +08:00
sdp_set_access_protos(record, aproto);
2007-10-22 22:11:04 +08:00
/* 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]);
2008-03-28 07:07:19 +08:00
sdp_set_profile_descs(record, pfseq);
2007-10-22 22:11:04 +08:00
features = sdp_data_alloc(SDP_UINT16, &feat);
2008-03-28 07:07:19 +08:00
sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
2007-10-22 22:11:04 +08:00
2008-03-28 07:07:19 +08:00
sdp_set_info_attr(record, "AVRCP CT", 0, 0);
2007-10-22 22:11:04 +08:00
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);
2008-03-28 07:07:19 +08:00
return record;
2007-10-22 22:11:04 +08:00
}
2008-03-28 07:07:19 +08:00
static sdp_record_t *avrcp_tg_record()
2007-10-22 22:11:04 +08:00
{
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];
2008-03-28 07:07:19 +08:00
sdp_record_t *record;
2007-10-22 22:11:04 +08:00
sdp_data_t *psm, *version, *features;
uint16_t lp = AVCTP_PSM, ver = 0x0103, feat = 0x000f;
2008-03-28 07:07:19 +08:00
record = sdp_record_alloc();
if (!record)
return NULL;
2007-10-22 22:11:04 +08:00
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(0, &root_uuid);
2008-03-28 07:07:19 +08:00
sdp_set_browse_groups(record, root);
2007-10-22 22:11:04 +08:00
/* Service Class ID List */
sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
svclass_id = sdp_list_append(0, &avrtg);
2008-03-28 07:07:19 +08:00
sdp_set_service_classes(record, svclass_id);
2007-10-22 22:11:04 +08:00
/* 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);
2008-03-28 07:07:19 +08:00
sdp_set_access_protos(record, aproto);
2007-10-22 22:11:04 +08:00
/* 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]);
2008-03-28 07:07:19 +08:00
sdp_set_profile_descs(record, pfseq);
2007-10-22 22:11:04 +08:00
features = sdp_data_alloc(SDP_UINT16, &feat);
2008-03-28 07:07:19 +08:00
sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
2007-10-22 22:11:04 +08:00
2008-03-28 07:07:19 +08:00
sdp_set_info_attr(record, "AVRCP TG", 0, 0);
2007-10-22 22:11:04 +08:00
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);
2008-03-28 07:07:19 +08:00
return record;
2007-10-22 22:11:04 +08:00
}
2008-05-27 22:02:41 +08:00
static struct avctp *find_session(const bdaddr_t *src, const bdaddr_t *dst)
2007-10-22 22:11:04 +08:00
{
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;
}
2008-05-27 22:02:41 +08:00
static struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst)
2007-10-22 22:11:04 +08:00
{
struct avctp *session;
assert(src != NULL);
assert(dst != NULL);
session = find_session(src, dst);
2008-06-18 03:37:36 +08:00
if (session)
return session;
2007-10-22 22:11:04 +08:00
session = g_new0(struct avctp, 1);
2007-10-25 18:38:47 +08:00
session->uinput = -1;
2007-10-22 22:11:04 +08:00
session->sock = -1;
bacpy(&session->src, src);
bacpy(&session->dst, dst);
sessions = g_slist_append(sessions, session);
return session;
}
2007-10-25 18:38:47 +08:00
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)
{
2007-10-25 01:25:56 +08:00
const char *status;
2007-10-25 18:38:47 +08:00
int pressed;
2007-10-25 01:25:56 +08:00
if (operand_count == 0)
return;
2007-10-25 18:38:47 +08:00
if (operands[0] & 0x80) {
2007-10-25 01:25:56 +08:00
status = "released";
2007-10-25 18:38:47 +08:00
pressed = 0;
} else {
2007-10-25 01:25:56 +08:00
status = "pressed";
2007-10-25 18:38:47 +08:00
pressed = 1;
}
2007-10-25 01:25:56 +08:00
switch (operands[0] & 0x7F) {
case PLAY_OP:
2007-10-25 01:25:56 +08:00
debug("AVRCP: PLAY %s", status);
2007-10-25 18:38:47 +08:00
send_key(session->uinput, KEY_PLAYPAUSE, pressed);
break;
case STOP_OP:
2007-10-25 01:25:56 +08:00
debug("AVRCP: STOP %s", status);
2007-10-25 18:38:47 +08:00
send_key(session->uinput, KEY_STOP, pressed);
break;
case PAUSE_OP:
2007-10-25 01:25:56 +08:00
debug("AVRCP: PAUSE %s", status);
2007-10-25 18:38:47 +08:00
send_key(session->uinput, KEY_PLAYPAUSE, pressed);
break;
case NEXT_OP:
2007-10-25 01:25:56 +08:00
debug("AVRCP: NEXT %s", status);
send_key(session->uinput, KEY_NEXTSONG, pressed);
break;
case PREV_OP:
2007-10-25 01:25:56 +08:00
debug("AVRCP: PREV %s", status);
send_key(session->uinput, KEY_PREVIOUSSONG, pressed);
2007-10-25 18:38:47 +08:00
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:
2007-10-25 01:25:56 +08:00
debug("AVRCP: unknown button 0x%02X %s", operands[0] & 0x7F, status);
break;
}
}
2008-05-27 22:02:41 +08:00
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,
2008-05-27 22:02:41 +08:00
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);
}
2007-10-22 22:11:04 +08:00
static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
gpointer data)
{
struct avctp *session = data;
unsigned char buf[1024], *operands;
2007-10-22 22:11:04 +08:00
struct avctp_header *avctp;
struct avrcp_header *avrcp;
int ret, packet_size, operand_count;
2007-10-22 22:11:04 +08:00
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;
2007-10-22 22:11:04 +08:00
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;
2007-10-22 22:11:04 +08:00
debug("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
2007-10-22 22:59:22 +08:00
"opcode 0x%02X, %d operands",
avctp->cr ? "response" : "command",
2007-10-22 22:11:04 +08:00
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) {
2007-10-25 18:38:47 +08:00
handle_panel_passthrough(session, operands, operand_count);
avctp->cr = AVCTP_RESPONSE;
avrcp->code = CTYPE_ACCEPTED;
ret = write(session->sock, buf, packet_size);
}
2007-10-22 22:11:04 +08:00
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);
}
2007-10-22 22:11:04 +08:00
return TRUE;
failed:
debug("AVCTP session %p got disconnected", session);
avctp_unref(session);
return FALSE;
}
2008-05-27 22:02:41 +08:00
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)
{
2008-07-29 04:15:27 +08:00
char address[18];
2008-05-27 22:02:41 +08:00
ba2str(&session->dst, address);
2008-07-29 04:15:27 +08:00
session->uinput = uinput_create(address);
2008-05-27 22:02:41 +08:00
if (session->uinput < 0)
2008-07-29 04:15:27 +08:00
error("AVRCP: failed to init uinput for %s", address);
2008-05-27 22:02:41 +08:00
else
2008-07-29 04:15:27 +08:00
debug("AVRCP: uinput initialized for %s", address);
2008-05-27 22:02:41 +08:00
}
2008-06-18 03:37:36 +08:00
static void avctp_connect_session(struct avctp *session)
2007-10-22 22:11:04 +08:00
{
GIOChannel *io;
session->state = AVCTP_STATE_CONNECTED;
2008-07-29 04:15:27 +08:00
session->dev = manager_find_device(&session->dst, NULL, FALSE);
if (!session->dev)
return;
session->dev->control->session = session;
2008-06-18 03:37:36 +08:00
2007-10-25 18:38:47 +08:00
init_uinput(session);
g_dbus_emit_signal(session->dev->conn, session->dev->path,
AUDIO_CONTROL_INTERFACE, "Connected",
DBUS_TYPE_INVALID);
2008-06-18 03:37:36 +08:00
if (session->io)
g_source_remove(session->io);
2007-10-22 22:11:04 +08:00
io = g_io_channel_unix_new(session->sock);
session->io = g_io_add_watch(io,
2008-06-18 03:37:36 +08:00
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc) session_cb, session);
2007-10-22 22:11:04 +08:00
g_io_channel_unref(io);
}
2008-06-18 03:37:36 +08:00
static void auth_cb(DBusError *derr, void *user_data)
{
2008-06-18 03:37:36 +08:00
struct avctp *session = user_data;
2008-06-18 03:37:36 +08:00
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);
}
2008-06-18 03:37:36 +08:00
avctp_unref(session);
return;
}
avctp_connect_session(session);
}
2008-05-27 22:02:41 +08:00
static void avctp_server_cb(GIOChannel *chan, int err, const bdaddr_t *src,
const bdaddr_t *dst, gpointer data)
2007-10-22 22:11:04 +08:00
{
socklen_t size;
struct l2cap_options l2o;
struct avctp *session;
GIOCondition flags = G_IO_ERR | G_IO_HUP | G_IO_NVAL;
char address[18];
2008-05-27 22:02:41 +08:00
if (err < 0) {
error("AVCTP server socket: %s (%d)", strerror(-err), -err);
return;
2007-10-22 22:11:04 +08:00
}
2008-05-27 22:02:41 +08:00
session = avctp_get(src, dst);
2007-10-22 22:11:04 +08:00
2007-10-25 18:38:47 +08:00
if (!session) {
error("Unable to create new AVCTP session");
2008-05-27 22:02:41 +08:00
goto drop;
2007-10-25 18:38:47 +08:00
}
2007-10-22 22:11:04 +08:00
if (session->sock >= 0) {
error("Refusing unexpected connect from %s", address);
2008-05-27 22:02:41 +08:00
goto drop;
2007-10-22 22:11:04 +08:00
}
session->state = AVCTP_STATE_CONNECTING;
2008-05-27 22:02:41 +08:00
session->sock = g_io_channel_unix_get_fd(chan);
2008-05-27 22:02:41 +08:00
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;
}
2008-05-27 22:02:41 +08:00
session->mtu = l2o.imtu;
2008-06-18 03:37:36 +08:00
2008-05-27 22:02:41 +08:00
session->io = g_io_add_watch(chan, flags, (GIOFunc) session_cb,
session);
g_io_channel_unref(chan);
if (avdtp_is_connected(src, dst))
2007-10-22 22:11:04 +08:00
goto proceed;
2008-06-18 03:37:36 +08:00
if (service_req_auth(src, dst, AVRCP_TARGET_UUID, auth_cb, session) < 0)
2008-05-27 22:02:41 +08:00
goto drop;
2008-06-18 03:37:36 +08:00
return;
2007-10-22 22:11:04 +08:00
proceed:
2008-06-18 03:37:36 +08:00
avctp_connect_session(session);
2008-05-27 22:02:41 +08:00
return;
drop:
2008-06-18 03:37:36 +08:00
close(session->sock);
2008-05-27 22:02:41 +08:00
}
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;
2007-10-22 22:11:04 +08:00
}
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;
2008-05-09 21:18:47 +08:00
int sk;
char address[18];
2008-05-09 21:18:47 +08:00
if (err < 0) {
avctp_unref(session);
error("AVCTP connect(%s): %s (%d)", address, strerror(-err),
-err);
return;
}
2008-05-09 21:18:47 +08:00
ba2str(&session->dst, address);
debug("AVCTP: connected to %s", address);
2008-05-09 21:18:47 +08:00
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);
2008-05-09 21:18:47 +08:00
if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) {
err = errno;
2008-05-09 21:18:47 +08:00
avctp_unref(session);
error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err),
err);
2008-05-09 21:18:47 +08:00
return;
}
2007-10-25 18:38:47 +08:00
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;
2008-05-09 21:18:47 +08:00
int err;
if (control->session)
return TRUE;
session = avctp_get(&dev->src, &dev->dst);
2007-10-25 18:38:47 +08:00
if (!session) {
error("Unable to create new AVCTP session");
return FALSE;
}
session->dev = dev;
2008-05-09 21:18:47 +08:00
session->state = AVCTP_STATE_CONNECTING;
2008-05-09 21:18:47 +08:00
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)
2007-10-22 22:11:04 +08:00
{
2008-03-28 07:07:19 +08:00
sdp_record_t *record;
gboolean tmp, master = TRUE;
GError *err = NULL;
2007-10-22 22:11:04 +08:00
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;
}
2007-10-22 22:11:04 +08:00
connection = dbus_connection_ref(conn);
2008-03-28 07:07:19 +08:00
record = avrcp_tg_record();
if (!record) {
2007-10-22 22:11:04 +08:00
error("Unable to allocate new service record");
return -1;
}
2008-03-28 07:07:19 +08:00
if (add_record_to_server(BDADDR_ANY, record) < 0) {
2007-10-22 22:11:04 +08:00
error("Unable to register AVRCP target service record");
2008-03-28 07:07:19 +08:00
sdp_record_free(record);
2007-10-22 22:11:04 +08:00
return -1;
}
2008-03-28 07:07:19 +08:00
tg_record_id = record->handle;
2007-10-22 22:11:04 +08:00
2008-03-28 07:07:19 +08:00
record = avrcp_ct_record();
if (!record) {
2007-10-22 22:11:04 +08:00
error("Unable to allocate new service record");
return -1;
}
2008-03-28 07:07:19 +08:00
if (add_record_to_server(BDADDR_ANY, record) < 0) {
2007-10-22 22:11:04 +08:00
error("Unable to register AVRCP controller service record");
2008-03-28 07:07:19 +08:00
sdp_record_free(record);
2007-10-22 22:11:04 +08:00
return -1;
}
2008-03-28 07:07:19 +08:00
ct_record_id = record->handle;
2007-10-22 22:11:04 +08:00
avctp_server = avctp_server_socket(master);
2007-10-22 22:11:04 +08:00
if (!avctp_server)
return -1;
return 0;
}
void avrcp_exit(void)
2007-10-22 22:11:04 +08:00
{
if (!avctp_server)
return;
g_io_channel_close(avctp_server);
g_io_channel_unref(avctp_server);
avctp_server = NULL;
2008-03-28 07:07:19 +08:00
remove_record_from_server(ct_record_id);
2007-10-22 22:11:04 +08:00
ct_record_id = 0;
2008-03-28 07:07:19 +08:00
remove_record_from_server(tg_record_id);
tg_record_id = 0;
2007-10-22 22:11:04 +08:00
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;
2008-07-29 04:15:27 +08:00
info("Registered interface %s on path %s",
AUDIO_CONTROL_INTERFACE, dev->path);
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;
}