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