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
;
95 pa_bool_t filter_added
:1;
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 (strcmp(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 add_matches(struct userdata
*u
, pa_bool_t 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
, &e
);
351 dbus_bus_add_match(pa_dbus_connection_get(u
->dbus_connection
), filter2
, &e
);
353 if (dbus_error_is_set(&e
)) {
354 pa_log("dbus_bus_add_match(%s) failed: %s", filter2
, e
.message
);
355 dbus_bus_remove_match(pa_dbus_connection_get(u
->dbus_connection
), filter2
, &e
);
359 dbus_bus_remove_match(pa_dbus_connection_get(u
->dbus_connection
), filter2
, &e
);
362 pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u
->dbus_connection
), filter_func
, u
, NULL
));
363 u
->filter_added
= TRUE
;
364 } else if (u
->filter_added
)
365 dbus_connection_remove_filter(pa_dbus_connection_get(u
->dbus_connection
), filter_func
, u
);
377 int pa__init(pa_module
*m
) {
378 pa_modargs
*ma
= NULL
;
381 DBusMessage
*msg
= NULL
, *r
= NULL
;
382 DBusMessageIter iter
, sub
;
387 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
388 pa_log("Failed to parse module arguments");
392 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
394 u
->sink_name
= pa_xstrdup(pa_modargs_get_value(ma
, "sink", NULL
));
395 u
->hci
= pa_xstrdup(pa_modargs_get_value(ma
, "hci", DEFAULT_HCI
));
396 u
->hci_path
= pa_sprintf_malloc("/org/bluez/%s", u
->hci
);
397 u
->bondings
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
399 if (!(u
->dbus_connection
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &e
))) {
400 pa_log("Failed to get D-Bus connection: %s", e
.message
);
404 if (add_matches(u
, TRUE
) < 0)
407 pa_assert_se(msg
= dbus_message_new_method_call("org.bluez", u
->hci_path
, "org.bluez.Adapter", "ListBondings"));
409 if (!(r
= dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u
->dbus_connection
), msg
, -1, &e
))) {
410 pa_log("org.bluez.Adapter.ListBondings failed: %s", e
.message
);
414 dbus_message_iter_init(r
, &iter
);
416 if (dbus_message_iter_get_arg_type(&iter
) != DBUS_TYPE_ARRAY
) {
417 pa_log("Malformed reply to org.bluez.Adapter.ListBondings.");
421 dbus_message_iter_recurse(&iter
, &sub
);
423 while (dbus_message_iter_get_arg_type(&sub
) == DBUS_TYPE_STRING
) {
424 const char *a
= NULL
;
426 dbus_message_iter_get_basic(&sub
, &a
);
429 dbus_message_iter_next(&sub
);
432 dbus_message_unref(r
);
433 dbus_message_unref(msg
);
437 if (pa_hashmap_size(u
->bondings
) == 0)
438 pa_log_warn("Warning: no phone device bonded.");
454 dbus_message_unref(msg
);
457 dbus_message_unref(r
);
462 void pa__done(pa_module
*m
) {
466 if (!(u
= m
->userdata
))
472 while ((b
= pa_hashmap_steal_first(u
->bondings
)))
475 pa_hashmap_free(u
->bondings
, NULL
, NULL
);
478 if (u
->dbus_connection
) {
479 add_matches(u
, FALSE
);
480 pa_dbus_connection_unref(u
->dbus_connection
);
483 pa_xfree(u
->sink_name
);
484 pa_xfree(u
->hci_path
);