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-mpris-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <math.h>
9 : :
10 : : #include <glib/gi18n.h>
11 : : #include <gio/gio.h>
12 : : #include <json-glib/json-glib.h>
13 : : #include <valent.h>
14 : :
15 : : #include "vdp-mpris-adapter.h"
16 : : #include "vdp-mpris-player.h"
17 : : #include "valent-mpris-utils.h"
18 : :
19 : : #include "valent-mpris-plugin.h"
20 : :
21 : : struct _ValentMprisPlugin
22 : : {
23 : : ValentDevicePlugin parent_instance;
24 : :
25 : : ValentMedia *media;
26 : : unsigned int media_watch : 1;
27 : : ValentMediaAdapter *adapter;
28 : : GPtrArray *players;
29 : :
30 : : GHashTable *transfers;
31 : : GHashTable *pending;
32 : : unsigned int pending_list : 1;
33 : : unsigned int flush_id;
34 : : };
35 : :
36 [ + + + - ]: 93 : G_DEFINE_FINAL_TYPE (ValentMprisPlugin, valent_mpris_plugin, VALENT_TYPE_DEVICE_PLUGIN)
37 : :
38 : : static void valent_mpris_plugin_send_player_info (ValentMprisPlugin *self,
39 : : ValentMediaPlayer *player,
40 : : gboolean now_playing,
41 : : gboolean volume);
42 : : static void valent_mpris_plugin_send_player_list (ValentMprisPlugin *self);
43 : :
44 : :
45 : : static gpointer
46 : 11 : valent_mpris_plugin_lookup_player (ValentMprisPlugin *self,
47 : : const char *name)
48 : : {
49 [ + - ]: 11 : for (unsigned int i = 0; i < self->players->len; i++)
50 : : {
51 : 11 : ValentMediaPlayer *player = g_ptr_array_index (self->players, i);
52 [ - + ]: 11 : if (g_strcmp0 (valent_media_player_get_name (player), name) == 0)
53 : : return player;
54 : : }
55 : :
56 : : return NULL;
57 : : }
58 : :
59 : : /*
60 : : * Local Players
61 : : */
62 : : static void
63 : 1 : send_album_art_cb (ValentTransfer *transfer,
64 : : GAsyncResult *result,
65 : : ValentMprisPlugin *self)
66 : : {
67 : 2 : g_autoptr (GError) error = NULL;
68 [ - + ]: 1 : g_autofree char *id = NULL;
69 : :
70 [ + - ]: 1 : g_assert (VALENT_IS_TRANSFER (transfer));
71 : :
72 [ - + ]: 1 : if (!valent_transfer_execute_finish (transfer, result, &error))
73 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
74 : :
75 : 1 : id = valent_transfer_dup_id (transfer);
76 : 1 : g_hash_table_remove (self->transfers, id);
77 : 1 : }
78 : :
79 : : static void
80 : 1 : valent_mpris_plugin_send_album_art (ValentMprisPlugin *self,
81 : : ValentMediaPlayer *player,
82 : : const char *requested_uri)
83 : : {
84 : 1 : g_autoptr (GVariant) metadata = NULL;
85 : 1 : const char *real_uri;
86 [ - - ]: 1 : g_autoptr (GFile) real_file = NULL;
87 [ + - - - ]: 1 : g_autoptr (GFile) requested_file = NULL;
88 [ + - - - ]: 1 : g_autoptr (JsonBuilder) builder = NULL;
89 [ - + - - ]: 1 : g_autoptr (JsonNode) packet = NULL;
90 [ + - - - ]: 1 : g_autoptr (ValentTransfer) transfer = NULL;
91 : 1 : ValentDevice *device;
92 : :
93 [ + - ]: 1 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
94 : :
95 : : /* Ignore concurrent requests */
96 [ + - ]: 1 : if (g_hash_table_contains (self->transfers, requested_uri))
97 : : return;
98 : :
99 : : /* Check player and URL are safe */
100 [ + - - + ]: 2 : if ((metadata = valent_media_player_get_metadata (player)) == NULL ||
101 : 1 : !g_variant_lookup (metadata, "mpris:artUrl", "&s", &real_uri))
102 : : {
103 : 0 : g_warning ("Album art request \"%s\" for track without album art",
104 : : requested_uri);
105 : 0 : return;
106 : : }
107 : :
108 : : /* Compare normalized URLs */
109 : 1 : requested_file = g_file_new_for_uri (requested_uri);
110 : 1 : real_file = g_file_new_for_uri (real_uri);
111 : :
112 [ - + ]: 1 : if (!g_file_equal (requested_file, real_file))
113 : : {
114 : 0 : g_warning ("Album art request \"%s\" doesn't match current track \"%s\"",
115 : : requested_uri, real_uri);
116 : 0 : return;
117 : : }
118 : :
119 : : /* Build the payload packet */
120 : 1 : valent_packet_init (&builder, "kdeconnect.mpris");
121 : 1 : json_builder_set_member_name (builder, "player");
122 : 1 : json_builder_add_string_value (builder, valent_media_player_get_name (player));
123 : 1 : json_builder_set_member_name (builder, "albumArtUrl");
124 : 1 : json_builder_add_string_value (builder, requested_uri);
125 : 1 : json_builder_set_member_name (builder, "transferringAlbumArt");
126 : 1 : json_builder_add_boolean_value (builder, TRUE);
127 : 1 : packet = valent_packet_end (&builder);
128 : :
129 : : /* Start the transfer */
130 : 1 : device = valent_resource_get_source (VALENT_RESOURCE (self));
131 : 1 : transfer = valent_device_transfer_new (device, packet, real_file);
132 : :
133 [ - + ]: 1 : g_hash_table_insert (self->transfers,
134 : 1 : g_strdup (requested_uri),
135 : : g_object_ref (transfer));
136 : :
137 [ + - ]: 1 : valent_transfer_execute (transfer,
138 : : NULL,
139 : : (GAsyncReadyCallback)send_album_art_cb,
140 : : self);
141 : : }
142 : :
143 : : static gboolean
144 : 11 : valent_mpris_plugin_flush (gpointer data)
145 : : {
146 : 11 : ValentMprisPlugin *self = VALENT_MPRIS_PLUGIN (data);
147 : 11 : GHashTableIter iter;
148 : 11 : ValentMediaPlayer *player;
149 : :
150 : 11 : g_hash_table_iter_init (&iter, self->pending);
151 [ + + ]: 20 : while (g_hash_table_iter_next (&iter, (void **)&player, NULL))
152 : : {
153 : 9 : valent_mpris_plugin_send_player_info (self, player, TRUE, TRUE);
154 : 9 : g_hash_table_iter_remove (&iter);
155 : : }
156 : :
157 [ + + ]: 11 : if (self->pending_list)
158 : : {
159 : 2 : valent_mpris_plugin_send_player_list (self);
160 : 2 : self->pending_list = FALSE;
161 : : }
162 : :
163 : 11 : self->flush_id = 0;
164 : :
165 : 11 : return G_SOURCE_REMOVE;
166 : : }
167 : :
168 : : static void
169 : 2 : on_player_seeked (ValentMediaPlayer *player,
170 : : double position,
171 : : ValentMprisPlugin *self)
172 : : {
173 : 2 : const char *name;
174 : 4 : g_autoptr (JsonBuilder) builder = NULL;
175 [ - + ]: 2 : g_autoptr (JsonNode) packet = NULL;
176 : :
177 [ + - ]: 2 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
178 : :
179 : 2 : name = valent_media_player_get_name (player);
180 : :
181 : : /* Convert seconds to milliseconds */
182 : 2 : valent_packet_init (&builder, "kdeconnect.mpris");
183 : 2 : json_builder_set_member_name (builder, "player");
184 : 2 : json_builder_add_string_value (builder, name);
185 : 2 : json_builder_set_member_name (builder, "pos");
186 : 2 : json_builder_add_int_value (builder, (int64_t)(position * 1000L));
187 : 2 : packet = valent_packet_end (&builder);
188 : :
189 [ + - ]: 2 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
190 : 2 : }
191 : :
192 : : static void
193 : 21 : on_player_changed (ValentMediaPlayer *player,
194 : : GParamSpec *pspec,
195 : : ValentMprisPlugin *self)
196 : : {
197 [ + - ]: 21 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
198 : :
199 [ + + ]: 21 : if (g_str_equal (pspec->name, "position"))
200 : : {
201 : 2 : double position = 0.0;
202 : :
203 : 2 : position = valent_media_player_get_position (player);
204 : 2 : on_player_seeked (player, position, self);
205 : : }
206 : : else
207 : : {
208 : 19 : g_hash_table_add (self->pending, player);
209 [ + + ]: 19 : if (self->flush_id == 0)
210 : 9 : self->flush_id = g_idle_add (valent_mpris_plugin_flush, self);
211 : : }
212 : 21 : }
213 : :
214 : : static void
215 : 1 : on_player_destroy (ValentMediaPlayer *player,
216 : : ValentMprisPlugin *self)
217 : : {
218 : 1 : g_hash_table_remove (self->pending, player);
219 : 1 : g_ptr_array_remove (self->players, player);
220 : :
221 : 1 : self->pending_list = TRUE;
222 [ - + ]: 1 : if (self->flush_id == 0)
223 : 0 : self->flush_id = g_idle_add (valent_mpris_plugin_flush, self);
224 : 1 : }
225 : :
226 : : static void
227 : 4 : on_players_changed (GListModel *list,
228 : : unsigned int position,
229 : : unsigned int removed,
230 : : unsigned int added,
231 : : ValentMprisPlugin *self)
232 : : {
233 [ + + ]: 5 : for (unsigned int i = 0; i < added; i++)
234 : : {
235 : 1 : g_autoptr (ValentMediaPlayer) player = NULL;
236 : :
237 : 1 : player = g_list_model_get_item (list, position + i);
238 : 1 : g_signal_connect_object (player,
239 : : "notify",
240 : : G_CALLBACK (on_player_changed),
241 : : self,
242 : : G_CONNECT_DEFAULT);
243 : 1 : g_signal_connect_object (player,
244 : : "destroy",
245 : : G_CALLBACK (on_player_destroy),
246 : : self,
247 : : G_CONNECT_DEFAULT);
248 [ + - ]: 1 : g_ptr_array_add (self->players, player);
249 : : }
250 : :
251 : 4 : self->pending_list = TRUE;
252 [ + - ]: 4 : if (self->flush_id == 0)
253 : 4 : self->flush_id = g_idle_add (valent_mpris_plugin_flush, self);
254 : 4 : }
255 : :
256 : : static void
257 : 6 : on_adapters_changed (GListModel *list,
258 : : unsigned int position,
259 : : unsigned int removed,
260 : : unsigned int added,
261 : : ValentMprisPlugin *self)
262 : : {
263 [ + - ]: 6 : g_assert (VALENT_IS_MEDIA (list));
264 [ + - ]: 6 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
265 : :
266 [ + + ]: 12 : for (unsigned int i = 0; i < added; i++)
267 : : {
268 : 6 : g_autoptr (ValentMediaAdapter) adapter = NULL;
269 : :
270 : 6 : adapter = g_list_model_get_item (list, position + i);
271 [ + + ]: 6 : if (VDP_IS_MPRIS_ADAPTER (adapter))
272 [ + - ]: 3 : continue;
273 : :
274 : 3 : g_signal_connect_object (adapter,
275 : : "items-changed",
276 : : G_CALLBACK (on_players_changed),
277 : : self,
278 : : G_CONNECT_DEFAULT);
279 [ + - ]: 3 : on_players_changed (G_LIST_MODEL (adapter),
280 : : 0,
281 : : 0,
282 : : g_list_model_get_n_items (G_LIST_MODEL (adapter)),
283 : : self);
284 : : }
285 : 6 : }
286 : :
287 : : static void
288 : 14 : valent_mpris_plugin_watch_media (ValentMprisPlugin *self,
289 : : gboolean watch)
290 : : {
291 : 14 : ValentMedia *media = valent_media_get_default ();
292 : :
293 [ + + ]: 14 : if (self->media_watch == watch)
294 : : return;
295 : :
296 [ + + ]: 6 : if (watch)
297 : : {
298 : 3 : g_signal_connect_object (media,
299 : : "items-changed",
300 : : G_CALLBACK (on_adapters_changed),
301 : : self,
302 : : G_CONNECT_DEFAULT);
303 : 3 : on_adapters_changed (G_LIST_MODEL (media),
304 : : 0,
305 : : 0,
306 : : g_list_model_get_n_items (G_LIST_MODEL (media)),
307 : : self);
308 : :
309 [ + - ]: 3 : if (self->adapter == NULL)
310 : : {
311 : 3 : ValentDevice *device = NULL;
312 : :
313 : 3 : device = valent_resource_get_source (VALENT_RESOURCE (self));
314 : 3 : self->adapter = vdp_mpris_adapter_new (device);
315 : 3 : valent_component_export_adapter (VALENT_COMPONENT (media),
316 : : VALENT_EXTENSION (self->adapter));
317 : : }
318 : : }
319 : : else
320 : : {
321 : 3 : unsigned int n_adapters = 0;
322 : :
323 : 3 : n_adapters = g_list_model_get_n_items (G_LIST_MODEL (media));
324 [ + + ]: 9 : for (unsigned int i = 0; i < n_adapters; i++)
325 : : {
326 : 6 : g_autoptr (ValentMediaAdapter) adapter = NULL;
327 : :
328 : 6 : adapter = g_list_model_get_item (G_LIST_MODEL (media), i);
329 [ + - ]: 6 : g_signal_handlers_disconnect_by_data (adapter, self);
330 : : }
331 : :
332 [ - + ]: 3 : for (unsigned int i = 0; i < self->players->len; i++)
333 : : {
334 : 0 : ValentMediaPlayer *player = NULL;
335 : :
336 : 0 : player = g_ptr_array_index (self->players, i);
337 : 0 : g_signal_handlers_disconnect_by_data (player, self);
338 : : }
339 : :
340 : 3 : g_hash_table_remove_all (self->pending);
341 : 3 : g_ptr_array_remove_range (self->players, 0, self->players->len);
342 [ + + ]: 3 : g_clear_handle_id (&self->flush_id, g_source_remove);
343 : 3 : g_signal_handlers_disconnect_by_data (media, self);
344 : :
345 [ + - ]: 3 : if (self->adapter != NULL)
346 : : {
347 : 3 : valent_component_unexport_adapter (VALENT_COMPONENT (media),
348 : : VALENT_EXTENSION (self->adapter));
349 [ + - ]: 3 : g_clear_object (&self->adapter);
350 : : }
351 : : }
352 : :
353 : 6 : self->media_watch = watch;
354 : : }
355 : :
356 : : static void
357 : 5 : valent_mpris_plugin_handle_action (ValentMprisPlugin *self,
358 : : ValentMediaPlayer *player,
359 : : const char *action)
360 : : {
361 [ + - ]: 5 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
362 [ - + ]: 5 : g_assert (VALENT_IS_MEDIA_PLAYER (player));
363 [ + - - + ]: 5 : g_assert (action && *action);
364 : :
365 [ + + ]: 5 : if (g_str_equal (action, "Next"))
366 : 1 : valent_media_player_next (player);
367 : :
368 [ + + ]: 4 : else if (g_str_equal (action, "Pause"))
369 : 1 : valent_media_player_pause (player);
370 : :
371 [ + + ]: 3 : else if (g_str_equal (action, "Play"))
372 : 1 : valent_media_player_play (player);
373 : :
374 [ - + ]: 2 : else if (g_str_equal (action, "PlayPause"))
375 [ # # # # : 0 : valent_mpris_play_pause (player);
# # ]
376 : :
377 [ + + ]: 2 : else if (g_str_equal (action, "Previous"))
378 : 1 : valent_media_player_previous (player);
379 : :
380 [ + - ]: 1 : else if (g_str_equal (action, "Stop"))
381 : 1 : valent_media_player_stop (player);
382 : :
383 : : else
384 : 0 : g_warning ("%s(): Unknown action: %s", G_STRFUNC, action);
385 : 5 : }
386 : :
387 : : static void
388 : 12 : valent_mpris_plugin_handle_mpris_request (ValentMprisPlugin *self,
389 : : JsonNode *packet)
390 : : {
391 : 12 : ValentMediaPlayer *player = NULL;
392 : 12 : const char *name;
393 : 12 : const char *action;
394 : 12 : const char *url;
395 : 12 : int64_t offset_us;
396 : 12 : int64_t position;
397 : 12 : gboolean request_now_playing;
398 : 12 : gboolean request_volume;
399 : 12 : const char *loop_status;
400 : 12 : ValentMediaRepeat repeat;
401 : 12 : gboolean shuffle;
402 : 12 : int64_t volume;
403 : :
404 [ + - ]: 12 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
405 : :
406 : : /* Start by checking for a player */
407 [ + + ]: 12 : if (valent_packet_get_string (packet, "player", &name))
408 : 11 : player = valent_mpris_plugin_lookup_player (self, name);
409 : :
410 [ + - - + ]: 11 : if (player == NULL || valent_packet_check_field (packet, "requestPlayerList"))
411 : : {
412 : 1 : valent_mpris_plugin_send_player_list (self);
413 : 1 : return;
414 : : }
415 : :
416 : : /* A request for a player's status */
417 : 11 : request_now_playing = valent_packet_check_field (packet, "requestNowPlaying");
418 : 11 : request_volume = valent_packet_check_field (packet, "requestVolume");
419 : :
420 [ + + ]: 11 : if (request_now_playing || request_volume)
421 : 1 : valent_mpris_plugin_send_player_info (self,
422 : : player,
423 : : request_now_playing,
424 : : request_volume);
425 : :
426 [ + + ]: 11 : if (valent_packet_get_string (packet, "action", &action))
427 : 5 : valent_mpris_plugin_handle_action (self, player, action);
428 : :
429 : : /* A request to change the relative position (microseconds to seconds) */
430 [ + + ]: 11 : if (valent_packet_get_int (packet, "Seek", &offset_us))
431 : 1 : valent_media_player_seek (player, offset_us / G_TIME_SPAN_SECOND);
432 : :
433 : : /* A request to change the absolute position (milliseconds to seconds) */
434 [ - + ]: 11 : if (valent_packet_get_int (packet, "SetPosition", &position))
435 : 0 : valent_media_player_set_position (player, position / 1000L);
436 : :
437 [ + + ]: 11 : if (valent_packet_get_string (packet, "setLoopStatus", &loop_status))
438 : : {
439 : 1 : repeat = valent_mpris_repeat_from_string (loop_status);
440 : 1 : valent_media_player_set_repeat (player, repeat);
441 : : }
442 : :
443 [ + + ]: 11 : if (valent_packet_get_boolean (packet, "setShuffle", &shuffle))
444 : 1 : valent_media_player_set_shuffle (player, shuffle);
445 : :
446 [ + + ]: 11 : if (valent_packet_get_int (packet, "setVolume", &volume))
447 : 1 : valent_media_player_set_volume (player, volume / 100.0);
448 : :
449 [ + + ]: 11 : if (valent_packet_get_string (packet, "albumArtUrl", &url))
450 : 1 : valent_mpris_plugin_send_album_art (self, player, url);
451 : : }
452 : :
453 : : static void
454 : 10 : valent_mpris_plugin_send_player_info (ValentMprisPlugin *self,
455 : : ValentMediaPlayer *player,
456 : : gboolean request_now_playing,
457 : : gboolean request_volume)
458 : : {
459 : 20 : g_autoptr (JsonBuilder) builder = NULL;
460 [ - + ]: 10 : g_autoptr (JsonNode) response = NULL;
461 : 10 : const char *name;
462 : :
463 [ + - ]: 10 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
464 [ - + ]: 10 : g_assert (VALENT_IS_MEDIA_PLAYER (player));
465 : :
466 : 10 : name = valent_media_player_get_name (player);
467 : :
468 : 10 : valent_packet_init (&builder, "kdeconnect.mpris");
469 : 10 : json_builder_set_member_name (builder, "player");
470 : 10 : json_builder_add_string_value (builder, name);
471 : :
472 : : /* Player State & Metadata */
473 [ + - ]: 10 : if (request_now_playing)
474 : : {
475 : 10 : ValentMediaActions flags;
476 : 10 : ValentMediaRepeat repeat;
477 : 10 : gboolean is_playing;
478 : 10 : double position;
479 : 10 : gboolean shuffle;
480 : 10 : const char *loop_status = "None";
481 : 10 : g_autoptr (GVariant) metadata = NULL;
482 [ + - ]: 10 : g_autofree char *artist = NULL;
483 : 10 : const char *title = NULL;
484 : :
485 : : /* Player State */
486 : 10 : flags = valent_media_player_get_flags (player);
487 : 10 : json_builder_set_member_name (builder, "canPause");
488 : 10 : json_builder_add_boolean_value (builder, (flags & VALENT_MEDIA_ACTION_PAUSE) != 0);
489 : 10 : json_builder_set_member_name (builder, "canPlay");
490 : 10 : json_builder_add_boolean_value (builder, (flags & VALENT_MEDIA_ACTION_PLAY) != 0);
491 : 10 : json_builder_set_member_name (builder, "canGoNext");
492 : 10 : json_builder_add_boolean_value (builder, (flags & VALENT_MEDIA_ACTION_NEXT) != 0);
493 : 10 : json_builder_set_member_name (builder, "canGoPrevious");
494 : 10 : json_builder_add_boolean_value (builder,(flags & VALENT_MEDIA_ACTION_PREVIOUS) != 0);
495 : 10 : json_builder_set_member_name (builder, "canSeek");
496 : 10 : json_builder_add_boolean_value (builder, (flags & VALENT_MEDIA_ACTION_SEEK) != 0);
497 : :
498 : 10 : repeat = valent_media_player_get_repeat (player);
499 : 10 : loop_status = valent_mpris_repeat_to_string (repeat);
500 : 10 : json_builder_set_member_name (builder, "loopStatus");
501 : 10 : json_builder_add_string_value (builder, loop_status);
502 : :
503 : 10 : shuffle = valent_media_player_get_shuffle (player);
504 : 10 : json_builder_set_member_name (builder, "shuffle");
505 : 10 : json_builder_add_boolean_value (builder, shuffle);
506 : :
507 : 10 : is_playing = valent_media_player_get_state (player) == VALENT_MEDIA_STATE_PLAYING;
508 : 10 : json_builder_set_member_name (builder, "isPlaying");
509 : 10 : json_builder_add_boolean_value (builder, is_playing);
510 : :
511 : : /* Convert seconds to milliseconds */
512 : 10 : position = valent_media_player_get_position (player);
513 : 10 : json_builder_set_member_name (builder, "pos");
514 : 10 : json_builder_add_int_value (builder, (int64_t)(position * 1000L));
515 : :
516 : : /* Track Metadata
517 : : *
518 : : * See: https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/
519 : : */
520 [ + - ]: 10 : if ((metadata = valent_media_player_get_metadata (player)) != NULL)
521 : : {
522 : 10 : g_autofree const char **artists = NULL;
523 : 10 : int64_t length_us;
524 : 10 : const char *art_url;
525 : 10 : const char *album;
526 : :
527 [ + + ]: 10 : if (g_variant_lookup (metadata, "xesam:artist", "^a&s", &artists) &&
528 [ + - + - ]: 4 : artists[0] != NULL && *artists[0] != '\0')
529 : : {
530 : 4 : artist = g_strjoinv (", ", (char **)artists);
531 : 4 : json_builder_set_member_name (builder, "artist");
532 : 4 : json_builder_add_string_value (builder, artist);
533 : : }
534 : :
535 [ + + ]: 10 : if (g_variant_lookup (metadata, "xesam:title", "&s", &title) &&
536 [ + - ]: 4 : *title != '\0')
537 : : {
538 : 4 : json_builder_set_member_name (builder, "title");
539 : 4 : json_builder_add_string_value (builder, title);
540 : : }
541 : :
542 [ + + ]: 10 : if (g_variant_lookup (metadata, "xesam:album", "&s", &album) &&
543 [ + - ]: 4 : *album != '\0')
544 : : {
545 : 4 : json_builder_set_member_name (builder, "album");
546 : 4 : json_builder_add_string_value (builder, album);
547 : : }
548 : :
549 : : /* Convert microseconds to milliseconds */
550 [ + + ]: 10 : if (g_variant_lookup (metadata, "mpris:length", "x", &length_us))
551 : : {
552 : 4 : json_builder_set_member_name (builder, "length");
553 : 4 : json_builder_add_int_value (builder, length_us / 1000L);
554 : : }
555 : :
556 [ + + ]: 10 : if (g_variant_lookup (metadata, "mpris:artUrl", "&s", &art_url))
557 : : {
558 : 1 : json_builder_set_member_name (builder, "albumArtUrl");
559 : 1 : json_builder_add_string_value (builder, art_url);
560 : : }
561 : : }
562 : : }
563 : :
564 : : /* Volume Level */
565 [ + - ]: 10 : if (request_volume)
566 : : {
567 : 10 : int64_t level;
568 : :
569 : 10 : level = (int64_t)floor (valent_media_player_get_volume (player) * 100);
570 : 10 : json_builder_set_member_name (builder, "volume");
571 : 10 : json_builder_add_int_value (builder, level);
572 : : }
573 : :
574 : : /* Send Response */
575 : 10 : response = valent_packet_end (&builder);
576 [ + - ]: 10 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), response);
577 : 10 : }
578 : :
579 : : static void
580 : 3 : valent_mpris_plugin_send_player_list (ValentMprisPlugin *self)
581 : : {
582 : 6 : g_autoptr (JsonBuilder) builder = NULL;
583 [ - + ]: 3 : g_autoptr (JsonNode) packet = NULL;
584 : :
585 [ + - ]: 3 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
586 : :
587 : 3 : valent_packet_init (&builder, "kdeconnect.mpris");
588 : :
589 : : /* Player List */
590 : 3 : json_builder_set_member_name (builder, "playerList");
591 : 3 : json_builder_begin_array (builder);
592 : :
593 [ + + ]: 5 : for (unsigned int i = 0; i < self->players->len; i++)
594 : : {
595 : 2 : ValentMediaPlayer *player = g_ptr_array_index (self->players, i);
596 : 2 : const char *name = valent_media_player_get_name (player);
597 : :
598 [ + - ]: 2 : if (name != NULL)
599 : 2 : json_builder_add_string_value (builder, name);
600 : : }
601 : :
602 : 3 : json_builder_end_array (builder);
603 : :
604 : : /* Indicate that the remote device may send us album art payloads */
605 : 3 : json_builder_set_member_name (builder, "supportAlbumArtPayload");
606 : 3 : json_builder_add_boolean_value (builder, TRUE);
607 : :
608 : 3 : packet = valent_packet_end (&builder);
609 [ + - ]: 3 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
610 : 3 : }
611 : :
612 : : /*
613 : : * ValentDevicePlugin
614 : : */
615 : : static void
616 : 10 : valent_mpris_plugin_update_state (ValentDevicePlugin *plugin,
617 : : ValentDeviceState state)
618 : : {
619 : 10 : ValentMprisPlugin *self = VALENT_MPRIS_PLUGIN (plugin);
620 : 10 : gboolean available;
621 : :
622 [ + - ]: 10 : g_assert (VALENT_IS_MPRIS_PLUGIN (self));
623 : :
624 : 10 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
625 : : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
626 : :
627 : 10 : valent_mpris_plugin_watch_media (self, available);
628 : 10 : }
629 : :
630 : : static void
631 : 17 : valent_mpris_plugin_handle_packet (ValentDevicePlugin *plugin,
632 : : const char *type,
633 : : JsonNode *packet)
634 : : {
635 : 17 : ValentMprisPlugin *self = VALENT_MPRIS_PLUGIN (plugin);
636 : :
637 [ + - ]: 17 : g_assert (VALENT_IS_MPRIS_PLUGIN (plugin));
638 [ - + ]: 17 : g_assert (type != NULL);
639 [ - + ]: 17 : g_assert (VALENT_IS_PACKET (packet));
640 : :
641 [ + + ]: 17 : if (g_str_equal (type, "kdeconnect.mpris"))
642 : 5 : vdp_mpris_adapter_handle_packet (VDP_MPRIS_ADAPTER (self->adapter), packet);
643 [ + - ]: 12 : else if (g_str_equal (type, "kdeconnect.mpris.request"))
644 : 12 : valent_mpris_plugin_handle_mpris_request (self, packet);
645 : : else
646 : 0 : g_assert_not_reached ();
647 : 17 : }
648 : :
649 : : /*
650 : : * ValentObject
651 : : */
652 : : static void
653 : 4 : valent_mpris_plugin_destroy (ValentObject *object)
654 : : {
655 : 4 : ValentMprisPlugin *self = VALENT_MPRIS_PLUGIN (object);
656 : :
657 : 4 : valent_mpris_plugin_watch_media (self, FALSE);
658 [ + + ]: 4 : g_clear_pointer (&self->players, g_ptr_array_unref);
659 [ + + ]: 4 : g_clear_pointer (&self->pending, g_hash_table_unref);
660 [ + + ]: 4 : g_clear_pointer (&self->transfers, g_hash_table_unref);
661 : :
662 : 4 : VALENT_OBJECT_CLASS (valent_mpris_plugin_parent_class)->destroy (object);
663 : 4 : }
664 : :
665 : : /*
666 : : * GObject
667 : : */
668 : : static void
669 : 2 : valent_mpris_plugin_class_init (ValentMprisPluginClass *klass)
670 : : {
671 : 2 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
672 : 2 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
673 : :
674 : 2 : vobject_class->destroy = valent_mpris_plugin_destroy;
675 : :
676 : 2 : plugin_class->handle_packet = valent_mpris_plugin_handle_packet;
677 : 2 : plugin_class->update_state = valent_mpris_plugin_update_state;
678 : : }
679 : :
680 : : static void
681 : 3 : valent_mpris_plugin_init (ValentMprisPlugin *self)
682 : : {
683 : 3 : self->transfers = g_hash_table_new_full (g_str_hash,
684 : : g_str_equal,
685 : : g_free,
686 : : g_object_unref);
687 : 3 : self->players = g_ptr_array_new ();
688 : 3 : self->pending = g_hash_table_new (NULL, NULL);
689 : 3 : }
690 : :
|