summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@nokia.com>2006-11-13 23:04:16 +0000
committerJohan Hedberg <johan.hedberg@nokia.com>2006-11-13 23:04:16 +0000
commit4cda48c7b528b3bf79dfcbaf23f982b15b6fa897 (patch)
tree3832959c503df5aa5c8db36254389a18feb3796e
parentbab9fc85aa30fc102603d3d5d686afe613579752 (diff)
Very basic functionality for bt.headsetd
-rw-r--r--audio/headset.c503
1 files changed, 503 insertions, 0 deletions
diff --git a/audio/headset.c b/audio/headset.c
index c04bb251..307b9212 100644
--- a/audio/headset.c
+++ b/audio/headset.c
@@ -27,8 +27,511 @@
#include <stdio.h>
#include <errno.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <dbus/dbus.h>
+
+#include "dbus.h"
+#include "logging.h"
+#include "glib-ectomy.c"
+
+#define HEADSET_PATH "/org/bluez/headset"
+
+struct pending_connect {
+ bdaddr_t bda;
+ DBusConnection *conn;
+ DBusMessage *msg;
+ GIOChannel *io;
+};
+
+struct hs_connection {
+ char address[18];
+ GIOChannel *rfcomm;
+ GIOChannel *sco;
+};
+
+static char *on_init_bda = NULL;
+static int on_init_ch = 2;
+
+static int started = 0;
+
+static DBusConnection *connection = NULL;
+
+static GMainLoop *main_loop = NULL;
+
+static struct hs_connection *connected_hs = NULL;
+
+static int set_nonblocking(int fd, int *err)
+{
+ long arg;
+
+ arg = fcntl(fd, F_GETFL);
+ if (arg < 0) {
+ if (err)
+ *err = errno;
+ error("fcntl(F_GETFL): %s (%d)", strerror(errno), errno);
+ return -1;
+ }
+
+ /* Return if already nonblocking */
+ if (arg & O_NONBLOCK)
+ return 0;
+
+ arg |= O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, arg) < 0) {
+ if (err)
+ *err = errno;
+ error("fcntl(F_SETFL, O_NONBLOCK): %s (%d)",
+ strerror(errno), errno);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void pending_connect_free(struct pending_connect *c, gboolean unref_io)
+{
+ if (unref_io && c->io)
+ g_io_channel_unref(c->io);
+ if (c->msg)
+ dbus_message_unref(c->msg);
+ if (c->conn)
+ dbus_connection_unref(c->conn);
+ free(c);
+}
+
+static void connect_failed(DBusConnection *conn, DBusMessage *msg, int err)
+{
+ DBusMessage *derr;
+
+ if (!conn)
+ return;
+
+ derr = dbus_message_new_error(msg, "org.bluez.Error.ConnectFailed",
+ strerror(err));
+ if (!derr) {
+ error("Unable to allocate new error return");
+ return;
+ }
+
+ dbus_connection_send(conn, derr, NULL);
+}
+
+static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, struct hs_connection *hs)
+{
+ int sk, ret;
+ unsigned char buf[1024];
+
+ debug("rfcomm_io_cb");
+
+ if (cond & G_IO_NVAL) {
+ g_io_channel_unref(chan);
+ return FALSE;
+ }
+
+ if (cond & (G_IO_ERR | G_IO_HUP))
+ goto failed;
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ ret = read(sk, buf, sizeof(buf) - 1);
+ if (ret > 0) {
+ buf[ret] = '\0';
+ printf("%s\n", buf);
+ }
+
+ return TRUE;
+
+failed:
+ info("Disconnected from %s", hs->address);
+ if (hs->sco)
+ g_io_channel_close(hs->sco);
+ g_io_channel_close(chan);
+ free(hs);
+ connected_hs = NULL;
+ return FALSE;
+}
+
+static gboolean rfcomm_connect_cb(GIOChannel *chan, GIOCondition cond, struct pending_connect *c)
+{
+ int sk, ret, err;
+ socklen_t len;
+ struct hs_connection *hs;
+
+ 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 = malloc(sizeof(struct hs_connection));
+ if (!hs) {
+ err = ENOMEM;
+ error("Allocating new hs connection struct failed!");
+ goto failed;
+ }
+
+ memset(hs, 0, sizeof(struct hs_connection));
+
+ ba2str(&c->bda, hs->address);
+ hs->rfcomm = chan;
+
+ debug("rfcomm_connect_cb: connected to %s", hs->address);
+
+ connected_hs = hs;
+
+ g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_IN | G_IO_NVAL,
+ (GIOFunc) rfcomm_io_cb, hs);
+
+ pending_connect_free(c, FALSE);
+
+ return FALSE;
+
+failed:
+ connect_failed(c->conn, c->msg, err);
+ pending_connect_free(c, TRUE);
+
+ return FALSE;
+}
+
+static int rfcomm_connect(DBusConnection *conn, DBusMessage *msg, bdaddr_t *src,
+ const char *bda, uint8_t ch, int *err)
+{
+ struct pending_connect *c = NULL;
+ struct sockaddr_rc addr;
+ int sk;
+
+ debug("Connecting to %s channel %d", bda, ch);
+
+ sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+ if (sk < 0) {
+ if (err)
+ *err = errno;
+ error("socket: %s (%d)", strerror(errno), errno);
+ return -1;
+ }
+
+ c = malloc(sizeof(struct pending_connect));
+ if (!c) {
+ if (err)
+ *err = ENOMEM;
+ goto failed;
+ }
+
+ memset(c, 0, sizeof(struct pending_connect));
+
+ memset(&addr, 0, sizeof(addr));
+ addr.rc_family = AF_BLUETOOTH;
+ bacpy(&addr.rc_bdaddr, src);
+ addr.rc_channel = 0;
+
+ if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ if (err)
+ *err = errno;
+ error("bind: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ if (set_nonblocking(sk, err) < 0)
+ goto failed;
+
+ str2ba(bda, &c->bda);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.rc_family = AF_BLUETOOTH;
+ bacpy(&addr.rc_bdaddr, &c->bda);
+ addr.rc_channel = ch;
+
+ if (conn && msg) {
+ c->conn = dbus_connection_ref(conn);
+ c->msg = dbus_message_ref(msg);
+ }
+
+ c->io = g_io_channel_unix_new(sk);
+ g_io_channel_set_close_on_unref(c->io, TRUE);
+
+ if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ if (!(errno == EAGAIN || errno == EINPROGRESS)) {
+ if (err)
+ *err = errno;
+ error("connect() failed: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ debug("Connect in progress");
+
+ g_io_add_watch(c->io, G_IO_OUT, (GIOFunc) rfcomm_connect_cb, c);
+ } else {
+ debug("Connect succeeded with first try");
+ rfcomm_connect_cb(c->io, G_IO_OUT, c);
+ }
+
+ return 0;
+
+failed:
+ if (c)
+ pending_connect_free(c, TRUE);
+ if (sk >= 0)
+ close(sk);
+ return -1;
+}
+
+static void sig_term(int sig)
+{
+ g_main_quit(main_loop);
+}
+
+static DBusHandlerResult start_message(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessage *reply;
+
+ info("Starting headset service");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply) {
+ error("Can't create reply message");
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+
+ dbus_connection_send(conn, reply, NULL);
+
+ dbus_message_unref(reply);
+
+ started = 1;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult stop_message(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessage *reply;
+
+ info("Stopping headset service");
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply) {
+ error("Can't create reply message");
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+
+ dbus_connection_send(conn, reply, NULL);
+
+ dbus_message_unref(reply);
+
+ started = 0;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult release_message(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessage *reply;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply) {
+ error("Can't create reply message");
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+
+ dbus_connection_send(conn, reply, NULL);
+
+ dbus_message_unref(reply);
+
+ info("Got Release method. Exiting.");
+
+ raise(SIGTERM);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult hs_message(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *interface;
+ const char *member;
+
+ interface = dbus_message_get_interface(msg);
+ member = dbus_message_get_member(msg);
+
+ if (strcmp(interface, "org.bluez.ServiceAgent") == 0) {
+ if (strcmp(member, "Start") == 0)
+ return start_message(conn, msg, data);
+ if (strcmp(member, "Stop") == 0)
+ return stop_message(conn, msg, data);
+ if (strcmp(member, "Release") == 0)
+ return release_message(conn, msg, data);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ if (strcmp(interface, "org.bluez.Headset") != 0)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ /* Handle Headset interface methods here */
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static const DBusObjectPathVTable hs_table = {
+ .message_function = hs_message,
+};
+
+static void register_reply(DBusPendingCall *call, void *data)
+{
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError derr;
+ int err;
+
+ dbus_error_init(&derr);
+ if (dbus_set_error_from_message(&derr, reply)) {
+ error("Registering failed: %s", derr.message);
+ dbus_error_free(&derr);
+ raise(SIGTERM);
+ }
+ else
+ debug("Successfully registered");
+
+ dbus_message_unref(reply);
+
+ if (!on_init_bda)
+ return;
+
+ if (rfcomm_connect(NULL, NULL, BDADDR_ANY, on_init_bda, on_init_ch, &err) < 0)
+ exit(1);
+}
+
+
+int headset_dbus_init(char *bda)
+{
+ DBusMessage *msg;
+ DBusPendingCall *pending;
+ const char *name = "Headset service";
+ const char *description = "A service for headsets";
+ const char *hs_path = HEADSET_PATH;
+
+ connection = init_dbus(NULL, NULL, NULL);
+ if (!connection)
+ return -1;
+
+ if (!dbus_connection_register_object_path(connection, hs_path,
+ &hs_table, NULL)) {
+ error("D-Bus failed to register %s path", hs_path);
+ return -1;
+ }
+
+ msg = dbus_message_new_method_call("org.bluez", "/org/bluez",
+ "org.bluez.Manager", "RegisterService");
+ if (!msg) {
+ error("Can't allocate new method call");
+ return -1;
+ }
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &hs_path,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &description,
+ DBUS_TYPE_INVALID);
+
+ if (!dbus_connection_send_with_reply(connection, msg, &pending, -1)) {
+ error("Sending Register method call failed");
+ dbus_message_unref(msg);
+ return -1;
+ }
+
+ dbus_pending_call_set_notify(pending, register_reply, NULL, NULL);
+ dbus_message_unref(msg);
+
+ return 0;
+}
+
int main(int argc, char *argv[])
{
+ struct sigaction sa;
+ int opt, daemonize = 1;
+
+ while ((opt = getopt(argc, argv, "nc:")) != EOF) {
+ switch (opt) {
+ case 'n':
+ daemonize = 0;
+ break;
+
+ case 'c':
+ on_init_ch = strtol(optarg, NULL, 0);
+ if (on_init_ch < 0 || on_init_ch > 255) {
+ error("Invalid channel");
+ exit(1);
+ }
+ break;
+
+ default:
+ printf("Usage: %s [-n] [-c channel] [bdaddr]\n", argv[0]);
+ exit(1);
+ }
+ }
+
+ if (argv[optind]) {
+ on_init_bda = argv[optind];
+ daemonize = 0;
+ }
+
+ if (daemonize && daemon(0, 0)) {
+ error("Can't daemonize: %s (%d)", strerror(errno), errno);
+ exit(1);
+ }
+
+ start_logging("bt.headsetd", "Bluetooth Headset daemon");
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = SA_NOCLDSTOP;
+ sa.sa_handler = sig_term;
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGCHLD, &sa, NULL);
+ sigaction(SIGPIPE, &sa, NULL);
+
+ enable_debug();
+
+ main_loop = g_main_new(FALSE);
+
+ if (headset_dbus_init(NULL) < 0) {
+ error("Unable to get on D-Bus");
+ exit(1);
+ }
+
+ g_main_run(main_loop);
+
return 0;
}