diff options
| -rw-r--r-- | audio/avdtp.c | 21 | ||||
| -rw-r--r-- | audio/control.c | 277 | ||||
| -rw-r--r-- | audio/control.h | 11 | ||||
| -rw-r--r-- | audio/device.c | 9 | ||||
| -rw-r--r-- | audio/manager.c | 37 | 
5 files changed, 342 insertions, 13 deletions
| diff --git a/audio/avdtp.c b/audio/avdtp.c index 8e2094bf..222c1dea 100644 --- a/audio/avdtp.c +++ b/audio/avdtp.c @@ -43,6 +43,7 @@  #include "device.h"  #include "manager.h" +#include "control.h"  #include "avdtp.h"  #include <bluetooth/l2cap.h> @@ -731,6 +732,13 @@ static void release_stream(struct avdtp_stream *stream, struct avdtp *session)  static void connection_lost(struct avdtp *session, int err)  { +	struct device *dev; + +	dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE, +					FALSE); +	if (dev) +		avrcp_disconnect(dev); +  	if (session->state == AVDTP_SESSION_STATE_CONNECTED) {  		char address[18]; @@ -1550,6 +1558,8 @@ static gboolean l2cap_connect_cb(GIOChannel *chan, GIOCondition cond,  	}  	if (session->state == AVDTP_SESSION_STATE_CONNECTING) { +		struct device *dev; +  		session->mtu = l2o.imtu;  		session->buf = g_malloc0(session->mtu);  		session->state = AVDTP_SESSION_STATE_CONNECTED; @@ -1557,6 +1567,11 @@ static gboolean l2cap_connect_cb(GIOChannel *chan, GIOCondition cond,  						G_IO_IN | G_IO_ERR | G_IO_HUP  						| G_IO_NVAL,  						(GIOFunc) session_cb, session); + +		dev = manager_find_device(&session->dst, +						AUDIO_CONTROL_INTERFACE, FALSE); +		if (dev) +			avrcp_connect(dev);  	}  	else if (session->pending_open)  		handle_transport_connect(session, sk, l2o.imtu); @@ -2682,6 +2697,7 @@ static void auth_cb(DBusPendingCall *call, void *data)  	struct avdtp *session = data;  	DBusMessage *reply = dbus_pending_call_steal_reply(call);  	DBusError err; +	struct device *dev;  	dbus_pending_call_unref(session->pending_auth);  	session->pending_auth = NULL; @@ -2712,6 +2728,11 @@ static void auth_cb(DBusPendingCall *call, void *data)  	session->state = AVDTP_SESSION_STATE_CONNECTED; +	dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE, +					FALSE); +	if (dev) +		avrcp_connect(dev); +  	g_source_remove(session->io);  	io = g_io_channel_unix_new(session->sock); diff --git a/audio/control.c b/audio/control.c index c61a79dd..83f48df4 100644 --- a/audio/control.c +++ b/audio/control.c @@ -41,6 +41,8 @@  #include <bluetooth/sdp_lib.h>  #include <bluetooth/l2cap.h> +#include "dbus.h" +#include "dbus-helper.h"  #include "logging.h"  #include "device.h"  #include "manager.h" @@ -58,6 +60,12 @@ 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 { @@ -99,6 +107,10 @@ struct avrcp_header {  #endif  struct avctp { +	struct device *dev; + +	avctp_state_t state; +  	bdaddr_t src;  	bdaddr_t dst; @@ -111,6 +123,11 @@ struct avctp {  	DBusPendingCall *pending_auth;  }; +struct control { +	struct avctp *session; +}; + +  static int avrcp_ct_record(sdp_buf_t *buf)  {  	sdp_list_t *svclass_id, *pfseq, *apseq, *root; @@ -314,10 +331,25 @@ static struct avctp *find_session(bdaddr_t *src, bdaddr_t *dst)  static void avctp_unref(struct avctp *session)  {  	sessions = g_slist_remove(sessions, session); + +	if (session->pending_auth) { +		manager_cancel_authorize(&session->dst, AVRCP_TARGET_UUID, +						NULL); +		dbus_pending_call_cancel(session->pending_auth); +		dbus_pending_call_unref(session->pending_auth); +	} + +	if (session->state == AVCTP_STATE_CONNECTED) +		dbus_connection_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); +	session->dev->control->session = NULL;  	g_free(session);  } @@ -427,6 +459,16 @@ static void auth_cb(DBusPendingCall *call, void *data)  		return;  	} +	session->state = AVCTP_STATE_CONNECTED; + +	session->dev = manager_device_connected(&session->dst, +						AVRCP_TARGET_UUID); +	session->dev->control->session = session; + +	dbus_connection_emit_signal(session->dev->conn, session->dev->path, +					AUDIO_CONTROL_INTERFACE, "Connected", +					DBUS_TYPE_INVALID); +  	g_source_remove(session->io);  	io = g_io_channel_unix_new(session->sock); @@ -498,6 +540,8 @@ static gboolean avctp_server_cb(GIOChannel *chan, GIOCondition cond, void *data)  		return TRUE;  	} +	session->state = AVCTP_STATE_CONNECTING; +  	if (avdtp_is_connected(&src, &dst))  		goto proceed; @@ -513,15 +557,176 @@ proceed:  	session->sock = cli_sk;  	io = g_io_channel_unix_new(session->sock); -	if (!session->pending_auth) +	if (!session->pending_auth) { +		session->state = AVCTP_STATE_CONNECTED; +		session->dev = manager_device_connected(&dst, +							AVRCP_TARGET_UUID); +		session->dev->control->session = session;  		flags |= G_IO_IN; +		dbus_connection_emit_signal(session->dev->conn, +						session->dev->path, +						AUDIO_CONTROL_INTERFACE, +						"Connected", +						DBUS_TYPE_INVALID); +	} +  	session->io = g_io_add_watch(io, flags, (GIOFunc) session_cb, session);  	g_io_channel_unref(io);  	return TRUE;  } -int control_init(DBusConnection *conn) +static gboolean avctp_connect_cb(GIOChannel *chan, GIOCondition cond, +					gpointer data) +{ +	struct avctp *session = data; +	struct l2cap_options l2o; +	socklen_t len; +	int ret, err, sk; +	char address[18]; + +	if (cond & G_IO_NVAL) +		return FALSE; + +	sk = g_io_channel_unix_get_fd(chan); + +	ba2str(&session->dst, address); + +	len = sizeof(ret); +	if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { +		err = errno; +		error("getsockopt(SO_ERROR): %s (%d)", strerror(err), err); +		goto failed; +	} + +	if (ret != 0) { +		err = ret; +		error("AVCTP connect(%s): %s (%d)", address, strerror(err), +				err); +		goto failed; +	} + +	if (cond & G_IO_HUP) { +		err = EIO; +		goto failed; +	} + +	debug("AVCTP: connected to %s", address); + +	memset(&l2o, 0, sizeof(l2o)); +	len = sizeof(l2o); +	if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, +				&len) < 0) { +		err = errno; +		error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err), +				err); +		goto failed; +	} + +	dbus_connection_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); +	return FALSE; + +failed: +	close(sk); + +	avctp_unref(session); + +	return FALSE; +} + +gboolean avrcp_connect(struct device *dev) +{ +	struct control *control = dev->control; +	struct avctp *session; +	struct sockaddr_l2 l2a; +	GIOChannel *io; +	int sk; + +	if (control->session) +		return TRUE; + +	session = avctp_get(&dev->src, &dev->dst); +	session->dev = dev; + +	memset(&l2a, 0, sizeof(l2a)); +	l2a.l2_family = AF_BLUETOOTH; +	bacpy(&l2a.l2_bdaddr, &dev->src); + +	sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); +	if (sk < 0) { +		error("Cannot create L2CAP socket. %s(%d)", strerror(errno), +				errno); +		goto failed; +	} + +	if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) { +		error("Bind failed. %s (%d)", strerror(errno), errno); +		goto failed; +	} + +	memset(&l2a, 0, sizeof(l2a)); +	l2a.l2_family = AF_BLUETOOTH; +	bacpy(&l2a.l2_bdaddr, &dev->dst); +	l2a.l2_psm = htobs(AVCTP_PSM); + +	if (set_nonblocking(sk) < 0) { +		error("Set non blocking: %s (%d)", strerror(errno), errno); +		goto failed; +	} + +	io = g_io_channel_unix_new(sk); +	g_io_channel_set_close_on_unref(io, FALSE); +	session->sock = sk; + +	if (connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) { +		if (!(errno == EAGAIN || errno == EINPROGRESS)) { +			error("Connect failed. %s(%d)", strerror(errno), +					errno); +			g_io_channel_close(io); +			g_io_channel_unref(io); +			goto failed; +		} + +		session->state = AVCTP_STATE_CONNECTING; + +		g_io_add_watch(io, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, +				(GIOFunc) avctp_connect_cb, session); +	} else +		avctp_connect_cb(io, G_IO_OUT, session); + +	g_io_channel_unref(io); + +	control->session = session; + +	return TRUE; + +failed: +	avctp_unref(session); +	return FALSE; +} + +void avrcp_disconnect(struct 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)  {  	sdp_buf_t buf; @@ -566,7 +771,7 @@ int control_init(DBusConnection *conn)  	return 0;  } -void control_exit(void) +void avrcp_exit(void)  {  	if (!avctp_server)  		return; @@ -585,3 +790,69 @@ void control_exit(void)  	connection = NULL;  } +static DBusHandlerResult control_is_connected(DBusConnection *conn, +						DBusMessage *msg, +						void *data) +{ +	struct device *device = data; +	struct control *control = device->control; +	DBusMessage *reply; +	dbus_bool_t connected; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	connected = (control->session != NULL); + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, +					DBUS_TYPE_INVALID); + +	send_message_and_unref(conn, reply); + +	return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusMethodVTable control_methods[] = { +	{ "IsConnected",	control_is_connected,	"",	"b"	}, +	{ NULL, NULL, NULL, NULL } +}; + +static DBusSignalVTable control_signals[] = { +	{ "Connected",			""	}, +	{ "Disconnected",		""	}, +	{ NULL, NULL } +}; + +struct control *control_init(struct device *dev) +{ +	if (!dbus_connection_register_interface(dev->conn, dev->path, +						AUDIO_CONTROL_INTERFACE, +						control_methods, +						control_signals, NULL)) +		return NULL; + +	return g_new0(struct control, 1); +} + +void control_free(struct 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 device *dev) +{ +	struct control *control = dev->control; + +	if (control->session && +			control->session->state != AVCTP_STATE_DISCONNECTED) +		return TRUE; + +	return FALSE; +} diff --git a/audio/control.h b/audio/control.h index 79d66ec7..aa183e02 100644 --- a/audio/control.h +++ b/audio/control.h @@ -23,5 +23,12 @@  #define AUDIO_CONTROL_INTERFACE "org.bluez.audio.Control" -int control_init(DBusConnection *conn); -void control_exit(void); +int avrcp_init(DBusConnection *conn); +void avrcp_exit(void); + +gboolean avrcp_connect(struct device *dev); +void avrcp_disconnect(struct device *dev); + +struct control *control_init(struct device *dev); +void control_free(struct device *dev); +gboolean control_is_active(struct device *dev); diff --git a/audio/device.c b/audio/device.c index 9b76e019..a2047b9e 100644 --- a/audio/device.c +++ b/audio/device.c @@ -49,6 +49,7 @@  #include "ipc.h"  #include "device.h"  #include "avdtp.h" +#include "control.h"  #include "headset.h"  #include "sink.h" @@ -185,6 +186,9 @@ static void device_free(struct device *dev)  	if (dev->sink)  		sink_free(dev); +	if (dev->control) +		control_free(dev); +  	if (dev->conn)  		dbus_connection_unref(dev->conn); @@ -486,6 +490,8 @@ uint8_t device_get_state(struct device *dev)  		hs_state = headset_get_state(dev);  		return hs_to_ipc_state(hs_state);  	} +	else if (dev->control && control_is_active(dev)) +		return STATE_CONNECTED;  	return STATE_DISCONNECTED;  } @@ -509,6 +515,9 @@ gboolean device_is_connected(struct device *dev, const char *interface)  	else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset &&  			headset_is_active(dev))  		return TRUE; +	else if (!strcmp(interface, AUDIO_CONTROL_INTERFACE) && dev->headset && +			control_is_active(dev)) +		return TRUE;  	return FALSE;  } diff --git a/audio/manager.c b/audio/manager.c index 2b628544..74588c78 100644 --- a/audio/manager.c +++ b/audio/manager.c @@ -279,9 +279,17 @@ static void handle_record(sdp_record_t *record, struct device *device)  		break;  	case AV_REMOTE_SVCLASS_ID:  		debug("Found AV Remote"); +		if (device->control == NULL) +			device->control = control_init(device); +		if (device->sink && sink_is_active(device)) +			avrcp_connect(device);  		break;  	case AV_REMOTE_TARGET_SVCLASS_ID:  		debug("Found AV Target"); +		if (device->control == NULL) +			device->control = control_init(device); +		if (device->sink && sink_is_active(device)) +			avrcp_connect(device);  		break;  	default:  		debug("Unrecognized UUID: 0x%04X", uuid16); @@ -639,8 +647,7 @@ struct device *manager_device_connected(bdaddr_t *bda, const char *uuid)  			return NULL;  		headset = TRUE; -	} -	else if (!strcmp(uuid, A2DP_SOURCE_UUID)) { +	} else if (!strcmp(uuid, A2DP_SOURCE_UUID)) {  		if (device->sink)  			return device; @@ -648,6 +655,14 @@ struct device *manager_device_connected(bdaddr_t *bda, const char *uuid)  		if (!device->sink)  			return NULL; +	} else if (!strcmp(uuid, AVRCP_TARGET_UUID)) { +		if (device->control) +			return device; + +		device->control = control_init(device); + +		if (!device->control) +			return NULL;  	} else  		return NULL; @@ -1050,10 +1065,12 @@ static void parse_stored_devices(char *key, char *value, void *data)  	/* Change storage to source adapter */  	bacpy(&device->store, src); -	if (strstr(value, "headset")) +	if (enabled->headset && strstr(value, "headset"))  		device->headset = headset_init(device, NULL, 0); -	if (strstr(value, "sink")) +	if (enabled->sink && strstr(value, "sink"))  		device->sink = sink_init(device); +	if (enabled->control && strstr(value, "control")) +		device->control = control_init(device);  	add_device(device, FALSE);  } @@ -1621,7 +1638,7 @@ int audio_init(DBusConnection *conn, struct enabled_interfaces *enable,  	if (a2dp_init(conn, sources, sinks) < 0)  		goto failed; -	if (enable->control && control_init(conn) < 0) +	if (enable->control && avrcp_init(conn) < 0)  		goto failed;  	if (!dbus_connection_register_interface(conn, AUDIO_MANAGER_PATH, @@ -1761,15 +1778,19 @@ struct device *manager_find_device(bdaddr_t *bda, const char *interface,  			continue;  		if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface) -			&& !dev->headset) +				&& !dev->headset)  			continue;  		if (interface && !strcmp(AUDIO_SINK_INTERFACE, interface) -			&& !dev->sink) +				&& !dev->sink)  			continue;  		if (interface && !strcmp(AUDIO_SOURCE_INTERFACE, interface) -			&& !dev->source) +				&& !dev->source) +			continue; + +		if (interface && !strcmp(AUDIO_CONTROL_INTERFACE, interface) +				&& !dev->control)  			continue;  		if (connected && !device_is_connected(dev, interface)) | 
