diff options
author | Johan Hedberg <johan.hedberg@nokia.com> | 2007-10-23 07:54:36 +0000 |
---|---|---|
committer | Johan Hedberg <johan.hedberg@nokia.com> | 2007-10-23 07:54:36 +0000 |
commit | 0ad3f2251089e00a57b6aa6def396e24f30ab1e4 (patch) | |
tree | 378fa05c2215381f82271ebec4f45762ec971665 /audio/control.c | |
parent | b636ccf2c9c5f5c7419c49c556b118930012d2e7 (diff) |
Integrate AVRCP further with the rest of the audio service
Diffstat (limited to 'audio/control.c')
-rw-r--r-- | audio/control.c | 277 |
1 files changed, 274 insertions, 3 deletions
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; +} |