• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***
2   This file is part of PulseAudio.
3 
4   Copyright 2006 Lennart Poettering
5 
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.
10 
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.
15 
16   You should have received a copy of the GNU Lesser General Public License
17   along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18 ***/
19 
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 
24 #include <unistd.h>
25 #include <errno.h>
26 #include <sys/types.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 
30 #include <pulse/xmalloc.h>
31 #include <pulse/volume.h>
32 #include <pulse/channelmap.h>
33 
34 #include <pulsecore/module.h>
35 #include <pulsecore/core-util.h>
36 #include <pulsecore/modargs.h>
37 #include <pulsecore/log.h>
38 #include <pulsecore/sink-input.h>
39 
40 PA_MODULE_AUTHOR("Lennart Poettering");
41 PA_MODULE_DESCRIPTION("Position event sounds between L and R depending on the position on screen of the widget triggering them.");
42 PA_MODULE_VERSION(PACKAGE_VERSION);
43 PA_MODULE_LOAD_ONCE(true);
44 
45 static const char* const valid_modargs[] = {
46     NULL
47 };
48 
49 struct userdata {
50     pa_hook_slot *sink_input_fixate_hook_slot;
51     const char *name;
52 };
53 
parse_pos(const char * pos,double * f)54 static int parse_pos(const char *pos, double *f) {
55 
56     if (pa_atod(pos, f) < 0) {
57         pa_log_warn("Failed to parse hpos/vpos property '%s'.", pos);
58         return -1;
59     }
60 
61     if (*f < 0.0 || *f > 1.0) {
62         pa_log_debug("Property hpos/vpos out of range %0.2f", *f);
63 
64         *f = PA_CLAMP(*f, 0.0, 1.0);
65     }
66 
67     return 0;
68 }
69 
sink_input_fixate_hook_callback(pa_core * core,pa_sink_input_new_data * data,struct userdata * u)70 static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *data, struct userdata *u) {
71     const char *hpos, *vpos, *role, *id;
72     double f;
73     char t[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
74     pa_cvolume v;
75 
76     pa_assert(data);
77 
78     if (!(role = pa_proplist_gets(data->proplist, PA_PROP_MEDIA_ROLE)))
79         return PA_HOOK_OK;
80 
81     if (!pa_streq(role, "event"))
82         return PA_HOOK_OK;
83 
84     if ((id = pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID))) {
85 
86         /* The test sounds should never be positioned in space, since
87          * they might be triggered themselves to configure the speakers
88          * in space, which we don't want to mess up. */
89 
90         if (pa_startswith(id, "audio-channel-"))
91             return PA_HOOK_OK;
92 
93         if (pa_streq(id, "audio-volume-change"))
94             return PA_HOOK_OK;
95 
96         if (pa_streq(id, "audio-test-signal"))
97             return PA_HOOK_OK;
98     }
99 
100     if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS)))
101         hpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_HPOS);
102 
103     if (!(vpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_VPOS)))
104         vpos = pa_proplist_gets(data->proplist, PA_PROP_WINDOW_VPOS);
105 
106     if (!hpos && !vpos)
107         return PA_HOOK_OK;
108 
109     pa_cvolume_reset(&v, data->sink->sample_spec.channels);
110 
111     if (hpos) {
112         if (parse_pos(hpos, &f) < 0)
113             return PA_HOOK_OK;
114 
115         if (pa_channel_map_can_balance(&data->sink->channel_map)) {
116             pa_log_debug("Positioning event sound '%s' horizontally at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
117             pa_cvolume_set_balance(&v, &data->sink->channel_map, f*2.0-1.0);
118         }
119     }
120 
121     if (vpos) {
122         if (parse_pos(vpos, &f) < 0)
123             return PA_HOOK_OK;
124 
125         if (pa_channel_map_can_fade(&data->sink->channel_map)) {
126             pa_log_debug("Positioning event sound '%s' vertically at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
127             pa_cvolume_set_fade(&v, &data->sink->channel_map, f*2.0-1.0);
128         }
129     }
130 
131     pa_log_debug("Final volume factor %s.",
132                  pa_cvolume_snprint_verbose(t,
133                                             sizeof(t),
134                                             &v,
135                                             &data->sink->channel_map,
136                                             data->sink->flags & PA_SINK_DECIBEL_VOLUME));
137     pa_sink_input_new_data_add_volume_factor_sink(data, u->name, &v);
138 
139     return PA_HOOK_OK;
140 }
141 
pa__init(pa_module * m)142 int pa__init(pa_module*m) {
143     pa_modargs *ma;
144     struct userdata *u;
145 
146     pa_assert(m);
147 
148     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
149         pa_log("Failed to parse module arguments");
150         goto fail;
151     }
152 
153     m->userdata = u = pa_xnew(struct userdata, 1);
154     u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u);
155 
156     pa_modargs_free(ma);
157     u->name = m->name;
158 
159     return 0;
160 
161 fail:
162     pa__done(m);
163 
164     return -1;
165 }
166 
pa__done(pa_module * m)167 void pa__done(pa_module*m) {
168     struct userdata* u;
169 
170     pa_assert(m);
171 
172     if (!(u = m->userdata))
173         return;
174 
175     if (u->sink_input_fixate_hook_slot)
176         pa_hook_slot_free(u->sink_input_fixate_hook_slot);
177 
178     pa_xfree(u);
179 }
180