1
/* $Id: module-bt-proximity.c 2050 2007-11-13 17:37:44Z lennart $ */
4
This file is part of PulseAudio.
6
Copyright 2005-2006 Lennart Poettering
8
PulseAudio is free software; you can redistribute it and/or modify
9
it under the terms of the GNU Lesser General Public License as published
10
by the Free Software Foundation; either version 2 of the License,
11
or (at your option) any later version.
13
PulseAudio is distributed in the hope that it will be useful, but
14
WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
General Public License for more details.
18
You should have received a copy of the GNU Lesser General Public License
19
along with PulseAudio; if not, write to the Free Software
20
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
34
#include <sys/types.h>
37
#include <pulse/xmalloc.h>
38
#include <pulsecore/module.h>
39
#include <pulsecore/log.h>
40
#include <pulsecore/namereg.h>
41
#include <pulsecore/sink.h>
42
#include <pulsecore/modargs.h>
43
#include <pulsecore/macro.h>
44
#include <pulsecore/core-util.h>
45
#include <pulsecore/core-error.h>
46
#include <pulsecore/start-child.h>
48
#include "dbus-util.h"
49
#include "module-bt-proximity-symdef.h"
51
PA_MODULE_AUTHOR("Lennart Poettering");
52
PA_MODULE_DESCRIPTION("Bluetooth Proximity Volume Control");
53
PA_MODULE_VERSION(PACKAGE_VERSION);
54
PA_MODULE_LOAD_ONCE(TRUE);
60
#define DEFAULT_HCI "hci0"
62
static const char* const valid_modargs[] = {
70
struct userdata *userdata;
76
pa_io_event *io_event;
87
pa_dbus_connection *dbus_connection;
100
static void update_volume(struct userdata *u) {
103
if (u->muted && u->n_found > 0) {
108
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) {
109
pa_log_warn("Sink device '%s' not available for unmuting.", pa_strnull(u->sink_name));
113
pa_log_info("Found %u BT devices, unmuting.", u->n_found);
114
pa_sink_set_mute(s, FALSE);
116
} else if (!u->muted && (u->n_found+u->n_unknown) <= 0) {
121
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) {
122
pa_log_warn("Sink device '%s' not available for muting.", pa_strnull(u->sink_name));
126
pa_log_info("No BT devices found, muting.");
127
pa_sink_set_mute(s, TRUE);
130
pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown);
133
static void bonding_free(struct bonding *b) {
136
if (b->state == FOUND)
137
pa_assert_se(b->userdata->n_found-- >= 1);
139
if (b->state == UNKNOWN)
140
pa_assert_se(b->userdata->n_unknown-- >= 1);
142
if (b->pid != (pid_t) -1) {
143
kill(b->pid, SIGTERM);
144
waitpid(b->pid, NULL, 0);
151
b->userdata->module->core->mainloop->io_free(b->io_event);
156
static void io_event_cb(
160
pa_io_event_flags_t events,
163
struct bonding *b = userdata;
169
if ((r = read(fd, &x, 1)) <= 0) {
170
pa_log_warn("Child watching '%s' died abnormally: %s", b->address, r == 0 ? "EOF" : pa_cstrerror(errno));
172
pa_assert_se(pa_hashmap_remove(b->userdata->bondings, b->address) == b);
177
pa_assert_se(r == 1);
179
if (b->state == UNKNOWN)
180
pa_assert_se(b->userdata->n_unknown-- >= 1);
183
pa_assert(b->state == UNKNOWN || b->state == NOT_FOUND);
186
b->userdata->n_found++;
188
pa_log_info("Device '%s' is alive.", b->address);
192
pa_assert(b->state == UNKNOWN || b->state == FOUND);
194
if (b->state == FOUND)
195
b->userdata->n_found--;
197
b->state = NOT_FOUND;
199
pa_log_info("Device '%s' is dead.", b->address);
202
update_volume(b->userdata);
205
static struct bonding* bonding_new(struct userdata *u, const char *a) {
206
struct bonding *b = NULL;
207
DBusMessage *m = NULL, *r = NULL;
214
pa_return_val_if_fail(strlen(a) == 17, NULL);
215
pa_return_val_if_fail(!pa_hashmap_get(u->bondings, a), NULL);
219
pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "GetRemoteMajorClass"));
220
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID));
221
r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), m, -1, &e);
224
pa_log("org.bluez.Adapter.GetRemoteMajorClass(%s) failed: %s", a, e.message);
228
if (!(dbus_message_get_args(r, &e, DBUS_TYPE_STRING, &class, DBUS_TYPE_INVALID))) {
229
pa_log("Malformed org.bluez.Adapter.GetRemoteMajorClass signal: %s", e.message);
233
if (strcmp(class, "phone")) {
234
pa_log_info("Found device '%s' of class '%s', ignoring.", a, class);
238
b = pa_xnew(struct bonding, 1);
240
pa_strlcpy(b->address, a, sizeof(b->address));
247
pa_log_info("Watching device '%s' of class '%s'.", b->address, class);
249
if ((b->fd = pa_start_child_for_read(PA_BT_PROXIMITY_HELPER, a, &b->pid)) < 0) {
250
pa_log("Failed to start helper tool.");
254
b->io_event = u->module->core->mainloop->io_new(
255
u->module->core->mainloop,
261
dbus_message_unref(m);
262
dbus_message_unref(r);
264
pa_hashmap_put(u->bondings, b->address, b);
270
dbus_message_unref(m);
272
dbus_message_unref(r);
281
static void bonding_remove(struct userdata *u, const char *a) {
285
pa_return_if_fail((b = pa_hashmap_remove(u->bondings, a)));
287
pa_log_info("No longer watching device '%s'", b->address);
291
static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *m, void *userdata) {
292
struct userdata *u = userdata;
297
if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingCreated")) {
300
if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) {
301
pa_log("Malformed org.bluez.Adapter.BondingCreated signal: %s", e.message);
307
return DBUS_HANDLER_RESULT_HANDLED;
309
} else if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingRemoved")) {
313
if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) {
314
pa_log("Malformed org.bluez.Adapter.BondingRemoved signal: %s", e.message);
318
bonding_remove(u, a);
320
return DBUS_HANDLER_RESULT_HANDLED;
327
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
330
static int add_matches(struct userdata *u, pa_bool_t add) {
331
char *filter1, *filter2;
338
filter1 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingCreated',path='%s'", u->hci_path);
339
filter2 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingRemoved',path='%s'", u->hci_path);
342
dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e);
344
if (dbus_error_is_set(&e)) {
345
pa_log("dbus_bus_add_match(%s) failed: %s", filter1, e.message);
349
dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e);
353
dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e);
355
if (dbus_error_is_set(&e)) {
356
pa_log("dbus_bus_add_match(%s) failed: %s", filter2, e.message);
357
dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e);
361
dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e);
365
pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u, NULL));
367
dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u);
379
int pa__init(pa_module*m) {
380
pa_modargs *ma = NULL;
383
DBusMessage *msg = NULL, *r = NULL;
384
DBusMessageIter iter, sub;
389
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
390
pa_log("Failed to parse module arguments");
394
m->userdata = u = pa_xnew0(struct userdata, 1);
396
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
397
u->hci = pa_xstrdup(pa_modargs_get_value(ma, "hci", DEFAULT_HCI));
398
u->hci_path = pa_sprintf_malloc("/org/bluez/%s", u->hci);
399
u->n_found = u->n_unknown = 0;
402
u->bondings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
404
if (!(u->dbus_connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &e))) {
405
pa_log("Failed to get D-Bus connection: %s", e.message);
409
if (add_matches(u, TRUE) < 0)
412
pa_assert_se(msg = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "ListBondings"));
414
if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), msg, -1, &e))) {
415
pa_log("org.bluez.Adapter.ListBondings failed: %s", e.message);
419
dbus_message_iter_init(r, &iter);
421
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
422
pa_log("Malformed reply to org.bluez.Adapter.ListBondings.");
426
dbus_message_iter_recurse(&iter, &sub);
428
while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
429
const char *a = NULL;
431
dbus_message_iter_get_basic(&sub, &a);
434
dbus_message_iter_next(&sub);
437
dbus_message_unref(r);
438
dbus_message_unref(msg);
442
if (pa_hashmap_size(u->bondings) == 0)
443
pa_log_warn("Warning: no phone device bonded.");
459
dbus_message_unref(msg);
462
dbus_message_unref(r);
467
void pa__done(pa_module*m) {
471
if (!(u = m->userdata))
477
while ((b = pa_hashmap_steal_first(u->bondings)))
480
pa_hashmap_free(u->bondings, NULL, NULL);
483
if (u->dbus_connection) {
484
add_matches(u, FALSE);
485
pa_dbus_connection_unref(u->dbus_connection);
488
pa_xfree(u->sink_name);
489
pa_xfree(u->hci_path);