summaryrefslogtreecommitdiffstats
path: root/audio/control.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/control.c')
-rw-r--r--audio/control.c277
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;
+}