1 /*
2  * Copyright (c) 2015-2019, 2021 gnome-mpv
3  *
4  * This file is part of Celluloid.
5  *
6  * Celluloid is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Celluloid is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Celluloid.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <gio/gio.h>
21 #include <glib.h>
22 #include <glib-object.h>
23 #include <mpv/client.h>
24 #include <string.h>
25 
26 #include "celluloid-mpris-player.h"
27 #include "celluloid-common.h"
28 #include "celluloid-main-window.h"
29 #include "celluloid-mpris.h"
30 #include "celluloid-mpris-player.h"
31 #include "celluloid-mpris-gdbus.h"
32 #include "celluloid-def.h"
33 
34 enum
35 {
36 	PROP_0,
37 	PROP_CONTROLLER,
38 	N_PROPERTIES
39 };
40 
41 struct _CelluloidMprisPlayer
42 {
43 	CelluloidMprisModule parent;
44 	CelluloidController *controller;
45 	GHashTable *readonly_table;
46 	guint reg_id;
47 };
48 
49 struct _CelluloidMprisPlayerClass
50 {
51 	CelluloidMprisModuleClass parent_class;
52 };
53 
54 static void
55 register_interface(CelluloidMprisModule *module);
56 
57 static void
58 unregister_interface(CelluloidMprisModule *module);
59 
60 static void
61 set_property(	GObject *object,
62 		guint property_id,
63 		const GValue *value,
64 		GParamSpec *pspec );
65 
66 static void
67 get_property(	GObject *object,
68 		guint property_id,
69 		GValue *value,
70 		GParamSpec *pspec );
71 
72 static void
73 append_metadata_tags(GVariantBuilder *builder, GPtrArray *list);
74 
75 static void
76 method_handler(	GDBusConnection *connection,
77 		const gchar *sender,
78 		const gchar *object_path,
79 		const gchar *interface_name,
80 		const gchar *method_name,
81 		GVariant *parameters,
82 		GDBusMethodInvocation *invocation,
83 		gpointer data );
84 
85 static GVariant *
86 get_prop_handler(	GDBusConnection *connection,
87 			const gchar *sender,
88 			const gchar *object_path,
89 			const gchar *interface_name,
90 			const gchar *property_name,
91 			GError **error,
92 			gpointer data );
93 
94 static gboolean
95 set_prop_handler(	GDBusConnection *connection,
96 			const gchar *sender,
97 			const gchar *object_path,
98 			const gchar *interface_name,
99 			const gchar *property_name,
100 			GVariant *value,
101 			GError **error,
102 			gpointer data );
103 
104 static void
105 update_playback_status(CelluloidMprisPlayer *player);
106 
107 static void
108 update_playlist_state(CelluloidMprisPlayer *player);
109 
110 static void
111 update_speed(CelluloidMprisPlayer *player);
112 
113 static void
114 update_loop(CelluloidMprisPlayer *player);
115 
116 static void
117 update_metadata(CelluloidMprisPlayer *player);
118 
119 static void
120 update_volume(CelluloidMprisPlayer *player);
121 
122 static void
123 idle_active_handler(	GObject *object,
124 			GParamSpec *pspec,
125 			gpointer data );
126 
127 static void
128 core_idle_handler(	GObject *object,
129 			GParamSpec *pspec,
130 			gpointer data );
131 
132 static void
133 playlist_pos_handler(	GObject *object,
134 			GParamSpec *pspec,
135 			gpointer data );
136 
137 static void
138 playlist_count_handler(	GObject *object,
139 			GParamSpec *pspec,
140 			gpointer data );
141 static void
142 speed_handler(	GObject *object,
143 		GParamSpec *pspec,
144 		gpointer data );
145 
146 static void
147 loop_handler(	GObject *object,
148 		GParamSpec *pspec,
149 		gpointer data );
150 
151 static void
152 metadata_handler(	GObject *object,
153 			GParamSpec *pspec,
154 			gpointer data );
155 
156 static void
157 volume_handler(	GObject *object,
158 		GParamSpec *pspec,
159 		gpointer data );
160 
161 static void
162 playback_restart_handler(CelluloidModel *model, gpointer data);
163 
164 static void
165 celluloid_mpris_player_class_init(CelluloidMprisPlayerClass *klass);
166 
167 static void
168 celluloid_mpris_player_init(CelluloidMprisPlayer *player);
169 
170 G_DEFINE_TYPE(CelluloidMprisPlayer, celluloid_mpris_player, CELLULOID_TYPE_MPRIS_MODULE);
171 
172 static void
register_interface(CelluloidMprisModule * module)173 register_interface(CelluloidMprisModule *module)
174 {
175 	CelluloidMprisPlayer *player = CELLULOID_MPRIS_PLAYER(module);
176 	CelluloidModel *model =	celluloid_controller_get_model
177 				(player->controller);
178 
179 	GDBusConnection *conn;
180 	GDBusInterfaceInfo *iface;
181 	GDBusInterfaceVTable vtable;
182 
183 	g_object_get(module, "conn", &conn, "iface", &iface, NULL);
184 
185 	celluloid_mpris_module_connect_signal
186 		(	module,
187 			model,
188 			"notify::core-idle",
189 			G_CALLBACK(core_idle_handler),
190 			player );
191 	celluloid_mpris_module_connect_signal
192 		(	module,
193 			model,
194 			"notify::idle-active",
195 			G_CALLBACK(idle_active_handler),
196 			player );
197 	celluloid_mpris_module_connect_signal
198 		(	module,
199 			model,
200 			"notify::playlist-pos",
201 			G_CALLBACK(playlist_pos_handler),
202 			player );
203 	celluloid_mpris_module_connect_signal
204 		(	module,
205 			model,
206 			"notify::playlist-count",
207 			G_CALLBACK(playlist_count_handler),
208 			player );
209 	celluloid_mpris_module_connect_signal
210 		(	module,
211 			model,
212 			"notify::speed",
213 			G_CALLBACK(speed_handler),
214 			player );
215 	celluloid_mpris_module_connect_signal
216 		(	module,
217 			model,
218 			"notify::loop-file",
219 			G_CALLBACK(loop_handler),
220 			player );
221 	celluloid_mpris_module_connect_signal
222 		(	module,
223 			model,
224 			"notify::loop-playlist",
225 			G_CALLBACK(loop_handler),
226 			player );
227 	celluloid_mpris_module_connect_signal
228 		(	module,
229 			model,
230 			"notify::metadata",
231 			G_CALLBACK(metadata_handler),
232 			player );
233 	celluloid_mpris_module_connect_signal
234 		(	module,
235 			model,
236 			"notify::volume",
237 			G_CALLBACK(volume_handler),
238 			player );
239 	celluloid_mpris_module_connect_signal
240 		(	module,
241 			model,
242 			"playback-restart",
243 			G_CALLBACK(playback_restart_handler),
244 			player );
245 
246 	celluloid_mpris_module_set_properties
247 		(	module,
248 			"PlaybackStatus", g_variant_new_string("Stopped"),
249 			"LoopStatus", g_variant_new_string("None"),
250 			"Rate", g_variant_new_double(1.0),
251 			"Metadata", g_variant_new("a{sv}", NULL),
252 			"Volume", g_variant_new_double(1.0),
253 			"MinimumRate", g_variant_new_double(0.01),
254 			"MaximumRate", g_variant_new_double(100.0),
255 			"CanGoNext", g_variant_new_boolean(FALSE),
256 			"CanGoPrevious", g_variant_new_boolean(FALSE),
257 			"CanPlay", g_variant_new_boolean(TRUE),
258 			"CanPause", g_variant_new_boolean(TRUE),
259 			"CanSeek", g_variant_new_boolean(FALSE),
260 			"CanControl", g_variant_new_boolean(TRUE),
261 			NULL );
262 
263 	vtable.method_call = (GDBusInterfaceMethodCallFunc)method_handler;
264 	vtable.get_property = (GDBusInterfaceGetPropertyFunc)get_prop_handler;
265 	vtable.set_property = (GDBusInterfaceSetPropertyFunc)set_prop_handler;
266 
267 	player->reg_id = g_dbus_connection_register_object
268 				(	conn,
269 					MPRIS_OBJ_ROOT_PATH,
270 					iface,
271 					&vtable,
272 					player,
273 					NULL,
274 					NULL );
275 
276 	update_playback_status(player);
277 	update_playlist_state(player);
278 	update_speed(player);
279 	update_metadata(player);
280 	update_volume(player);
281 }
282 
283 static void
unregister_interface(CelluloidMprisModule * module)284 unregister_interface(CelluloidMprisModule *module)
285 {
286 	CelluloidMprisPlayer *player = CELLULOID_MPRIS_PLAYER(module);
287 	GDBusConnection *conn = NULL;
288 
289 	g_object_get(module, "conn", &conn, NULL);
290 	g_dbus_connection_unregister_object(conn, player->reg_id);
291 }
292 
293 static void
set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)294 set_property(	GObject *object,
295 		guint property_id,
296 		const GValue *value,
297 		GParamSpec *pspec )
298 {
299 	CelluloidMprisPlayer *self = CELLULOID_MPRIS_PLAYER(object);
300 
301 	switch(property_id)
302 	{
303 		case PROP_CONTROLLER:
304 		self->controller = g_value_get_pointer(value);
305 		break;
306 
307 		default:
308 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
309 		break;
310 	}
311 }
312 
313 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)314 get_property(	GObject *object,
315 		guint property_id,
316 		GValue *value,
317 		GParamSpec *pspec )
318 {
319 	CelluloidMprisPlayer *self = CELLULOID_MPRIS_PLAYER(object);
320 
321 	switch(property_id)
322 	{
323 		case PROP_CONTROLLER:
324 		g_value_set_pointer(value, self->controller);
325 		break;
326 
327 		default:
328 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
329 		break;
330 	}
331 }
332 
333 static void
append_metadata_tags(GVariantBuilder * builder,GPtrArray * list)334 append_metadata_tags(GVariantBuilder *builder, GPtrArray *list)
335 {
336 	const struct
337 	{
338 		const gchar *mpv_name;
339 		const gchar *tag_name;
340 		const gboolean is_array;
341 	}
342 	tag_map[] = {	{"Album", "xesam:album", FALSE},
343 			{"Album_Artist", "xesam:albumArtist", TRUE},
344 			{"Artist", "xesam:artist", TRUE},
345 			{"Comment", "xesam:comment", TRUE},
346 			{"Composer", "xesam:composer", FALSE},
347 			{"Genre", "xesam:genre", TRUE},
348 			{"Title", "xesam:title", FALSE},
349 			{NULL, NULL, FALSE} };
350 
351 	const guint list_len = list?list->len:0;
352 
353 	for(guint i = 0; i < list_len; i++)
354 	{
355 		const gchar *tag_name;
356 		GVariant *tag_value;
357 		CelluloidMetadataEntry *entry = g_ptr_array_index(list, i);
358 		gboolean is_array = TRUE;
359 		gint j = -1;
360 
361 		/* Translate applicable mpv tag names to MPRIS2-compatible tag
362 		 * names.
363 		 */
364 		while(	tag_map[++j].mpv_name &&
365 			g_ascii_strcasecmp(entry->key, tag_map[j].mpv_name) != 0 );
366 		tag_name = tag_map[j].mpv_name?tag_map[j].tag_name:entry->key;
367 		is_array = tag_map[j].mpv_name?tag_map[j].is_array:FALSE;
368 
369 		if(is_array)
370 		{
371 			GVariantBuilder tag_builder;
372 			GVariant *elem_value;
373 
374 			elem_value = g_variant_new_string(entry->value);
375 
376 			g_variant_builder_init
377 				(&tag_builder, G_VARIANT_TYPE("as"));
378 			g_variant_builder_add_value
379 				(&tag_builder, elem_value);
380 
381 			tag_value = g_variant_new("as", &tag_builder);
382 		}
383 		else
384 		{
385 			tag_value = g_variant_new_string(entry->value);
386 		}
387 
388 		g_debug("Adding metadata tag \"%s\"", tag_name);
389 		g_variant_builder_add(builder, "{sv}", tag_name, tag_value);
390 	}
391 }
392 
393 static void
method_handler(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer data)394 method_handler(	GDBusConnection *connection,
395 		const gchar *sender,
396 		const gchar *object_path,
397 		const gchar *interface_name,
398 		const gchar *method_name,
399 		GVariant *parameters,
400 		GDBusMethodInvocation *invocation,
401 		gpointer data )
402 {
403 	CelluloidMprisPlayer *player = data;
404 	CelluloidModel *model =	celluloid_controller_get_model
405 				(player->controller);
406 	gboolean unknown_method = FALSE;
407 
408 	if(g_strcmp0(method_name, "Next") == 0)
409 	{
410 		celluloid_model_next_playlist_entry(model);
411 	}
412 	else if(g_strcmp0(method_name, "Previous") == 0)
413 	{
414 		celluloid_model_previous_playlist_entry(model);
415 	}
416 	else if(g_strcmp0(method_name, "Pause") == 0)
417 	{
418 		celluloid_model_pause(model);
419 	}
420 	else if(g_strcmp0(method_name, "PlayPause") == 0)
421 	{
422 		gboolean pause = FALSE;
423 
424 		g_object_get(model, "pause", &pause, NULL);
425 		g_object_set(model, "pause", !pause, NULL);
426 	}
427 	else if(g_strcmp0(method_name, "Stop") == 0)
428 	{
429 		celluloid_model_stop(model);
430 	}
431 	else if(g_strcmp0(method_name, "Play") == 0)
432 	{
433 		celluloid_model_play(model);
434 	}
435 	else if(g_strcmp0(method_name, "Seek") == 0)
436 	{
437 		gint64 offset_us;
438 
439 		g_variant_get(parameters, "(x)", &offset_us);
440 		celluloid_model_seek_offset(model, (gdouble)offset_us/1.0e6);
441 	}
442 	else if(g_strcmp0(method_name, "SetPosition") == 0)
443 	{
444 		const gchar *prefix = MPRIS_TRACK_ID_PREFIX;
445 		const gsize prefix_len = strlen(prefix);
446 		gint64 time_us = -1;
447 		const gchar *track_id = NULL;
448 
449 		g_variant_get(parameters, "(&ox)", &track_id, &time_us);
450 
451 		if(strncmp(track_id, prefix, prefix_len) == 0)
452 		{
453 			gint64 index =	g_ascii_strtoll
454 					(track_id+prefix_len, NULL, 0);
455 
456 			celluloid_model_set_playlist_position(model, index);
457 			celluloid_model_seek(model, (gdouble)time_us/1.0e6);
458 		}
459 	}
460 	else if(g_strcmp0(method_name, "OpenUri") == 0)
461 	{
462 		const gchar *uri;
463 
464 		g_variant_get(parameters, "(&s)", &uri);
465 		celluloid_model_load_file(model, uri, FALSE);
466 	}
467 	else
468 	{
469 		unknown_method = TRUE;
470 	}
471 
472 	if(unknown_method)
473 	{
474 		g_dbus_method_invocation_return_error
475 			(	invocation,
476 				CELLULOID_MPRIS_ERROR,
477 				CELLULOID_MPRIS_ERROR_UNKNOWN_METHOD,
478 				"Attempted to call unknown method \"%s\"",
479 				method_name );
480 	}
481 	else
482 	{
483 		g_dbus_method_invocation_return_value
484 			(invocation, g_variant_new("()", NULL));
485 	}
486 }
487 
488 static GVariant *
get_prop_handler(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GError ** error,gpointer data)489 get_prop_handler(	GDBusConnection *connection,
490 			const gchar *sender,
491 			const gchar *object_path,
492 			const gchar *interface_name,
493 			const gchar *property_name,
494 			GError **error,
495 			gpointer data )
496 {
497 	CelluloidMprisPlayer *player = CELLULOID_MPRIS_PLAYER(data);
498 	CelluloidMprisModule *module = CELLULOID_MPRIS_MODULE(data);
499 	GVariant *value = NULL;
500 
501 	if(!g_hash_table_contains(player->readonly_table, property_name))
502 	{
503 		g_set_error
504 			(	error,
505 				CELLULOID_MPRIS_ERROR,
506 				CELLULOID_MPRIS_ERROR_UNKNOWN_PROPERTY,
507 				"Failed to get value of unknown property \"%s\"",
508 				property_name );
509 	}
510 	else if(g_strcmp0(property_name, "Position") == 0)
511 	{
512 		CelluloidModel *model;
513 		gdouble position;
514 
515 		model = celluloid_controller_get_model(player->controller);
516 		position = celluloid_model_get_time_position(model);
517 		value = g_variant_new_int64((gint64)(position*1e6));
518 	}
519 	else
520 	{
521 		celluloid_mpris_module_get_properties
522 			(	module,
523 				property_name, &value,
524 				NULL );
525 	}
526 
527 	return value?g_variant_ref(value):NULL;
528 }
529 
530 static gboolean
set_prop_handler(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * property_name,GVariant * value,GError ** error,gpointer data)531 set_prop_handler(	GDBusConnection *connection,
532 			const gchar *sender,
533 			const gchar *object_path,
534 			const gchar *interface_name,
535 			const gchar *property_name,
536 			GVariant *value,
537 			GError **error,
538 			gpointer data )
539 {
540 	CelluloidMprisPlayer *player = CELLULOID_MPRIS_PLAYER(data);
541 	CelluloidModel *model =	celluloid_controller_get_model
542 				(player->controller);
543 	gboolean result = TRUE;
544 
545 	if(!g_hash_table_contains(player->readonly_table, property_name))
546 	{
547 		result = FALSE;
548 
549 		g_set_error
550 			(	error,
551 				CELLULOID_MPRIS_ERROR,
552 				CELLULOID_MPRIS_ERROR_UNKNOWN_PROPERTY,
553 				"Failed to set value of unknown property \"%s\"",
554 				property_name );
555 	}
556 	else if(GPOINTER_TO_INT(g_hash_table_lookup(player->readonly_table, property_name)))
557 	{
558 		g_set_error
559 			(	error,
560 				CELLULOID_MPRIS_ERROR,
561 				CELLULOID_MPRIS_ERROR_SET_READONLY,
562 				"Attempted to set value of readonly property \"%s\"",
563 				property_name );
564 	}
565 	else if(g_strcmp0(property_name, "LoopStatus") == 0)
566 	{
567 		const gchar *loop = g_variant_get_string(value, NULL);
568 		const gchar *loop_file =	g_strcmp0(loop, "Track") == 0 ?
569 						"inf":"no";
570 		const gchar *loop_playlist =	g_strcmp0(loop, "Playlist") == 0 ?
571 						"inf":"no";
572 
573 		g_object_set(	G_OBJECT(model),
574 				"loop-file", loop_file,
575 				"loop-playlist", loop_playlist,
576 				NULL );
577 	}
578 	else if(g_strcmp0(property_name, "Rate") == 0)
579 	{
580 		g_object_set(	G_OBJECT(model),
581 				"speed", g_variant_get_double(value),
582 				NULL );
583 	}
584 	else if(g_strcmp0(property_name, "Volume") == 0)
585 	{
586 		g_object_set(	G_OBJECT(model),
587 				"volume", 100*g_variant_get_double(value),
588 				NULL );
589 	}
590 
591 	return result;
592 }
593 
594 static void
update_playback_status(CelluloidMprisPlayer * player)595 update_playback_status(CelluloidMprisPlayer *player)
596 {
597 	CelluloidModel *model =	celluloid_controller_get_model
598 				(player->controller);
599 	const gchar *state;
600 	gint idle_active;
601 	gint core_idle;
602 	gboolean can_seek;
603 
604 	g_object_get(	G_OBJECT(model),
605 			"idle-active", &idle_active,
606 			"core-idle", &core_idle,
607 			NULL );
608 
609 	if(!core_idle && !idle_active)
610 	{
611 		state = "Playing";
612 		can_seek = TRUE;
613 	}
614 	else if(core_idle && idle_active)
615 	{
616 		state = "Stopped";
617 		can_seek = FALSE;
618 	}
619 	else
620 	{
621 		state = "Paused";
622 		can_seek = TRUE;
623 	}
624 
625 	celluloid_mpris_module_set_properties(	CELLULOID_MPRIS_MODULE(player),
626 						"PlaybackStatus",
627 						g_variant_new_string(state),
628 						"CanSeek",
629 						g_variant_new_boolean(can_seek),
630 						NULL );
631 }
632 
633 static void
update_playlist_state(CelluloidMprisPlayer * player)634 update_playlist_state(CelluloidMprisPlayer *player)
635 {
636 	CelluloidModel *model =	celluloid_controller_get_model
637 				(player->controller);
638 	gboolean can_prev;
639 	gboolean can_next;
640 	gint64 playlist_count;
641 	gint64 playlist_pos;
642 	gint rc = 0;
643 
644 	g_object_get(	G_OBJECT(model),
645 			"playlist-count", &playlist_count,
646 			"playlist-pos", &playlist_pos,
647 			NULL );
648 
649 	can_prev = (rc >= 0 && playlist_pos > 0);
650 	can_next = (rc >= 0 && playlist_pos < playlist_count-1);
651 
652 	celluloid_mpris_module_set_properties(	CELLULOID_MPRIS_MODULE(player),
653 						"CanGoPrevious",
654 						g_variant_new_boolean(can_prev),
655 						"CanGoNext",
656 						g_variant_new_boolean(can_next),
657 						NULL );
658 }
659 
660 static void
update_speed(CelluloidMprisPlayer * player)661 update_speed(CelluloidMprisPlayer *player)
662 {
663 	CelluloidModel *model =	celluloid_controller_get_model
664 				(player->controller);
665 	gdouble speed = 1.0;
666 
667 	g_object_get(G_OBJECT(model), "speed", &speed, NULL);
668 
669 	celluloid_mpris_module_set_properties(	CELLULOID_MPRIS_MODULE(player),
670 						"Rate",
671 						g_variant_new_double(speed),
672 						NULL );
673 }
674 
675 static void
update_loop(CelluloidMprisPlayer * player)676 update_loop(CelluloidMprisPlayer *player)
677 {
678 	CelluloidModel *model =	celluloid_controller_get_model
679 				(player->controller);
680 	gchar *loop_file = NULL;
681 	gchar *loop_playlist = NULL;
682 	const gchar *loop = NULL;
683 
684 	g_object_get(	model,
685 			"loop-file", &loop_file,
686 			"loop-playlist", &loop_playlist,
687 			NULL );
688 
689 	loop =	g_strcmp0(loop_file, "inf") == 0 ? "Track" :
690 		g_strcmp0(loop_playlist, "inf") == 0 ? "Playlist" :
691 		"None";
692 
693 	celluloid_mpris_module_set_properties(	CELLULOID_MPRIS_MODULE(player),
694 						"LoopStatus",
695 						g_variant_new_string(loop),
696 						NULL );
697 
698 	g_free(loop_file);
699 	g_free(loop_playlist);
700 }
701 
702 static void
update_metadata(CelluloidMprisPlayer * player)703 update_metadata(CelluloidMprisPlayer *player)
704 {
705 	CelluloidModel *model =	celluloid_controller_get_model
706 				(player->controller);
707 	GPtrArray *metadata = NULL;
708 	GVariantBuilder builder;
709 	gchar *path;
710 	gchar *uri;
711 	gchar *playlist_pos_str;
712 	gchar *trackid;
713 	gdouble duration = 0;
714 	gint64 playlist_pos = 0;
715 
716 	g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
717 	path = celluloid_model_get_current_path(model)?:g_strdup("");
718 	uri = g_filename_to_uri(path, NULL, NULL)?:g_strdup(path);
719 
720 	if(uri)
721 	{
722 		g_variant_builder_add(	&builder,
723 					"{sv}",
724 					"xesam:url",
725 					g_variant_new_string(uri) );
726 
727 	}
728 
729 	g_object_get(	model,
730 			"duration", &duration,
731 			"playlist-pos", &playlist_pos,
732 			"metadata", &metadata,
733 			NULL );
734 
735 	g_variant_builder_add(	&builder,
736 				"{sv}",
737 				"mpris:length",
738 				g_variant_new_int64
739 				((gint64)(duration*1e6)) );
740 
741 	playlist_pos_str = g_strdup_printf("%" G_GINT64_FORMAT, playlist_pos);
742 	trackid = g_strconcat(	MPRIS_TRACK_ID_PREFIX,
743 				playlist_pos_str,
744 				NULL );
745 	g_variant_builder_add(	&builder,
746 				"{sv}",
747 				"mpris:trackid",
748 				g_variant_new_object_path(trackid) );
749 
750 	append_metadata_tags(&builder, metadata);
751 
752 	celluloid_mpris_module_set_properties(	CELLULOID_MPRIS_MODULE(player),
753 						"Metadata",
754 						g_variant_new("a{sv}", &builder),
755 						NULL );
756 
757 	g_free(path);
758 	g_free(uri);
759 	g_free(playlist_pos_str);
760 	g_free(trackid);
761 }
762 
763 static void
update_volume(CelluloidMprisPlayer * player)764 update_volume(CelluloidMprisPlayer *player)
765 {
766 	CelluloidModel *model =	celluloid_controller_get_model
767 				(player->controller);
768 	gdouble volume = 0.0;
769 
770 	g_object_get(G_OBJECT(model), "volume", &volume, NULL);
771 
772 	celluloid_mpris_module_set_properties
773 		(	CELLULOID_MPRIS_MODULE(player),
774 			"Volume",
775 			g_variant_new_double(volume/100.0),
776 			NULL );
777 }
778 
779 static void
idle_active_handler(GObject * object,GParamSpec * pspec,gpointer data)780 idle_active_handler(	GObject *object,
781 			GParamSpec *pspec,
782 			gpointer data )
783 {
784 	update_playback_status(data);
785 }
786 
787 static void
core_idle_handler(GObject * object,GParamSpec * pspec,gpointer data)788 core_idle_handler(	GObject *object,
789 			GParamSpec *pspec,
790 			gpointer data )
791 {
792 	update_playback_status(data);
793 }
794 
795 static void
playlist_pos_handler(GObject * object,GParamSpec * pspec,gpointer data)796 playlist_pos_handler(	GObject *object,
797 			GParamSpec *pspec,
798 			gpointer data )
799 {
800 	update_playlist_state(data);
801 }
802 
803 static void
playlist_count_handler(GObject * object,GParamSpec * pspec,gpointer data)804 playlist_count_handler(	GObject *object,
805 			GParamSpec *pspec,
806 			gpointer data )
807 {
808 	update_playlist_state(data);
809 }
810 
811 static void
speed_handler(GObject * object,GParamSpec * pspec,gpointer data)812 speed_handler(	GObject *object,
813 		GParamSpec *pspec,
814 		gpointer data )
815 {
816 	update_speed(data);
817 }
818 
819 static void
loop_handler(GObject * object,GParamSpec * pspec,gpointer data)820 loop_handler(	GObject *object,
821 		GParamSpec *pspec,
822 		gpointer data )
823 {
824 	update_loop(data);
825 }
826 
827 static void
metadata_handler(GObject * object,GParamSpec * pspec,gpointer data)828 metadata_handler(	GObject *object,
829 			GParamSpec *pspec,
830 			gpointer data )
831 {
832 	update_metadata(data);
833 }
834 
835 static void
volume_handler(GObject * object,GParamSpec * pspec,gpointer data)836 volume_handler(	GObject *object,
837 		GParamSpec *pspec,
838 		gpointer data )
839 {
840 	update_volume(data);
841 }
842 
843 static void
playback_restart_handler(CelluloidModel * model,gpointer data)844 playback_restart_handler(CelluloidModel *model, gpointer data)
845 {
846 	GDBusConnection *conn;
847 	GDBusInterfaceInfo *iface;
848 	gdouble position;
849 
850 	position = celluloid_model_get_time_position(model);
851 
852 	g_object_get(	CELLULOID_MPRIS_MODULE(data),
853 			"conn", &conn,
854 			"iface", &iface,
855 			NULL );
856 
857 	g_dbus_connection_emit_signal
858 		(	conn,
859 			NULL,
860 			MPRIS_OBJ_ROOT_PATH,
861 			iface->name,
862 			"Seeked",
863 			g_variant_new("(x)", (gint64)(position*1e6)),
864 			NULL );
865 }
866 
867 static void
celluloid_mpris_player_class_init(CelluloidMprisPlayerClass * klass)868 celluloid_mpris_player_class_init(CelluloidMprisPlayerClass *klass)
869 {
870 	CelluloidMprisModuleClass *module_class =
871 		CELLULOID_MPRIS_MODULE_CLASS(klass);
872 	GObjectClass *object_class = G_OBJECT_CLASS(klass);
873 	GParamSpec *pspec = NULL;
874 
875 	module_class->register_interface = register_interface;
876 	module_class->unregister_interface = unregister_interface;
877 	object_class->set_property = set_property;
878 	object_class->get_property = get_property;
879 
880 	pspec = g_param_spec_pointer
881 		(	"controller",
882 			"Controller",
883 			"The CelluloidApplication to use",
884 			G_PARAM_CONSTRUCT_ONLY|G_PARAM_READWRITE );
885 	g_object_class_install_property(object_class, PROP_CONTROLLER, pspec);
886 }
887 
888 static void
celluloid_mpris_player_init(CelluloidMprisPlayer * player)889 celluloid_mpris_player_init(CelluloidMprisPlayer *player)
890 {
891 	const struct
892 	{
893 		const gchar *name;
894 		gboolean readonly;
895 	}
896 	properties[] =
897 	{
898 		{"PlaybackStatus", TRUE},
899 		{"LoopStatus", FALSE},
900 		{"Rate", FALSE},
901 		{"Metadata", TRUE},
902 		{"Volume", FALSE},
903 		{"MinimumRate", TRUE},
904 		{"MaximumRate", TRUE},
905 		{"CanGoNext", TRUE},
906 		{"CanGoPrevious", TRUE},
907 		{"CanPlay", TRUE},
908 		{"CanPause", TRUE},
909 		{"CanSeek", TRUE},
910 		{"CanControl", TRUE},
911 		{NULL, FALSE}
912 	};
913 
914 	player->controller =
915 		NULL;
916 	player->readonly_table =
917 		g_hash_table_new_full(g_str_hash, g_int_equal, g_free, NULL);
918 	player->reg_id =
919 		0;
920 
921 	for(gint i = 0; properties[i].name; i++)
922 	{
923 		g_hash_table_replace
924 			(	player->readonly_table,
925 				g_strdup(properties[i].name),
926 				GINT_TO_POINTER(properties[i].readonly) );
927 	}
928 }
929 
930 CelluloidMprisModule *
celluloid_mpris_player_new(CelluloidController * controller,GDBusConnection * conn)931 celluloid_mpris_player_new(	CelluloidController *controller,
932 				GDBusConnection *conn )
933 {
934 	GType type;
935 	GDBusInterfaceInfo *iface;
936 
937 	type = celluloid_mpris_player_get_type();
938 	iface = celluloid_mpris_org_mpris_media_player2_player_interface_info();
939 
940 	return CELLULOID_MPRIS_MODULE(g_object_new(	type,
941 							"controller", controller,
942 							"conn", conn,
943 							"iface", iface,
944 							NULL ));
945 }
946