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 : 3 : valent_device_send_packet_cb (ValentDevice *device,
19 : : GAsyncResult *result,
20 : : gpointer user_data)
21 : : {
22 : 6 : g_autoptr (GError) error = NULL;
23 : :
24 [ - + ]: 3 : 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 : 3 : }
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 : : self->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 : : self->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 [ + + + - ]: 17 : G_DEFINE_FINAL_TYPE (ValentSystemvolumeDevice, valent_systemvolume_device, VALENT_TYPE_MIXER_ADAPTER)
224 : :
225 : : #if 0
226 : : static void
227 : : valent_systemvolume_device_request_sinks (ValentSystemvolumeDevice *self)
228 : : {
229 : : g_autoptr (JsonBuilder) builder = NULL;
230 : : g_autoptr (JsonNode) packet = NULL;
231 : :
232 : : valent_packet_init (&builder, "kdeconnect.systemvolume.request");
233 : : json_builder_set_member_name (builder, "requestSinks");
234 : : json_builder_add_boolean_value (builder, TRUE);
235 : : packet = valent_packet_end (&builder);
236 : :
237 : : valent_device_send_packet (self->device,
238 : : packet,
239 : : self->cancellable,
240 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
241 : : NULL);
242 : : }
243 : : #endif
244 : :
245 : : /*
246 : : * ValentMixerAdapter
247 : : */
248 : : static void
249 : 4 : on_device_state_changed (ValentDevice *device,
250 : : GParamSpec *pspec,
251 : : ValentSystemvolumeDevice *self)
252 : : {
253 : 4 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
254 : 4 : gboolean available;
255 : :
256 [ + - ]: 4 : g_assert (VALENT_IS_SYSTEMVOLUME_DEVICE (self));
257 : :
258 : 4 : state = valent_device_get_state (device);
259 : 4 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
260 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
261 : :
262 [ + - + + ]: 4 : if (available && self->cancellable == NULL)
263 : : {
264 : 2 : self->cancellable = g_cancellable_new ();
265 : : }
266 [ # # ]: 0 : else if (!available && self->cancellable != NULL)
267 : : {
268 : 0 : GHashTableIter iter;
269 : 0 : ValentMixerStream *output;
270 : :
271 : 0 : g_cancellable_cancel (self->cancellable);
272 [ # # ]: 0 : g_clear_object (&self->cancellable);
273 : :
274 : 0 : g_hash_table_iter_init (&iter, self->outputs);
275 [ # # ]: 0 : while (g_hash_table_iter_next (&iter, NULL, (void **)&output))
276 : : {
277 : 0 : valent_mixer_adapter_stream_removed (VALENT_MIXER_ADAPTER (self),
278 : : output);
279 : 0 : g_hash_table_iter_remove (&iter);
280 : : }
281 : : }
282 : 4 : }
283 : :
284 : : static ValentMixerStream *
285 : 2 : valent_systemvolume_device_get_default_output (ValentMixerAdapter *adapter)
286 : : {
287 : 2 : ValentSystemvolumeDevice *self = VALENT_SYSTEMVOLUME_DEVICE (adapter);
288 : :
289 [ + - ]: 2 : g_assert (VALENT_IS_SYSTEMVOLUME_DEVICE (self));
290 : :
291 : 2 : return self->default_output;
292 : : }
293 : :
294 : : static void
295 : 1 : valent_systemvolume_device_set_default_output (ValentMixerAdapter *adapter,
296 : : ValentMixerStream *stream)
297 : : {
298 : 1 : ValentSystemvolumeDevice *self = VALENT_SYSTEMVOLUME_DEVICE (adapter);
299 : 1 : g_autoptr (JsonBuilder) builder = NULL;
300 [ - - - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
301 : :
302 [ + - ]: 1 : g_assert (VALENT_IS_SYSTEMVOLUME_DEVICE (self));
303 : :
304 [ - + ]: 1 : if (self->default_output == stream)
305 [ # # ]: 0 : return;
306 : :
307 : 1 : valent_packet_init (&builder, "kdeconnect.systemvolume.request");
308 : 1 : json_builder_set_member_name (builder, "name");
309 : 1 : json_builder_add_string_value (builder, valent_mixer_stream_get_name (stream));
310 : 1 : json_builder_set_member_name (builder, "enabled");
311 : 1 : json_builder_add_boolean_value (builder, TRUE);
312 : 1 : packet = valent_packet_end (&builder);
313 : :
314 [ + - ]: 1 : valent_device_send_packet (self->device,
315 : : packet,
316 : : self->cancellable,
317 : : (GAsyncReadyCallback) valent_device_send_packet_cb,
318 : : NULL);
319 : : }
320 : :
321 : : /*
322 : : * GObject
323 : : */
324 : : static void
325 : 2 : valent_systemvolume_device_constructed (GObject *object)
326 : : {
327 : 2 : ValentSystemvolumeDevice *self = VALENT_SYSTEMVOLUME_DEVICE (object);
328 : :
329 : 2 : G_OBJECT_CLASS (valent_systemvolume_device_parent_class)->constructed (object);
330 : :
331 : 2 : self->device = valent_resource_get_source (VALENT_RESOURCE (self));
332 : 2 : g_signal_connect_object (self->device,
333 : : "notify::state",
334 : : G_CALLBACK (on_device_state_changed),
335 : : self,
336 : : G_CONNECT_DEFAULT);
337 : 2 : on_device_state_changed (self->device, NULL, self);
338 : 2 : }
339 : :
340 : : static void
341 : 2 : valent_systemvolume_device_finalize (GObject *object)
342 : : {
343 : 2 : ValentSystemvolumeDevice *self = VALENT_SYSTEMVOLUME_DEVICE (object);
344 : :
345 [ + - ]: 2 : g_clear_object (&self->cancellable);
346 [ + + ]: 2 : g_clear_object (&self->default_output);
347 [ + - ]: 2 : g_clear_pointer (&self->outputs, g_hash_table_unref);
348 : :
349 : 2 : G_OBJECT_CLASS (valent_systemvolume_device_parent_class)->finalize (object);
350 : 2 : }
351 : :
352 : : static void
353 : 1 : valent_systemvolume_device_class_init (ValentSystemvolumeDeviceClass *klass)
354 : : {
355 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
356 : 1 : ValentMixerAdapterClass *adapter_class = VALENT_MIXER_ADAPTER_CLASS (klass);
357 : :
358 : 1 : object_class->constructed = valent_systemvolume_device_constructed;
359 : 1 : object_class->finalize = valent_systemvolume_device_finalize;
360 : :
361 : 1 : adapter_class->get_default_output = valent_systemvolume_device_get_default_output;
362 : 1 : adapter_class->set_default_output = valent_systemvolume_device_set_default_output;
363 : : }
364 : :
365 : : static void
366 : 2 : valent_systemvolume_device_init (ValentSystemvolumeDevice *self)
367 : : {
368 : 2 : self->outputs = g_hash_table_new_full (g_str_hash, g_str_equal,
369 : : g_free, g_object_unref);
370 : 2 : }
371 : :
372 : : /**
373 : : * valent_systemvolume_device_new:
374 : : * @device: a `ValentDevice`
375 : : *
376 : : * Create a new `ValentSystemvolumeDevice`.
377 : : *
378 : : * Returns: (transfer full): a new message store
379 : : */
380 : : ValentMixerAdapter *
381 : 2 : valent_systemvolume_device_new (ValentDevice *device)
382 : : {
383 : 4 : g_autoptr (ValentContext) context = NULL;
384 [ + - ]: 2 : g_autofree char *iri = NULL;
385 : :
386 [ + - ]: 2 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
387 : :
388 : 2 : context = valent_context_new (valent_device_get_context (device),
389 : : "plugin",
390 : : "systemvolume");
391 : 2 : iri = tracker_sparql_escape_uri_printf ("urn:valent:mixer:%s",
392 : : valent_device_get_id (device));
393 : 2 : return g_object_new (VALENT_TYPE_SYSTEMVOLUME_DEVICE,
394 : : "iri", iri,
395 : : "context", context,
396 : : "source", device,
397 : : "title", valent_device_get_name (device),
398 : : NULL);
399 : : }
400 : :
401 : : static const char *
402 : 7 : valent_systemvolume_device_handle_stream (ValentSystemvolumeDevice *self,
403 : : JsonObject *state)
404 : : {
405 : 7 : ValentMixerStream *stream = NULL;
406 : 7 : gboolean new_stream = FALSE;
407 : 7 : JsonNode *node;
408 : 7 : const char *name = NULL;
409 : :
410 : 7 : node = json_object_get_member (state, "name");
411 [ + - - + ]: 7 : if (node == NULL || json_node_get_value_type (node) != G_TYPE_STRING)
412 : : {
413 : 0 : g_warning ("%s(): expected \"name\" field holding a string", G_STRFUNC);
414 : 0 : return NULL;
415 : : }
416 : 7 : name = json_node_get_string (node);
417 : :
418 : 7 : stream = g_hash_table_lookup (self->outputs, name);
419 [ + + ]: 7 : if (stream == NULL)
420 : : {
421 : 2 : const char *description = NULL;
422 : :
423 : 2 : node = json_object_get_member (state, "description");
424 [ - + ]: 2 : if (node != NULL)
425 : : {
426 [ + - ]: 2 : if (json_node_get_value_type (node) == G_TYPE_STRING)
427 : : {
428 : 2 : description = json_node_get_string (node);
429 : : }
430 : : else
431 : : {
432 : 0 : g_warning ("%s(): expected \"description\" field holding a string",
433 : : G_STRFUNC);
434 : : }
435 : : }
436 : :
437 : 2 : stream = g_object_new (VALENT_TYPE_SYSTEMVOLUME_STREAM,
438 : : "name", name,
439 : : "description", description,
440 : : "direction", VALENT_MIXER_OUTPUT,
441 : : NULL);
442 : 2 : ((ValentSystemvolumeStream *)stream)->device = self->device;
443 : 2 : ((ValentSystemvolumeStream *)stream)->cancellable = self->cancellable;
444 [ - + ]: 2 : g_hash_table_replace (self->outputs, g_strdup (name), stream /* owned */);
445 : 2 : new_stream = TRUE;
446 : : }
447 : :
448 : 7 : valent_systemvolume_stream_update (stream, state);
449 : :
450 : 7 : node = json_object_get_member (state, "enabled");
451 [ + - ]: 7 : if (node != NULL)
452 : : {
453 [ + - ]: 7 : if (json_node_get_value_type (node) == G_TYPE_BOOLEAN)
454 : : {
455 [ + + + + ]: 13 : if (json_node_get_boolean (node) &&
456 : 6 : g_set_object (&self->default_output, stream))
457 : : {
458 : 3 : g_object_notify (G_OBJECT (self), "default-output");
459 : : }
460 : : }
461 : : else
462 : : {
463 : 0 : g_warning ("%s(): expected \"enabled\" field holding a boolean",
464 : : G_STRFUNC);
465 : : }
466 : : }
467 : :
468 [ + + ]: 7 : if (new_stream)
469 : 2 : valent_mixer_adapter_stream_added (VALENT_MIXER_ADAPTER (self), stream);
470 : :
471 : : return name;
472 : : }
473 : :
474 : : /**
475 : : * valent_systemvolume_device_handle_packet:
476 : : * @self: a `ValentSystemvolumeDevice`
477 : : * @packet: a `kdeconnect.systemvolume.*` packet
478 : : *
479 : : * Handle a packet of mixer.
480 : : */
481 : : void
482 : 6 : valent_systemvolume_device_handle_packet (ValentSystemvolumeDevice *self,
483 : : JsonNode *packet)
484 : : {
485 : 6 : JsonArray *sinks;
486 : :
487 : 6 : VALENT_ENTRY;
488 : :
489 [ + - ]: 6 : g_assert (VALENT_IS_SYSTEMVOLUME_DEVICE (self));
490 [ - + ]: 6 : g_assert (VALENT_IS_PACKET (packet));
491 : :
492 [ + + ]: 6 : if (valent_packet_get_array (packet, "sinkList", &sinks))
493 : : {
494 : 3 : g_autoptr (GStrvBuilder) names = NULL;
495 [ + - ]: 3 : g_auto (GStrv) namev = NULL;
496 : 3 : unsigned int n_sinks = 0;
497 : 3 : GHashTableIter iter;
498 : 3 : const char *name = NULL;
499 : 3 : ValentMixerStream *output = NULL;
500 : :
501 : 3 : names = g_strv_builder_new ();
502 : 3 : n_sinks = json_array_get_length (sinks);
503 [ + + ]: 7 : for (unsigned int i = 0; i < n_sinks; i++)
504 : : {
505 : 4 : JsonObject *sink = json_array_get_object_element (sinks, i);
506 : :
507 : 4 : name = valent_systemvolume_device_handle_stream (self, sink);
508 [ + - ]: 4 : if (name != NULL)
509 : 4 : g_strv_builder_add (names, name);
510 : : }
511 : 3 : namev = g_strv_builder_end (names);
512 : :
513 : 3 : g_hash_table_iter_init (&iter, self->outputs);
514 [ + + ]: 11 : while (g_hash_table_iter_next (&iter, (void **)&name, (void **)&output))
515 : : {
516 [ + + ]: 5 : if (!g_strv_contains ((const char * const *)namev, name))
517 : : {
518 : 1 : valent_mixer_adapter_stream_removed (VALENT_MIXER_ADAPTER (self),
519 : : output);
520 : 1 : g_hash_table_iter_remove (&iter);
521 : : }
522 : : }
523 : : }
524 : : else
525 : : {
526 : 3 : JsonObject *body;
527 : :
528 : 3 : body = valent_packet_get_body (packet);
529 : 3 : valent_systemvolume_device_handle_stream (self, body);
530 : : }
531 : :
532 : 6 : VALENT_EXIT;
533 : : }
534 : :
|