LCOV - code coverage report
Current view: top level - src/plugins/systemvolume - valent-systemvolume-plugin.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 95.0 % 202 192
Test Date: 2024-04-23 06:02:46 Functions: 100.0 % 18 18
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 63.9 % 122 78

             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-systemvolume-plugin"
       5                 :             : 
       6                 :             : #include "config.h"
       7                 :             : 
       8                 :             : #include <gio/gio.h>
       9                 :             : #include <json-glib/json-glib.h>
      10                 :             : #include <valent.h>
      11                 :             : 
      12                 :             : #include "valent-systemvolume-plugin.h"
      13                 :             : 
      14                 :             : 
      15                 :             : struct _ValentSystemvolumePlugin
      16                 :             : {
      17                 :             :   ValentDevicePlugin  parent_instance;
      18                 :             : 
      19                 :             :   ValentMixer        *mixer;
      20                 :             :   unsigned int        mixer_watch : 1;
      21                 :             :   GPtrArray          *states;
      22                 :             : };
      23                 :             : 
      24                 :             : static void valent_systemvolume_plugin_handle_request     (ValentSystemvolumePlugin *self,
      25                 :             :                                                            JsonNode                 *packet);
      26                 :             : static void valent_systemvolume_plugin_handle_sink_change (ValentSystemvolumePlugin *self,
      27                 :             :                                                            JsonNode                 *packet);
      28                 :             : static void valent_systemvolume_plugin_send_sinklist      (ValentSystemvolumePlugin *self);
      29                 :             : 
      30   [ +  +  +  - ]:          50 : G_DEFINE_FINAL_TYPE (ValentSystemvolumePlugin, valent_systemvolume_plugin, VALENT_TYPE_DEVICE_PLUGIN)
      31                 :             : 
      32                 :             : 
      33                 :             : /*
      34                 :             :  * Local Mixer
      35                 :             :  */
      36                 :             : typedef struct
      37                 :             : {
      38                 :             :   ValentMixerStream *stream;
      39                 :             :   unsigned long      notify_id;
      40                 :             :   char              *name;
      41                 :             :   char              *description;
      42                 :             :   unsigned int       volume;
      43                 :             :   unsigned int       muted : 1;
      44                 :             :   unsigned int       enabled : 1;
      45                 :             : } StreamState;
      46                 :             : 
      47                 :             : static StreamState *
      48                 :           9 : stream_state_find (ValentSystemvolumePlugin *self,
      49                 :             :                    const char               *name)
      50                 :             : {
      51         [ +  + ]:          11 :   for (unsigned int i = 0; i < self->states->len; i++)
      52                 :             :     {
      53                 :          10 :       StreamState *state = g_ptr_array_index (self->states, i);
      54                 :             : 
      55         [ +  + ]:          10 :       if (g_strcmp0 (state->name, name) == 0)
      56                 :             :         return state;
      57                 :             :     }
      58                 :             : 
      59                 :             :   return NULL;
      60                 :             : }
      61                 :             : 
      62                 :             : static void
      63                 :           4 : on_stream_changed (ValentMixerStream        *stream,
      64                 :             :                    GParamSpec               *pspec,
      65                 :             :                    ValentSystemvolumePlugin *self)
      66                 :             : {
      67                 :           4 :   StreamState *state;
      68                 :           4 :   const char *name;
      69                 :           4 :   const char *description;
      70                 :           4 :   gboolean enabled;
      71                 :           4 :   gboolean muted;
      72                 :           4 :   unsigned int volume;
      73                 :           4 :   g_autoptr (JsonBuilder) builder = NULL;
      74   [ -  +  -  - ]:           4 :   g_autoptr (JsonNode) packet = NULL;
      75                 :             : 
      76         [ +  - ]:           4 :   g_assert (VALENT_IS_MIXER_STREAM (stream));
      77         [ -  + ]:           4 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
      78                 :             : 
      79                 :             :   /* If this is an unknown stream, send a new sink list */
      80                 :           4 :   name = valent_mixer_stream_get_name (stream);
      81                 :             : 
      82         [ -  + ]:           4 :   if ((state = stream_state_find (self, name)) == NULL)
      83                 :             :     {
      84                 :           0 :       valent_systemvolume_plugin_send_sinklist (self);
      85                 :           0 :       return;
      86                 :             :     }
      87                 :             : 
      88                 :             :   /* If the description changed it's probably because the port changed, so
      89                 :             :    * update the state and send the whole list */
      90                 :           4 :   description = valent_mixer_stream_get_description (stream);
      91                 :             : 
      92         [ -  + ]:           4 :   if (g_set_str (&state->description, description))
      93                 :             :     {
      94                 :           0 :       valent_systemvolume_plugin_send_sinklist (self);
      95                 :           0 :       return;
      96                 :             :     }
      97                 :             : 
      98                 :             :   /* If none of the other properties changed, there's nothing to update */
      99                 :           4 :   enabled = valent_mixer_get_default_output (self->mixer) == stream;
     100                 :           4 :   muted = valent_mixer_stream_get_muted (stream);
     101                 :           4 :   volume = valent_mixer_stream_get_level (stream);
     102                 :             : 
     103         [ +  - ]:           4 :   if (state->enabled == enabled &&
     104         [ +  + ]:           4 :       state->muted == muted &&
     105         [ +  - ]:           2 :       state->volume == volume)
     106                 :             :     return;
     107                 :             : 
     108                 :             :   /* Sink update */
     109                 :           4 :   valent_packet_init (&builder, "kdeconnect.systemvolume");
     110                 :           4 :   json_builder_set_member_name (builder, "name");
     111                 :           4 :   json_builder_add_string_value (builder, state->name);
     112                 :             : 
     113         [ +  + ]:           4 :   if (state->muted != muted)
     114                 :             :     {
     115                 :           2 :       state->muted = muted;
     116                 :           2 :       json_builder_set_member_name (builder, "muted");
     117                 :           2 :       json_builder_add_boolean_value (builder, state->muted);
     118                 :             :     }
     119                 :             : 
     120         [ +  + ]:           4 :   if (state->volume != volume)
     121                 :             :     {
     122                 :           2 :       state->volume = volume;
     123                 :           2 :       json_builder_set_member_name (builder, "volume");
     124                 :           2 :       json_builder_add_int_value (builder, state->volume);
     125                 :             :     }
     126                 :             : 
     127         [ -  + ]:           4 :   if (state->enabled != enabled)
     128                 :             :     {
     129                 :           0 :       state->enabled = enabled;
     130                 :           0 :       json_builder_set_member_name (builder, "enabled");
     131                 :           0 :       json_builder_add_boolean_value (builder, state->enabled);
     132                 :             :     }
     133                 :             : 
     134                 :           4 :   packet = valent_packet_end (&builder);
     135                 :             : 
     136         [ +  - ]:           4 :   valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
     137                 :             : }
     138                 :             : 
     139                 :             : static StreamState *
     140                 :           4 : stream_state_new (ValentSystemvolumePlugin *self,
     141                 :             :                   ValentMixerStream        *stream)
     142                 :             : {
     143                 :           4 :   StreamState *state;
     144                 :             : 
     145         [ +  - ]:           4 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     146         [ -  + ]:           4 :   g_assert (VALENT_IS_MIXER_STREAM (stream));
     147                 :             : 
     148                 :           4 :   state = g_new0 (StreamState, 1);
     149                 :           4 :   state->stream = g_object_ref (stream);
     150                 :           4 :   g_signal_connect_object (state->stream,
     151                 :             :                            "notify",
     152                 :             :                            G_CALLBACK (on_stream_changed),
     153                 :             :                            self, 0);
     154                 :             : 
     155         [ -  + ]:           4 :   state->name = g_strdup (valent_mixer_stream_get_name (stream));
     156         [ -  + ]:           4 :   state->description = g_strdup (valent_mixer_stream_get_description (stream));
     157                 :           4 :   state->volume = valent_mixer_stream_get_level (stream);
     158                 :           4 :   state->muted = valent_mixer_stream_get_muted (stream);
     159                 :           4 :   state->enabled = valent_mixer_get_default_output (self->mixer) == stream;
     160                 :             : 
     161                 :           4 :   return state;
     162                 :             : }
     163                 :             : 
     164                 :             : static void
     165                 :           4 : stream_state_free (gpointer data)
     166                 :             : {
     167                 :           4 :   StreamState *state = data;
     168                 :             : 
     169         [ -  + ]:           4 :   g_clear_signal_handler (&state->notify_id, state->stream);
     170         [ +  - ]:           4 :   g_clear_object (&state->stream);
     171         [ +  - ]:           4 :   g_clear_pointer (&state->name, g_free);
     172         [ +  - ]:           4 :   g_clear_pointer (&state->description, g_free);
     173                 :           4 :   g_clear_pointer (&state, g_free);
     174                 :           4 : }
     175                 :             : 
     176                 :             : static void
     177                 :           2 : on_default_output_changed (ValentMixer              *mixer,
     178                 :             :                            GParamSpec               *pspec,
     179                 :             :                            ValentSystemvolumePlugin *self)
     180                 :             : {
     181                 :           2 :   ValentMixerStream *default_output = NULL;
     182                 :             : 
     183         [ +  - ]:           2 :   g_assert (VALENT_IS_MIXER (mixer));
     184         [ -  + ]:           2 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     185                 :             : 
     186                 :           2 :   default_output = valent_mixer_get_default_output (mixer);
     187                 :             : 
     188         [ +  + ]:           6 :   for (unsigned int i = 0; i < self->states->len; i++)
     189                 :             :     {
     190                 :           4 :       StreamState *state = g_ptr_array_index (self->states, i);
     191                 :             : 
     192                 :           4 :       state->enabled = state->stream == default_output;
     193                 :             :     }
     194                 :             : 
     195                 :             :   /* It's unclear whether the `enabled` field with a value of `false` is
     196                 :             :    * relevant in the protocol, we resend the whole list */
     197                 :           2 :   valent_systemvolume_plugin_send_sinklist (self);
     198                 :           2 : }
     199                 :             : 
     200                 :             : static void
     201                 :           3 : on_items_changed (GListModel               *list,
     202                 :             :                   unsigned int              position,
     203                 :             :                   unsigned int              removed,
     204                 :             :                   unsigned int              added,
     205                 :             :                   ValentSystemvolumePlugin *self)
     206                 :             : {
     207                 :           3 :   unsigned int n_streams = 0;
     208                 :             : 
     209         [ +  - ]:           3 :   g_assert (G_IS_LIST_MODEL (list));
     210         [ -  + ]:           3 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     211                 :             : 
     212                 :           3 :   g_ptr_array_remove_range (self->states, 0, self->states->len);
     213                 :           3 :   n_streams = g_list_model_get_n_items (list);
     214                 :             : 
     215         [ +  + ]:           7 :   for (unsigned int i = 0; i < n_streams; i++)
     216                 :             :     {
     217                 :           4 :       g_autoptr (ValentMixerStream) stream = NULL;
     218                 :             : 
     219                 :           4 :       stream = g_list_model_get_item (list, i);
     220                 :             : 
     221         [ -  + ]:           4 :       if (valent_mixer_stream_get_direction (stream) != VALENT_MIXER_OUTPUT)
     222         [ #  # ]:           0 :         continue;
     223                 :             : 
     224         [ +  - ]:           4 :       g_ptr_array_add (self->states, stream_state_new (self, stream));
     225                 :             :     }
     226                 :             : 
     227                 :           3 :   valent_systemvolume_plugin_send_sinklist (self);
     228                 :           3 : }
     229                 :             : 
     230                 :             : static void
     231                 :           6 : valent_systemvolume_plugin_watch_mixer (ValentSystemvolumePlugin *self,
     232                 :             :                                         gboolean                  watch)
     233                 :             : {
     234         [ +  - ]:           6 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     235                 :             : 
     236         [ +  + ]:           6 :   if (self->mixer_watch == watch)
     237                 :             :     return;
     238                 :             : 
     239         [ +  + ]:           2 :   if (self->mixer == NULL)
     240                 :           1 :     self->mixer = valent_mixer_get_default ();
     241                 :             : 
     242         [ +  + ]:           2 :   if (watch)
     243                 :             :     {
     244                 :           1 :       GListModel *list = G_LIST_MODEL (self->mixer);
     245                 :             : 
     246                 :           1 :       g_signal_connect_object (self->mixer,
     247                 :             :                                "notify::default-output",
     248                 :             :                                G_CALLBACK (on_default_output_changed),
     249                 :             :                                self, 0);
     250                 :           1 :       g_signal_connect_object (self->mixer,
     251                 :             :                                "items-changed",
     252                 :             :                                G_CALLBACK (on_items_changed),
     253                 :             :                                self, 0);
     254                 :           1 :       on_items_changed (list, 0, 0, g_list_model_get_n_items (list), self);
     255                 :           1 :       self->mixer_watch = TRUE;
     256                 :             :     }
     257                 :             :   else
     258                 :             :     {
     259                 :           1 :       g_signal_handlers_disconnect_by_data (self->mixer, self);
     260                 :           1 :       g_ptr_array_remove_range (self->states, 0, self->states->len);
     261                 :           1 :       self->mixer_watch = FALSE;
     262                 :             :     }
     263                 :             : }
     264                 :             : 
     265                 :             : /*
     266                 :             :  * Packet Providers
     267                 :             :  */
     268                 :             : static void
     269                 :           7 : valent_systemvolume_plugin_send_sinklist (ValentSystemvolumePlugin *self)
     270                 :             : {
     271                 :          14 :   g_autoptr (JsonBuilder) builder = NULL;
     272         [ -  + ]:           7 :   g_autoptr (JsonNode) packet = NULL;
     273                 :           7 :   unsigned int max_volume = 100;
     274                 :             : 
     275         [ +  - ]:           7 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     276                 :             : 
     277                 :             :   /* Sink List */
     278                 :           7 :   valent_packet_init (&builder, "kdeconnect.systemvolume");
     279                 :           7 :   json_builder_set_member_name (builder, "sinkList");
     280                 :           7 :   json_builder_begin_array (builder);
     281                 :             : 
     282         [ +  + ]:          17 :   for (unsigned int i = 0; i < self->states->len; i++)
     283                 :             :     {
     284                 :          10 :       StreamState *state;
     285                 :             : 
     286                 :          10 :       state = g_ptr_array_index (self->states, i);
     287                 :             : 
     288                 :          10 :       json_builder_begin_object (builder);
     289                 :          10 :       json_builder_set_member_name (builder, "name");
     290                 :          10 :       json_builder_add_string_value (builder, state->name);
     291                 :          10 :       json_builder_set_member_name (builder, "description");
     292                 :          10 :       json_builder_add_string_value (builder, state->description);
     293                 :          10 :       json_builder_set_member_name (builder, "muted");
     294                 :          10 :       json_builder_add_boolean_value (builder, state->muted);
     295                 :          10 :       json_builder_set_member_name (builder, "volume");
     296                 :          10 :       json_builder_add_int_value (builder, state->volume);
     297                 :          10 :       json_builder_set_member_name (builder, "maxVolume");
     298                 :          10 :       json_builder_add_int_value (builder, max_volume);
     299                 :          10 :       json_builder_set_member_name (builder, "enabled");
     300                 :          10 :       json_builder_add_boolean_value (builder, state->enabled);
     301                 :          10 :       json_builder_end_object (builder);
     302                 :             :     }
     303                 :             : 
     304                 :           7 :   json_builder_end_array (builder);
     305                 :           7 :   packet = valent_packet_end (&builder);
     306                 :             : 
     307         [ +  - ]:           7 :   valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
     308                 :           7 : }
     309                 :             : 
     310                 :             : static void
     311                 :           5 : valent_systemvolume_plugin_handle_sink_change (ValentSystemvolumePlugin *self,
     312                 :             :                                                JsonNode                 *packet)
     313                 :             : {
     314                 :           5 :   StreamState *state;
     315                 :           5 :   const char *name;
     316                 :           5 :   int64_t volume;
     317                 :           5 :   gboolean muted;
     318                 :           5 :   gboolean enabled;
     319                 :             : 
     320         [ +  - ]:           5 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     321         [ -  + ]:           5 :   g_assert (VALENT_IS_PACKET (packet));
     322                 :             : 
     323         [ -  + ]:           5 :   if (!valent_packet_get_string (packet, "name", &name))
     324                 :             :     {
     325                 :           0 :       g_debug ("%s(): expected \"name\" field holding a string",
     326                 :             :                G_STRFUNC);
     327                 :           1 :       return;
     328                 :             :     }
     329                 :             : 
     330         [ +  + ]:           5 :   if ((state = stream_state_find (self, name)) == NULL)
     331                 :             :     {
     332                 :           1 :       valent_systemvolume_plugin_send_sinklist (self);
     333                 :           1 :       return;
     334                 :             :     }
     335                 :             : 
     336   [ +  +  +  - ]:           4 :   if (valent_packet_get_int (packet, "volume", &volume) && volume >= 0)
     337                 :           1 :     valent_mixer_stream_set_level (state->stream, volume);
     338                 :             : 
     339         [ +  + ]:           4 :   if (valent_packet_get_boolean (packet, "muted", &muted))
     340                 :           1 :     valent_mixer_stream_set_muted (state->stream, muted);
     341                 :             : 
     342   [ +  +  +  - ]:           4 :   if (valent_packet_get_boolean (packet, "enabled", &enabled) && enabled)
     343                 :           2 :     valent_mixer_set_default_output (self->mixer, state->stream);
     344                 :             : }
     345                 :             : 
     346                 :             : static void
     347                 :           6 : valent_systemvolume_plugin_handle_request (ValentSystemvolumePlugin *self,
     348                 :             :                                            JsonNode                 *packet)
     349                 :             : {
     350         [ +  - ]:           6 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     351                 :             : 
     352                 :             :   /* A request for a list of audio outputs */
     353         [ +  + ]:           6 :   if (valent_packet_check_field (packet, "requestSinks"))
     354                 :           1 :     valent_systemvolume_plugin_send_sinklist (self);
     355                 :             : 
     356                 :             :   /* A request to change an audio output */
     357         [ +  - ]:           5 :   else if (valent_packet_check_field (packet, "name"))
     358                 :           5 :     valent_systemvolume_plugin_handle_sink_change (self, packet);
     359                 :             : 
     360                 :             :   else
     361                 :           0 :     g_warn_if_reached ();
     362                 :           6 : }
     363                 :             : 
     364                 :             : /*
     365                 :             :  * ValentDevicePlugin
     366                 :             :  */
     367                 :             : static void
     368                 :           4 : valent_systemvolume_plugin_update_state (ValentDevicePlugin *plugin,
     369                 :             :                                          ValentDeviceState   state)
     370                 :             : {
     371                 :           4 :   ValentSystemvolumePlugin *self = VALENT_SYSTEMVOLUME_PLUGIN (plugin);
     372                 :           4 :   gboolean available;
     373                 :             : 
     374         [ +  - ]:           4 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     375                 :             : 
     376                 :           4 :   available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
     377                 :             :               (state & VALENT_DEVICE_STATE_PAIRED) != 0;
     378                 :             : 
     379                 :             :   /* Watch stream changes */
     380         [ +  + ]:           4 :   if (available)
     381                 :           1 :     valent_systemvolume_plugin_watch_mixer (self, TRUE);
     382                 :             :   else
     383                 :           3 :     valent_systemvolume_plugin_watch_mixer (self, FALSE);
     384                 :           4 : }
     385                 :             : 
     386                 :             : static void
     387                 :           6 : valent_systemvolume_plugin_handle_packet (ValentDevicePlugin *plugin,
     388                 :             :                                           const char         *type,
     389                 :             :                                           JsonNode           *packet)
     390                 :             : {
     391                 :           6 :   ValentSystemvolumePlugin *self = VALENT_SYSTEMVOLUME_PLUGIN (plugin);
     392                 :             : 
     393         [ +  - ]:           6 :   g_assert (VALENT_IS_SYSTEMVOLUME_PLUGIN (self));
     394         [ -  + ]:           6 :   g_assert (type != NULL);
     395         [ -  + ]:           6 :   g_assert (VALENT_IS_PACKET (packet));
     396                 :             : 
     397         [ +  - ]:           6 :   if (g_str_equal (type, "kdeconnect.systemvolume.request"))
     398                 :           6 :     valent_systemvolume_plugin_handle_request (self, packet);
     399                 :             :   else
     400                 :           6 :     g_assert_not_reached ();
     401                 :           6 : }
     402                 :             : 
     403                 :             : /*
     404                 :             :  * ValentObject
     405                 :             :  */
     406                 :             : static void
     407                 :           2 : valent_systemvolume_plugin_destroy (ValentObject *object)
     408                 :             : {
     409                 :           2 :   ValentSystemvolumePlugin *self = VALENT_SYSTEMVOLUME_PLUGIN (object);
     410                 :             : 
     411                 :           2 :   valent_systemvolume_plugin_watch_mixer (self, FALSE);
     412         [ +  + ]:           2 :   g_clear_pointer (&self->states, g_ptr_array_unref);
     413                 :             : 
     414                 :           2 :   VALENT_OBJECT_CLASS (valent_systemvolume_plugin_parent_class)->destroy (object);
     415                 :           2 : }
     416                 :             : 
     417                 :             : /*
     418                 :             :  * GObject
     419                 :             :  */
     420                 :             : static void
     421                 :           1 : valent_systemvolume_plugin_constructed (GObject *object)
     422                 :             : {
     423                 :           1 :   ValentSystemvolumePlugin *self = VALENT_SYSTEMVOLUME_PLUGIN (object);
     424                 :             : 
     425                 :           1 :   self->states = g_ptr_array_new_with_free_func (stream_state_free);
     426                 :             : 
     427                 :           1 :   G_OBJECT_CLASS (valent_systemvolume_plugin_parent_class)->constructed (object);
     428                 :           1 : }
     429                 :             : 
     430                 :             : static void
     431                 :           1 : valent_systemvolume_plugin_class_init (ValentSystemvolumePluginClass *klass)
     432                 :             : {
     433                 :           1 :   GObjectClass *object_class = G_OBJECT_CLASS (klass);
     434                 :           1 :   ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
     435                 :           1 :   ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
     436                 :             : 
     437                 :           1 :   object_class->constructed = valent_systemvolume_plugin_constructed;
     438                 :             : 
     439                 :           1 :   vobject_class->destroy = valent_systemvolume_plugin_destroy;
     440                 :             : 
     441                 :           1 :   plugin_class->handle_packet = valent_systemvolume_plugin_handle_packet;
     442                 :           1 :   plugin_class->update_state = valent_systemvolume_plugin_update_state;
     443                 :             : }
     444                 :             : 
     445                 :             : static void
     446                 :           1 : valent_systemvolume_plugin_init (ValentSystemvolumePlugin *self)
     447                 :             : {
     448                 :           1 : }
     449                 :             : 
        

Generated by: LCOV version 2.0-1