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