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
[] = {
68 struct userdata
*userdata
;
74 pa_io_event
*io_event
;
85 pa_dbus_connection
*dbus_connection
;
96 pa_bool_t filter_added
:1;
99 static void update_volume(struct userdata
*u
) {
102 if (u
->muted
&& u
->n_found
> 0) {
107 if (!(s
= pa_namereg_get(u
->module
->core
, u
->sink_name
, PA_NAMEREG_SINK
))) {
108 pa_log_warn("Sink device '%s' not available for unmuting.", pa_strnull(u
->sink_name
));
112 pa_log_info("Found %u BT devices, unmuting.", u
->n_found
);
113 pa_sink_set_mute(s
, FALSE
, FALSE
);
115 } else if (!u
->muted
&& (u
->n_found
+u
->n_unknown
) <= 0) {
120 if (!(s
= pa_namereg_get(u
->module
->core
, u
->sink_name
, PA_NAMEREG_SINK
))) {
121 pa_log_warn("Sink device '%s' not available for muting.", pa_strnull(u
->sink_name
));
125 pa_log_info("No BT devices found, muting.");
126 pa_sink_set_mute(s
, TRUE
, FALSE
);
129 pa_log_info("%u devices now active, %u with unknown state.", u
->n_found
, u
->n_unknown
);
132 static void bonding_free(struct bonding
*b
) {
135 if (b
->state
== FOUND
)
136 pa_assert_se(b
->userdata
->n_found
-- >= 1);
138 if (b
->state
== UNKNOWN
)
139 pa_assert_se(b
->userdata
->n_unknown
-- >= 1);
141 if (b
->pid
!= (pid_t
) -1) {
142 kill(b
->pid
, SIGTERM
);
143 waitpid(b
->pid
, NULL
, 0);
150 b
->userdata
->module
->core
->mainloop
->io_free(b
->io_event
);
155 static void io_event_cb(
159 pa_io_event_flags_t events
,
162 struct bonding
*b
= userdata
;
168 if ((r
= read(fd
, &x
, 1)) <= 0) {
169 pa_log_warn("Child watching '%s' died abnormally: %s", b
->address
, r
== 0 ? "EOF" : pa_cstrerror(errno
));
171 pa_assert_se(pa_hashmap_remove(b
->userdata
->bondings
, b
->address
) == b
);
176 pa_assert_se(r
== 1);
178 if (b
->state
== UNKNOWN
)
179 pa_assert_se(b
->userdata
->n_unknown
-- >= 1);
182 pa_assert(b
->state
== UNKNOWN
|| b
->state
== NOT_FOUND
);
185 b
->userdata
->n_found
++;
187 pa_log_info("Device '%s' is alive.", b
->address
);
191 pa_assert(b
->state
== UNKNOWN
|| b
->state
== FOUND
);
193 if (b
->state
== FOUND
)
194 b
->userdata
->n_found
--;
196 b
->state
= NOT_FOUND
;
198 pa_log_info("Device '%s' is dead.", b
->address
);
201 update_volume(b
->userdata
);
204 static struct bonding
* bonding_new(struct userdata
*u
, const char *a
) {
205 struct bonding
*b
= NULL
;
206 DBusMessage
*m
= NULL
, *r
= NULL
;
213 pa_return_val_if_fail(strlen(a
) == 17, NULL
);
214 pa_return_val_if_fail(!pa_hashmap_get(u
->bondings
, a
), NULL
);
218 pa_assert_se(m
= dbus_message_new_method_call("org.bluez", u
->hci_path
, "org.bluez.Adapter", "GetRemoteMajorClass"));
219 pa_assert_se(dbus_message_append_args(m
, DBUS_TYPE_STRING
, &a
, DBUS_TYPE_INVALID
));
220 r
= dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u
->dbus_connection
), m
, -1, &e
);
223 pa_log("org.bluez.Adapter.GetRemoteMajorClass(%s) failed: %s", a
, e
.message
);
227 if (!(dbus_message_get_args(r
, &e
, DBUS_TYPE_STRING
, &class, DBUS_TYPE_INVALID
))) {
228 pa_log("Malformed org.bluez.Adapter.GetRemoteMajorClass signal: %s", e
.message
);
232 if (strcmp(class, "phone")) {
233 pa_log_info("Found device '%s' of class '%s', ignoring.", a
, class);
237 b
= pa_xnew(struct bonding
, 1);
239 pa_strlcpy(b
->address
, a
, sizeof(b
->address
));
246 pa_log_info("Watching device '%s' of class '%s'.", b
->address
, class);
248 if ((b
->fd
= pa_start_child_for_read(PA_BT_PROXIMITY_HELPER
, a
, &b
->pid
)) < 0) {
249 pa_log("Failed to start helper tool.");
253 b
->io_event
= u
->module
->core
->mainloop
->io_new(
254 u
->module
->core
->mainloop
,
260 dbus_message_unref(m
);
261 dbus_message_unref(r
);
263 pa_hashmap_put(u
->bondings
, b
->address
, b
);
269 dbus_message_unref(m
);
271 dbus_message_unref(r
);
280 static void bonding_remove(struct userdata
*u
, const char *a
) {
284 pa_return_if_fail((b
= pa_hashmap_remove(u
->bondings
, a
)));
286 pa_log_info("No longer watching device '%s'", b
->address
);
290 static DBusHandlerResult
filter_func(DBusConnection
*connection
, DBusMessage
*m
, void *userdata
) {
291 struct userdata
*u
= userdata
;
296 if (dbus_message_is_signal(m
, "org.bluez.Adapter", "BondingCreated")) {
299 if (!(dbus_message_get_args(m
, &e
, DBUS_TYPE_STRING
, &a
, DBUS_TYPE_INVALID
))) {
300 pa_log("Malformed org.bluez.Adapter.BondingCreated signal: %s", e
.message
);
306 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
308 } else if (dbus_message_is_signal(m
, "org.bluez.Adapter", "BondingRemoved")) {
312 if (!(dbus_message_get_args(m
, &e
, DBUS_TYPE_STRING
, &a
, DBUS_TYPE_INVALID
))) {
313 pa_log("Malformed org.bluez.Adapter.BondingRemoved signal: %s", e
.message
);
317 bonding_remove(u
, a
);
319 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
326 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
329 static int add_matches(struct userdata
*u
, pa_bool_t add
) {
330 char *filter1
, *filter2
;
337 filter1
= pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingCreated',path='%s'", u
->hci_path
);
338 filter2
= pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingRemoved',path='%s'", u
->hci_path
);
341 dbus_bus_add_match(pa_dbus_connection_get(u
->dbus_connection
), filter1
, &e
);
343 if (dbus_error_is_set(&e
)) {
344 pa_log("dbus_bus_add_match(%s) failed: %s", filter1
, e
.message
);
348 dbus_bus_remove_match(pa_dbus_connection_get(u
->dbus_connection
), filter1
, &e
);
352 dbus_bus_add_match(pa_dbus_connection_get(u
->dbus_connection
), filter2
, &e
);
354 if (dbus_error_is_set(&e
)) {
355 pa_log("dbus_bus_add_match(%s) failed: %s", filter2
, e
.message
);
356 dbus_bus_remove_match(pa_dbus_connection_get(u
->dbus_connection
), filter2
, &e
);
360 dbus_bus_remove_match(pa_dbus_connection_get(u
->dbus_connection
), filter2
, &e
);
363 pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u
->dbus_connection
), filter_func
, u
, NULL
));
364 u
->filter_added
= TRUE
;
365 } else if (u
->filter_added
)
366 dbus_connection_remove_filter(pa_dbus_connection_get(u
->dbus_connection
), filter_func
, u
);
378 int pa__init(pa_module
*m
) {
379 pa_modargs
*ma
= NULL
;
382 DBusMessage
*msg
= NULL
, *r
= NULL
;
383 DBusMessageIter iter
, sub
;
388 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
389 pa_log("Failed to parse module arguments");
393 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
395 u
->sink_name
= pa_xstrdup(pa_modargs_get_value(ma
, "sink", NULL
));
396 u
->hci
= pa_xstrdup(pa_modargs_get_value(ma
, "hci", DEFAULT_HCI
));
397 u
->hci_path
= pa_sprintf_malloc("/org/bluez/%s", u
->hci
);
398 u
->bondings
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
400 if (!(u
->dbus_connection
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &e
))) {
401 pa_log("Failed to get D-Bus connection: %s", e
.message
);
405 if (add_matches(u
, TRUE
) < 0)
408 pa_assert_se(msg
= dbus_message_new_method_call("org.bluez", u
->hci_path
, "org.bluez.Adapter", "ListBondings"));
410 if (!(r
= dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u
->dbus_connection
), msg
, -1, &e
))) {
411 pa_log("org.bluez.Adapter.ListBondings failed: %s", e
.message
);
415 dbus_message_iter_init(r
, &iter
);
417 if (dbus_message_iter_get_arg_type(&iter
) != DBUS_TYPE_ARRAY
) {
418 pa_log("Malformed reply to org.bluez.Adapter.ListBondings.");
422 dbus_message_iter_recurse(&iter
, &sub
);
424 while (dbus_message_iter_get_arg_type(&sub
) == DBUS_TYPE_STRING
) {
425 const char *a
= NULL
;
427 dbus_message_iter_get_basic(&sub
, &a
);
430 dbus_message_iter_next(&sub
);
433 dbus_message_unref(r
);
434 dbus_message_unref(msg
);
438 if (pa_hashmap_size(u
->bondings
) == 0)
439 pa_log_warn("Warning: no phone device bonded.");
455 dbus_message_unref(msg
);
458 dbus_message_unref(r
);
463 void pa__done(pa_module
*m
) {
467 if (!(u
= m
->userdata
))
473 while ((b
= pa_hashmap_steal_first(u
->bondings
)))
476 pa_hashmap_free(u
->bondings
, NULL
, NULL
);
479 if (u
->dbus_connection
) {
480 add_matches(u
, FALSE
);
481 pa_dbus_connection_unref(u
->dbus_connection
);
484 pa_xfree(u
->sink_name
);
485 pa_xfree(u
->hci_path
);