2 This file is part of PulseAudio.
4 Copyright 2005-2006 Lennart Poettering
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of the License,
9 or (at your option) any later version.
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
32 #include <sys/types.h>
35 #include <pulse/xmalloc.h>
36 #include <pulsecore/module.h>
37 #include <pulsecore/log.h>
38 #include <pulsecore/namereg.h>
39 #include <pulsecore/sink.h>
40 #include <pulsecore/modargs.h>
41 #include <pulsecore/macro.h>
42 #include <pulsecore/core-util.h>
43 #include <pulsecore/core-error.h>
44 #include <pulsecore/start-child.h>
45 #include <pulsecore/dbus-shared.h>
47 #include "module-bluetooth-proximity-symdef.h"
49 PA_MODULE_AUTHOR("Lennart Poettering");
50 PA_MODULE_DESCRIPTION("Bluetooth Proximity Volume Control");
51 PA_MODULE_VERSION(PACKAGE_VERSION);
52 PA_MODULE_LOAD_ONCE(true);
58 #define DEFAULT_HCI "hci0"
60 static const char* const valid_modargs[] = {
67 struct userdata *userdata;
73 pa_io_event *io_event;
84 pa_dbus_connection *dbus_connection;
98 static void update_volume(struct userdata *u) {
101 if (u->muted && u->n_found > 0) {
106 if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK))) {
107 pa_log_warn("Sink device '%s' not available for unmuting.", pa_strnull(u->sink_name));
111 pa_log_info("Found %u BT devices, unmuting.", u->n_found);
112 pa_sink_set_mute(s, false, false);
114 } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) {
119 if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK))) {
120 pa_log_warn("Sink device '%s' not available for muting.", pa_strnull(u->sink_name));
124 pa_log_info("No BT devices found, muting.");
125 pa_sink_set_mute(s, true, false);
128 pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown);
131 static void bonding_free(struct bonding *b) {
134 if (b->state == FOUND)
135 pa_assert_se(b->userdata->n_found-- >= 1);
137 if (b->state == UNKNOWN)
138 pa_assert_se(b->userdata->n_unknown-- >= 1);
140 if (b->pid != (pid_t) -1) {
141 kill(b->pid, SIGTERM);
142 waitpid(b->pid, NULL, 0);
149 b->userdata->module->core->mainloop->io_free(b->io_event);
154 static void io_event_cb(
158 pa_io_event_flags_t events,
161 struct bonding *b = userdata;
167 if ((r = read(fd, &x, 1)) <= 0) {
168 pa_log_warn("Child watching '%s' died abnormally: %s", b->address, r == 0 ? "EOF" : pa_cstrerror(errno));
170 pa_assert_se(pa_hashmap_remove(b->userdata->bondings, b->address) == b);
175 pa_assert_se(r == 1);
177 if (b->state == UNKNOWN)
178 pa_assert_se(b->userdata->n_unknown-- >= 1);
181 pa_assert(b->state == UNKNOWN || b->state == NOT_FOUND);
184 b->userdata->n_found++;
186 pa_log_info("Device '%s' is alive.", b->address);
190 pa_assert(b->state == UNKNOWN || b->state == FOUND);
192 if (b->state == FOUND)
193 b->userdata->n_found--;
195 b->state = NOT_FOUND;
197 pa_log_info("Device '%s' is dead.", b->address);
200 update_volume(b->userdata);
203 static struct bonding* bonding_new(struct userdata *u, const char *a) {
204 struct bonding *b = NULL;
205 DBusMessage *m = NULL, *r = NULL;
212 pa_return_val_if_fail(strlen(a) == 17, NULL);
213 pa_return_val_if_fail(!pa_hashmap_get(u->bondings, a), NULL);
217 pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "GetRemoteMajorClass"));
218 pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID));
219 r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), m, -1, &e);
222 pa_log("org.bluez.Adapter.GetRemoteMajorClass(%s) failed: %s", a, e.message);
226 if (!(dbus_message_get_args(r, &e, DBUS_TYPE_STRING, &class, DBUS_TYPE_INVALID))) {
227 pa_log("Malformed org.bluez.Adapter.GetRemoteMajorClass signal: %s", e.message);
231 if (!pa_streq(class, "phone")) {
232 pa_log_info("Found device '%s' of class '%s', ignoring.", a, class);
236 b = pa_xnew(struct bonding, 1);
238 pa_strlcpy(b->address, a, sizeof(b->address));
245 pa_log_info("Watching device '%s' of class '%s'.", b->address, class);
247 if ((b->fd = pa_start_child_for_read(PA_BT_PROXIMITY_HELPER, a, &b->pid)) < 0) {
248 pa_log("Failed to start helper tool.");
252 b->io_event = u->module->core->mainloop->io_new(
253 u->module->core->mainloop,
259 dbus_message_unref(m);
260 dbus_message_unref(r);
262 pa_hashmap_put(u->bondings, b->address, b);
268 dbus_message_unref(m);
270 dbus_message_unref(r);
279 static void bonding_remove(struct userdata *u, const char *a) {
283 pa_return_if_fail((b = pa_hashmap_remove(u->bondings, a)));
285 pa_log_info("No longer watching device '%s'", b->address);
289 static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *m, void *userdata) {
290 struct userdata *u = userdata;
295 if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingCreated")) {
298 if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) {
299 pa_log("Malformed org.bluez.Adapter.BondingCreated signal: %s", e.message);
305 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
307 } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingRemoved")) {
311 if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) {
312 pa_log("Malformed org.bluez.Adapter.BondingRemoved signal: %s", e.message);
316 bonding_remove(u, a);
318 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
325 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
328 static int update_matches(struct userdata *u, bool add) {
329 char *filter1, *filter2;
336 filter1 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingCreated',path='%s'", u->hci_path);
337 filter2 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingRemoved',path='%s'", u->hci_path);
340 dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e);
342 if (dbus_error_is_set(&e)) {
343 pa_log("dbus_bus_add_match(%s) failed: %s", filter1, e.message);
347 dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, NULL);
350 dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e);
352 if (dbus_error_is_set(&e)) {
353 pa_log("dbus_bus_add_match(%s) failed: %s", filter2, e.message);
354 dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, NULL);
358 dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, NULL);
361 pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u, NULL));
362 u->filter_added = true;
363 } else if (u->filter_added)
364 dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u);
376 int pa__init(pa_module*m) {
377 pa_modargs *ma = NULL;
380 DBusMessage *msg = NULL, *r = NULL;
381 DBusMessageIter iter, sub;
386 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
387 pa_log("Failed to parse module arguments");
391 m->userdata = u = pa_xnew0(struct userdata, 1);
393 u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
394 u->hci = pa_xstrdup(pa_modargs_get_value(ma, "hci", DEFAULT_HCI));
395 u->hci_path = pa_sprintf_malloc("/org/bluez/%s", u->hci);
396 u->bondings = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) bonding_free);
398 if (!(u->dbus_connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &e))) {
399 pa_log("Failed to get D-Bus connection: %s", e.message);
403 if (update_matches(u, true) < 0)
406 pa_assert_se(msg = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "ListBondings"));
408 if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), msg, -1, &e))) {
409 pa_log("org.bluez.Adapter.ListBondings failed: %s", e.message);
413 dbus_message_iter_init(r, &iter);
415 if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
416 pa_log("Malformed reply to org.bluez.Adapter.ListBondings.");
420 dbus_message_iter_recurse(&iter, &sub);
422 while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
423 const char *a = NULL;
425 dbus_message_iter_get_basic(&sub, &a);
428 dbus_message_iter_next(&sub);
431 dbus_message_unref(r);
432 dbus_message_unref(msg);
436 if (pa_hashmap_size(u->bondings) == 0)
437 pa_log_warn("Warning: no phone device bonded.");
453 dbus_message_unref(msg);
456 dbus_message_unref(r);
461 void pa__done(pa_module*m) {
465 if (!(u = m->userdata))
469 pa_hashmap_free(u->bondings);
471 if (u->dbus_connection) {
472 update_matches(u, false);
473 pa_dbus_connection_unref(u->dbus_connection);
476 pa_xfree(u->sink_name);
477 pa_xfree(u->hci_path);