From 6834de18b98fea0e7d370a296318115a659047af Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Fri, 24 Nov 2006 15:41:08 +0000 Subject: More implementation. E.g. signals and Play and Stop methods --- audio/headset.c | 343 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 266 insertions(+), 77 deletions(-) diff --git a/audio/headset.c b/audio/headset.c index 350f5973..4491fc2e 100644 --- a/audio/headset.c +++ b/audio/headset.c @@ -65,7 +65,7 @@ struct pending_connect { GIOChannel *io; }; -struct hs_connection { +struct headset { char address[18]; GIOChannel *rfcomm; @@ -78,7 +78,7 @@ struct hs_connection { int data_length; }; -static gboolean connect_in_progress = FALSE; +static struct pending_connect *connect_in_progress = NULL; static uint8_t config_channel = 0; @@ -92,7 +92,7 @@ static DBusConnection *connection = NULL; static GMainLoop *main_loop = NULL; -static struct hs_connection *connected_hs = NULL; +static struct headset *hs = NULL; static GIOChannel *server_sk = NULL; @@ -101,6 +101,8 @@ static DBusHandlerResult hs_connect(DBusConnection *conn, DBusMessage *msg, static DBusHandlerResult hs_disconnect(DBusConnection *conn, DBusMessage *msg); static DBusHandlerResult hs_ring(DBusConnection *conn, DBusMessage *msg); static DBusHandlerResult hs_cancel_ringing(DBusConnection *conn, DBusMessage *msg); +static DBusHandlerResult hs_play(DBusConnection *conn, DBusMessage *msg); +static DBusHandlerResult hs_stop(DBusConnection *conn, DBusMessage *msg); static int set_nonblocking(int fd, int *err) { @@ -140,7 +142,7 @@ static void pending_connect_free(struct pending_connect *c, gboolean unref_io) dbus_connection_unref(c->conn); free(c); - connect_in_progress = FALSE; + connect_in_progress = NULL; } static DBusHandlerResult error_reply(DBusConnection *conn, DBusMessage *msg, @@ -198,14 +200,14 @@ static DBusHandlerResult err_failed(DBusConnection *conn, DBusMessage *msg) static void send_gain_setting(const char *buf) { + /* Not yet implemented */ } -static void send_button_press(void) +static void send_simple_signal(const char *name) { DBusMessage *signal; - signal = dbus_message_new_signal(HEADSET_PATH, "org.bluez.Headset", - "AnswerRequested"); + signal = dbus_message_new_signal(HEADSET_PATH, "org.bluez.Headset", name); if (!signal) { error("Unable to allocate new AnswerRequested signal"); return; @@ -228,14 +230,14 @@ static void parse_headset_event(const char *buf, char *rsp, int rsp_len) buf += 2; if (!strncmp(buf, "+CKPD", 5)) - send_button_press(); + send_simple_signal("AnswerRequested"); else if (!strncmp(buf, "+VG", 3)) send_gain_setting(buf); snprintf(rsp, rsp_len, "\r\nOK\r\n"); } -static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, struct hs_connection *hs) +static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, gpointer user_data) { int sk, ret; unsigned char buf[BUF_SIZE]; @@ -252,75 +254,81 @@ static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, struct hs_conn ret = read(sk, buf, sizeof(buf) - 1); if (ret > 0) { - int free_space = sizeof(connected_hs->buf) - - connected_hs->data_start - - connected_hs->data_length - 1; + int free_space; char *cr; + free_space = sizeof(hs->buf) - hs->data_start + - hs->data_length - 1; + if (free_space < ret) { + /* Very likely that the HS is sending us garbage so + * just ignore the data and disconnect */ error("Too much data to fit incomming buffer"); goto failed; } - memcpy(&connected_hs->buf[connected_hs->data_start], buf, ret); - connected_hs->data_length += ret; + memcpy(&hs->buf[hs->data_start], buf, ret); + hs->data_length += ret; /* Make sure the data is null terminated so we can use string * functions */ - connected_hs->buf[connected_hs->data_length] = '\0'; + hs->buf[hs->data_length] = '\0'; - cr = strchr(&connected_hs->buf[connected_hs->data_start], '\r'); + cr = strchr(&hs->buf[hs->data_start], '\r'); if (cr) { char rsp[BUF_SIZE]; - int len, written = 0; - off_t cmd_len = 1 + (off_t) cr - - (off_t) &connected_hs->buf[connected_hs->data_start]; - + int len, written; + off_t cmd_len; + + cmd_len = 1 + (off_t) cr - (off_t) &hs->buf[hs->data_start]; *cr = '\0'; memset(rsp, 0, sizeof(rsp)); - parse_headset_event(&connected_hs->buf[connected_hs->data_start], + parse_headset_event(&hs->buf[hs->data_start], rsp, sizeof(rsp)); len = strlen(rsp); + written = 0; while (written < len) { int ret; ret = write(sk, &rsp[written], len - written); if (ret < 0) { - error("write: %s (%d)", errno, strerror(errno)); + error("write: %s (%d)", + strerror(errno), errno); break; } written += ret; } - connected_hs->data_start += cmd_len; - connected_hs->data_length -= cmd_len; + hs->data_start += cmd_len; + hs->data_length -= cmd_len; - if (!connected_hs->data_length) - connected_hs->data_start = 0; + if (!hs->data_length) + hs->data_start = 0; } } - if (connected_hs->ring_timer) { - g_timeout_remove(connected_hs->ring_timer); - connected_hs->ring_timer = 0; + if (hs->ring_timer) { + g_timeout_remove(hs->ring_timer); + hs->ring_timer = 0; } return TRUE; failed: info("Disconnected from %s", hs->address); + send_simple_signal("Disconnected"); if (hs->sco) g_io_channel_close(hs->sco); g_io_channel_close(chan); free(hs); - connected_hs = NULL; + hs = NULL; return FALSE; } @@ -351,40 +359,110 @@ static gboolean server_io_cb(GIOChannel *chan, GIOCondition cond, void *data) return TRUE; } - if (connected_hs || connect_in_progress) { + if (hs || connect_in_progress) { debug("Refusing new connection since one already exists"); close(cli_sk); return TRUE; } - connected_hs = malloc(sizeof(struct hs_connection)); - if (!connected_hs) { + hs = malloc(sizeof(struct headset)); + if (!hs) { error("Allocating new hs connection struct failed!"); close(cli_sk); return TRUE; } - memset(connected_hs, 0, sizeof(struct hs_connection)); + memset(hs, 0, sizeof(struct headset)); - connected_hs->rfcomm = g_io_channel_unix_new(cli_sk); - if (!connected_hs->rfcomm) { + hs->rfcomm = g_io_channel_unix_new(cli_sk); + if (!hs->rfcomm) { error("Allocating new GIOChannel failed!"); close(cli_sk); - free(connected_hs); - connected_hs = NULL; + free(hs); + hs = NULL; return TRUE; } - ba2str(&addr.rc_bdaddr, connected_hs->address); + ba2str(&addr.rc_bdaddr, hs->address); + + debug("Accepted connection from %s", hs->address); + + send_simple_signal("Connected"); + + g_io_add_watch(hs->rfcomm, G_IO_IN, (GIOFunc) rfcomm_io_cb, + hs); + + return TRUE; +} + +static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond, gpointer user_data) +{ + if (cond & G_IO_NVAL) { + g_io_channel_unref(chan); + return FALSE; + } - debug("Accepted connection from %s", connected_hs->address); + if (cond & (G_IO_HUP | G_IO_ERR)) { + error("Audio connection got disconnected"); + g_io_channel_close(chan); + hs->sco = NULL; + send_simple_signal("Stopped"); + return FALSE; + } - g_io_add_watch(connected_hs->rfcomm, G_IO_IN, (GIOFunc) rfcomm_io_cb, - connected_hs); + debug("sco_io_cb: Unhandled IO condition"); return TRUE; } +static gboolean sco_connect_cb(GIOChannel *chan, GIOCondition cond, + struct pending_connect *c) +{ + int ret, sk, err; + DBusMessage *reply; + socklen_t len; + + if (cond & G_IO_NVAL) { + g_io_channel_unref(chan); + return FALSE; + } + + sk = g_io_channel_unix_get_fd(chan); + + 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("connect(): %s (%d)", strerror(ret), ret); + goto failed; + } + + hs->sco = chan; + g_io_add_watch(chan, 0, sco_io_cb, NULL); + + reply = dbus_message_new_method_return(c->msg); + if (reply) { + dbus_connection_send(c->conn, reply, NULL); + dbus_message_unref(reply); + } + + pending_connect_free(c, FALSE); + + send_simple_signal("Playing"); + + return FALSE; + +failed: + err_connect_failed(c->conn, c->msg, err); + pending_connect_free(c, TRUE); + + return FALSE; +} static gboolean rfcomm_connect_cb(GIOChannel *chan, GIOCondition cond, struct pending_connect *c) { @@ -411,21 +489,23 @@ static gboolean rfcomm_connect_cb(GIOChannel *chan, GIOCondition cond, struct pe goto failed; } - connected_hs = malloc(sizeof(struct hs_connection)); - if (!connected_hs) { + hs = malloc(sizeof(struct headset)); + if (!hs) { err = ENOMEM; error("Allocating new hs connection struct failed!"); goto failed; } - memset(connected_hs, 0, sizeof(struct hs_connection)); + memset(hs, 0, sizeof(struct headset)); - ba2str(&c->bda, connected_hs->address); - connected_hs->rfcomm = chan; + ba2str(&c->bda, hs->address); + hs->rfcomm = chan; - debug("Connected to %s", connected_hs->address); + send_simple_signal("Connected"); - g_io_add_watch(chan, G_IO_IN, (GIOFunc) rfcomm_io_cb, connected_hs); + debug("Connected to %s", hs->address); + + g_io_add_watch(chan, G_IO_IN, (GIOFunc) rfcomm_io_cb, hs); if (c->msg) { DBusMessage *reply; @@ -773,13 +853,13 @@ static DBusHandlerResult stop_message(DBusConnection *conn, dbus_message_unref(reply); - if (connected_hs) { - if (connected_hs->sco) - g_io_channel_close(connected_hs->sco); - if (connected_hs->rfcomm) - g_io_channel_close(connected_hs->rfcomm); - free(connected_hs); - connected_hs = NULL; + if (hs) { + if (hs->sco) + g_io_channel_close(hs->sco); + if (hs->rfcomm) + g_io_channel_close(hs->rfcomm); + free(hs); + hs = NULL; } if (!config_channel && record_id) { @@ -852,7 +932,11 @@ static DBusHandlerResult hs_message(DBusConnection *conn, if (strcmp(member, "CancelRinging") == 0) return hs_cancel_ringing(conn, msg); - /* Handle Headset interface methods here */ + if (strcmp(member, "Play") == 0) + return hs_play(conn, msg); + + if (strcmp(member, "Stop") == 0) + return hs_stop(conn, msg); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -1096,22 +1180,24 @@ static DBusHandlerResult hs_disconnect(DBusConnection *conn, DBusMessage *msg) return DBUS_HANDLER_RESULT_HANDLED; } - if (!connected_hs || strcasecmp(address, connected_hs->address) != 0) + if (!hs || strcasecmp(address, hs->address) != 0) return err_not_connected(conn, msg); reply = dbus_message_new_method_return(msg); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; - if (connected_hs->sco) - g_io_channel_close(connected_hs->sco); - if (connected_hs->rfcomm) - g_io_channel_close(connected_hs->rfcomm); + if (hs->sco) + g_io_channel_close(hs->sco); + if (hs->rfcomm) + g_io_channel_close(hs->rfcomm); - info("Disconnected from %s", connected_hs->address); + info("Disconnected from %s", hs->address); + + send_simple_signal("Disconnected"); - free(connected_hs); - connected_hs = NULL; + free(hs); + hs = NULL; dbus_connection_send(conn, reply, NULL); @@ -1143,7 +1229,7 @@ static DBusHandlerResult hs_connect(DBusConnection *conn, DBusMessage *msg, } } - if (connected_hs) + if (hs) return err_already_connected(conn, msg); c = malloc(sizeof(struct pending_connect)); @@ -1152,7 +1238,7 @@ static DBusHandlerResult hs_connect(DBusConnection *conn, DBusMessage *msg, return DBUS_HANDLER_RESULT_NEED_MEMORY; } - connect_in_progress = TRUE; + connect_in_progress = c; memset(c, 0, sizeof(struct pending_connect)); @@ -1192,7 +1278,7 @@ static int send_ring(GIOChannel *io) const char *ring_str = "\r\nRING\r\n"; int sk, written, len; - sk = g_io_channel_unix_get_fd(connected_hs->rfcomm); + sk = g_io_channel_unix_get_fd(hs->rfcomm); len = strlen(ring_str); written = 0; @@ -1213,7 +1299,7 @@ static int send_ring(GIOChannel *io) static gboolean ring_timer(gpointer user_data) { - if (send_ring(connected_hs->rfcomm) < 0) + if (send_ring(hs->rfcomm) < 0) error("Sending RING failed"); return TRUE; @@ -1223,24 +1309,24 @@ static DBusHandlerResult hs_ring(DBusConnection *conn, DBusMessage *msg) { DBusMessage *reply; - if (!connected_hs) + if (!hs) return err_not_connected(conn, msg); reply = dbus_message_new_method_return(msg); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; - if (connected_hs->ring_timer) { + if (hs->ring_timer) { debug("Got Ring method call while ringing already in progress"); goto done; } - if (send_ring(connected_hs->rfcomm) < 0) { + if (send_ring(hs->rfcomm) < 0) { dbus_message_unref(reply); return err_failed(conn, msg); } - connected_hs->ring_timer = g_timeout_add(RING_INTERVAL, ring_timer, NULL); + hs->ring_timer = g_timeout_add(RING_INTERVAL, ring_timer, NULL); done: dbus_connection_send(conn, reply, NULL); @@ -1253,20 +1339,20 @@ static DBusHandlerResult hs_cancel_ringing(DBusConnection *conn, DBusMessage *ms { DBusMessage *reply; - if (!connected_hs) + if (!hs) return err_not_connected(conn, msg); reply = dbus_message_new_method_return(msg); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; - if (!connected_hs->ring_timer) { + if (!hs->ring_timer) { debug("Got CancelRinging method call but ringing is not in progress"); goto done; } - g_timeout_remove(connected_hs->ring_timer); - connected_hs->ring_timer = 0; + g_timeout_remove(hs->ring_timer); + hs->ring_timer = 0; done: dbus_connection_send(conn, reply, NULL); @@ -1275,6 +1361,109 @@ done: return DBUS_HANDLER_RESULT_HANDLED; } +static DBusHandlerResult hs_play(DBusConnection *conn, DBusMessage *msg) +{ + struct sockaddr_sco addr; + struct pending_connect *c; + int sk, err; + + if (!hs) + return err_not_connected(conn, msg); + + if (hs->sco) + return err_already_connected(conn, msg); + + c = malloc(sizeof(struct pending_connect)); + if (!c) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + memset(c, 0, sizeof(struct pending_connect)); + + c->conn = dbus_connection_ref(conn); + c->msg = dbus_message_ref(msg); + + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sk < 0) { + err = errno; + error("socket(BTPROTO_SCO): %s (%d)", strerror(err), err); + err_connect_failed(conn, msg, err); + goto failed; + } + + c->io = g_io_channel_unix_new(sk); + if (!c->io) { + close(sk); + pending_connect_free(c, TRUE); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + g_io_channel_set_close_on_unref(c->io, TRUE); + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, BDADDR_ANY); + if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + err = errno; + error("socket(BTPROTO_SCO): %s (%d)", strerror(err), err); + err_connect_failed(conn, msg, err); + goto failed; + } + + if (set_nonblocking(sk, &err) < 0) { + err_connect_failed(conn, msg, err); + goto failed; + } + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + str2ba(hs->address, &addr.sco_bdaddr); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + if (!(errno == EAGAIN || errno == EINPROGRESS)) { + err = errno; + error("connect: %s (%d)", strerror(errno), errno); + goto failed; + } + + debug("Connect in progress"); + + g_io_add_watch(c->io, G_IO_OUT, (GIOFunc) sco_connect_cb, c); + } else { + debug("Connect succeeded with first try"); + sco_connect_cb(c->io, G_IO_OUT, c); + } + + return 0; + +failed: + if (c) + pending_connect_free(c, TRUE); + close(sk); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult hs_stop(DBusConnection *conn, DBusMessage *msg) +{ + DBusMessage *reply; + + if (!hs || !hs->sco) + return err_not_connected(conn, msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + g_io_channel_close(hs->sco); + hs->sco = NULL; + + send_simple_signal("Stopped"); + + dbus_connection_send(conn, reply, NULL); + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + int main(int argc, char *argv[]) { struct sigaction sa; -- cgit