diff options
author | João Paulo Rechi Vita <joao.vita@gmail.com> | 2008-08-31 09:22:36 -0300 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2008-09-11 01:12:10 +0300 |
commit | 4ae124b1c92f21bc6b089863bf9c97145f44eb99 (patch) | |
tree | 443bd635b17a9f585eceaf3c8bf75b2c023cca86 /src/modules/bluetooth | |
parent | 8b02c2fbc16db2a1d7dc34835bd939f984816f7e (diff) |
Move bluetooth proximity module to src/modules/bluetooth/
Diffstat (limited to 'src/modules/bluetooth')
-rw-r--r-- | src/modules/bluetooth/module-bluetooth-proximity.c | 489 | ||||
-rw-r--r-- | src/modules/bluetooth/proximity-helper.c | 202 |
2 files changed, 691 insertions, 0 deletions
diff --git a/src/modules/bluetooth/module-bluetooth-proximity.c b/src/modules/bluetooth/module-bluetooth-proximity.c new file mode 100644 index 00000000..5cca36e5 --- /dev/null +++ b/src/modules/bluetooth/module-bluetooth-proximity.c @@ -0,0 +1,489 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/modargs.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/start-child.h> + +#include "dbus-util.h" +#include "module-bluetooth-proximity-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Bluetooth Proximity Volume Control"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "sink=<sink name> " + "hci=<hci device> " +); + +#define DEFAULT_HCI "hci0" + +static const char* const valid_modargs[] = { + "sink", + "rssi", + "hci", + NULL, +}; + +struct bonding { + struct userdata *userdata; + char address[18]; + + pid_t pid; + int fd; + + pa_io_event *io_event; + + enum { + UNKNOWN, + FOUND, + NOT_FOUND + } state; +}; + +struct userdata { + pa_module *module; + pa_dbus_connection *dbus_connection; + + char *sink_name; + char *hci, *hci_path; + + pa_hashmap *bondings; + + unsigned n_found; + unsigned n_unknown; + + pa_bool_t muted; +}; + +static void update_volume(struct userdata *u) { + pa_assert(u); + + if (u->muted && u->n_found > 0) { + pa_sink *s; + + u->muted = FALSE; + + if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) { + pa_log_warn("Sink device '%s' not available for unmuting.", pa_strnull(u->sink_name)); + return; + } + + pa_log_info("Found %u BT devices, unmuting.", u->n_found); + pa_sink_set_mute(s, FALSE); + + } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) { + pa_sink *s; + + u->muted = TRUE; + + if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) { + pa_log_warn("Sink device '%s' not available for muting.", pa_strnull(u->sink_name)); + return; + } + + pa_log_info("No BT devices found, muting."); + pa_sink_set_mute(s, TRUE); + + } else + pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown); +} + +static void bonding_free(struct bonding *b) { + pa_assert(b); + + if (b->state == FOUND) + pa_assert_se(b->userdata->n_found-- >= 1); + + if (b->state == UNKNOWN) + pa_assert_se(b->userdata->n_unknown-- >= 1); + + if (b->pid != (pid_t) -1) { + kill(b->pid, SIGTERM); + waitpid(b->pid, NULL, 0); + } + + if (b->fd >= 0) + pa_close(b->fd); + + if (b->io_event) + b->userdata->module->core->mainloop->io_free(b->io_event); + + pa_xfree(b); +} + +static void io_event_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct bonding *b = userdata; + char x; + ssize_t r; + + pa_assert(b); + + if ((r = read(fd, &x, 1)) <= 0) { + pa_log_warn("Child watching '%s' died abnormally: %s", b->address, r == 0 ? "EOF" : pa_cstrerror(errno)); + + pa_assert_se(pa_hashmap_remove(b->userdata->bondings, b->address) == b); + bonding_free(b); + return; + } + + pa_assert_se(r == 1); + + if (b->state == UNKNOWN) + pa_assert_se(b->userdata->n_unknown-- >= 1); + + if (x == '+') { + pa_assert(b->state == UNKNOWN || b->state == NOT_FOUND); + + b->state = FOUND; + b->userdata->n_found++; + + pa_log_info("Device '%s' is alive.", b->address); + + } else { + pa_assert(x == '-'); + pa_assert(b->state == UNKNOWN || b->state == FOUND); + + if (b->state == FOUND) + b->userdata->n_found--; + + b->state = NOT_FOUND; + + pa_log_info("Device '%s' is dead.", b->address); + } + + update_volume(b->userdata); +} + +static struct bonding* bonding_new(struct userdata *u, const char *a) { + struct bonding *b = NULL; + DBusMessage *m = NULL, *r = NULL; + DBusError e; + const char *class; + + pa_assert(u); + pa_assert(a); + + pa_return_val_if_fail(strlen(a) == 17, NULL); + pa_return_val_if_fail(!pa_hashmap_get(u->bondings, a), NULL); + + dbus_error_init(&e); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "GetRemoteMajorClass")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID)); + r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), m, -1, &e); + + if (!r) { + pa_log("org.bluez.Adapter.GetRemoteMajorClass(%s) failed: %s", a, e.message); + goto fail; + } + + if (!(dbus_message_get_args(r, &e, DBUS_TYPE_STRING, &class, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.GetRemoteMajorClass signal: %s", e.message); + goto fail; + } + + if (strcmp(class, "phone")) { + pa_log_info("Found device '%s' of class '%s', ignoring.", a, class); + goto fail; + } + + b = pa_xnew(struct bonding, 1); + b->userdata = u; + pa_strlcpy(b->address, a, sizeof(b->address)); + b->pid = (pid_t) -1; + b->fd = -1; + b->io_event = NULL; + b->state = UNKNOWN; + u->n_unknown ++; + + pa_log_info("Watching device '%s' of class '%s'.", b->address, class); + + if ((b->fd = pa_start_child_for_read(PA_BT_PROXIMITY_HELPER, a, &b->pid)) < 0) { + pa_log("Failed to start helper tool."); + goto fail; + } + + b->io_event = u->module->core->mainloop->io_new( + u->module->core->mainloop, + b->fd, + PA_IO_EVENT_INPUT, + io_event_cb, + b); + + dbus_message_unref(m); + dbus_message_unref(r); + + pa_hashmap_put(u->bondings, b->address, b); + + return b; + +fail: + if (m) + dbus_message_unref(m); + if (r) + dbus_message_unref(r); + + if (b) + bonding_free(b); + + dbus_error_free(&e); + return NULL; +} + +static void bonding_remove(struct userdata *u, const char *a) { + struct bonding *b; + pa_assert(u); + + pa_return_if_fail((b = pa_hashmap_remove(u->bondings, a))); + + pa_log_info("No longer watching device '%s'", b->address); + bonding_free(b); +} + +static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + DBusError e; + + dbus_error_init(&e); + + if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingCreated")) { + const char *a; + + if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.BondingCreated signal: %s", e.message); + goto finish; + } + + bonding_new(u, a); + + return DBUS_HANDLER_RESULT_HANDLED; + + } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingRemoved")) { + + const char *a; + + if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.BondingRemoved signal: %s", e.message); + goto finish; + } + + bonding_remove(u, a); + + return DBUS_HANDLER_RESULT_HANDLED; + } + +finish: + + dbus_error_free(&e); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int add_matches(struct userdata *u, pa_bool_t add) { + char *filter1, *filter2; + DBusError e; + int r = -1; + + pa_assert(u); + dbus_error_init(&e); + + filter1 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingCreated',path='%s'", u->hci_path); + filter2 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingRemoved',path='%s'", u->hci_path); + + if (add) { + dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e); + + if (dbus_error_is_set(&e)) { + pa_log("dbus_bus_add_match(%s) failed: %s", filter1, e.message); + goto finish; + } + } else + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e); + + + if (add) { + dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + + if (dbus_error_is_set(&e)) { + pa_log("dbus_bus_add_match(%s) failed: %s", filter2, e.message); + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + goto finish; + } + } else + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + + if (add) + pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u, NULL)); + else + dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u); + + r = 0; + +finish: + pa_xfree(filter1); + pa_xfree(filter2); + dbus_error_free(&e); + + return r; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + DBusError e; + DBusMessage *msg = NULL, *r = NULL; + DBusMessageIter iter, sub; + + pa_assert(m); + dbus_error_init(&e); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->module = m; + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + u->hci = pa_xstrdup(pa_modargs_get_value(ma, "hci", DEFAULT_HCI)); + u->hci_path = pa_sprintf_malloc("/org/bluez/%s", u->hci); + u->n_found = u->n_unknown = 0; + u->muted = FALSE; + + u->bondings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + if (!(u->dbus_connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &e))) { + pa_log("Failed to get D-Bus connection: %s", e.message); + goto fail; + } + + if (add_matches(u, TRUE) < 0) + goto fail; + + pa_assert_se(msg = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "ListBondings")); + + if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), msg, -1, &e))) { + pa_log("org.bluez.Adapter.ListBondings failed: %s", e.message); + goto fail; + } + + dbus_message_iter_init(r, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + pa_log("Malformed reply to org.bluez.Adapter.ListBondings."); + goto fail; + } + + dbus_message_iter_recurse(&iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + const char *a = NULL; + + dbus_message_iter_get_basic(&sub, &a); + bonding_new(u, a); + + dbus_message_iter_next(&sub); + } + + dbus_message_unref(r); + dbus_message_unref(msg); + + pa_modargs_free(ma); + + if (pa_hashmap_size(u->bondings) == 0) + pa_log_warn("Warning: no phone device bonded."); + + update_volume(u); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + dbus_error_free(&e); + + if (msg) + dbus_message_unref(msg); + + if (r) + dbus_message_unref(r); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->bondings) { + struct bonding *b; + + while ((b = pa_hashmap_steal_first(u->bondings))) + bonding_free(b); + + pa_hashmap_free(u->bondings, NULL, NULL); + } + + if (u->dbus_connection) { + add_matches(u, FALSE); + pa_dbus_connection_unref(u->dbus_connection); + } + + pa_xfree(u->sink_name); + pa_xfree(u->hci_path); + pa_xfree(u->hci); + pa_xfree(u); +} diff --git a/src/modules/bluetooth/proximity-helper.c b/src/modules/bluetooth/proximity-helper.c new file mode 100644 index 00000000..3767f01c --- /dev/null +++ b/src/modules/bluetooth/proximity-helper.c @@ -0,0 +1,202 @@ +/* + * Small SUID helper that allows us to ping a BT device. Borrows + * heavily from bluez-utils' l2ping, which is licensed as GPL2+ + * and comes with a copyright like this: + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2007 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#undef NDEBUG + +#include <assert.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/time.h> +#include <sys/select.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> + +#define PING_STRING "PulseAudio" +#define IDENT 200 +#define TIMEOUT 4 +#define INTERVAL 2 + +static void update_status(int found) { + static int status = -1; + + if (!found && status != 0) + printf("-"); + if (found && status <= 0) + printf("+"); + + fflush(stdout); + status = !!found; +} + +int main(int argc, char *argv[]) { + struct sockaddr_l2 addr; + union { + l2cap_cmd_hdr hdr; + uint8_t buf[L2CAP_CMD_HDR_SIZE + sizeof(PING_STRING)]; + } packet; + int fd = -1; + uint8_t id = IDENT; + int connected = 0; + + assert(argc == 2); + + for (;;) { + fd_set fds; + struct timeval end; + ssize_t r; + + if (!connected) { + + if (fd >= 0) + close(fd); + + if ((fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP)) < 0) { + fprintf(stderr, "socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP) failed: %s", strerror(errno)); + goto finish; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, BDADDR_ANY); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "bind() failed: %s", strerror(errno)); + goto finish; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + str2ba(argv[1], &addr.l2_bdaddr); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + sleep(INTERVAL); + continue; + } + + fprintf(stderr, "connect() failed: %s", strerror(errno)); + goto finish; + } + + connected = 1; + } + + assert(connected); + + memset(&packet, 0, sizeof(packet)); + strcpy((char*) packet.buf + L2CAP_CMD_HDR_SIZE, PING_STRING); + packet.hdr.ident = id; + packet.hdr.len = htobs(sizeof(PING_STRING)); + packet.hdr.code = L2CAP_ECHO_REQ; + + if ((r = send(fd, &packet, sizeof(packet), 0)) < 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + connected = 0; + sleep(INTERVAL); + continue; + } + + fprintf(stderr, "send() failed: %s", strerror(errno)); + goto finish; + } + + assert(r == sizeof(packet)); + + gettimeofday(&end, NULL); + end.tv_sec += TIMEOUT; + + for (;;) { + struct timeval now, delta; + + gettimeofday(&now, NULL); + + if (timercmp(&end, &now, <=)) { + update_status(0); + connected = 0; + sleep(INTERVAL); + break; + } + + timersub(&end, &now, &delta); + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if (select(fd+1, &fds, NULL, NULL, &delta) < 0) { + fprintf(stderr, "select() failed: %s", strerror(errno)); + goto finish; + } + + if ((r = recv(fd, &packet, sizeof(packet), 0)) <= 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + connected = 0; + sleep(INTERVAL); + break; + } + + fprintf(stderr, "send() failed: %s", r == 0 ? "EOF" : strerror(errno)); + goto finish; + } + + assert(r >= L2CAP_CMD_HDR_SIZE); + + if (packet.hdr.ident != id) + continue; + + if (packet.hdr.code == L2CAP_ECHO_RSP || packet.hdr.code == L2CAP_COMMAND_REJ) { + + if (++id >= 0xFF) + id = IDENT; + + update_status(1); + sleep(INTERVAL); + break; + } + } + } + +finish: + + if (fd >= 0) + close(fd); + + return 1; +} |