diff options
Diffstat (limited to 'audio')
-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)) |