Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-pa-mixer"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gvc-mixer-control.h>
9 : : #include <gvc-mixer-sink.h>
10 : : #include <gvc-mixer-source.h>
11 : : #include <gvc-mixer-stream.h>
12 : : #include <valent.h>
13 : :
14 : : #include "valent-pa-mixer.h"
15 : : #include "valent-pa-stream.h"
16 : :
17 : :
18 : : struct _ValentPaMixer
19 : : {
20 : : ValentMixerAdapter parent_instance;
21 : :
22 : : GvcMixerControl *control;
23 : :
24 : : GHashTable *streams;
25 : : unsigned int input;
26 : : unsigned int output;
27 : : unsigned int vol_max;
28 : : };
29 : :
30 : 0 : G_DEFINE_FINAL_TYPE (ValentPaMixer, valent_pa_mixer, VALENT_TYPE_MIXER_ADAPTER)
31 : :
32 : :
33 : : /*
34 : : * Gvc Callbacks
35 : : */
36 : : static void
37 : 0 : on_default_sink_changed (GvcMixerControl *control,
38 : : unsigned int stream_id,
39 : : ValentPaMixer *self)
40 : : {
41 : 0 : g_assert (GVC_IS_MIXER_CONTROL (control));
42 : 0 : g_assert (VALENT_IS_PA_MIXER (self));
43 : :
44 : 0 : if (self->output == stream_id)
45 : : return;
46 : :
47 : 0 : self->output = stream_id;
48 : 0 : g_object_notify (G_OBJECT (self), "default-output");
49 : : }
50 : :
51 : : static void
52 : 0 : on_default_source_changed (GvcMixerControl *control,
53 : : unsigned int stream_id,
54 : : ValentPaMixer *self)
55 : : {
56 : 0 : g_assert (GVC_IS_MIXER_CONTROL (control));
57 : 0 : g_assert (VALENT_IS_PA_MIXER (self));
58 : :
59 : 0 : if (self->input == stream_id)
60 : : return;
61 : :
62 : 0 : self->input = stream_id;
63 : 0 : g_object_notify (G_OBJECT (self), "default-input");
64 : : }
65 : :
66 : : static void
67 : 0 : on_stream_added (GvcMixerControl *control,
68 : : unsigned int stream_id,
69 : : ValentPaMixer *self)
70 : : {
71 : 0 : GvcMixerStream *base_stream = NULL;
72 : 0 : ValentMixerStream *stream = NULL;
73 : 0 : ValentMixerDirection direction;
74 : :
75 : 0 : base_stream = gvc_mixer_control_lookup_stream_id (control, stream_id);
76 : :
77 : : /* Ignore source outputs and sink inputs */
78 : 0 : if (GVC_IS_MIXER_SINK (base_stream))
79 : : direction = VALENT_MIXER_OUTPUT;
80 : 0 : else if (GVC_IS_MIXER_SOURCE (base_stream))
81 : : direction = VALENT_MIXER_INPUT;
82 : : else
83 : : return;
84 : :
85 : 0 : stream = g_object_new (VALENT_TYPE_PA_STREAM,
86 : : "base-stream", base_stream,
87 : : "direction", direction,
88 : : "vol-max", self->vol_max,
89 : : NULL);
90 : :
91 : 0 : if (!g_hash_table_replace (self->streams, GUINT_TO_POINTER (stream_id), stream))
92 : : {
93 : 0 : g_warning ("%s: Duplicate Stream: %s",
94 : : G_OBJECT_TYPE_NAME (self),
95 : : valent_mixer_stream_get_name (stream));
96 : : }
97 : :
98 : 0 : valent_mixer_adapter_stream_added (VALENT_MIXER_ADAPTER (self), stream);
99 : : }
100 : :
101 : : static void
102 : 0 : on_stream_removed (GvcMixerControl *control,
103 : : unsigned int stream_id,
104 : : ValentPaMixer *self)
105 : : {
106 : 0 : ValentMixerStream *stream = NULL;
107 : :
108 : 0 : g_assert (GVC_IS_MIXER_CONTROL (control));
109 : 0 : g_assert (VALENT_IS_PA_MIXER (self));
110 : :
111 : 0 : stream = g_hash_table_lookup (self->streams, GUINT_TO_POINTER (stream_id));
112 : :
113 : 0 : if (stream == NULL)
114 : : return;
115 : :
116 : : /* FIXME: If the stream being removed is the default, the change notification
117 : : * will come after the removal notification. As a side effect, if the
118 : : * kdeconnect-android activity is open it will automatically select a
119 : : * remaining stream and override any automatic selection the system
120 : : * wants to perform.
121 : : */
122 : :
123 : 0 : valent_mixer_adapter_stream_removed (VALENT_MIXER_ADAPTER (self), stream);
124 : 0 : g_hash_table_remove (self->streams, GUINT_TO_POINTER (stream_id));
125 : : }
126 : :
127 : : static void
128 : 0 : on_stream_changed (GvcMixerControl *control,
129 : : unsigned int stream_id,
130 : : ValentPaMixer *self)
131 : : {
132 : 0 : ValentMixerStream *stream = NULL;
133 : :
134 : 0 : g_assert (GVC_IS_MIXER_CONTROL (control));
135 : 0 : g_assert (VALENT_IS_PA_MIXER (self));
136 : :
137 : 0 : stream = g_hash_table_lookup (self->streams, GUINT_TO_POINTER (stream_id));
138 : :
139 : 0 : if (stream == NULL)
140 : : return;
141 : :
142 : 0 : g_object_notify (G_OBJECT (stream), "level");
143 : : }
144 : :
145 : : static void
146 : 0 : valent_pa_mixer_load (ValentPaMixer *self)
147 : : {
148 : 0 : g_autoptr (GSList) sources = NULL;
149 : 0 : g_autoptr (GSList) sinks = NULL;
150 : 0 : GvcMixerStream *stream = NULL;
151 : :
152 : 0 : g_assert (VALENT_IS_PA_MIXER (self));
153 : :
154 : : /* Get current defaults */
155 : 0 : self->vol_max = gvc_mixer_control_get_vol_max_norm (self->control);
156 : :
157 : : /* Query the default streams before emitting signals */
158 : 0 : if ((stream = gvc_mixer_control_get_default_sink (self->control)) != NULL)
159 : 0 : self->output = gvc_mixer_stream_get_id (stream);
160 : :
161 : 0 : if ((stream = gvc_mixer_control_get_default_source (self->control)) != NULL)
162 : 0 : self->input = gvc_mixer_stream_get_id (stream);
163 : :
164 : : /* Get current streams */
165 : 0 : sinks = gvc_mixer_control_get_sinks (self->control);
166 : :
167 : 0 : for (const GSList *iter = sinks; iter; iter = iter->next)
168 : 0 : on_stream_added (self->control, gvc_mixer_stream_get_id (iter->data), self);
169 : :
170 : 0 : sources = gvc_mixer_control_get_sources (self->control);
171 : :
172 : 0 : for (const GSList *iter = sources; iter; iter = iter->next)
173 : 0 : on_stream_added (self->control, gvc_mixer_stream_get_id (iter->data), self);
174 : :
175 : : /* Watch stream changes */
176 : 0 : g_object_connect (self->control,
177 : : "signal::default-sink-changed", on_default_sink_changed, self,
178 : : "signal::default-source-changed", on_default_source_changed, self,
179 : : "signal::stream-added", on_stream_added, self,
180 : : "signal::stream-removed", on_stream_removed, self,
181 : : "signal::stream-changed", on_stream_changed, self,
182 : : NULL);
183 : 0 : }
184 : :
185 : : static void
186 : 0 : valent_pa_mixer_unload (ValentPaMixer *self)
187 : : {
188 : 0 : ValentMixerAdapter *adapter = VALENT_MIXER_ADAPTER (self);
189 : 0 : GHashTableIter iter;
190 : 0 : ValentMixerStream *stream;
191 : :
192 : 0 : g_assert (VALENT_IS_PA_MIXER (self));
193 : :
194 : : /* Clear the current defaults */
195 : 0 : self->input = 0;
196 : 0 : g_object_notify (G_OBJECT (self), "default-input");
197 : 0 : self->output = 0;
198 : 0 : g_object_notify (G_OBJECT (self), "default-output");
199 : :
200 : 0 : g_hash_table_iter_init (&iter, self->streams);
201 : :
202 : 0 : while (g_hash_table_iter_next (&iter, NULL, (void **)&stream))
203 : : {
204 : 0 : valent_mixer_adapter_stream_removed (adapter, stream);
205 : 0 : g_hash_table_iter_remove (&iter);
206 : : }
207 : :
208 : 0 : g_signal_handlers_disconnect_by_func (self->control, on_default_sink_changed, self);
209 : 0 : g_signal_handlers_disconnect_by_func (self->control, on_default_source_changed, self);
210 : 0 : g_signal_handlers_disconnect_by_func (self->control, on_stream_added, self);
211 : 0 : g_signal_handlers_disconnect_by_func (self->control, on_stream_removed, self);
212 : 0 : g_signal_handlers_disconnect_by_func (self->control, on_stream_changed, self);
213 : 0 : }
214 : :
215 : : static void
216 : 0 : on_state_changed (GvcMixerControl *control,
217 : : GvcMixerControlState state,
218 : : ValentPaMixer *self)
219 : : {
220 : 0 : g_autoptr (GError) error = NULL;
221 : :
222 : 0 : g_assert (VALENT_IS_PA_MIXER (self));
223 : :
224 : 0 : switch (state)
225 : : {
226 : 0 : case GVC_STATE_CLOSED:
227 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
228 : : VALENT_PLUGIN_STATE_INACTIVE,
229 : : error);
230 : 0 : valent_pa_mixer_unload (self);
231 : 0 : break;
232 : :
233 : 0 : case GVC_STATE_READY:
234 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
235 : : VALENT_PLUGIN_STATE_ACTIVE,
236 : : error);
237 : 0 : valent_pa_mixer_load (self);
238 : 0 : break;
239 : :
240 : 0 : case GVC_STATE_CONNECTING:
241 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
242 : : VALENT_PLUGIN_STATE_INACTIVE,
243 : : error);
244 : 0 : break;
245 : :
246 : 0 : case GVC_STATE_FAILED:
247 : 0 : g_set_error_literal (&error,
248 : : G_IO_ERROR,
249 : : G_IO_ERROR_FAILED,
250 : : "failed to connect to PulseAudio server");
251 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
252 : : VALENT_PLUGIN_STATE_ERROR,
253 : : error);
254 : 0 : valent_pa_mixer_unload (self);
255 : 0 : break;
256 : : }
257 : 0 : }
258 : :
259 : : /*
260 : : * ValentMixerAdapter
261 : : */
262 : : static ValentMixerStream *
263 : 0 : valent_pa_mixer_get_default_input (ValentMixerAdapter *adapter)
264 : : {
265 : 0 : ValentPaMixer *self = VALENT_PA_MIXER (adapter);
266 : :
267 : 0 : return g_hash_table_lookup (self->streams, GUINT_TO_POINTER (self->input));
268 : : }
269 : :
270 : : static void
271 : 0 : valent_pa_mixer_set_default_input (ValentMixerAdapter *adapter,
272 : : ValentMixerStream *stream)
273 : : {
274 : 0 : ValentPaMixer *self = VALENT_PA_MIXER (adapter);
275 : 0 : GvcMixerStream *base_stream = NULL;
276 : :
277 : 0 : g_object_get (stream, "base-stream", &base_stream, NULL);
278 : 0 : gvc_mixer_control_set_default_source (self->control, base_stream);
279 : 0 : g_clear_object (&base_stream);
280 : 0 : }
281 : :
282 : : static ValentMixerStream *
283 : 0 : valent_pa_mixer_get_default_output (ValentMixerAdapter *adapter)
284 : : {
285 : 0 : ValentPaMixer *self = VALENT_PA_MIXER (adapter);
286 : :
287 : 0 : return g_hash_table_lookup (self->streams, GUINT_TO_POINTER (self->output));
288 : : }
289 : :
290 : : static void
291 : 0 : valent_pa_mixer_set_default_output (ValentMixerAdapter *adapter,
292 : : ValentMixerStream *stream)
293 : : {
294 : 0 : ValentPaMixer *self = VALENT_PA_MIXER (adapter);
295 : 0 : GvcMixerStream *base_stream = NULL;
296 : :
297 : 0 : g_object_get (stream, "base-stream", &base_stream, NULL);
298 : 0 : gvc_mixer_control_set_default_sink (self->control, base_stream);
299 : 0 : g_clear_object (&base_stream);
300 : 0 : }
301 : :
302 : : /*
303 : : * ValentObject
304 : : */
305 : : static void
306 : 0 : valent_pa_mixer_destroy (ValentObject *object)
307 : : {
308 : 0 : ValentPaMixer *self = VALENT_PA_MIXER (object);
309 : :
310 : 0 : g_signal_handlers_disconnect_by_data (self->control, self);
311 : 0 : gvc_mixer_control_close (self->control);
312 : 0 : g_hash_table_remove_all (self->streams);
313 : :
314 : 0 : VALENT_OBJECT_CLASS (valent_pa_mixer_parent_class)->destroy (object);
315 : 0 : }
316 : :
317 : : /*
318 : : * GObject
319 : : */
320 : : static void
321 : 0 : valent_pa_mixer_constructed (GObject *object)
322 : : {
323 : 0 : ValentPaMixer *self = VALENT_PA_MIXER (object);
324 : :
325 : 0 : self->vol_max = gvc_mixer_control_get_vol_max_norm (self->control);
326 : :
327 : 0 : g_signal_connect_object (self->control,
328 : : "state-changed",
329 : : G_CALLBACK (on_state_changed),
330 : : self, 0);
331 : 0 : gvc_mixer_control_open (self->control);
332 : :
333 : 0 : G_OBJECT_CLASS (valent_pa_mixer_parent_class)->constructed (object);
334 : 0 : }
335 : :
336 : : static void
337 : 0 : valent_pa_mixer_finalize (GObject *object)
338 : : {
339 : 0 : ValentPaMixer *self = VALENT_PA_MIXER (object);
340 : :
341 : 0 : g_clear_pointer (&self->streams, g_hash_table_unref);
342 : 0 : g_clear_object (&self->control);
343 : :
344 : 0 : G_OBJECT_CLASS (valent_pa_mixer_parent_class)->finalize (object);
345 : 0 : }
346 : :
347 : : static void
348 : 0 : valent_pa_mixer_class_init (ValentPaMixerClass *klass)
349 : : {
350 : 0 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
351 : 0 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
352 : 0 : ValentMixerAdapterClass *adapter_class = VALENT_MIXER_ADAPTER_CLASS (klass);
353 : :
354 : 0 : object_class->constructed = valent_pa_mixer_constructed;
355 : 0 : object_class->finalize = valent_pa_mixer_finalize;
356 : :
357 : 0 : vobject_class->destroy = valent_pa_mixer_destroy;
358 : :
359 : 0 : adapter_class->get_default_input = valent_pa_mixer_get_default_input;
360 : 0 : adapter_class->set_default_input = valent_pa_mixer_set_default_input;
361 : 0 : adapter_class->get_default_output = valent_pa_mixer_get_default_output;
362 : 0 : adapter_class->set_default_output = valent_pa_mixer_set_default_output;
363 : : }
364 : :
365 : : static void
366 : 0 : valent_pa_mixer_init (ValentPaMixer *self)
367 : : {
368 : 0 : self->control = g_object_new (GVC_TYPE_MIXER_CONTROL,
369 : : "name", "Valent",
370 : : NULL);
371 : 0 : self->streams = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
372 : 0 : }
373 : :
|