LCOV - code coverage report
Current view: top level - src/plugins/pipewire - valent-pipewire-mixer.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 0.0 % 561 0
Test Date: 2024-12-02 22:49:30 Functions: 0.0 % 37 0
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             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                 :             :                             &registry_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                 :             : 
        

Generated by: LCOV version 2.0-1