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