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-pipewire-mixer"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <math.h>
9 : :
10 : : #include <pipewire/pipewire.h>
11 : : #include <pipewire/core.h>
12 : : #include <pipewire/loop.h>
13 : : #include <pipewire/extensions/metadata.h>
14 : : #include <spa/param/audio/format-utils.h>
15 : : #include <spa/param/props.h>
16 : : #include <spa/pod/iter.h>
17 : : #include <valent.h>
18 : :
19 : : #include "valent-pipewire-mixer.h"
20 : : #include "valent-pipewire-stream.h"
21 : :
22 : : #define MIXER_DEVICE "Audio/Device"
23 : : #define MIXER_SINK "Audio/Sink"
24 : : #define MIXER_SOURCE "Audio/Source"
25 : :
26 : :
27 : : struct _ValentPipewireMixer
28 : : {
29 : : ValentMixerAdapter parent_instance;
30 : :
31 : : GHashTable *streams;
32 : : char *default_input;
33 : : char *default_output;
34 : :
35 : : /* PipeWire */
36 : : struct pw_thread_loop *loop;
37 : : struct pw_context *context;
38 : :
39 : : struct pw_core *core;
40 : : struct spa_hook core_listener;
41 : : struct pw_registry *registry;
42 : : struct spa_hook registry_listener;
43 : : struct pw_metadata *metadata;
44 : : struct spa_hook metadata_listener;
45 : : struct spa_list devices;
46 : : struct spa_list nodes;
47 : :
48 : : gboolean closed;
49 : : };
50 : :
51 : 0 : G_DEFINE_FINAL_TYPE (ValentPipewireMixer, valent_pipewire_mixer, VALENT_TYPE_MIXER_ADAPTER)
52 : :
53 : :
54 : : /*
55 : : * Pipewire
56 : : */
57 : : struct node_data
58 : : {
59 : : ValentPipewireMixer *adapter;
60 : :
61 : : uint32_t id;
62 : : uint64_t serial;
63 : : uint32_t device_id;
64 : :
65 : : struct pw_node *proxy;
66 : : struct spa_hook proxy_listener;
67 : : struct spa_hook object_listener;
68 : : struct spa_list link;
69 : :
70 : : /* State*/
71 : : char *node_name;
72 : : char *node_description;
73 : : enum spa_direction direction;
74 : : float volume;
75 : : uint8_t n_channels;
76 : : bool mute;
77 : : };
78 : :
79 : : struct device_data
80 : : {
81 : : ValentPipewireMixer *adapter;
82 : :
83 : : uint32_t id;
84 : : uint64_t serial;
85 : :
86 : : struct pw_device *proxy;
87 : : struct spa_hook proxy_listener;
88 : : struct spa_hook object_listener;
89 : : struct spa_list link;
90 : :
91 : : /* State*/
92 : : char *input_description;
93 : : uint32_t input_device;
94 : : uint32_t input_port;
95 : : char *output_description;
96 : : uint32_t output_device;
97 : : uint32_t output_port;
98 : : };
99 : :
100 : : struct registry_data
101 : : {
102 : : ValentPipewireMixer *adapter;
103 : :
104 : : struct pw_registry *registry;
105 : : struct pw_proxy *proxy;
106 : : };
107 : :
108 : : static const struct pw_node_events node_events;
109 : : static const struct pw_proxy_events node_proxy_events;
110 : : static const struct pw_device_events device_events;
111 : : static const struct pw_proxy_events device_proxy_events;
112 : : static const struct pw_core_events core_events;
113 : : static const struct pw_metadata_events metadata_events;
114 : : static const struct pw_registry_events registry_events;
115 : :
116 : : static void valent_pipewire_mixer_open (ValentPipewireMixer *self);
117 : : static void valent_pipewire_mixer_close (ValentPipewireMixer *self);
118 : :
119 : :
120 : : static inline struct device_data *
121 : 0 : valent_pipewire_mixer_lookup_device (ValentPipewireMixer *self,
122 : : uint32_t device_id)
123 : : {
124 : 0 : struct device_data *device = NULL;
125 : :
126 : 0 : spa_list_for_each (device, &self->devices, link)
127 : : {
128 : 0 : if G_UNLIKELY (device == NULL)
129 : : continue;
130 : :
131 : 0 : if (device->id == device_id)
132 : : return device;
133 : : }
134 : :
135 : : return NULL;
136 : : }
137 : :
138 : : static inline struct node_data *
139 : 0 : valent_pipewire_mixer_lookup_device_node (ValentPipewireMixer *self,
140 : : uint32_t device_id,
141 : : enum spa_direction direction)
142 : : {
143 : 0 : struct device_data *device = NULL;
144 : 0 : struct node_data *node = NULL;
145 : :
146 : 0 : if ((device = valent_pipewire_mixer_lookup_device (self, device_id)) == NULL)
147 : : return NULL;
148 : :
149 : 0 : spa_list_for_each (node, &self->nodes, link)
150 : : {
151 : 0 : if G_UNLIKELY (node == NULL)
152 : : continue;
153 : :
154 : 0 : if (node->device_id == device->id && node->direction == direction)
155 : : return node;
156 : : }
157 : :
158 : : return NULL;
159 : : }
160 : :
161 : : static inline struct node_data *
162 : 0 : valent_pipewire_mixer_lookup_node (ValentPipewireMixer *self,
163 : : uint32_t node_id)
164 : : {
165 : 0 : struct node_data *node = NULL;
166 : :
167 : 0 : spa_list_for_each (node, &self->nodes, link)
168 : : {
169 : 0 : if G_UNLIKELY (node == NULL)
170 : : continue;
171 : :
172 : 0 : if (node->id == node_id)
173 : : return node;
174 : : }
175 : :
176 : : return NULL;
177 : : }
178 : :
179 : : static inline struct node_data *
180 : 0 : valent_pipewire_mixer_lookup_node_name (ValentPipewireMixer *self,
181 : : const char *node_name)
182 : : {
183 : 0 : struct node_data *node = NULL;
184 : :
185 : 0 : spa_list_for_each (node, &self->nodes, link)
186 : : {
187 : 0 : if G_UNLIKELY (node == NULL)
188 : : continue;
189 : :
190 : 0 : if (g_strcmp0 (node->node_name, node_name) == 0)
191 : : return node;
192 : : }
193 : :
194 : : return NULL;
195 : : }
196 : :
197 : :
198 : : /*
199 : : * ValentMixerAdapter <-> PipeWire
200 : : */
201 : : typedef struct
202 : : {
203 : : GRecMutex mutex;
204 : :
205 : : ValentPipewireMixer *adapter;
206 : : uint32_t device_id;
207 : : uint32_t node_id;
208 : :
209 : : /* ValentMixerStream */
210 : : char *name;
211 : : char *description;
212 : : ValentMixerDirection direction;
213 : : gboolean muted;
214 : : uint32_t level;
215 : : } StreamState;
216 : :
217 : : static inline void
218 : 0 : stream_state_free (gpointer data)
219 : : {
220 : 0 : StreamState *state = (StreamState *)data;
221 : :
222 : 0 : g_rec_mutex_lock (&state->mutex);
223 : 0 : g_clear_object (&state->adapter);
224 : 0 : g_clear_pointer (&state->name, g_free);
225 : 0 : g_clear_pointer (&state->description, g_free);
226 : 0 : g_rec_mutex_unlock (&state->mutex);
227 : 0 : g_rec_mutex_clear (&state->mutex);
228 : 0 : g_clear_pointer (&state, g_free);
229 : 0 : }
230 : :
231 : : static inline StreamState *
232 : 0 : stream_state_new (ValentPipewireMixer *self,
233 : : struct node_data *node)
234 : : {
235 : 0 : struct device_data *device = NULL;
236 : 0 : StreamState *state = NULL;
237 : 0 : ValentMixerDirection direction;
238 : 0 : g_autofree char *description = NULL;
239 : :
240 : 0 : g_assert (VALENT_IS_PIPEWIRE_MIXER (self));
241 : :
242 : 0 : device = valent_pipewire_mixer_lookup_device (self, node->device_id);
243 : :
244 : 0 : if (node->direction == SPA_DIRECTION_INPUT)
245 : : direction = VALENT_MIXER_INPUT;
246 : : else
247 : 0 : direction = VALENT_MIXER_OUTPUT;
248 : :
249 : 0 : if (direction == VALENT_MIXER_INPUT &&
250 : 0 : (device != NULL && device->input_description != NULL))
251 : : {
252 : 0 : description = g_strdup_printf ("%s (%s)",
253 : : device->input_description,
254 : : node->node_description);
255 : : }
256 : 0 : else if (direction == VALENT_MIXER_OUTPUT &&
257 : 0 : (device != NULL && device->output_description != NULL))
258 : : {
259 : 0 : description = g_strdup_printf ("%s (%s)",
260 : : device->output_description,
261 : : node->node_description);
262 : : }
263 : : else
264 : : {
265 : 0 : description = g_strdup (node->node_description);
266 : : }
267 : :
268 : 0 : state = g_new0 (StreamState, 1);
269 : 0 : g_rec_mutex_init (&state->mutex);
270 : 0 : g_rec_mutex_lock (&state->mutex);
271 : 0 : state->adapter = g_object_ref (self);
272 : 0 : state->device_id = node->device_id;
273 : 0 : state->node_id = node->id;
274 : :
275 : 0 : state->name = g_strdup (node->node_name);
276 : 0 : state->description = g_steal_pointer (&description);
277 : 0 : state->direction = direction;
278 : 0 : state->level = (uint32_t)ceil (cbrt (node->volume) * 100.0);
279 : 0 : state->muted = !!node->mute;
280 : 0 : g_rec_mutex_unlock (&state->mutex);
281 : :
282 : 0 : return state;
283 : : }
284 : :
285 : : static inline gboolean
286 : 0 : stream_state_flush (gpointer data)
287 : : {
288 : 0 : StreamState *state = (StreamState *)data;
289 : 0 : ValentPipewireMixer *self = NULL;
290 : 0 : ValentMixerStream *stream = NULL;
291 : :
292 : 0 : g_assert (VALENT_IS_MAIN_THREAD ());
293 : :
294 : 0 : g_rec_mutex_lock (&state->mutex);
295 : 0 : self = VALENT_PIPEWIRE_MIXER (state->adapter);
296 : :
297 : 0 : if (g_atomic_int_get (&self->closed))
298 : 0 : goto closed;
299 : :
300 : 0 : if ((stream = g_hash_table_lookup (self->streams, state->name)) == NULL)
301 : : {
302 : 0 : stream = g_object_new (VALENT_TYPE_PIPEWIRE_STREAM,
303 : : "adapter", self,
304 : : "device-id", state->device_id,
305 : : "node-id", state->node_id,
306 : : "name", state->name,
307 : 0 : "direction", state->direction,
308 : : "level", state->level,
309 : : "muted", state->muted,
310 : : NULL);
311 : 0 : valent_pipewire_stream_update (VALENT_PIPEWIRE_STREAM (stream),
312 : 0 : state->description,
313 : : state->level,
314 : : state->muted);
315 : :
316 : : /* Ensure there is a default stream set when `items-changed` is emitted */
317 : 0 : if (self->default_input == NULL && state->direction == VALENT_MIXER_INPUT)
318 : 0 : self->default_input = g_strdup (state->name);
319 : 0 : if (self->default_output == NULL && state->direction == VALENT_MIXER_OUTPUT)
320 : 0 : self->default_output = g_strdup (state->name);
321 : :
322 : 0 : g_hash_table_replace (self->streams, g_strdup (state->name), stream);
323 : 0 : valent_mixer_adapter_stream_added (VALENT_MIXER_ADAPTER (self), stream);
324 : : }
325 : : else
326 : : {
327 : 0 : valent_pipewire_stream_update (VALENT_PIPEWIRE_STREAM (stream),
328 : 0 : state->description,
329 : : state->level,
330 : : state->muted);
331 : : }
332 : :
333 : 0 : closed:
334 : 0 : g_rec_mutex_unlock (&state->mutex);
335 : :
336 : 0 : return G_SOURCE_REMOVE;
337 : : }
338 : :
339 : : static inline int
340 : 0 : stream_state_main (struct spa_loop *loop,
341 : : bool async,
342 : : uint32_t seq,
343 : : const void *data,
344 : : size_t size,
345 : : void *user_data)
346 : : {
347 : 0 : struct node_data *ndata = (struct node_data *)user_data;
348 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (ndata->adapter);
349 : 0 : struct device_data *ddata = NULL;
350 : 0 : StreamState *state = NULL;
351 : :
352 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (self)))
353 : : return 0;
354 : :
355 : 0 : if ((ddata = valent_pipewire_mixer_lookup_device (self, ndata->device_id)) == NULL)
356 : : return 0;
357 : :
358 : 0 : state = stream_state_new (self, ndata);
359 : 0 : g_main_context_invoke_full (NULL,
360 : : G_PRIORITY_DEFAULT,
361 : : stream_state_flush,
362 : : g_steal_pointer (&state),
363 : : stream_state_free);
364 : :
365 : 0 : return 0;
366 : : }
367 : :
368 : : static inline int
369 : 0 : stream_state_update (struct spa_loop *loop,
370 : : bool async,
371 : : uint32_t seq,
372 : : const void *data,
373 : : size_t size,
374 : : void *user_data)
375 : : {
376 : 0 : StreamState *state = (StreamState *)user_data;
377 : 0 : struct node_data *ndata = NULL;
378 : 0 : struct device_data *ddata = NULL;
379 : 0 : struct spa_pod_builder builder;
380 : 0 : struct spa_pod_frame f[2];
381 : 0 : struct spa_pod *param;
382 : 0 : char buffer[1024] = { 0, };
383 : 0 : float volumes[SPA_AUDIO_MAX_CHANNELS] = { 0.0, };
384 : 0 : float volume = 0.0;
385 : 0 : uint32_t route_device = 0;
386 : 0 : uint32_t route_index = 0;
387 : :
388 : 0 : VALENT_ENTRY;
389 : :
390 : 0 : g_rec_mutex_lock (&state->mutex);
391 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (state->adapter)))
392 : 0 : VALENT_GOTO (closed);
393 : :
394 : 0 : ndata = valent_pipewire_mixer_lookup_node (state->adapter, state->node_id);
395 : 0 : ddata = valent_pipewire_mixer_lookup_device (state->adapter, state->device_id);
396 : :
397 : 0 : if (ndata == NULL || ddata == NULL)
398 : 0 : VALENT_GOTO (closed);
399 : :
400 : 0 : if (ndata->direction == SPA_DIRECTION_OUTPUT)
401 : : {
402 : 0 : route_device = ddata->output_device;
403 : 0 : route_index = ddata->output_port;
404 : : }
405 : 0 : else if (ndata->direction == SPA_DIRECTION_INPUT)
406 : : {
407 : 0 : route_device = ddata->input_device;
408 : 0 : route_index = ddata->input_port;
409 : : }
410 : :
411 : 0 : volume = ((float)state->level / 100);
412 : 0 : for (uint32_t i = 0; i < ndata->n_channels; i++)
413 : 0 : volumes[i] = volume * volume * volume;
414 : :
415 : :
416 : 0 : builder = SPA_POD_BUILDER_INIT (buffer, sizeof (buffer));
417 : 0 : spa_pod_builder_push_object (&builder, &f[0],
418 : : SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route);
419 : 0 : spa_pod_builder_add (&builder,
420 : : SPA_PARAM_ROUTE_index, SPA_POD_Int (route_index),
421 : : SPA_PARAM_ROUTE_device, SPA_POD_Int (route_device),
422 : : 0);
423 : :
424 : 0 : spa_pod_builder_prop (&builder, SPA_PARAM_ROUTE_props, 0);
425 : 0 : spa_pod_builder_push_object (&builder, &f[1],
426 : : SPA_TYPE_OBJECT_Props, SPA_PARAM_Route);
427 : 0 : spa_pod_builder_add (&builder,
428 : 0 : SPA_PROP_mute, SPA_POD_Bool ((bool)state->muted),
429 : 0 : SPA_PROP_channelVolumes, SPA_POD_Array (sizeof (float),
430 : : SPA_TYPE_Float,
431 : : ndata->n_channels,
432 : : volumes),
433 : : 0);
434 : 0 : spa_pod_builder_pop (&builder, &f[1]);
435 : :
436 : 0 : spa_pod_builder_prop (&builder, SPA_PARAM_ROUTE_save, 0);
437 : 0 : spa_pod_builder_bool (&builder, true);
438 : 0 : param = spa_pod_builder_pop (&builder, &f[0]);
439 : :
440 : 0 : pw_device_set_param (ddata->proxy, SPA_PARAM_Route, 0, param);
441 : :
442 : 0 : closed:
443 : 0 : g_rec_mutex_unlock (&state->mutex);
444 : 0 : g_clear_pointer (&state, stream_state_free);
445 : :
446 : 0 : VALENT_RETURN (0);
447 : : }
448 : :
449 : :
450 : : typedef struct
451 : : {
452 : : GRecMutex mutex;
453 : :
454 : : ValentPipewireMixer *adapter;
455 : : struct spa_source *source;
456 : :
457 : : char *default_input;
458 : : char *default_output;
459 : : GPtrArray *streams;
460 : : } MixerState;
461 : :
462 : : static inline void
463 : 0 : mixer_state_free (gpointer data)
464 : : {
465 : 0 : MixerState *state = (MixerState *)data;
466 : :
467 : 0 : g_rec_mutex_lock (&state->mutex);
468 : 0 : g_clear_object (&state->adapter);
469 : 0 : g_clear_pointer (&state->default_input, g_free);
470 : 0 : g_clear_pointer (&state->default_output, g_free);
471 : 0 : g_clear_pointer (&state->streams, g_ptr_array_unref);
472 : 0 : g_rec_mutex_unlock (&state->mutex);
473 : 0 : g_rec_mutex_clear (&state->mutex);
474 : 0 : g_clear_pointer (&state, g_free);
475 : 0 : }
476 : :
477 : : static inline gboolean
478 : 0 : mixer_state_flush (gpointer data)
479 : : {
480 : 0 : MixerState *state = (MixerState *)data;
481 : 0 : ValentPipewireMixer *self = NULL;
482 : :
483 : 0 : g_assert (VALENT_IS_MAIN_THREAD ());
484 : :
485 : 0 : g_rec_mutex_lock (&state->mutex);
486 : 0 : self = VALENT_PIPEWIRE_MIXER (state->adapter);
487 : :
488 : 0 : if (!g_atomic_int_get (&self->closed))
489 : : {
490 : 0 : if (state->default_input != NULL)
491 : : {
492 : 0 : if (g_set_str (&self->default_input, state->default_input))
493 : 0 : g_object_notify (G_OBJECT (self), "default-input");
494 : : }
495 : :
496 : 0 : if (state->default_output != NULL)
497 : : {
498 : 0 : if (g_set_str (&self->default_output, state->default_output))
499 : 0 : g_object_notify (G_OBJECT (self), "default-output");
500 : : }
501 : : }
502 : 0 : g_rec_mutex_unlock (&state->mutex);
503 : :
504 : 0 : return G_SOURCE_REMOVE;
505 : : }
506 : :
507 : : static inline gboolean
508 : 0 : has_stream (gconstpointer a,
509 : : gconstpointer b)
510 : : {
511 : 0 : return g_strcmp0 (((StreamState *)a)->name, (const char *)b) == 0;
512 : : }
513 : :
514 : : static inline gboolean
515 : 0 : mixer_streams_flush (gpointer data)
516 : : {
517 : 0 : MixerState *state = (MixerState *)data;
518 : 0 : ValentPipewireMixer *self = NULL;
519 : 0 : GHashTableIter iter;
520 : 0 : const char *name;
521 : 0 : ValentMixerStream *stream;
522 : :
523 : 0 : g_assert (VALENT_IS_MAIN_THREAD ());
524 : :
525 : 0 : g_rec_mutex_lock (&state->mutex);
526 : 0 : self = VALENT_PIPEWIRE_MIXER (state->adapter);
527 : :
528 : 0 : if (!g_atomic_int_get (&self->closed))
529 : : {
530 : 0 : g_hash_table_iter_init (&iter, self->streams);
531 : :
532 : 0 : while (g_hash_table_iter_next (&iter, (void **)&name, (void **)&stream))
533 : : {
534 : 0 : unsigned int index_ = 0;
535 : :
536 : 0 : if (g_ptr_array_find_with_equal_func (state->streams, name, has_stream, &index_))
537 : : {
538 : 0 : g_ptr_array_remove_index (state->streams, index_);
539 : 0 : continue;
540 : : }
541 : :
542 : 0 : valent_mixer_adapter_stream_removed (VALENT_MIXER_ADAPTER (self), stream);
543 : 0 : g_hash_table_iter_remove (&iter);
544 : : }
545 : :
546 : 0 : for (unsigned int i = 0; i < state->streams->len; i++)
547 : 0 : stream_state_flush (g_ptr_array_index (state->streams, i));
548 : : }
549 : 0 : g_rec_mutex_unlock (&state->mutex);
550 : :
551 : 0 : return G_SOURCE_REMOVE;
552 : : }
553 : :
554 : :
555 : : /*
556 : : * Nodes
557 : : */
558 : : static inline void
559 : 0 : on_node_info (void *object,
560 : : const struct pw_node_info *info)
561 : : {
562 : 0 : struct node_data *ndata = (struct node_data *)object;
563 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (ndata->adapter);
564 : :
565 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (self)))
566 : : return;
567 : :
568 : 0 : if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS)
569 : : {
570 : 0 : for (uint32_t i = 0; i < info->n_params; i++)
571 : : {
572 : 0 : uint32_t id = info->params[i].id;
573 : 0 : uint32_t flags = info->params[i].flags;
574 : :
575 : 0 : if (id == SPA_PARAM_Props && (flags & SPA_PARAM_INFO_READ) != 0)
576 : : {
577 : 0 : pw_node_enum_params (ndata->proxy, 0, id, 0, UINT32_MAX, NULL);
578 : 0 : pw_core_sync (self->core, PW_ID_CORE, 0);
579 : : }
580 : : }
581 : : }
582 : : }
583 : :
584 : : static void
585 : 0 : on_node_param (void *object,
586 : : int seq,
587 : : uint32_t id,
588 : : uint32_t index,
589 : : uint32_t next,
590 : : const struct spa_pod *param)
591 : : {
592 : 0 : struct node_data *ndata = (struct node_data *)object;
593 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (ndata->adapter);
594 : 0 : gboolean notify = FALSE;
595 : 0 : bool mute = false;
596 : 0 : uint32_t csize, ctype;
597 : 0 : uint32_t n_channels = 0;
598 : 0 : float *volumes = NULL;
599 : 0 : float volume = 0.0;
600 : :
601 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (self)))
602 : 0 : return;
603 : :
604 : 0 : if (id != SPA_PARAM_Props || param == NULL)
605 : : return;
606 : :
607 : 0 : if (spa_pod_parse_object (param, SPA_TYPE_OBJECT_Props, NULL,
608 : : SPA_PROP_mute, SPA_POD_Bool (&mute),
609 : : SPA_PROP_volume, SPA_POD_Float (&volume),
610 : : SPA_PROP_channelVolumes, SPA_POD_Array (&csize,
611 : : &ctype,
612 : : &n_channels,
613 : : &volumes)) < 0)
614 : 0 : return;
615 : :
616 : 0 : if (ndata->mute != mute)
617 : : {
618 : 0 : ndata->mute = mute;
619 : 0 : notify = TRUE;
620 : : }
621 : :
622 : 0 : if (n_channels > 0)
623 : : {
624 : 0 : volume = 0.0;
625 : :
626 : 0 : for (uint32_t i = 0; i < n_channels; i++)
627 : 0 : volume = MAX (volume, volumes[i]);
628 : : }
629 : :
630 : 0 : if (!G_APPROX_VALUE (ndata->volume, volume, 0.0000001))
631 : : {
632 : 0 : ndata->volume = volume;
633 : 0 : ndata->n_channels = n_channels;
634 : 0 : notify = TRUE;
635 : : }
636 : :
637 : 0 : if (notify)
638 : : {
639 : 0 : pw_loop_invoke (pw_thread_loop_get_loop (self->loop),
640 : : stream_state_main,
641 : : 0,
642 : : NULL,
643 : : 0,
644 : : false,
645 : : ndata);
646 : : }
647 : : }
648 : :
649 : : static const struct pw_node_events node_events = {
650 : : .info = on_node_info,
651 : : .param = on_node_param,
652 : : };
653 : :
654 : :
655 : : static void
656 : 0 : on_node_proxy_removed (void *data)
657 : : {
658 : 0 : struct node_data *ndata = data;
659 : :
660 : 0 : VALENT_PROBE;
661 : :
662 : 0 : spa_hook_remove (&ndata->object_listener);
663 : 0 : pw_proxy_destroy ((struct pw_proxy*)ndata->proxy);
664 : 0 : }
665 : :
666 : : static void
667 : 0 : on_node_proxy_destroyed (void *data)
668 : : {
669 : 0 : struct node_data *ndata = (struct node_data *)data;
670 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (ndata->adapter);
671 : 0 : MixerState *state = NULL;
672 : :
673 : 0 : VALENT_NOTE ("id: %u, serial: %zu", ndata->id, ndata->serial);
674 : :
675 : 0 : g_clear_pointer (&ndata->node_name, g_free);
676 : 0 : g_clear_pointer (&ndata->node_description, g_free);
677 : 0 : spa_list_remove (&ndata->link);
678 : :
679 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (ndata->adapter)))
680 : 0 : return;
681 : :
682 : 0 : state = g_new0 (MixerState, 1);
683 : 0 : g_rec_mutex_init (&state->mutex);
684 : 0 : g_rec_mutex_lock (&state->mutex);
685 : 0 : state->adapter = g_object_ref (self);
686 : 0 : state->streams = g_ptr_array_new_with_free_func (stream_state_free);
687 : :
688 : 0 : spa_list_for_each (ndata, &self->nodes, link)
689 : 0 : g_ptr_array_add (state->streams, stream_state_new (self, ndata));
690 : :
691 : 0 : g_rec_mutex_unlock (&state->mutex);
692 : :
693 : 0 : g_main_context_invoke_full (NULL,
694 : : G_PRIORITY_DEFAULT,
695 : : mixer_streams_flush,
696 : : g_steal_pointer (&state),
697 : : mixer_state_free);
698 : : }
699 : :
700 : : static const struct pw_proxy_events node_proxy_events = {
701 : : PW_VERSION_PROXY_EVENTS,
702 : : .removed = on_node_proxy_removed,
703 : : .destroy = on_node_proxy_destroyed,
704 : : };
705 : :
706 : :
707 : : static void
708 : 0 : on_device_info (void *object,
709 : : const struct pw_device_info *info)
710 : : {
711 : 0 : struct device_data *ddata = (struct device_data *)object;
712 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (ddata->adapter);
713 : :
714 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (self)))
715 : : return;
716 : :
717 : 0 : if ((info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) != 0)
718 : : {
719 : 0 : for (uint32_t i = 0; i < info->n_params; i++)
720 : : {
721 : 0 : uint32_t id = info->params[i].id;
722 : 0 : uint32_t flags = info->params[i].flags;
723 : :
724 : 0 : if (id == SPA_PARAM_Route && (flags & SPA_PARAM_INFO_READ) != 0)
725 : : {
726 : 0 : pw_device_enum_params (ddata->proxy, 0, id, 0, UINT32_MAX, NULL);
727 : 0 : pw_core_sync (self->core, PW_ID_CORE, 0);
728 : : }
729 : : }
730 : : }
731 : : }
732 : :
733 : : static void
734 : 0 : on_device_param (void *data,
735 : : int seq,
736 : : uint32_t id,
737 : : uint32_t index,
738 : : uint32_t next,
739 : : const struct spa_pod *param)
740 : : {
741 : 0 : struct device_data *ddata = (struct device_data *)data;
742 : 0 : struct node_data *ndata = NULL;
743 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (ddata->adapter);
744 : 0 : const char *name;
745 : 0 : const char *description;
746 : 0 : uint32_t route_index = 0;
747 : 0 : uint32_t route_device = 0;
748 : 0 : enum spa_direction direction = 0;
749 : 0 : enum spa_param_availability available = 0;
750 : 0 : struct spa_pod *props = NULL;
751 : :
752 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (self)))
753 : 0 : return;
754 : :
755 : 0 : if (id != SPA_PARAM_Route || param == NULL)
756 : : return;
757 : :
758 : 0 : if (spa_pod_parse_object (param, SPA_TYPE_OBJECT_ParamRoute, NULL,
759 : : SPA_PARAM_ROUTE_name, SPA_POD_String (&name),
760 : : SPA_PARAM_ROUTE_description, SPA_POD_String (&description),
761 : : SPA_PARAM_ROUTE_direction, SPA_POD_Id (&direction),
762 : : SPA_PARAM_ROUTE_index, SPA_POD_Int (&route_index),
763 : : SPA_PARAM_ROUTE_device, SPA_POD_Int (&route_device),
764 : : SPA_PARAM_ROUTE_available, SPA_POD_Id (&available),
765 : : SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod (&props)) < 0)
766 : 0 : return;
767 : :
768 : 0 : if (direction == SPA_DIRECTION_INPUT)
769 : : {
770 : 0 : ddata->input_device = route_device;
771 : 0 : ddata->input_port = route_index;
772 : :
773 : 0 : if (!g_set_str (&ddata->input_description, description))
774 : : return;
775 : : }
776 : 0 : else if (direction == SPA_DIRECTION_OUTPUT)
777 : : {
778 : 0 : ddata->output_device = route_device;
779 : 0 : ddata->output_port = route_index;
780 : :
781 : 0 : if (!g_set_str (&ddata->output_description, description))
782 : : return;
783 : : }
784 : :
785 : : /* There may not be a node yet */
786 : 0 : ndata = valent_pipewire_mixer_lookup_device_node (self, ddata->id, direction);
787 : :
788 : 0 : if (ndata != NULL)
789 : : {
790 : 0 : pw_loop_invoke (pw_thread_loop_get_loop (self->loop),
791 : : stream_state_main,
792 : : 0,
793 : : NULL,
794 : : 0,
795 : : false,
796 : : ndata);
797 : : }
798 : : }
799 : :
800 : :
801 : : static const struct pw_device_events device_events = {
802 : : PW_VERSION_DEVICE_EVENTS,
803 : : .info = on_device_info,
804 : : .param = on_device_param,
805 : : };
806 : :
807 : :
808 : : static void
809 : 0 : on_device_proxy_removed (void *data)
810 : : {
811 : 0 : struct device_data *ddata = data;
812 : :
813 : 0 : spa_hook_remove (&ddata->object_listener);
814 : 0 : pw_proxy_destroy ((struct pw_proxy *)ddata->proxy);
815 : 0 : }
816 : :
817 : : static void
818 : 0 : on_device_proxy_destroyed (void *data)
819 : : {
820 : 0 : struct device_data *ddata = data;
821 : :
822 : 0 : VALENT_NOTE ("id: %u, serial: %zu", ddata->id, ddata->serial);
823 : :
824 : 0 : g_clear_pointer (&ddata->input_description, g_free);
825 : 0 : g_clear_pointer (&ddata->output_description, g_free);
826 : 0 : spa_list_remove (&ddata->link);
827 : 0 : }
828 : :
829 : : static const struct pw_proxy_events device_proxy_events = {
830 : : PW_VERSION_PROXY_EVENTS,
831 : : .removed = on_device_proxy_removed,
832 : : .destroy = on_device_proxy_destroyed,
833 : : };
834 : :
835 : :
836 : : static int
837 : 0 : on_metadata_property (void *data,
838 : : uint32_t id,
839 : : const char *key,
840 : : const char *type,
841 : : const char *value)
842 : : {
843 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (data);
844 : 0 : MixerState *state = NULL;
845 : 0 : g_autoptr (JsonNode) node = NULL;
846 : 0 : JsonObject *root = NULL;
847 : 0 : const char *name = NULL;
848 : :
849 : 0 : VALENT_NOTE ("id: %u, key: %s, type: %s, value: %s", id, key, type, value);
850 : :
851 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (self)))
852 : : return 0;
853 : :
854 : 0 : if G_UNLIKELY (key == NULL || type == NULL || value == NULL)
855 : : return 0;
856 : :
857 : 0 : if (!g_str_equal (key, "default.audio.sink") &&
858 : 0 : !g_str_equal (key, "default.audio.source"))
859 : : return 0;
860 : :
861 : 0 : if (!g_str_equal (type, "Spa:String:JSON"))
862 : : return 0;
863 : :
864 : 0 : if ((node = json_from_string (value, NULL)) == NULL ||
865 : 0 : (root = json_node_get_object (node)) == NULL ||
866 : 0 : (name = json_object_get_string_member (root, "name")) == NULL)
867 : : {
868 : 0 : g_warning ("%s(): Failed to parse metadata", G_STRFUNC);
869 : 0 : return 0;
870 : : }
871 : :
872 : 0 : state = g_new0 (MixerState, 1);
873 : 0 : g_rec_mutex_init (&state->mutex);
874 : 0 : g_rec_mutex_lock (&state->mutex);
875 : 0 : state->adapter = g_object_ref (self);
876 : :
877 : 0 : if (g_str_equal (key, "default.audio.sink"))
878 : 0 : g_set_str (&state->default_output, name);
879 : 0 : else if (g_str_equal (key, "default.audio.source"))
880 : 0 : g_set_str (&state->default_input, name);
881 : :
882 : 0 : g_rec_mutex_unlock (&state->mutex);
883 : :
884 : 0 : g_main_context_invoke_full (NULL,
885 : : G_PRIORITY_DEFAULT,
886 : : mixer_state_flush,
887 : : g_steal_pointer (&state),
888 : : mixer_state_free);
889 : 0 : pw_core_sync (self->core, PW_ID_CORE, 0);
890 : :
891 : : return 0;
892 : : }
893 : :
894 : : static const struct pw_metadata_events metadata_events = {
895 : : PW_VERSION_METADATA_EVENTS,
896 : : on_metadata_property
897 : : };
898 : :
899 : :
900 : : /*
901 : : * Pipewire Registry
902 : : */
903 : : static void
904 : 0 : registry_event_global (void *data,
905 : : uint32_t id,
906 : : uint32_t permissions,
907 : : const char *type,
908 : : uint32_t version,
909 : : const struct spa_dict *props)
910 : : {
911 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (data);
912 : :
913 : 0 : if (valent_object_in_destruction (VALENT_OBJECT (self)))
914 : : return;
915 : :
916 : 0 : if G_UNLIKELY (id == SPA_ID_INVALID)
917 : : return;
918 : :
919 : 0 : if (g_strcmp0 (type, PW_TYPE_INTERFACE_Device) == 0)
920 : : {
921 : 0 : struct pw_device *device = NULL;
922 : 0 : struct device_data *ddata = NULL;
923 : 0 : const char *media_class = NULL;
924 : :
925 : 0 : VALENT_NOTE ("id: %u, permissions: %u, type: %s, version: %u",
926 : : id, permissions, type, version);
927 : :
928 : : /* Only audio devices are of interest, for now */
929 : 0 : media_class = spa_dict_lookup (props, PW_KEY_MEDIA_CLASS);
930 : :
931 : 0 : if (g_strcmp0 (media_class, "Audio/Device") != 0)
932 : : return;
933 : :
934 : 0 : device = pw_registry_bind (self->registry, id, type,
935 : : PW_VERSION_PORT, sizeof (*ddata));
936 : 0 : g_return_if_fail (device != NULL);
937 : :
938 : 0 : ddata = pw_proxy_get_user_data ((struct pw_proxy *)device);
939 : 0 : ddata->adapter = self;
940 : 0 : ddata->proxy = device;
941 : 0 : ddata->id = id;
942 : :
943 : 0 : spa_list_append (&self->devices, &ddata->link);
944 : 0 : pw_device_add_listener (ddata->proxy,
945 : : &ddata->object_listener,
946 : : &device_events,
947 : : ddata);
948 : 0 : pw_proxy_add_listener ((struct pw_proxy *)ddata->proxy,
949 : : &ddata->proxy_listener,
950 : : &device_proxy_events,
951 : : ddata);
952 : 0 : pw_core_sync (self->core, PW_ID_CORE, 0);
953 : : }
954 : 0 : else if (g_strcmp0 (type, PW_TYPE_INTERFACE_Node) == 0)
955 : : {
956 : 0 : struct pw_node *node = NULL;
957 : 0 : struct node_data *ndata = NULL;
958 : 0 : struct device_data *ddata = NULL;
959 : 0 : uint32_t device_id;
960 : 0 : const char *media_class = NULL;
961 : :
962 : 0 : VALENT_NOTE ("id: %u, permissions: %u, type: %s, version: %u",
963 : : id, permissions, type, version);
964 : :
965 : : /* Only audio sinks and sources are of interest, for now */
966 : 0 : media_class = spa_dict_lookup (props, PW_KEY_MEDIA_CLASS);
967 : :
968 : 0 : if (g_strcmp0 (media_class, "Audio/Sink") != 0 &&
969 : 0 : g_strcmp0 (media_class, "Audio/Source") != 0)
970 : 0 : return;
971 : :
972 : : /* Only nodes with devices are of interest */
973 : 0 : if (!spa_atou32 (spa_dict_lookup (props, PW_KEY_DEVICE_ID), &device_id, 10) ||
974 : 0 : (ddata = valent_pipewire_mixer_lookup_device (self, device_id)) == NULL)
975 : : return;
976 : :
977 : 0 : node = pw_registry_bind (self->registry, id, type,
978 : : PW_VERSION_NODE, sizeof (*ndata));
979 : 0 : g_return_if_fail (node != NULL);
980 : :
981 : 0 : ndata = pw_proxy_get_user_data ((struct pw_proxy *)node);
982 : 0 : ndata->adapter = self;
983 : 0 : ndata->proxy = node;
984 : 0 : ndata->id = id;
985 : 0 : ndata->device_id = device_id;
986 : :
987 : 0 : ndata->node_name = g_strdup (spa_dict_lookup (props, PW_KEY_NODE_NAME));
988 : 0 : ndata->node_description = g_strdup (spa_dict_lookup (props, PW_KEY_NODE_DESCRIPTION));
989 : :
990 : 0 : if (g_str_equal (media_class, "Audio/Sink"))
991 : 0 : ndata->direction = SPA_DIRECTION_OUTPUT;
992 : 0 : else if (g_str_equal (media_class, "Audio/Source"))
993 : 0 : ndata->direction = SPA_DIRECTION_INPUT;
994 : :
995 : 0 : spa_list_append (&self->nodes, &ndata->link);
996 : 0 : pw_node_add_listener (ndata->proxy,
997 : : &ndata->object_listener,
998 : : &node_events,
999 : : ndata);
1000 : 0 : pw_proxy_add_listener ((struct pw_proxy *)ndata->proxy,
1001 : : &ndata->proxy_listener,
1002 : : &node_proxy_events,
1003 : : ndata);
1004 : 0 : pw_core_sync (self->core, PW_ID_CORE, 0);
1005 : : }
1006 : 0 : else if (g_strcmp0 (type, PW_TYPE_INTERFACE_Metadata) == 0)
1007 : : {
1008 : 0 : const char *metadata_name = NULL;
1009 : :
1010 : 0 : VALENT_NOTE ("id: %u, permissions: %u, type: %s, version: %u",
1011 : : id, permissions, type, version);
1012 : :
1013 : 0 : metadata_name = spa_dict_lookup (props, PW_KEY_METADATA_NAME);
1014 : :
1015 : 0 : if (g_strcmp0 (metadata_name, "default") == 0)
1016 : : {
1017 : 0 : if (self->metadata != NULL)
1018 : 0 : spa_hook_remove (&self->metadata_listener);
1019 : :
1020 : 0 : self->metadata = pw_registry_bind (self->registry, id, type,
1021 : : PW_VERSION_METADATA, 0);
1022 : :
1023 : 0 : if (self->metadata != NULL)
1024 : : {
1025 : 0 : pw_metadata_add_listener (self->metadata,
1026 : : &self->metadata_listener,
1027 : : &metadata_events,
1028 : : self);
1029 : : }
1030 : : }
1031 : :
1032 : 0 : pw_core_sync (self->core, PW_ID_CORE, 0);
1033 : : }
1034 : : }
1035 : :
1036 : : static const struct pw_registry_events registry_events = {
1037 : : PW_VERSION_REGISTRY_EVENTS,
1038 : : .global = registry_event_global,
1039 : : };
1040 : :
1041 : :
1042 : : static void
1043 : 0 : on_core_done (void *data,
1044 : : uint32_t id,
1045 : : int seq)
1046 : : {
1047 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (data);
1048 : :
1049 : 0 : VALENT_NOTE ("id: %u, seq: %d", id, seq);
1050 : :
1051 : 0 : if (id == PW_ID_CORE)
1052 : 0 : pw_thread_loop_signal (self->loop, FALSE);
1053 : 0 : }
1054 : :
1055 : : static void
1056 : 0 : on_core_error (void *data,
1057 : : uint32_t id,
1058 : : int seq,
1059 : : int res,
1060 : : const char *message)
1061 : : {
1062 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (data);
1063 : :
1064 : 0 : VALENT_NOTE ("id: %u, seq: %i, res: %i, message: %s", id, seq, res, message);
1065 : :
1066 : 0 : if (id == PW_ID_CORE)
1067 : 0 : g_warning ("%s(): %s (%i)", G_STRFUNC, message, res);
1068 : :
1069 : 0 : pw_thread_loop_signal (self->loop, FALSE);
1070 : 0 : }
1071 : :
1072 : : static const struct pw_core_events core_events = {
1073 : : PW_VERSION_CORE_EVENTS,
1074 : : .done = on_core_done,
1075 : : .error = on_core_error,
1076 : : };
1077 : :
1078 : :
1079 : : /*
1080 : : *
1081 : : */
1082 : : static void
1083 : 0 : valent_pipewire_mixer_open (ValentPipewireMixer *self)
1084 : : {
1085 : 0 : struct pw_properties *context_properties = NULL;
1086 : 0 : g_autoptr (GError) error = NULL;
1087 : :
1088 : 0 : g_assert (VALENT_IS_PIPEWIRE_MIXER (self));
1089 : 0 : g_assert (VALENT_IS_MAIN_THREAD ());
1090 : :
1091 : 0 : self->loop = pw_thread_loop_new ("valent", NULL);
1092 : 0 : pw_thread_loop_lock (self->loop);
1093 : :
1094 : 0 : if (self->loop == NULL || pw_thread_loop_start (self->loop) != 0)
1095 : : {
1096 : 0 : pw_thread_loop_unlock (self->loop);
1097 : 0 : g_set_error_literal (&error,
1098 : : G_IO_ERROR,
1099 : : G_IO_ERROR_FAILED,
1100 : : "failed to start the thread loop");
1101 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
1102 : : VALENT_PLUGIN_STATE_ERROR,
1103 : : error);
1104 : 0 : return;
1105 : : }
1106 : :
1107 : 0 : spa_list_init (&self->devices);
1108 : 0 : spa_list_init (&self->nodes);
1109 : :
1110 : : /* Register as a manager */
1111 : 0 : context_properties = pw_properties_new (PW_KEY_CONFIG_NAME, "client-rt.conf",
1112 : : PW_KEY_MEDIA_TYPE, "Audio",
1113 : : PW_KEY_MEDIA_CATEGORY, "Manager",
1114 : : PW_KEY_MEDIA_ROLE, "Music",
1115 : : NULL);
1116 : 0 : self->context = pw_context_new (pw_thread_loop_get_loop (self->loop),
1117 : : context_properties,
1118 : : 0);
1119 : :
1120 : 0 : if (self->context == NULL)
1121 : : {
1122 : 0 : pw_thread_loop_unlock (self->loop);
1123 : 0 : g_set_error_literal (&error,
1124 : : G_IO_ERROR,
1125 : : G_IO_ERROR_FAILED,
1126 : : "failed to create context");
1127 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
1128 : : VALENT_PLUGIN_STATE_ERROR,
1129 : : error);
1130 : 0 : return;
1131 : : }
1132 : :
1133 : : /* Failure here usually means missing Flatpak permissions */
1134 : 0 : self->core = pw_context_connect (self->context, NULL, 0);
1135 : :
1136 : 0 : if (self->core == NULL)
1137 : : {
1138 : 0 : pw_thread_loop_unlock (self->loop);
1139 : 0 : g_set_error_literal (&error,
1140 : : G_IO_ERROR,
1141 : : G_IO_ERROR_PERMISSION_DENIED,
1142 : : "failed to connect context");
1143 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
1144 : : VALENT_PLUGIN_STATE_ERROR,
1145 : : error);
1146 : 0 : return;
1147 : : }
1148 : :
1149 : 0 : spa_zero (self->core_listener);
1150 : 0 : pw_core_add_listener (self->core,
1151 : : &self->core_listener,
1152 : : &core_events,
1153 : : self);
1154 : :
1155 : 0 : self->registry = pw_core_get_registry (self->core, PW_VERSION_REGISTRY, 0);
1156 : :
1157 : 0 : if (self->registry == NULL)
1158 : : {
1159 : 0 : pw_thread_loop_unlock (self->loop);
1160 : 0 : g_set_error_literal (&error,
1161 : : G_IO_ERROR,
1162 : : G_IO_ERROR_PERMISSION_DENIED,
1163 : : "failed to connect to registry");
1164 : 0 : valent_extension_plugin_state_changed (VALENT_EXTENSION (self),
1165 : : VALENT_PLUGIN_STATE_ERROR,
1166 : : error);
1167 : 0 : return;
1168 : : }
1169 : :
1170 : 0 : spa_zero (self->registry_listener);
1171 : 0 : pw_registry_add_listener (self->registry,
1172 : : &self->registry_listener,
1173 : : ®istry_events,
1174 : : self);
1175 : 0 : pw_core_sync (self->core, PW_ID_CORE, 0);
1176 : 0 : pw_thread_loop_unlock (self->loop);
1177 : : }
1178 : :
1179 : : static void
1180 : 0 : valent_pipewire_mixer_close (ValentPipewireMixer *self)
1181 : : {
1182 : 0 : VALENT_ENTRY;
1183 : :
1184 : 0 : g_assert (VALENT_IS_PIPEWIRE_MIXER (self));
1185 : 0 : g_assert (VALENT_IS_MAIN_THREAD ());
1186 : :
1187 : 0 : g_atomic_int_set (&self->closed, TRUE);
1188 : :
1189 : 0 : if (self->loop != NULL)
1190 : : {
1191 : 0 : pw_thread_loop_lock (self->loop);
1192 : :
1193 : 0 : if (self->metadata != NULL)
1194 : : {
1195 : 0 : spa_hook_remove (&self->metadata_listener);
1196 : 0 : pw_proxy_destroy ((struct pw_proxy *)self->metadata);
1197 : 0 : self->metadata = NULL;
1198 : : }
1199 : :
1200 : 0 : if (self->registry != NULL)
1201 : : {
1202 : 0 : spa_hook_remove (&self->registry_listener);
1203 : 0 : pw_proxy_destroy ((struct pw_proxy *)self->registry);
1204 : 0 : self->registry = NULL;
1205 : : }
1206 : :
1207 : 0 : if (self->core != NULL)
1208 : : {
1209 : 0 : spa_hook_remove (&self->core_listener);
1210 : 0 : g_clear_pointer (&self->core, pw_core_disconnect);
1211 : : }
1212 : :
1213 : 0 : g_clear_pointer (&self->context, pw_context_destroy);
1214 : :
1215 : 0 : pw_thread_loop_unlock (self->loop);
1216 : 0 : pw_thread_loop_stop (self->loop);
1217 : :
1218 : 0 : g_clear_pointer (&self->loop, pw_thread_loop_destroy);
1219 : : }
1220 : :
1221 : 0 : VALENT_EXIT;
1222 : : }
1223 : :
1224 : :
1225 : : /*
1226 : : * ValentMixerAdapter
1227 : : */
1228 : : static ValentMixerStream *
1229 : 0 : valent_pipewire_mixer_get_default_input (ValentMixerAdapter *adapter)
1230 : : {
1231 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (adapter);
1232 : :
1233 : 0 : return g_hash_table_lookup (self->streams, self->default_input);
1234 : : }
1235 : :
1236 : : static void
1237 : 0 : valent_pipewire_mixer_set_default_input (ValentMixerAdapter *adapter,
1238 : : ValentMixerStream *stream)
1239 : : {
1240 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (adapter);
1241 : 0 : struct node_data *ndata = NULL;
1242 : 0 : const char *name = NULL;
1243 : :
1244 : 0 : g_assert (VALENT_IS_PIPEWIRE_MIXER (self));
1245 : 0 : g_assert (VALENT_IS_MIXER_STREAM (stream));
1246 : :
1247 : 0 : name = valent_mixer_stream_get_name (stream);
1248 : :
1249 : 0 : if (g_strcmp0 (self->default_input, name) == 0)
1250 : : return;
1251 : :
1252 : 0 : pw_thread_loop_lock (self->loop);
1253 : 0 : if ((ndata = valent_pipewire_mixer_lookup_node_name (self, name)) != NULL)
1254 : : {
1255 : 0 : g_autofree char *json = NULL;
1256 : :
1257 : 0 : json = g_strdup_printf ("{\"name\": \"%s\"}", name);
1258 : 0 : pw_metadata_set_property (self->metadata, PW_ID_CORE,
1259 : : "default.audio.source", "Spa:Id", json);
1260 : :
1261 : : /* Emit now, since we won't get notification from pipewire */
1262 : 0 : if (g_set_str (&self->default_input, name))
1263 : 0 : g_object_notify (G_OBJECT (self), "default-input");
1264 : : }
1265 : 0 : pw_thread_loop_unlock (self->loop);
1266 : : }
1267 : :
1268 : : static ValentMixerStream *
1269 : 0 : valent_pipewire_mixer_get_default_output (ValentMixerAdapter *adapter)
1270 : : {
1271 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (adapter);
1272 : :
1273 : 0 : return g_hash_table_lookup (self->streams, self->default_output);
1274 : : }
1275 : :
1276 : : static void
1277 : 0 : valent_pipewire_mixer_set_default_output (ValentMixerAdapter *adapter,
1278 : : ValentMixerStream *stream)
1279 : : {
1280 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (adapter);
1281 : 0 : struct node_data *ndata = NULL;
1282 : 0 : const char *name = NULL;
1283 : :
1284 : 0 : g_assert (VALENT_IS_PIPEWIRE_MIXER (self));
1285 : 0 : g_assert (VALENT_IS_MIXER_STREAM (stream));
1286 : :
1287 : 0 : name = valent_mixer_stream_get_name (stream);
1288 : :
1289 : 0 : if (g_strcmp0 (self->default_output, name) == 0)
1290 : : return;
1291 : :
1292 : 0 : pw_thread_loop_lock (self->loop);
1293 : 0 : if ((ndata = valent_pipewire_mixer_lookup_node_name (self, name)) != NULL)
1294 : : {
1295 : 0 : g_autofree char *json = NULL;
1296 : :
1297 : 0 : json = g_strdup_printf ("{\"name\": \"%s\"}", name);
1298 : 0 : pw_metadata_set_property (self->metadata, PW_ID_CORE,
1299 : : "default.audio.sink", "Spa:Id", json);
1300 : :
1301 : : /* Emit now, since we won't get notification from pipewire */
1302 : 0 : if (g_set_str (&self->default_output, name))
1303 : 0 : g_object_notify (G_OBJECT (self), "default-output");
1304 : : }
1305 : 0 : pw_thread_loop_unlock (self->loop);
1306 : : }
1307 : :
1308 : : /*
1309 : : * GObject
1310 : : */
1311 : : static void
1312 : 0 : valent_pipewire_mixer_constructed (GObject *object)
1313 : : {
1314 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (object);
1315 : :
1316 : 0 : valent_pipewire_mixer_open (self);
1317 : :
1318 : 0 : G_OBJECT_CLASS (valent_pipewire_mixer_parent_class)->constructed (object);
1319 : 0 : }
1320 : :
1321 : : static void
1322 : 0 : valent_pipewire_mixer_destroy (ValentObject *object)
1323 : : {
1324 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (object);
1325 : :
1326 : 0 : valent_pipewire_mixer_close (self);
1327 : 0 : g_hash_table_remove_all (self->streams);
1328 : :
1329 : 0 : VALENT_OBJECT_CLASS (valent_pipewire_mixer_parent_class)->destroy (object);
1330 : 0 : }
1331 : :
1332 : : static void
1333 : 0 : valent_pipewire_mixer_finalize (GObject *object)
1334 : : {
1335 : 0 : ValentPipewireMixer *self = VALENT_PIPEWIRE_MIXER (object);
1336 : :
1337 : 0 : pw_deinit ();
1338 : 0 : g_clear_pointer (&self->streams, g_hash_table_unref);
1339 : :
1340 : 0 : G_OBJECT_CLASS (valent_pipewire_mixer_parent_class)->finalize (object);
1341 : 0 : }
1342 : :
1343 : : static void
1344 : 0 : valent_pipewire_mixer_class_init (ValentPipewireMixerClass *klass)
1345 : : {
1346 : 0 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
1347 : 0 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
1348 : 0 : ValentMixerAdapterClass *adapter_class = VALENT_MIXER_ADAPTER_CLASS (klass);
1349 : :
1350 : 0 : object_class->constructed = valent_pipewire_mixer_constructed;
1351 : 0 : object_class->finalize = valent_pipewire_mixer_finalize;
1352 : :
1353 : 0 : vobject_class->destroy = valent_pipewire_mixer_destroy;
1354 : :
1355 : 0 : adapter_class->get_default_input = valent_pipewire_mixer_get_default_input;
1356 : 0 : adapter_class->set_default_input = valent_pipewire_mixer_set_default_input;
1357 : 0 : adapter_class->get_default_output = valent_pipewire_mixer_get_default_output;
1358 : 0 : adapter_class->set_default_output = valent_pipewire_mixer_set_default_output;
1359 : : }
1360 : :
1361 : : static void
1362 : 0 : valent_pipewire_mixer_init (ValentPipewireMixer *self)
1363 : : {
1364 : 0 : self->closed = FALSE;
1365 : 0 : self->streams = g_hash_table_new_full (g_str_hash,
1366 : : g_str_equal,
1367 : : g_free,
1368 : : g_object_unref);
1369 : 0 : pw_init (NULL, NULL);
1370 : 0 : }
1371 : :
1372 : : void
1373 : 0 : valent_pipewire_mixer_set_stream_state (ValentPipewireMixer *adapter,
1374 : : uint32_t device_id,
1375 : : uint32_t node_id,
1376 : : unsigned int level,
1377 : : gboolean muted)
1378 : : {
1379 : 0 : StreamState *state = NULL;
1380 : :
1381 : 0 : g_assert (VALENT_IS_PIPEWIRE_MIXER (adapter));
1382 : 0 : g_assert (device_id > 0);
1383 : 0 : g_assert (node_id > 0);
1384 : :
1385 : 0 : VALENT_NOTE ("device: %u, node: %u, level: %u, muted: %u",
1386 : : device_id, node_id, level, muted);
1387 : :
1388 : 0 : state = g_new0 (StreamState, 1);
1389 : 0 : g_rec_mutex_init (&state->mutex);
1390 : 0 : g_rec_mutex_lock (&state->mutex);
1391 : 0 : state->adapter = g_object_ref (adapter);
1392 : 0 : state->device_id = device_id;
1393 : 0 : state->node_id = node_id;
1394 : 0 : state->level = level;
1395 : 0 : state->muted = muted;
1396 : 0 : g_rec_mutex_unlock (&state->mutex);
1397 : :
1398 : 0 : pw_thread_loop_lock (adapter->loop);
1399 : 0 : pw_loop_invoke (pw_thread_loop_get_loop (adapter->loop),
1400 : : stream_state_update,
1401 : : 0,
1402 : : NULL,
1403 : : 0,
1404 : : false,
1405 : : state);
1406 : 0 : pw_thread_loop_unlock (adapter->loop);
1407 : 0 : }
1408 : :
|