diff options
author | Johan Hedberg <johan.hedberg@nokia.com> | 2006-11-13 23:04:16 +0000 |
---|---|---|
committer | Johan Hedberg <johan.hedberg@nokia.com> | 2006-11-13 23:04:16 +0000 |
commit | 4cda48c7b528b3bf79dfcbaf23f982b15b6fa897 (patch) | |
tree | 3832959c503df5aa5c8db36254389a18feb3796e | |
parent | bab9fc85aa30fc102603d3d5d686afe613579752 (diff) |
Very basic functionality for bt.headsetd
-rw-r--r-- | audio/headset.c | 503 |
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; } |