1/***
2    This file is part of PulseAudio.
3
4    Copyright 2008 Colin Guthrie
5    Copyright 2017 Sebastian Dröge <sebastian@centricular.com>
6
7    PulseAudio is free software; you can redistribute it and/or modify
8    it under the terms of the GNU Lesser General Public License as published
9    by the Free Software Foundation; either version 2.1 of the License,
10    or (at your option) any later version.
11
12    PulseAudio is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15    General Public License for more details.
16
17    You should have received a copy of the GNU Lesser General Public License
18    along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19***/
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include <pulse/xmalloc.h>
26
27#include <pulsecore/core.h>
28#include <pulsecore/core-util.h>
29#include <pulsecore/i18n.h>
30#include <pulsecore/source.h>
31#include <pulsecore/modargs.h>
32#include <pulsecore/log.h>
33
34PA_MODULE_AUTHOR("Sebastian Dröge");
35PA_MODULE_DESCRIPTION(_("Always keeps at least one source loaded even if it's a null one"));
36PA_MODULE_VERSION(PACKAGE_VERSION);
37PA_MODULE_LOAD_ONCE(true);
38PA_MODULE_USAGE(
39        "source_name=<name of source>");
40
41#define DEFAULT_SOURCE_NAME "auto_null"
42
43static const char* const valid_modargs[] = {
44    "source_name",
45    NULL,
46};
47
48struct userdata {
49    uint32_t null_module;
50    bool ignore;
51    char *source_name;
52};
53
54static void load_null_source_if_needed(pa_core *c, pa_source *source, struct userdata* u) {
55    pa_source *target;
56    uint32_t idx;
57    char *t;
58    pa_module *m;
59
60    pa_assert(c);
61    pa_assert(u);
62
63    if (u->null_module != PA_INVALID_INDEX)
64        return; /* We've already got a null-source loaded */
65
66    /* Loop through all sources and check to see if we have *any*
67     * sources. Ignore the source passed in (if it's not null), and
68     * don't count filter or monitor sources. */
69    PA_IDXSET_FOREACH(target, c->sources, idx)
70        if (!source || ((target != source) && !pa_source_is_filter(target) && target->monitor_of == NULL))
71            break;
72
73    if (target)
74        return;
75
76    pa_log_debug("Autoloading null-source as no other sources detected.");
77
78    u->ignore = true;
79
80    t = pa_sprintf_malloc("source_name=%s", u->source_name);
81    pa_module_load(&m, c, "module-null-source", t);
82    u->null_module = m ? m->index : PA_INVALID_INDEX;
83    pa_xfree(t);
84
85    u->ignore = false;
86
87    if (!m)
88        pa_log_warn("Unable to load module-null-source");
89}
90
91static pa_hook_result_t put_hook_callback(pa_core *c, pa_source *source, void* userdata) {
92    struct userdata *u = userdata;
93
94    pa_assert(c);
95    pa_assert(source);
96    pa_assert(u);
97
98    /* This is us detecting ourselves on load... just ignore this. */
99    if (u->ignore)
100        return PA_HOOK_OK;
101
102    /* There's no point in doing anything if the core is shut down anyway */
103    if (c->state == PA_CORE_SHUTDOWN)
104        return PA_HOOK_OK;
105
106    /* Auto-loaded null-source not active, so ignoring newly detected source. */
107    if (u->null_module == PA_INVALID_INDEX)
108        return PA_HOOK_OK;
109
110    /* This is us detecting ourselves on load in a different way... just ignore this too. */
111    if (source->module && source->module->index == u->null_module)
112        return PA_HOOK_OK;
113
114    /* We don't count filter or monitor sources since they need a real source */
115    if (pa_source_is_filter(source) || source->monitor_of != NULL)
116        return PA_HOOK_OK;
117
118    pa_log_info("A new source has been discovered. Unloading null-source.");
119
120    pa_module_unload_request_by_index(c, u->null_module, true);
121    u->null_module = PA_INVALID_INDEX;
122
123    return PA_HOOK_OK;
124}
125
126static pa_hook_result_t unlink_hook_callback(pa_core *c, pa_source *source, void* userdata) {
127    struct userdata *u = userdata;
128
129    pa_assert(c);
130    pa_assert(source);
131    pa_assert(u);
132
133    /* First check to see if it's our own null-source that's been removed... */
134    if (u->null_module != PA_INVALID_INDEX && source->module && source->module->index == u->null_module) {
135        pa_log_debug("Autoloaded null-source removed");
136        u->null_module = PA_INVALID_INDEX;
137        return PA_HOOK_OK;
138    }
139
140    /* There's no point in doing anything if the core is shut down anyway */
141    if (c->state == PA_CORE_SHUTDOWN)
142        return PA_HOOK_OK;
143
144    load_null_source_if_needed(c, source, u);
145
146    return PA_HOOK_OK;
147}
148
149int pa__init(pa_module*m) {
150    pa_modargs *ma = NULL;
151    struct userdata *u;
152
153    pa_assert(m);
154
155    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
156        pa_log("Failed to parse module arguments");
157        return -1;
158    }
159
160    m->userdata = u = pa_xnew(struct userdata, 1);
161    u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME));
162    pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) put_hook_callback, u);
163    pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) unlink_hook_callback, u);
164    u->null_module = PA_INVALID_INDEX;
165    u->ignore = false;
166
167    pa_modargs_free(ma);
168
169    load_null_source_if_needed(m->core, NULL, u);
170
171    return 0;
172}
173
174void pa__done(pa_module*m) {
175    struct userdata *u;
176
177    pa_assert(m);
178
179    if (!(u = m->userdata))
180        return;
181
182    if (u->null_module != PA_INVALID_INDEX && m->core->state != PA_CORE_SHUTDOWN)
183        pa_module_unload_request_by_index(m->core, u->null_module, true);
184
185    pa_xfree(u->source_name);
186    pa_xfree(u);
187}
188