1/***
2  This file is part of PulseAudio.
3
4  Copyright 2006 Lennart Poettering
5  Copyright 2009 Canonical Ltd
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/sink-input.h>
29#include <pulsecore/source-output.h>
30#include <pulsecore/source.h>
31#include <pulsecore/modargs.h>
32#include <pulsecore/log.h>
33#include <pulsecore/namereg.h>
34#include <pulsecore/core-util.h>
35
36/* Ignore HDMI devices by default. HDMI monitors don't necessarily have audio
37 * output on them, and even if they do, waking up from sleep or changing
38 * monitor resolution may appear as a plugin event, which causes trouble if the
39 * user doesn't want to use the monitor for audio. */
40#define DEFAULT_BLACKLIST "hdmi"
41
42PA_MODULE_AUTHOR("Michael Terry");
43PA_MODULE_DESCRIPTION("When a sink/source is added, switch to it or conditionally switch to it");
44PA_MODULE_VERSION(PACKAGE_VERSION);
45PA_MODULE_LOAD_ONCE(true);
46PA_MODULE_USAGE(
47        "only_from_unavailable=<boolean, only switch from unavailable ports> "
48        "ignore_virtual=<boolean, ignore new virtual sinks and sources, defaults to true> "
49        "blacklist=<regex, ignore matching devices> "
50);
51
52static const char* const valid_modargs[] = {
53    "only_from_unavailable",
54    "ignore_virtual",
55    "blacklist",
56    NULL,
57};
58
59struct userdata {
60    bool only_from_unavailable;
61    bool ignore_virtual;
62    char *blacklist;
63};
64
65static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void* userdata) {
66    const char *s;
67    struct userdata *u = userdata;
68
69    pa_assert(c);
70    pa_assert(sink);
71    pa_assert(userdata);
72
73    /* Don't want to run during startup or shutdown */
74    if (c->state != PA_CORE_RUNNING)
75        return PA_HOOK_OK;
76
77    pa_log_debug("Trying to switch to new sink %s", sink->name);
78
79    /* Don't switch to any internal devices except HDMI */
80    s = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_STRING);
81    if (s && !pa_startswith(s, "hdmi")) {
82        s = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_BUS);
83        if (pa_safe_streq(s, "pci") || pa_safe_streq(s, "isa")) {
84            pa_log_debug("Refusing to switch to sink on %s bus", s);
85            return PA_HOOK_OK;
86        }
87    }
88
89    /* Ignore sinks matching the blacklist regex */
90    if (u->blacklist && (pa_match(u->blacklist, sink->name) > 0)) {
91        pa_log_info("Refusing to switch to blacklisted sink %s", sink->name);
92        return PA_HOOK_OK;
93    }
94
95    /* Ignore virtual sinks if not configured otherwise on the command line */
96    if (u->ignore_virtual && !(sink->flags & PA_SINK_HARDWARE)) {
97        pa_log_debug("Refusing to switch to virtual sink");
98        return PA_HOOK_OK;
99    }
100
101    /* No default sink, nothing to move away, just set the new default */
102    if (!c->default_sink) {
103        pa_core_set_configured_default_sink(c, sink->name);
104        return PA_HOOK_OK;
105    }
106
107    if (c->default_sink == sink) {
108        pa_log_debug("%s already is the default sink", sink->name);
109        return PA_HOOK_OK;
110    }
111
112    if (u->only_from_unavailable)
113        if (!c->default_sink->active_port || c->default_sink->active_port->available != PA_AVAILABLE_NO) {
114            pa_log_debug("Current default sink is available and module argument only_from_unavailable was set");
115            return PA_HOOK_OK;
116        }
117
118    /* Actually do the switch to the new sink */
119    pa_core_set_configured_default_sink(c, sink->name);
120
121    return PA_HOOK_OK;
122}
123
124static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, void* userdata) {
125    const char *s;
126    struct userdata *u = userdata;
127
128    pa_assert(c);
129    pa_assert(source);
130    pa_assert(userdata);
131
132    /* Don't want to run during startup or shutdown */
133    if (c->state != PA_CORE_RUNNING)
134        return PA_HOOK_OK;
135
136    /* Don't switch to a monitoring source */
137    if (source->monitor_of)
138        return PA_HOOK_OK;
139
140    pa_log_debug("Trying to switch to new source %s", source->name);
141
142    /* Don't switch to any internal devices */
143    s = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_BUS);
144    if (pa_safe_streq(s, "pci") || pa_safe_streq(s, "isa")) {
145        pa_log_debug("Refusing to switch to source on %s bus", s);
146        return PA_HOOK_OK;
147    }
148
149    /* Ignore sources matching the blacklist regex */
150    if (u->blacklist && (pa_match(u->blacklist, source->name) > 0)) {
151        pa_log_info("Refusing to switch to blacklisted source %s", source->name);
152        return PA_HOOK_OK;
153    }
154
155    /* Ignore virtual sources if not configured otherwise on the command line */
156    if (u->ignore_virtual && !(source->flags & PA_SOURCE_HARDWARE)) {
157        pa_log_debug("Refusing to switch to virtual source");
158        return PA_HOOK_OK;
159    }
160
161    /* No default source, nothing to move away, just set the new default */
162    if (!c->default_source) {
163        pa_core_set_configured_default_source(c, source->name);
164        return PA_HOOK_OK;
165    }
166
167    if (c->default_source == source) {
168        pa_log_debug("%s already is the default source", source->name);
169        return PA_HOOK_OK;
170    }
171
172    if (u->only_from_unavailable)
173        if (!c->default_source->active_port || c->default_source->active_port->available != PA_AVAILABLE_NO) {
174            pa_log_debug("Current default source is available and module argument only_from_unavailable was set");
175            return PA_HOOK_OK;
176        }
177
178    /* Actually do the switch to the new source */
179    pa_core_set_configured_default_source(c, source->name);
180
181    return PA_HOOK_OK;
182}
183
184int pa__init(pa_module*m) {
185    pa_modargs *ma;
186    struct userdata *u;
187
188    pa_assert(m);
189
190    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
191        pa_log("Failed to parse module arguments");
192        return -1;
193    }
194
195    m->userdata = u = pa_xnew0(struct userdata, 1);
196
197    pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+30, (pa_hook_cb_t) sink_put_hook_callback, u);
198    pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+20, (pa_hook_cb_t) source_put_hook_callback, u);
199
200    if (pa_modargs_get_value_boolean(ma, "only_from_unavailable", &u->only_from_unavailable) < 0) {
201        pa_log("Failed to get a boolean value for only_from_unavailable.");
202        goto fail;
203    }
204
205    u->ignore_virtual = true;
206    if (pa_modargs_get_value_boolean(ma, "ignore_virtual", &u->ignore_virtual) < 0) {
207        pa_log("Failed to get a boolean value for ignore_virtual.");
208        goto fail;
209    }
210
211    u->blacklist = pa_xstrdup(pa_modargs_get_value(ma, "blacklist", DEFAULT_BLACKLIST));
212
213    /* An empty string disables all blacklisting. */
214    if (!*u->blacklist) {
215        pa_xfree(u->blacklist);
216        u->blacklist = NULL;
217    }
218
219    if (u->blacklist != NULL && !pa_is_regex_valid(u->blacklist)) {
220        pa_log_error("A blacklist pattern was provided but is not a valid regex");
221        pa_xfree(u->blacklist);
222        goto fail;
223    }
224
225    pa_modargs_free(ma);
226    return 0;
227
228fail:
229    if (ma)
230        pa_modargs_free(ma);
231
232    pa__done(m);
233
234    return -1;
235}
236
237void pa__done(pa_module*m) {
238    struct userdata *u;
239
240    pa_assert(m);
241
242    if (!(u = m->userdata))
243        return;
244
245    if (u->blacklist)
246        pa_xfree(u->blacklist);
247
248    pa_xfree(u);
249}
250