1/***
2  This file is part of PulseAudio.
3
4  Written by David Henningsson <david.henningsson@canonical.com>
5  Copyright 2010 Canonical Ltd.
6
7  Some code taken from other parts of PulseAudio, these are
8  Copyright 2006-2009 Lennart Poettering
9
10  PulseAudio is free software; you can redistribute it and/or modify
11  it under the terms of the GNU Lesser General Public License as published
12  by the Free Software Foundation; either version 2.1 of the License,
13  or (at your option) any later version.
14
15  PulseAudio is distributed in the hope that it will be useful, but
16  WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  General Public License for more details.
19
20  You should have received a copy of the GNU Lesser General Public License
21  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
22***/
23
24#ifdef HAVE_CONFIG_H
25#include <config.h>
26#endif
27
28#include <pulse/proplist.h>
29#include <pulse/xmalloc.h>
30
31#include <pulsecore/log.h>
32#include <pulsecore/modargs.h>
33#include <pulsecore/core-util.h>
34#include <pulsecore/dbus-shared.h>
35#include <pulsecore/strbuf.h>
36
37PA_MODULE_AUTHOR("David Henningsson");
38PA_MODULE_DESCRIPTION("Adds JACK sink/source ports when JACK is started");
39PA_MODULE_LOAD_ONCE(false);
40PA_MODULE_VERSION(PACKAGE_VERSION);
41PA_MODULE_USAGE(
42    "channels=<number of channels> "
43    "sink_name=<name for the sink> "
44    "sink_properties=<properties for the card> "
45    "sink_client_name=<jack client name> "
46    "sink_channels=<number of channels> "
47    "sink_channel_map=<channel map> "
48    "source_name=<name for the source> "
49    "source_properties=<properties for the source> "
50    "source_client_name=<jack client name> "
51    "source_channels=<number of channels> "
52    "source_channel_map=<channel map> "
53    "connect=<connect ports?>");
54
55#define JACK_SERVICE_NAME "org.jackaudio.service"
56#define JACK_INTERFACE_NAME "org.jackaudio.JackControl"
57#define JACK_INTERFACE_PATH "/org/jackaudio/Controller"
58
59#define SERVICE_FILTER                          \
60        "type='signal',"                        \
61        "sender='" DBUS_SERVICE_DBUS "',"       \
62        "interface='" DBUS_INTERFACE_DBUS "',"  \
63        "member='NameOwnerChanged',"            \
64        "arg0='" JACK_SERVICE_NAME "'"
65
66#define RUNNING_FILTER(_a)                      \
67        "type='signal',"                        \
68        "sender='" JACK_SERVICE_NAME "',"       \
69        "interface='" JACK_INTERFACE_NAME "',"  \
70        "member='" _a "'"
71
72static const char* const valid_modargs[] = {
73    "channels",
74    "sink_enabled",
75    "sink_name",
76    "sink_properties",
77    "sink_client_name",
78    "sink_channels",
79    "sink_channel_map",
80    "source_enabled",
81    "source_name",
82    "source_properties",
83    "source_client_name",
84    "source_channels",
85    "source_channel_map",
86    "connect",
87    NULL
88};
89
90#define JACK_SS_SINK 0
91#define JACK_SS_SOURCE 1
92#define JACK_SS_COUNT 2
93
94static const char* const modnames[JACK_SS_COUNT] = {
95    "module-jack-sink",
96    "module-jack-source"
97};
98
99static const char* const modtypes[JACK_SS_COUNT] = {
100    "sink",
101    "source"
102};
103
104struct moddata {
105    bool enabled;
106    char *name;
107    pa_proplist *proplist;
108    char *client_name;
109    uint32_t channels;
110    pa_channel_map channel_map;
111};
112
113struct userdata {
114    pa_module *module;
115    pa_core *core;
116    pa_dbus_connection *connection;
117    bool filter_added, match_added;
118    bool is_service_started;
119    bool autoconnect_ports;
120    struct moddata mod_args[JACK_SS_COUNT];
121    /* Using index here protects us from module unloading without us knowing */
122    int jack_module_index[JACK_SS_COUNT];
123};
124
125static void ensure_ports_stopped(struct userdata* u) {
126    unsigned i;
127    pa_assert(u);
128
129    for (i = 0; i < JACK_SS_COUNT; i++)
130        if (u->jack_module_index[i]) {
131            pa_module_unload_request_by_index(u->core, u->jack_module_index[i], true);
132            u->jack_module_index[i] = 0;
133            pa_log_info("Stopped %s.", modnames[i]);
134        }
135}
136
137static char* proplist_to_arg(pa_proplist *p) {
138    const char *key;
139    void *state = NULL;
140    pa_strbuf *buf;
141
142    pa_assert(p);
143
144    buf = pa_strbuf_new();
145
146    while ((key = pa_proplist_iterate(p, &state))) {
147        const char *v;
148        char *escaped;
149
150        if (!pa_strbuf_isempty(buf))
151            pa_strbuf_puts(buf, " ");
152
153        if ((v = pa_proplist_gets(p, key))) {
154            pa_strbuf_printf(buf, "%s=\"", key);
155
156            escaped = pa_escape(v, "\"'");
157            pa_strbuf_puts(buf, escaped);
158            pa_xfree(escaped);
159
160            pa_strbuf_puts(buf, "\"");
161        } else {
162            const void *value;
163            size_t nbytes;
164            char *c;
165
166            pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0);
167            c = pa_xmalloc(nbytes*2+1);
168            pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1);
169
170            pa_strbuf_printf(buf, "%s=hex:%s", key, c);
171            pa_xfree(c);
172        }
173    }
174
175    return pa_strbuf_to_string_free(buf);
176}
177
178static void ensure_ports_started(struct userdata* u) {
179    unsigned i;
180    char *escaped;
181    pa_assert(u);
182
183    for (i = 0; i < JACK_SS_COUNT; i++)
184        if (u->mod_args[i].enabled && !u->jack_module_index[i]) {
185            pa_strbuf *args_buf = pa_strbuf_new();
186            char *args;
187            pa_module *m;
188            pa_strbuf_printf(args_buf, "connect=%s", pa_yes_no(u->autoconnect_ports));
189            if (u->mod_args[i].name) {
190                escaped = pa_escape(u->mod_args[i].name, "'");
191                pa_strbuf_printf(args_buf, " %s_name='%s'", modtypes[i], escaped);
192                pa_xfree(escaped);
193            }
194            if (!pa_proplist_isempty(u->mod_args[i].proplist)) {
195                escaped = proplist_to_arg(u->mod_args[i].proplist);
196                pa_strbuf_printf(args_buf, " %s_properties='%s'", modtypes[i], escaped);
197                pa_xfree(escaped);
198            }
199            if (u->mod_args[i].client_name) {
200                escaped = pa_escape(u->mod_args[i].client_name, "'");
201                pa_strbuf_printf(args_buf, " client_name='%s'", escaped);
202                pa_xfree(escaped);
203            }
204            if (u->mod_args[i].channels > 0)
205                pa_strbuf_printf(args_buf, " channels=%" PRIu32, u->mod_args[i].channels);
206            if (u->mod_args[i].channel_map.channels > 0) {
207                char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
208                pa_channel_map_snprint(cm, sizeof(cm), &u->mod_args[i].channel_map);
209                pa_strbuf_printf(args_buf, " channel_map='%s'", cm);
210            }
211            args = pa_strbuf_to_string_free(args_buf);
212            pa_module_load(&m, u->core, modnames[i], args);
213            pa_xfree(args);
214
215            if (m) {
216                pa_log_info("Successfully started %s.", modnames[i]);
217                u->jack_module_index[i] = m->index;
218            }
219            else
220                pa_log_info("Failed to start %s.", modnames[i]);
221        }
222}
223
224static bool check_service_started(struct userdata* u) {
225    DBusError error;
226    DBusMessage *m = NULL, *reply = NULL;
227    bool new_status = false;
228    dbus_bool_t call_result;
229    pa_assert(u);
230
231    dbus_error_init(&error);
232
233    /* Just a safety check; it isn't such a big deal if the name disappears just after the call. */
234    if (!dbus_bus_name_has_owner(pa_dbus_connection_get(u->connection),
235            JACK_SERVICE_NAME, &error)) {
236        pa_log_debug("jackdbus isn't running.");
237        goto finish;
238    }
239
240    if (!(m = dbus_message_new_method_call(JACK_SERVICE_NAME, JACK_INTERFACE_PATH, JACK_INTERFACE_NAME, "IsStarted"))) {
241        pa_log("Failed to allocate IsStarted() method call.");
242        goto finish;
243    }
244
245    if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) {
246        pa_log("IsStarted() call failed: %s: %s", error.name, error.message);
247        goto finish;
248    }
249
250    if (!dbus_message_get_args(reply, &error, DBUS_TYPE_BOOLEAN, &call_result, DBUS_TYPE_INVALID)) {
251        pa_log("IsStarted() call return failed: %s: %s", error.name, error.message);
252        goto finish;
253    }
254
255    new_status = call_result;
256
257finish:
258    if (m)
259        dbus_message_unref(m);
260    if (reply)
261        dbus_message_unref(reply);
262
263    dbus_error_free(&error);
264    if (new_status)
265        ensure_ports_started(u);
266    else
267        ensure_ports_stopped(u);
268    u->is_service_started = new_status;
269    return new_status;
270}
271
272static DBusHandlerResult dbus_filter_handler(DBusConnection *c, DBusMessage *s, void *userdata) {
273    struct userdata *u = NULL;
274    DBusError error;
275
276    pa_assert(userdata);
277    u = ((pa_module*) userdata)->userdata;
278    pa_assert(u);
279
280    dbus_error_init(&error);
281
282    if (dbus_message_is_signal(s, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) {
283        const char *name, *old, *new;
284        if (!dbus_message_get_args(s, &error,
285                                   DBUS_TYPE_STRING, &name,
286                                   DBUS_TYPE_STRING, &old,
287                                   DBUS_TYPE_STRING, &new,
288                                   DBUS_TYPE_INVALID))
289            goto finish;
290        if (!pa_streq(name, JACK_SERVICE_NAME))
291            goto finish;
292
293        ensure_ports_stopped(u);
294        check_service_started(u);
295    }
296
297    else if (dbus_message_is_signal(s, JACK_INTERFACE_NAME, "ServerStarted")) {
298        ensure_ports_stopped(u);
299        check_service_started(u);
300    }
301
302    else if (dbus_message_is_signal(s, JACK_INTERFACE_NAME, "ServerStopped")) {
303        ensure_ports_stopped(u);
304    }
305
306finish:
307    dbus_error_free(&error);
308    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
309}
310
311int pa__init(pa_module *m) {
312    DBusError error;
313    pa_dbus_connection *connection = NULL;
314    struct userdata *u = NULL;
315    pa_modargs *ma;
316    uint32_t channels = 0;
317    unsigned i;
318    char argname[32];
319    const char *name;
320
321    pa_assert(m);
322
323    dbus_error_init(&error);
324
325    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
326        pa_log("Failed to parse module arguments");
327        goto fail;
328    }
329
330    m->userdata = u = pa_xnew0(struct userdata, 1);
331    u->core = m->core;
332    u->module = m;
333    u->autoconnect_ports = true;
334
335    if (pa_modargs_get_value_boolean(ma, "connect", &u->autoconnect_ports) < 0) {
336        pa_log("Failed to parse connect= argument.");
337        goto fail;
338    }
339
340    if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || (channels > 0 && !pa_channels_valid(channels))) {
341        pa_log("Failed to parse channels= argument.");
342        goto fail;
343    }
344
345    for (i = 0; i < JACK_SS_COUNT; i++) {
346        u->mod_args[i].enabled = true;
347        pa_snprintf(argname, sizeof(argname), "%s_enabled", modtypes[i]);
348        if (pa_modargs_get_value_boolean(ma, argname, &u->mod_args[i].enabled) < 0) {
349            pa_log("Failed to parse %s= argument.", argname);
350            goto fail;
351        }
352
353        pa_snprintf(argname, sizeof(argname), "%s_name", modtypes[i]);
354        name = pa_modargs_get_value(ma, argname, NULL);
355        u->mod_args[i].name = pa_xstrdup(name);
356
357        u->mod_args[i].proplist = pa_proplist_new();
358        pa_snprintf(argname, sizeof(argname), "%s_properties", modtypes[i]);
359        if (pa_modargs_get_proplist(ma, argname, u->mod_args[i].proplist, PA_UPDATE_REPLACE) < 0) {
360            pa_log("Invalid %s properties", modtypes[i]);
361            goto fail;
362        }
363
364        pa_snprintf(argname, sizeof(argname), "%s_client_name", modtypes[i]);
365        name = pa_modargs_get_value(ma, argname, NULL);
366        u->mod_args[i].client_name = pa_xstrdup(name);
367
368        u->mod_args[i].channels = channels;
369        pa_snprintf(argname, sizeof(argname), "%s_channels", modtypes[i]);
370        if (pa_modargs_get_value_u32(ma, argname, &u->mod_args[i].channels) < 0
371                || (u->mod_args[i].channels > 0 && !pa_channels_valid(u->mod_args[i].channels))) {
372            pa_log("Failed to parse %s= argument.", argname);
373            goto fail;
374        }
375
376        pa_channel_map_init(&u->mod_args[i].channel_map);
377        pa_snprintf(argname, sizeof(argname), "%s_channel_map", modtypes[i]);
378        if (pa_modargs_get_value(ma, argname, NULL)) {
379            if (pa_modargs_get_channel_map(ma, argname, &u->mod_args[i].channel_map) < 0
380                    || (u->mod_args[i].channels > 0 && u->mod_args[i].channel_map.channels != u->mod_args[i].channels)) {
381                pa_log("Failed to parse %s= argument.", argname);
382                goto fail;
383            }
384        }
385    }
386
387    if (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
388
389        if (connection)
390            pa_dbus_connection_unref(connection);
391
392        pa_log_error("Unable to contact D-Bus session bus: %s: %s", error.name, error.message);
393        goto fail;
394    }
395    u->connection = connection;
396
397    if (!dbus_connection_add_filter(pa_dbus_connection_get(connection), dbus_filter_handler, m, NULL)) {
398        pa_log_error("Unable to add D-Bus filter");
399        goto fail;
400    }
401    u->filter_added = 1;
402
403    if (pa_dbus_add_matches(
404                pa_dbus_connection_get(connection), &error, SERVICE_FILTER,
405                RUNNING_FILTER("ServerStarted"), RUNNING_FILTER("ServerStopped"), NULL) < 0) {
406        pa_log_error("Unable to subscribe to signals: %s: %s", error.name, error.message);
407        goto fail;
408    }
409    u->match_added = 1;
410
411    check_service_started(u);
412
413    pa_modargs_free(ma);
414    return 0;
415
416fail:
417    if (ma)
418        pa_modargs_free(ma);
419
420    dbus_error_free(&error);
421    pa__done(m);
422
423    return -1;
424}
425
426void pa__done(pa_module *m) {
427    struct userdata *u;
428    unsigned i;
429
430    pa_assert(m);
431
432    if (!(u = m->userdata))
433        return;
434
435    ensure_ports_stopped(u);
436
437    if (u->match_added) {
438        pa_dbus_remove_matches(
439                pa_dbus_connection_get(u->connection), SERVICE_FILTER,
440                RUNNING_FILTER("ServerStarted"), RUNNING_FILTER("ServerStopped"), NULL);
441    }
442
443    if (u->filter_added) {
444        dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), dbus_filter_handler, m);
445    }
446
447    if (u->connection) {
448        pa_dbus_connection_unref(u->connection);
449    }
450
451    for (i = 0; i < JACK_SS_COUNT; i++) {
452        pa_xfree(u->mod_args[i].name);
453
454        if (u->mod_args[i].proplist)
455            pa_proplist_free(u->mod_args[i].proplist);
456
457        pa_xfree(u->mod_args[i].client_name);
458    }
459
460    pa_xfree(u);
461}
462