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