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-device"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <inttypes.h>
9 : : #include <math.h>
10 : :
11 : : #include <gio/gio.h>
12 : : #include <libtracker-sparql/tracker-sparql.h>
13 : : #include <valent.h>
14 : :
15 : : #include "valent-systemvolume-device.h"
16 : :
17 : : static inline void
18 : 5 : valent_device_send_packet_cb (ValentDevice *device,
19 : : GAsyncResult *result,
20 : : gpointer user_data)
21 : : {
22 : 10 : g_autoptr (GError) error = NULL;
23 : :
24 [ - + ]: 5 : if (!valent_device_send_packet_finish (device, result, &error))
25 : : {
26 [ # # ]: 0 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
27 : 0 : g_critical ("%s(): %s", G_STRFUNC, error->message);
28 [ # # ]: 0 : else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED))
29 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
30 [ # # ]: 0 : else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
31 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
32 : : }
33 : 5 : }
34 : :
35 : : #define VALENT_TYPE_SYSTEMVOLUME_STREAM (valent_systemvolume_stream_get_type ())
36 [ + - + - : 4 : G_DECLARE_FINAL_TYPE (ValentSystemvolumeStream, valent_systemvolume_stream, VALENT, SYSTEMVOLUME_STREAM, ValentMixerStream)
- + ]
37 : :
38 : : struct _ValentSystemvolumeStream
39 : : {
40 : : ValentMixerStream parent_instance;
41 : :
42 : : ValentDevice *device;
43 : : GCancellable *cancellable;
44 : :
45 : : int64_t max_volume;
46 : : int64_t volume;
47 : : unsigned int muted : 1;
48 : : };
49 : :
50 [ + + + - ]: 8 : G_DEFINE_FINAL_TYPE (ValentSystemvolumeStream, valent_systemvolume_stream, VALENT_TYPE_MIXER_STREAM)
51 : :
52 : : /*
53 : : * ValentMixerStream
54 : : */
55 : : static unsigned int
56 : 1 : valent_systemvolume_stream_get_level (ValentMixerStream *stream)
57 : : {
58 : 1 : ValentSystemvolumeStream *self = VALENT_SYSTEMVOLUME_STREAM (stream);
59 : 1 : double percent;
60 : :
61 [ + - ]: 1 : g_assert (VALENT_IS_SYSTEMVOLUME_STREAM (self));
62 : :
63 : 1 : percent = (double)self->volume / (double)self->max_volume;
64 : :
65 : 1 : return (unsigned int)round (percent * 100.0);
66 : : }
67 : :
68 : : static void
69 : 1 : valent_systemvolume_stream_set_level (ValentMixerStream *stream,
70 : : unsigned int level)
71 : : {
72 : 1 : ValentSystemvolumeStream *self = VALENT_SYSTEMVOLUME_STREAM (stream);
73 : 2 : g_autoptr (JsonBuilder) builder = NULL;
74 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
75 : 1 : double percent;
76 : 1 : int64_t volume;
77 : :
78 [ + - ]: 1 : g_assert (VALENT_IS_SYSTEMVOLUME_STREAM (self));
79 : :
80 : 1 : percent = (double)level / 100.0;
81 : 1 : volume = (int64_t)round (percent * self->max_volume);
82 : :
83 : 1 : valent_packet_init (&builder, "kdeconnect.systemvolume.request");
84 : 1 : json_builder_set_member_name (builder, "name");
85 : 1 : json_builder_add_string_value (builder, valent_mixer_stream_get_name (stream));
86 : 1 : json_builder_set_member_name (builder, "volume");
87 : 1 : json_builder_add_int_value (builder, volume);
88 : 1 : packet = valent_packet_end (&builder);
89 : :
90 [ + - ]: 1 : valent_device_send_packet (self->device,
91 : : packet,
92 : : NULL, /* cancellable */
93 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
94 : : NULL);
95 : 1 : }
96 : :
97 : : static gboolean
98 : 1 : valent_systemvolume_stream_get_muted (ValentMixerStream *stream)
99 : : {
100 : 1 : ValentSystemvolumeStream *self = VALENT_SYSTEMVOLUME_STREAM (stream);
101 : :
102 [ + - ]: 1 : g_assert (VALENT_IS_SYSTEMVOLUME_STREAM (self));
103 : :
104 : 1 : return self->muted;
105 : : }
106 : :
107 : : static void
108 : 1 : valent_systemvolume_stream_set_muted (ValentMixerStream *stream,
109 : : gboolean state)
110 : : {
111 : 1 : ValentSystemvolumeStream *self = VALENT_SYSTEMVOLUME_STREAM (stream);
112 : 2 : g_autoptr (JsonBuilder) builder = NULL;
113 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
114 : :
115 [ + - ]: 1 : g_assert (VALENT_IS_SYSTEMVOLUME_STREAM (self));
116 : :
117 : 1 : valent_packet_init (&builder, "kdeconnect.systemvolume.request");
118 : 1 : json_builder_set_member_name (builder, "name");
119 : 1 : json_builder_add_string_value (builder, valent_mixer_stream_get_name (stream));
120 : 1 : json_builder_set_member_name (builder, "muted");
121 : 1 : json_builder_add_boolean_value (builder, state);
122 : 1 : packet = valent_packet_end (&builder);
123 : :
124 [ + - ]: 1 : valent_device_send_packet (self->device,
125 : : packet,
126 : : NULL, /* cancellable */
127 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
128 : : NULL);
129 : 1 : }
130 : :
131 : : static void
132 : 7 : valent_systemvolume_stream_update (ValentMixerStream *stream,
133 : : JsonObject *state)
134 : : {
135 : 7 : ValentSystemvolumeStream *self = VALENT_SYSTEMVOLUME_STREAM (stream);
136 : 7 : JsonNode *node;
137 : :
138 : 7 : node = json_object_get_member (state, "maxVolume");
139 [ + + ]: 7 : if (node != NULL)
140 : : {
141 [ + - ]: 4 : if (json_node_get_value_type (node) == G_TYPE_INT64)
142 : : {
143 : 4 : self->max_volume = json_node_get_int (node);
144 : : }
145 : : else
146 : : {
147 : 0 : g_warning ("%s(): expected \"maxVolume\" field holding an integer",
148 : : G_STRFUNC);
149 : : }
150 : : }
151 : :
152 : 7 : node = json_object_get_member (state, "muted");
153 [ + + ]: 7 : if (node != NULL)
154 : : {
155 [ + - ]: 5 : if (json_node_get_value_type (node) == G_TYPE_BOOLEAN)
156 : : {
157 : 5 : gboolean muted = json_node_get_boolean (node);
158 [ + + ]: 5 : if (self->muted != muted)
159 : : {
160 : 2 : self->muted = muted;
161 : 2 : g_object_notify (G_OBJECT (self), "muted");
162 : : }
163 : : }
164 : : else
165 : : {
166 : 0 : g_warning ("%s(): expected \"muted\" field holding a boolean",
167 : : G_STRFUNC);
168 : : }
169 : : }
170 : :
171 : 7 : node = json_object_get_member (state, "volume");
172 [ + + ]: 7 : if (node != NULL)
173 : : {
174 [ + - ]: 5 : if (json_node_get_value_type (node) == G_TYPE_INT64)
175 : : {
176 : 5 : int64_t volume = json_node_get_int (node);
177 [ + + ]: 5 : if (self->volume != volume)
178 : : {
179 : 4 : self->volume = volume;
180 : 4 : g_object_notify (G_OBJECT (self), "level");
181 : : }
182 : : }
183 : : else
184 : : {
185 : 0 : g_warning ("%s(): expected \"volume\" field holding an integer",
186 : : G_STRFUNC);
187 : : }
188 : : }
189 : 7 : }
190 : :
191 : : static void
192 : 1 : valent_systemvolume_stream_class_init (ValentSystemvolumeStreamClass *klass)
193 : : {
194 : 1 : ValentMixerStreamClass *stream_class = VALENT_MIXER_STREAM_CLASS (klass);
195 : :
196 : 1 : stream_class->get_level = valent_systemvolume_stream_get_level;
197 : 1 : stream_class->set_level = valent_systemvolume_stream_set_level;
198 : 1 : stream_class->get_muted = valent_systemvolume_stream_get_muted;
199 : 1 : stream_class->set_muted = valent_systemvolume_stream_set_muted;
200 : : }
201 : :
202 : : static void
203 : 2 : valent_systemvolume_stream_init (ValentSystemvolumeStream *self)
204 : : {
205 : 2 : }
206 : :
207 : :
208 : : /* <private>
209 : : * ValentSystemvolumeDevice:
210 : : *
211 : : * A `ValentMixerAdapter` for KDE Connect devices.
212 : : */
213 : : struct _ValentSystemvolumeDevice
214 : : {
215 : : ValentMixerAdapter parent_instance;
216 : :
217 : : ValentDevice *device;
218 : : GCancellable *cancellable;
219 : : ValentMixerStream *default_output;
220 : : GHashTable *outputs;
221 : : };
222 : :
223 [ + + + - ]: 21 : G_DEFINE_FINAL_TYPE (ValentSystemvolumeDevice, valent_systemvolume_device, VALENT_TYPE_MIXER_ADAPTER)
224 : :
225 : : /*
226 : : * ValentMixerAdapter
227 : : */
228 : : static void
229 : 8 : on_device_state_changed (ValentDevice *device,
230 : : GParamSpec *pspec,
231 : : ValentSystemvolumeDevice *self)
232 : : {
233 : 8 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
234 : 8 : gboolean available;
235 : :
236 [ + - ]: 8 : g_assert (VALENT_IS_SYSTEMVOLUME_DEVICE (self));
237 : :
238 : 8 : state = valent_device_get_state (device);
239 : 8 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
240 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
241 : :
242 [ + + ]: 8 : if (available)
243 : : {
244 : 2 : g_autoptr (JsonBuilder) builder = NULL;
245 [ - + ]: 2 : g_autoptr (JsonNode) packet = NULL;
246 [ + - ]: 2 : g_autoptr (GCancellable) cancellable = NULL;
247 : :
248 : 2 : cancellable = g_cancellable_new ();
249 : 2 : self->cancellable = valent_object_chain_cancellable (VALENT_OBJECT (self),
250 : : cancellable);
251 : :
252 : 2 : valent_packet_init (&builder, "kdeconnect.systemvolume.request");
253 : 2 : json_builder_set_member_name (builder, "requestSinks");
254 : 2 : json_builder_add_boolean_value (builder, TRUE);
255 : 2 : packet = valent_packet_end (&builder);
256 : :
257 [ + - ]: 2 : valent_device_send_packet (self->device,
258 : : packet,
259 : : self->cancellable,
260 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
261 : : NULL);
262 : : }
263 : : else
264 : : {
265 : 6 : GHashTableIter iter;
266 : 6 : ValentMixerStream *output;
267 : :
268 : 6 : g_cancellable_cancel (self->cancellable);
269 [ + + ]: 6 : g_clear_object (&self->cancellable);
270 : :
271 : 6 : g_hash_table_iter_init (&iter, self->outputs);
272 [ + + ]: 7 : while (g_hash_table_iter_next (&iter, NULL, (void **)&output))
273 : : {
274 : 1 : valent_mixer_adapter_stream_removed (VALENT_MIXER_ADAPTER (self),
275 : : output);
276 : 1 : g_hash_table_iter_remove (&iter);
277 : : }
278 : : }
279 : 8 : }
280 : :
281 : : static ValentMixerStream *
282 : 2 : valent_systemvolume_device_get_default_output (ValentMixerAdapter *adapter)
283 : : {
284 : 2 : ValentSystemvolumeDevice *self = VALENT_SYSTEMVOLUME_DEVICE (adapter);
285 : :
286 [ + - ]: 2 : g_assert (VALENT_IS_SYSTEMVOLUME_DEVICE (self));
287 : :
288 : 2 : return self->default_output;
289 : : }
290 : :
291 : : static void
292 : 1 : valent_systemvolume_device_set_default_output (ValentMixerAdapter *adapter,
293 : : ValentMixerStream *stream)
294 : : {
295 : 1 : ValentSystemvolumeDevice *self = VALENT_SYSTEMVOLUME_DEVICE (adapter);
296 : 1 : g_autoptr (JsonBuilder) builder = NULL;
297 [ - - - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
298 : :
299 : :
300 [ + - ]: 1 : g_assert (VALENT_IS_SYSTEMVOLUME_DEVICE (self));
301 : :
302 [ - + ]: 1 : if (self->default_output == stream)
303 [ # # ]: 0 : return;
304 : :
305 : 1 : valent_packet_init (&builder, "kdeconnect.systemvolume.request");
306 : 1 : json_builder_set_member_name (builder, "name");
307 : 1 : json_builder_add_string_value (builder, valent_mixer_stream_get_name (stream));
308 : 1 : json_builder_set_member_name (builder, "enabled");
309 : 1 : json_builder_add_boolean_value (builder, TRUE);
310 : 1 : packet = valent_packet_end (&builder);
311 : :
312 [ + - ]: 1 : valent_device_send_packet (self->device,
313 : : packet,
314 : : self->cancellable,
315 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
316 : : NULL);
317 : : }
318 : :
319 : : /*
320 : : * GObject
321 : : */
322 : : static void
323 : 2 : valent_systemvolume_device_constructed (GObject *object)
324 : : {
325 : 2 : ValentSystemvolumeDevice *self = VALENT_SYSTEMVOLUME_DEVICE (object);
326 : :
327 : 2 : G_OBJECT_CLASS (valent_systemvolume_device_parent_class)->constructed (object);
328 : :
329 : 2 : self->device = valent_extension_get_object (VALENT_EXTENSION (self));
330 : 2 : g_signal_connect_object (self->device,
331 : : "notify::state",
332 : : G_CALLBACK (on_device_state_changed),
333 : : self,
334 : : G_CONNECT_DEFAULT);
335 : 2 : on_device_state_changed (self->device, NULL, self);
336 : 2 : }
337 : :
338 : : static void
339 : 2 : valent_systemvolume_device_finalize (GObject *object)
340 : : {
341 : 2 : ValentSystemvolumeDevice *self = VALENT_SYSTEMVOLUME_DEVICE (object);
342 : :
343 [ - + ]: 2 : g_clear_object (&self->cancellable);
344 [ + + ]: 2 : g_clear_object (&self->default_output);
345 [ + - ]: 2 : g_clear_pointer (&self->outputs, g_hash_table_unref);
346 : :
347 : 2 : G_OBJECT_CLASS (valent_systemvolume_device_parent_class)->finalize (object);
348 : 2 : }
349 : :
350 : : static void
351 : 1 : valent_systemvolume_device_class_init (ValentSystemvolumeDeviceClass *klass)
352 : : {
353 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
354 : 1 : ValentMixerAdapterClass *adapter_class = VALENT_MIXER_ADAPTER_CLASS (klass);
355 : :
356 : 1 : object_class->constructed = valent_systemvolume_device_constructed;
357 : 1 : object_class->finalize = valent_systemvolume_device_finalize;
358 : :
359 : 1 : adapter_class->get_default_output = valent_systemvolume_device_get_default_output;
360 : 1 : adapter_class->set_default_output = valent_systemvolume_device_set_default_output;
361 : : }
362 : :
363 : : static void
364 : 2 : valent_systemvolume_device_init (ValentSystemvolumeDevice *self)
365 : : {
366 : 2 : self->outputs = g_hash_table_new_full (g_str_hash, g_str_equal,
367 : : g_free, g_object_unref);
368 : 2 : }
369 : :
370 : : /**
371 : : * valent_systemvolume_device_new:
372 : : * @device: a `ValentDevice`
373 : : *
374 : : * Create a new `ValentSystemvolumeDevice`.
375 : : *
376 : : * Returns: (transfer full): a new message store
377 : : */
378 : : ValentMixerAdapter *
379 : 2 : valent_systemvolume_device_new (ValentDevice *device)
380 : : {
381 : 4 : g_autoptr (ValentContext) context = NULL;
382 [ + - ]: 2 : g_autofree char *iri = NULL;
383 : :
384 [ + - ]: 2 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
385 : :
386 : 2 : context = valent_context_new (valent_device_get_context (device),
387 : : "plugin",
388 : : "systemvolume");
389 : 2 : iri = tracker_sparql_escape_uri_printf ("urn:valent:mixer:%s",
390 : : valent_device_get_id (device));
391 : 2 : return g_object_new (VALENT_TYPE_SYSTEMVOLUME_DEVICE,
392 : : "iri", iri,
393 : : "object", device,
394 : : "context", context,
395 : : NULL);
396 : : }
397 : :
398 : : static const char *
399 : 7 : valent_systemvolume_device_handle_stream (ValentSystemvolumeDevice *self,
400 : : JsonObject *state)
401 : : {
402 : 7 : ValentMixerStream *stream = NULL;
403 : 7 : gboolean new_stream = FALSE;
404 : 7 : JsonNode *node;
405 : 7 : const char *name = NULL;
406 : :
407 : 7 : node = json_object_get_member (state, "name");
408 [ + - - + ]: 7 : if (node == NULL || json_node_get_value_type (node) != G_TYPE_STRING)
409 : : {
410 : 0 : g_warning ("%s(): expected \"name\" field holding a string", G_STRFUNC);
411 : 0 : return NULL;
412 : : }
413 : 7 : name = json_node_get_string (node);
414 : :
415 : 7 : stream = g_hash_table_lookup (self->outputs, name);
416 [ + + ]: 7 : if (stream == NULL)
417 : : {
418 : 2 : const char *description = NULL;
419 : :
420 : 2 : node = json_object_get_member (state, "description");
421 [ - + ]: 2 : if (node != NULL)
422 : : {
423 [ + - ]: 2 : if (json_node_get_value_type (node) == G_TYPE_STRING)
424 : : {
425 : 2 : description = json_node_get_string (node);
426 : : }
427 : : else
428 : : {
429 : 0 : g_warning ("%s(): expected \"description\" field holding a string",
430 : : G_STRFUNC);
431 : : }
432 : : }
433 : :
434 : 2 : stream = g_object_new (VALENT_TYPE_SYSTEMVOLUME_STREAM,
435 : : "name", name,
436 : : "description", description,
437 : : "direction", VALENT_MIXER_OUTPUT,
438 : : NULL);
439 : 2 : ((ValentSystemvolumeStream *)stream)->device = self->device;
440 : 2 : ((ValentSystemvolumeStream *)stream)->cancellable = self->cancellable;
441 [ - + ]: 2 : g_hash_table_replace (self->outputs, g_strdup (name), stream /* owned */);
442 : 2 : new_stream = TRUE;
443 : : }
444 : :
445 : 7 : valent_systemvolume_stream_update (stream, state);
446 : :
447 : 7 : node = json_object_get_member (state, "enabled");
448 [ + - ]: 7 : if (node != NULL)
449 : : {
450 [ + - ]: 7 : if (json_node_get_value_type (node) == G_TYPE_BOOLEAN)
451 : : {
452 [ + + + + ]: 13 : if (json_node_get_boolean (node) &&
453 : 6 : g_set_object (&self->default_output, stream))
454 : : {
455 : 3 : g_object_notify (G_OBJECT (self), "default-output");
456 : : }
457 : : }
458 : : else
459 : : {
460 : 0 : g_warning ("%s(): expected \"enabled\" field holding a boolean",
461 : : G_STRFUNC);
462 : : }
463 : : }
464 : :
465 [ + + ]: 7 : if (new_stream)
466 : 2 : valent_mixer_adapter_stream_added (VALENT_MIXER_ADAPTER (self), stream);
467 : :
468 : : return name;
469 : : }
470 : :
471 : : /**
472 : : * valent_systemvolume_device_handle_packet:
473 : : * @self: a `ValentSystemvolumeDevice`
474 : : * @packet: a `kdeconnect.systemvolume.*` packet
475 : : *
476 : : * Handle a packet of mixer.
477 : : */
478 : : void
479 : 6 : valent_systemvolume_device_handle_packet (ValentSystemvolumeDevice *self,
480 : : JsonNode *packet)
481 : : {
482 : 6 : JsonArray *sinks;
483 : :
484 : 6 : VALENT_ENTRY;
485 : :
486 [ + - ]: 6 : g_assert (VALENT_IS_SYSTEMVOLUME_DEVICE (self));
487 [ - + ]: 6 : g_assert (VALENT_IS_PACKET (packet));
488 : :
489 [ + + ]: 6 : if (valent_packet_get_array (packet, "sinkList", &sinks))
490 : : {
491 : 3 : g_autoptr (GStrvBuilder) names = NULL;
492 [ + - ]: 3 : g_auto (GStrv) namev = NULL;
493 : 3 : unsigned int n_sinks = 0;
494 : 3 : GHashTableIter iter;
495 : 3 : const char *name = NULL;
496 : 3 : ValentMixerStream *output = NULL;
497 : :
498 : 3 : names = g_strv_builder_new ();
499 : 3 : n_sinks = json_array_get_length (sinks);
500 [ + + ]: 7 : for (unsigned int i = 0; i < n_sinks; i++)
501 : : {
502 : 4 : JsonObject *sink = json_array_get_object_element (sinks, i);
503 : :
504 : 4 : name = valent_systemvolume_device_handle_stream (self, sink);
505 [ + - ]: 4 : if (name != NULL)
506 : 4 : g_strv_builder_add (names, name);
507 : : }
508 : 3 : namev = g_strv_builder_end (names);
509 : :
510 : 3 : g_hash_table_iter_init (&iter, self->outputs);
511 [ + + ]: 11 : while (g_hash_table_iter_next (&iter, (void **)&name, (void **)&output))
512 : : {
513 [ + + ]: 5 : if (!g_strv_contains ((const char * const *)namev, name))
514 : : {
515 : 1 : valent_mixer_adapter_stream_removed (VALENT_MIXER_ADAPTER (self),
516 : : output);
517 : 1 : g_hash_table_iter_remove (&iter);
518 : : }
519 : : }
520 : : }
521 : : else
522 : : {
523 : 3 : JsonObject *body;
524 : :
525 : 3 : body = valent_packet_get_body (packet);
526 : 3 : valent_systemvolume_device_handle_stream (self, body);
527 : : }
528 : :
529 : 6 : VALENT_EXIT;
530 : : }
531 : :
|