1 #include <stdint.h>
2 #include <inttypes.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 #include <sys/stat.h>
7 
8 #include <glib.h>
9 #include <gio/gio.h>
10 
11 #include "logging.h"
12 #include "mprisServer.h"
13 
14 #define BUS_NAME "org.mpris.MediaPlayer2.DeaDBeeF"
15 #define OBJECT_NAME "/org/mpris/MediaPlayer2"
16 #define PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
17 #define PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties"
18 #define CURRENT_TRACK -1
19 
20 static const char xmlForNode[] =
21 	"<node name='/org/mpris/MediaPlayer2'>"
22 	"	<interface name='org.mpris.MediaPlayer2'>"
23 	"		<method name='Raise'/>"
24 	"		<method name='Quit'/>"
25 	"		<property access='read'	name='CanQuit'             type='b'/>"
26 	"		<property access='read'	name='CanRaise'            type='b'/>"
27 	"		<property access='read'	name='HasTrackList'        type='b'/>"
28 	"		<property access='read'	name='Identity'            type='s'/>"
29 	"		<property access='read' name='DesktopEntry'        type='s'/>"
30 	"		<property access='read'	name='SupportedUriSchemes' type='as'/>"
31 	"		<property access='read'	name='SupportedMimeTypes'  type='as'/>"
32 	"	</interface>"
33 	"	<interface name='org.mpris.MediaPlayer2.Player'>"
34 	"		<method name='Next'/>"
35 	"		<method name='Previous'/>"
36 	"		<method name='Pause'/>"
37 	"		<method name='PlayPause'/>"
38 	"		<method name='Stop'/>"
39 	"		<method name='Play'/>"
40 	"		<method name='Seek'>"
41 	"			<arg name='Offset'      type='x'/>"
42 	"		</method>"
43 	"		<method name='SetPosition'>"
44 	"			<arg name='TrackId'     type='o'/>"
45 	"			<arg name='Position'    type='x'/>"
46 	"		</method>"
47 	"		<method name='OpenUri'>"
48 	"			<arg name='Uri'         type='s'/>"
49 	"		</method>"
50 	"		<signal name='Seeked'>"
51 	"			<arg name='Position'    type='x' direction='out'/>"
52 	"		</signal>"
53 	"		<property access='read'	     name='PlaybackStatus' type='s'/>"
54 	"		<property access='readwrite' name='LoopStatus'     type='s'/>"
55 	"		<property access='readwrite' name='Rate'           type='d'/>"
56 	"		<property access='readwrite' name='Shuffle'        type='b'/>"
57 	"		<property access='read'      name='Metadata'       type='a{sv}'/>"
58 	"		<property access='readwrite' name='Volume'         type='d'/>"
59 	"		<property access='read'      name='Position'       type='x'>"
60 	"			<annotation name='org.freedesktop.DBus.Property.EmitsChangedSignal' value='false'/>"
61 	"		</property>"
62 	"		<property access='read'         name='MinimumRate'   type='d'/>"
63 	"		<property access='read'         name='MaximumRate'   type='d'/>"
64 	"		<property access='read'         name='CanGoNext'     type='b'/>"
65 	"		<property access='read'         name='CanGoPrevious' type='b'/>"
66 	"		<property access='read'         name='CanPlay'       type='b'/>"
67 	"		<property access='read'         name='CanPause'      type='b'/>"
68 	"		<property access='read'         name='CanSeek'       type='b'/>"
69 	"		<property access='read'         name='CanControl'    type='b'>"
70 	"			<annotation name='org.freedesktop.DBus.Property.EmitsChangedSignal' value='false'/>"
71 	"		</property>"
72 	"	</interface>"
73 	"</node>";
74 
75 static GDBusConnection *globalConnection = NULL;
76 static GMainLoop *loop;
77 
coverartCallback(const char * fname,const char * artist,const char * album,void * userData)78 static void coverartCallback(const char *fname, const char *artist, const char *album, void *userData) {
79 	if (fname != NULL) { // cover was not ready
80 		debug("Async loaded cover for %s", album);
81 		emitMetadataChanged(-1, userData);
82 	}
83 }
84 
getMetadataForTrack(int track_id,struct MprisData * mprisData)85 GVariant* getMetadataForTrack(int track_id, struct MprisData *mprisData) {
86 	int id;
87 	DB_playItem_t *track = NULL;
88 	DB_functions_t *deadbeef = mprisData->deadbeef;
89 	ddb_playlist_t *pl = NULL;
90 	GVariant *tmp;
91 	GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
92 	int playlistIndex;
93 
94 	track = deadbeef->streamer_get_playing_track();
95 	if (track) {
96 		pl = deadbeef->plt_get_for_idx(deadbeef->streamer_get_current_playlist());
97 		id = deadbeef->plt_get_item_idx(pl, track, PL_MAIN);
98 		playlistIndex = deadbeef->streamer_get_current_playlist();
99 	}
100 	if (pl) {
101 		deadbeef->plt_unref(pl);
102 	}
103 
104 	if (track != NULL) {
105 		char buf[500];
106 		int buf_size = sizeof(buf);
107 		int64_t duration = deadbeef->pl_get_item_duration(track) * 1000000;
108 		const char *album = deadbeef->pl_find_meta(track, "album");
109 		const char *albumArtist = deadbeef->pl_find_meta(track, "albumartist");
110 		if (albumArtist == NULL)
111 			albumArtist = deadbeef->pl_find_meta(track, "album artist");
112 		if (albumArtist == NULL)
113 			albumArtist = deadbeef->pl_find_meta(track, "band");
114 		const char *artist = deadbeef->pl_find_meta(track, "artist");
115 		const char *lyrics = deadbeef->pl_find_meta(track, "lyrics");
116 		const char *comment = deadbeef->pl_find_meta(track, "comment");
117 		const char *date = deadbeef->pl_find_meta_raw(track, "year");
118 		const char *title = deadbeef->pl_find_meta(track, "title");
119 		const char *trackNumber = deadbeef->pl_find_meta(track, "track");
120 		const char *uri = deadbeef->pl_find_meta(track, ":URI");
121 		const char *genres = deadbeef->pl_find_meta(track, "genre");
122 
123 		deadbeef->pl_lock();
124 
125 		sprintf(buf, "/DeaDBeeF/%d/%d", playlistIndex, id);
126 		debug("get Metadata trackid: %s", buf);
127 		g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new("o", buf));
128 
129 		debug("get Metadata duration: %" PRId64, duration);
130 		if (duration > 0) {
131 			g_variant_builder_add(builder, "{sv}", "mpris:length", g_variant_new("x", duration));
132 		}
133 
134 		debug("get Metadata album: %s", album);
135 		if (album != NULL) {
136 			g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new("s", album));
137 		}
138 
139 		debug("get Metadata albumArtist: %s", albumArtist);
140 		if (albumArtist != NULL) {
141 			GVariantBuilder *albumArtistBuilder = g_variant_builder_new(G_VARIANT_TYPE("as"));
142 			g_variant_builder_add(albumArtistBuilder, "s", albumArtist);
143 			g_variant_builder_add(builder, "{sv}", "xesam:albumArtist", g_variant_builder_end(albumArtistBuilder));
144 			g_variant_builder_unref(albumArtistBuilder);
145 		}
146 
147 		debug("get Metadata artist: %s", artist);
148 		if (artist != NULL) {
149 			GVariantBuilder *artistBuilder = g_variant_builder_new(G_VARIANT_TYPE("as"));
150 			g_variant_builder_add(artistBuilder, "s", artist);
151 			g_variant_builder_add(builder, "{sv}", "xesam:artist", g_variant_builder_end(artistBuilder));
152 			g_variant_builder_unref(artistBuilder);
153 		}
154 
155 		if (mprisData->artwork != NULL) {
156 			char *artworkPath = NULL;
157 			char *albumArtUri = NULL;
158 
159 			debug("getting cover for album %s", album);
160 			artworkPath = mprisData->artwork->get_album_art(uri, artist, album, -1, coverartCallback, mprisData);
161 			if (artworkPath == NULL) {
162 				debug("cover for %s not ready. Using default artwork", album);
163 				const char *defaultPath = mprisData->artwork->get_default_cover();
164 				if (defaultPath != NULL) {
165 					albumArtUri = malloc(strlen(defaultPath) + 7 + 1); // strlen(defaultPath) + strlen("file://") + "/0"
166 
167 					strcpy(albumArtUri, "file://");
168 					strcpy(albumArtUri + 7, defaultPath);
169 				}
170 			} else {
171 				debug("cover for %s ready. Artwork is: %s", album, artworkPath);
172 				albumArtUri = malloc(strlen(artworkPath) + 7 + 1); // strlen(artworkPath) + strlen("file://") + "/0"
173 
174 				strcpy(albumArtUri, "file://");
175 				strcpy(albumArtUri + 7, artworkPath);
176 
177 				free(artworkPath);
178 			}
179 
180 			if (albumArtUri != NULL) {
181 				g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new("s", albumArtUri));
182 				free(albumArtUri);
183 			}
184 		}
185 
186 		debug("get Metadata lyrics: %s", lyrics);
187 		if (lyrics != NULL) {
188 			g_variant_builder_add(builder, "{sv}", "xesam:asText", g_variant_new("s", lyrics));
189 		}
190 
191 		debug("get Metadata comment: %s", comment);
192 		if (comment != NULL) {
193 			GVariantBuilder *commentBuilder = g_variant_builder_new(G_VARIANT_TYPE("as"));
194 			g_variant_builder_add(commentBuilder, "s", comment);
195 			g_variant_builder_add(builder, "{sv}", "xesam:comment", g_variant_builder_end(commentBuilder));
196 			g_variant_builder_unref(commentBuilder);
197 		}
198 
199 		if (date == NULL)
200 			date = deadbeef->pl_find_meta(track, "date");
201 		debug("get Metadata contentCreated: %s", date); //TODO format date
202 		if (date != NULL) {
203 			g_variant_builder_add(builder, "{sv}", "xesam:contentCreated", g_variant_new("s", date));
204 		}
205 
206 		debug("get Metdata genres: %s", genres);
207 		if (genres != NULL) {
208 			char *genresCpy = malloc(strlen(genres) + 1);
209 			strcpy(genresCpy, genres);
210 			GVariantBuilder *genreBuilder = g_variant_builder_new(G_VARIANT_TYPE("as"));
211 
212 			char *genre = strtok(genresCpy, "\n");
213 
214 			while (genre != NULL) {
215 				debug("genre is %s with length %d", genre, strlen(genre));
216 				g_variant_builder_add(genreBuilder, "s", genre);
217 				genre = strtok(NULL, "\n");
218 			}
219 
220 			g_variant_builder_add(builder, "{sv}", "xesam:genre", g_variant_builder_end(genreBuilder));
221 			g_variant_builder_unref(genreBuilder);
222 			free(genresCpy);
223 		}
224 
225 		debug("get Metadata title: %s", title);
226 		if (title != NULL) {
227 			g_variant_builder_add(builder, "{sv}", "xesam:title", g_variant_new("s", title));
228 		}
229 
230 		debug("get Metadata trackNumber: %s", trackNumber);
231 		if (trackNumber != NULL) {
232 			int trackNumberAsInt = atoi(trackNumber);
233 			if (trackNumberAsInt > 0) {
234 				g_variant_builder_add(builder, "{sv}", "xesam:trackNumber", g_variant_new("i", trackNumberAsInt));
235 			}
236 		}
237 
238 		char *fullUri = malloc(strlen(uri) + 7 + 1); // strlen(uri) + strlen("file://") + \0
239 		strcpy(fullUri, "file://");
240 		strcpy(fullUri + 7, uri);
241 		debug("get Metadata URI: %s", fullUri);
242 		g_variant_builder_add(builder, "{sv}", "xesam:url", g_variant_new("s", fullUri));
243 		free(fullUri);
244 
245 		deadbeef->pl_unlock();
246 		deadbeef->pl_item_unref(track);
247 	} else {
248 		debug("get Metadata trackid: /org/mpris/MediaPlayer2/TrackList/NoTrack");
249 		g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new("o", "/org/mpris/MediaPlayer2/TrackList/NoTrack"));
250 	}
251 	tmp = g_variant_builder_end(builder);
252 	g_variant_builder_unref(builder);
253 
254 	return tmp;
255 }
256 
deadbeef_can_seek(DB_functions_t * deadbeef)257 gboolean deadbeef_can_seek(DB_functions_t *deadbeef) {
258 	gboolean can_seek = FALSE;
259 	DB_output_t *output = deadbeef->get_output();
260 	if (output) {
261 		DB_playItem_t *track = deadbeef->streamer_get_playing_track();
262 		if (track) {
263 			can_seek = deadbeef->pl_get_item_duration(track) > 0;
264 			deadbeef->pl_item_unref(track);
265 		}
266 	}
267 	return can_seek;
268 }
269 
deadbeef_hasselectedorplayingtrack(struct MprisData * userData,int offset)270 static gboolean deadbeef_hasselectedorplayingtrack(struct MprisData *userData, int offset) {
271 	DB_functions_t *deadbeef = ((struct MprisData *)userData)->deadbeef;
272 	ddb_playlist_t *pl = NULL;
273 	DB_playItem_t *playing_track = deadbeef->streamer_get_playing_track();
274 	int idx;
275 	if (playing_track) {
276 		pl = deadbeef->plt_get_for_idx(deadbeef->streamer_get_current_playlist());
277 		if (pl) {
278 			idx = deadbeef->plt_get_item_idx(pl, playing_track, PL_MAIN) + offset;
279 		}
280 		deadbeef->pl_item_unref(playing_track);
281 	} else {
282 		pl = deadbeef->plt_get_curr();
283 		if (pl) {
284 			idx = deadbeef->plt_get_cursor(pl, PL_MAIN) + offset;
285 		}
286 	}
287 
288 	if (pl) {
289 		DB_playItem_t *track = deadbeef->plt_get_item_for_idx(pl, idx, PL_MAIN);
290 		deadbeef->plt_unref(pl);
291 		if (track) {
292 			deadbeef->pl_item_unref(track);
293 			return TRUE;
294 		}
295 	}
296 	return FALSE;
297 }
298 
onRootMethodCallHandler(GDBusConnection * connection,const char * sender,const char * objectPath,const char * interfaceName,const char * methodName,GVariant * parameters,GDBusMethodInvocation * invocation,void * userData)299 static void onRootMethodCallHandler(GDBusConnection *connection, const char *sender, const char *objectPath,
300                                     const char *interfaceName, const char *methodName, GVariant *parameters,
301                                     GDBusMethodInvocation *invocation, void *userData) {
302 	debug("Method call on root interface. sender: %s, methodName %s", sender, methodName);
303 	DB_functions_t *deadbeef = ((struct MprisData *)userData)->deadbeef;
304 
305 	if (strcmp(methodName, "Quit") == 0) {
306 		g_dbus_method_invocation_return_value(invocation, NULL);
307 		deadbeef->sendmessage(DB_EV_TERMINATE, 0, 0, 0);
308 	} else if (strcmp(methodName, "Raise") == 0) {
309 		GDesktopAppInfo *dskapp = g_desktop_app_info_new ("deadbeef.desktop");
310 		g_app_info_launch ((GAppInfo *) dskapp, NULL, NULL, NULL);
311 		g_dbus_method_invocation_return_value(invocation, NULL);
312 	} else {
313 		debug("Error! Unsupported method. %s.%s", interfaceName, methodName);
314 		g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED,
315                                               "Method %s.%s not supported", interfaceName, methodName);
316 	}
317 }
318 
onRootGetPropertyHandler(GDBusConnection * connection,const char * sender,const char * objectPath,const char * interfaceName,const char * propertyName,GError ** error,void * userData)319 static GVariant* onRootGetPropertyHandler(GDBusConnection *connection, const char *sender, const char *objectPath,
320                                           const char *interfaceName, const char *propertyName, GError **error,
321                                           void *userData) {
322 	debug("Get property call on root interface. sender: %s, propertyName: %s", sender, propertyName);
323 	GVariant *result = NULL;
324 
325 	if (strcmp(propertyName, "CanQuit") == 0) {
326 		result = g_variant_new_boolean(TRUE);
327 	} else if (strcmp(propertyName, "CanRaise") == 0) {
328 		result = g_variant_new_boolean(TRUE);
329 	} else if (strcmp(propertyName, "HasTrackList") == 0) {
330 		result = g_variant_new_boolean(FALSE);
331 	} else if (strcmp(propertyName, "Identity") == 0) {
332 		result = g_variant_new_string("DeaDBeeF");
333 	} else if (strcmp(propertyName, "DesktopEntry") == 0) {
334 		result = g_variant_new_string("deadbeef");
335 	} else if (strcmp(propertyName, "SupportedUriSchemes") == 0) {
336 		GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
337 		//TODO find uri schemata
338 		g_variant_builder_add(builder, "s", "file");
339 		g_variant_builder_add(builder, "s", "http");
340 		g_variant_builder_add(builder, "s", "cdda");
341 		result = g_variant_builder_end(builder);
342 		g_variant_builder_unref(builder);
343 	} else if (strcmp(propertyName, "SupportedMimeTypes") == 0){
344 		GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
345 
346 		// MIME types from the .desktop file
347 		g_variant_builder_add(builder, "s", "audio/aac");
348 		g_variant_builder_add(builder, "s", "audio/aacp");
349 		g_variant_builder_add(builder, "s", "audio/x-it");
350 		g_variant_builder_add(builder, "s", "audio/x-flac");
351 		g_variant_builder_add(builder, "s", "audio/x-mod");
352 		g_variant_builder_add(builder, "s", "audio/mpeg");
353 		g_variant_builder_add(builder, "s", "audio/x-mpeg");
354 		g_variant_builder_add(builder, "s", "audio/x-mpegurl");
355 		g_variant_builder_add(builder, "s", "audio/mp3");
356 		g_variant_builder_add(builder, "s", "audio/prs.sid");
357 		g_variant_builder_add(builder, "s", "audio/x-scpls");
358 		g_variant_builder_add(builder, "s", "audio/x-s3m");
359 		g_variant_builder_add(builder, "s", "application/ogg");
360 		g_variant_builder_add(builder, "s", "application/x-ogg");
361 		g_variant_builder_add(builder, "s", "audio/x-vorbis+ogg");
362 		g_variant_builder_add(builder, "s", "audio/ogg");
363 		g_variant_builder_add(builder, "s", "audio/wma");
364 		g_variant_builder_add(builder, "s", "audio/x-xm");
365 		result = g_variant_builder_end(builder);
366 	}
367 	return result;
368 }
369 
370 static const GDBusInterfaceVTable rootInterfaceVTable = {
371 	onRootMethodCallHandler,
372 	onRootGetPropertyHandler,
373 	NULL
374 };
375 
onPlayerMethodCallHandler(GDBusConnection * connection,const char * sender,const char * objectPath,const char * interfaceName,const char * methodName,GVariant * parameters,GDBusMethodInvocation * invocation,void * userData)376 static void onPlayerMethodCallHandler(GDBusConnection *connection, const char *sender, const char *objectPath,
377                                       const char *interfaceName, const char *methodName, GVariant *parameters,
378                                       GDBusMethodInvocation *invocation, void *userData) {
379 	debug("Method call on Player interface. sender: %s, methodName %s", sender, methodName);
380 	debug("Parameter signature is %s", g_variant_get_type_string (parameters));
381 	DB_functions_t *deadbeef = ((struct MprisData *)userData)->deadbeef;
382 
383 	if (strcmp(methodName, "Next") == 0) {
384 		g_dbus_method_invocation_return_value(invocation, NULL);
385 		deadbeef->sendmessage(DB_EV_NEXT, 0, 0, 0);
386 	} else if (strcmp(methodName, "Previous") == 0) {
387 		g_dbus_method_invocation_return_value(invocation, NULL);
388 		deadbeef->sendmessage(DB_EV_PREV, 0, 0, 0);
389 	} else if (strcmp(methodName, "Pause") == 0) {
390 		g_dbus_method_invocation_return_value(invocation, NULL);
391 		deadbeef->sendmessage(DB_EV_PAUSE, 0, 0, 0);
392 	} else if (strcmp(methodName, "PlayPause") == 0) {
393 		g_dbus_method_invocation_return_value(invocation, NULL);
394 
395 		if (deadbeef->get_output()->state() == OUTPUT_STATE_PLAYING) {
396 			deadbeef->sendmessage(DB_EV_PAUSE, 0, 0, 0);
397 		} else {
398 			deadbeef->sendmessage(DB_EV_PLAY_CURRENT, 0, 0, 0);
399 		}
400 
401 	} else if (strcmp(methodName, "Stop") == 0) {
402 		g_dbus_method_invocation_return_value(invocation, NULL);
403 		deadbeef->sendmessage(DB_EV_STOP, 0, 0, 0);
404 	} else if (strcmp(methodName, "Play") == 0) {
405 		if (deadbeef->get_output()->state() != OUTPUT_STATE_PLAYING)
406 			deadbeef->sendmessage(DB_EV_PLAY_CURRENT, 0, 0, 0);
407 		g_dbus_method_invocation_return_value(invocation, NULL);
408 	} else if (strcmp(methodName, "Seek") == 0) {
409 		DB_playItem_t *track = deadbeef->streamer_get_playing_track();
410 
411 		if (track != NULL) {
412 			float durationInMilliseconds = deadbeef->pl_get_item_duration(track) * 1000.0;
413 			float positionInMilliseconds= deadbeef->streamer_get_playpos() * 1000.0;
414 			int64_t offsetInMicroseconds;
415 			g_variant_get(parameters, "(x)", &offsetInMicroseconds);
416 			float offsetInMilliseconds = offsetInMicroseconds / 1000.0;
417 
418 			float newPositionInMilliseconds = positionInMilliseconds + offsetInMilliseconds;
419 			if (newPositionInMilliseconds < 0) {
420 				newPositionInMilliseconds = 0;
421 			}
422 			if (newPositionInMilliseconds > durationInMilliseconds) {
423 				deadbeef->sendmessage(DB_EV_NEXT, 0, 0, 0);
424 			} else {
425 				deadbeef->sendmessage(DB_EV_SEEK, 0, newPositionInMilliseconds, 0);
426 			}
427 
428 			deadbeef->pl_item_unref(track);
429 		}
430 		g_dbus_method_invocation_return_value(invocation, NULL);
431 	} else if (strcmp(methodName, "SetPosition") == 0) {
432 		int64_t position = 0;
433 		const char *trackId = NULL;
434 
435 		g_variant_get(parameters, "(&ox)", &trackId, &position);
436 		debug("Set %s position %d.", trackId, position);
437 
438 		DB_playItem_t *track = deadbeef->streamer_get_playing_track();
439 		if (track != NULL) {
440 			ddb_playlist_t *pl = deadbeef->plt_get_for_idx(deadbeef->streamer_get_current_playlist());
441 			int playid = deadbeef->plt_get_item_idx(pl, track, PL_MAIN);
442 			int playlistIndex = deadbeef->streamer_get_current_playlist();
443 			char buf[200];
444 			sprintf(buf, "/DeaDBeeF/%d/%d", playlistIndex, playid);
445 			if (strcmp(buf, trackId) == 0) {
446 				deadbeef->sendmessage(DB_EV_SEEK, 0, position / 1000.0, 0);
447 			}
448 			deadbeef->pl_item_unref(track);
449 			deadbeef->plt_unref(pl);
450 		}
451 		g_dbus_method_invocation_return_value(invocation, NULL);
452 	} else if (strcmp(methodName, "OpenUri") == 0) {
453 		const char *uri = NULL;
454 
455 		g_variant_get(parameters, "(&s)", &uri);
456 		debug("OpenUri: %s\n", uri);
457 		//TODO it is probably better to create a new playlist for that. Maybe we can even play it without playlist
458 		ddb_playlist_t *pl = deadbeef->plt_get_curr();
459 		int ret = deadbeef->plt_add_file2(0, pl, uri, NULL, NULL);
460 		if (ret == 0) {
461 			ddb_playlist_t *pl = deadbeef->plt_get_curr();
462 			DB_playItem_t *track = deadbeef->plt_get_last(pl, PL_MAIN);
463 			int track_id = deadbeef->plt_get_item_idx(pl, track, PL_MAIN);
464 			deadbeef->plt_unref(pl);
465 			deadbeef->pl_item_unref(track);
466 			deadbeef->sendmessage(DB_EV_PLAY_NUM, 0, track_id, 0);
467 		}
468 		g_dbus_method_invocation_return_value(invocation, NULL);
469 	} else {
470 		debug("Error! Unsupported method. %s.%s", interfaceName, methodName);
471 		g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED,
472                                               "Method %s.%s not supported", interfaceName, methodName);
473 	}
474 }
475 
onPlayerGetPropertyHandler(GDBusConnection * connection,const char * sender,const char * objectPath,const char * interfaceName,const char * propertyName,GError ** error,void * userData)476 static GVariant* onPlayerGetPropertyHandler(GDBusConnection *connection, const char *sender, const char *objectPath,
477                                             const char *interfaceName, const char *propertyName, GError **error,
478                                             void *userData) {
479 	debug("Get property call on Player interface. sender: %s, propertyName: %s", sender, propertyName);
480 	DB_functions_t *deadbeef = ((struct MprisData *)userData)->deadbeef;
481 	GVariant *result = NULL;
482 
483 	if (strcmp(propertyName, "PlaybackStatus") == 0) {
484 		DB_output_t *output = deadbeef->get_output();
485 
486 		if (output != NULL) {
487 			switch (output->state()) {
488 			case OUTPUT_STATE_PLAYING:
489 				result = g_variant_new_string("Playing");
490 				break;
491 			case OUTPUT_STATE_PAUSED:
492 				result = g_variant_new_string("Paused");
493 				break;
494 			case OUTPUT_STATE_STOPPED:
495 			default:
496 				result = g_variant_new_string("Stopped");
497 				break;
498 			}
499 		} else {
500 			result = g_variant_new_string("Stopped");
501 		}
502 	} else if (strcmp(propertyName, "LoopStatus") == 0) {
503 		int loop = deadbeef->conf_get_int("playback.loop", PLAYBACK_MODE_LOOP_ALL);
504 
505 		switch (loop) {
506 		case PLAYBACK_MODE_NOLOOP:
507 			result = g_variant_new_string("None");
508 			break;
509 		case PLAYBACK_MODE_LOOP_ALL:
510 			result = g_variant_new_string("Playlist");
511 			break;
512 		case PLAYBACK_MODE_LOOP_SINGLE:
513 			result = g_variant_new_string("Track");
514 			break;
515 		default:
516 			result = g_variant_new_string("None");
517 			break;
518 		}
519 	} else if (strcmp(propertyName, "Rate") == 0
520 			|| strcmp(propertyName, "MaximumRate") == 0
521 			|| strcmp(propertyName, "MinimumRate") == 0) {
522 		result = g_variant_new("d", 1.0);
523 	} else if (strcmp(propertyName, "Shuffle") == 0) {
524 		if (deadbeef->conf_get_int("playback.order", PLAYBACK_ORDER_LINEAR) == PLAYBACK_ORDER_LINEAR) {
525 			result = g_variant_new_boolean(FALSE);
526 		} else {
527 			result = g_variant_new_boolean(TRUE);
528 		}
529 	} else if (strcmp(propertyName, "Metadata") == 0) {
530 		result = getMetadataForTrack(CURRENT_TRACK, userData);
531 	} else if (strcmp(propertyName, "Volume") == 0) {
532 		float volume = (deadbeef->volume_get_db() * 0.02) + 1;
533 
534 		result = g_variant_new("d", volume);
535 	} else if (strcmp(propertyName, "Position") == 0) {
536 		DB_playItem_t *track = deadbeef->streamer_get_playing_track();
537 
538 		if (track == NULL) {
539 			result = g_variant_new("x", 0);
540 		} else {
541 			float positionInSeconds = deadbeef->streamer_get_playpos();
542 
543 			result = g_variant_new("x", (uint64_t)(positionInSeconds * 1000000.0));
544 			deadbeef->pl_item_unref(track);
545 		}
546 	} else if (strcmp(propertyName, "CanGoNext") == 0) {
547 		result = g_variant_new_boolean(deadbeef_hasselectedorplayingtrack(userData, 1));
548 	} else if (strcmp(propertyName, "CanGoPrevious") == 0) {
549 		result = g_variant_new_boolean(deadbeef_hasselectedorplayingtrack(userData, -1));
550 	} else if (strcmp(propertyName, "CanPlay") == 0) {
551 		result = g_variant_new_boolean(deadbeef_hasselectedorplayingtrack(userData, 0));
552 	} else if (strcmp(propertyName, "CanPause") == 0) {
553 		result = g_variant_new_boolean(TRUE);
554 	} else if (strcmp(propertyName, "CanSeek") == 0) {
555 		result = g_variant_new_boolean(deadbeef_can_seek(deadbeef));
556 	} else if (strcmp(propertyName, "CanControl") == 0) {
557 		result = g_variant_new_boolean(TRUE);
558 	}
559 	return result;
560 }
561 
onPlayerSetPropertyHandler(GDBusConnection * connection,const char * sender,const char * objectPath,const char * interfaceName,const char * propertyName,GVariant * value,GError ** error,gpointer userData)562 static int onPlayerSetPropertyHandler(GDBusConnection *connection, const char *sender, const char *objectPath,
563                                       const char *interfaceName, const char *propertyName, GVariant *value,
564                                       GError **error, gpointer userData) {
565 	debug("Set property call on Player interface. sender: %s, propertyName: %s", sender, propertyName);
566 	DB_functions_t *deadbeef = ((struct MprisData *)userData)->deadbeef;
567 
568 	if (strcmp(propertyName, "LoopStatus") == 0) {
569 		char *status;
570 		g_variant_get(value, "s", &status);
571 		if (status != NULL) {
572 			debug("status is %s", status);
573 			if (strcmp(status, "None") == 0) {
574 				deadbeef->conf_set_int("playback.loop", PLAYBACK_MODE_NOLOOP);
575 			} else if (strcmp(status, "Playlist") == 0) {
576 				deadbeef->conf_set_int("playback.loop", PLAYBACK_MODE_LOOP_ALL);
577 			} else if (strcmp(status, "Track") == 0) {
578 				deadbeef->conf_set_int("playback.loop", PLAYBACK_MODE_LOOP_SINGLE);
579 			}
580 
581 			deadbeef->sendmessage(DB_EV_CONFIGCHANGED, 0, 0, 0);
582 		}
583 	} else if (strcmp(propertyName, "Rate") == 0) {
584 		debug("Setting the rate is not supported");
585 	} else if (strcmp(propertyName, "Shuffle") == 0) {
586 		if (g_variant_get_boolean(value)) {
587 			deadbeef->conf_set_int("playback.order", PLAYBACK_ORDER_RANDOM);
588 		} else {
589 			deadbeef->conf_set_int("playback.order", PLAYBACK_ORDER_LINEAR);
590 		}
591 		deadbeef->sendmessage(DB_EV_CONFIGCHANGED, 0, 0, 0);
592 	} else if (strcmp(propertyName, "Volume") == 0) {
593 		double volume = g_variant_get_double(value);
594 		if (volume > 1.0) {
595 			volume = 1.0;
596 		} else if (volume < 0.0) {
597 			volume = 0.0;
598 		}
599 		float newVolume = ((float)volume * 50) - 50;
600 
601 		deadbeef->volume_set_db(newVolume);
602 	}
603 
604 	return TRUE;
605 }
606 
607 static const GDBusInterfaceVTable playerInterfaceVTable = {
608 	onPlayerMethodCallHandler,
609 	onPlayerGetPropertyHandler,
610 	onPlayerSetPropertyHandler
611 };
612 
613 //***********
614 //* SIGNALS *
615 //***********
emitVolumeChanged(float volume)616 void emitVolumeChanged(float volume) {
617 	GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
618 	volume = (volume * 0.02) + 1;
619 	debug("Volume property changed: %f", volume);
620 
621 	g_variant_builder_add(builder, "{sv}", "Volume", g_variant_new("d", volume));
622 	GVariant *signal[] = {
623 		g_variant_new_string(PLAYER_INTERFACE),
624 		g_variant_builder_end(builder),
625 		g_variant_new_strv(NULL, 0)
626 	};
627 
628 	g_dbus_connection_emit_signal(globalConnection, NULL, OBJECT_NAME, PROPERTIES_INTERFACE, "PropertiesChanged",
629                                   g_variant_new_tuple(signal, 3), NULL);
630 
631 	g_variant_builder_unref(builder);
632 }
633 
emitSeeked(float position)634 void emitSeeked(float position) {
635 	int64_t positionInMicroseconds = position * 1000000.0;
636 	debug("Seeked to %" PRId64, positionInMicroseconds);
637 
638 	g_dbus_connection_emit_signal(globalConnection, NULL, OBJECT_NAME, PLAYER_INTERFACE, "Seeked",
639                                   g_variant_new("(x)", positionInMicroseconds), NULL);
640 }
641 
emitMetadataChanged(int trackId,struct MprisData * userData)642 void emitMetadataChanged(int trackId, struct MprisData *userData) {
643 	GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
644 
645 	g_variant_builder_add(builder, "{sv}", "Metadata", getMetadataForTrack(trackId, userData));
646 
647 	GVariant *signal[] = {
648 			g_variant_new_string(PLAYER_INTERFACE),
649 			g_variant_builder_end(builder),
650 			g_variant_new_strv(NULL, 0)
651 	};
652 
653 	g_dbus_connection_emit_signal(globalConnection, NULL, OBJECT_NAME, PROPERTIES_INTERFACE, "PropertiesChanged",
654                                   g_variant_new_tuple(signal, 3), NULL);
655 
656 	g_variant_builder_unref(builder);
657 }
658 
emitCanGoChanged(struct MprisData * userData)659 void emitCanGoChanged(struct MprisData *userData) {
660 	GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
661 
662 	g_variant_builder_add(builder, "{sv}", "CanPlay", g_variant_new_boolean(deadbeef_hasselectedorplayingtrack(userData, 0)));
663 	g_variant_builder_add(builder, "{sv}", "CanGoNext", g_variant_new_boolean(deadbeef_hasselectedorplayingtrack(userData, 1)));
664 	g_variant_builder_add(builder, "{sv}", "CanGoPrevious", g_variant_new_boolean(deadbeef_hasselectedorplayingtrack(userData, -1)));
665 
666 	GVariant *signal[] = {
667 			g_variant_new_string(PLAYER_INTERFACE),
668 			g_variant_builder_end(builder),
669 			g_variant_new_strv(NULL, 0)
670 	};
671 
672 	g_dbus_connection_emit_signal(globalConnection, NULL, OBJECT_NAME, PROPERTIES_INTERFACE, "PropertiesChanged",
673                                   g_variant_new_tuple(signal, 3), NULL);
674 
675 	g_variant_builder_unref(builder);
676 }
677 
emitPlaybackStatusChanged(int status,struct MprisData * userData)678 void emitPlaybackStatusChanged(int status, struct MprisData *userData) {
679 	GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
680 	DB_functions_t *deadbeef = ((struct MprisData *)userData)->deadbeef;
681 
682 	switch (status) {
683 		case OUTPUT_STATE_PLAYING:
684 			g_variant_builder_add(builder, "{sv}", "PlaybackStatus", g_variant_new_string("Playing"));
685 			break;
686 		case OUTPUT_STATE_PAUSED:
687 			g_variant_builder_add(builder, "{sv}", "PlaybackStatus", g_variant_new_string("Paused"));
688 			break;
689 		case OUTPUT_STATE_STOPPED:
690 		default:
691 			g_variant_builder_add(builder, "{sv}", "PlaybackStatus", g_variant_new_string("Stopped"));
692 			break;
693 	}
694 
695 	g_variant_builder_add(builder, "{sv}", "CanSeek", g_variant_new_boolean(deadbeef_can_seek(deadbeef)));
696 
697 
698 	GVariant *signal[] = {
699 		g_variant_new_string(PLAYER_INTERFACE),
700 		g_variant_builder_end(builder),
701 		g_variant_new_strv(NULL, 0)
702 	};
703 
704 	g_dbus_connection_emit_signal(globalConnection, NULL, OBJECT_NAME, PROPERTIES_INTERFACE, "PropertiesChanged",
705                                   g_variant_new_tuple(signal, 3), NULL);
706 
707 	g_variant_builder_unref(builder);
708 }
709 
emitLoopStatusChanged(int status)710 void emitLoopStatusChanged(int status) {
711 	GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
712 
713 	switch (status) {
714 	case PLAYBACK_MODE_NOLOOP:
715 		g_variant_builder_add(builder, "{sv}", "LoopStatus", g_variant_new_string("None"));
716 		break;
717 	case PLAYBACK_MODE_LOOP_ALL:
718 		g_variant_builder_add(builder, "{sv}", "LoopStatus", g_variant_new_string("Playlist"));
719 		break;
720 	case PLAYBACK_MODE_LOOP_SINGLE:
721 		g_variant_builder_add(builder, "{sv}", "LoopStatus", g_variant_new_string("Track"));
722 		break;
723 	default:
724 		g_variant_builder_add(builder, "{sv}", "LoopStatus", g_variant_new_string("None"));
725 		break;
726 	}
727 	GVariant *signal[] = {
728 		g_variant_new_string(PLAYER_INTERFACE),
729 		g_variant_builder_end(builder),
730 		g_variant_new_strv(NULL, 0)
731 	};
732 
733 	g_dbus_connection_emit_signal(globalConnection, NULL, OBJECT_NAME, PROPERTIES_INTERFACE, "PropertiesChanged",
734                                   g_variant_new_tuple(signal, 3), NULL);
735 
736 	g_variant_builder_unref(builder);
737 }
738 
emitShuffleStatusChanged(int status)739 void emitShuffleStatusChanged(int status) {
740 	GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
741 
742 	g_variant_builder_add(builder, "{sv}", "Shuffle", g_variant_new_boolean(status != PLAYBACK_ORDER_LINEAR));
743 	GVariant *signal[] = {
744 		g_variant_new_string(PLAYER_INTERFACE),
745 		g_variant_builder_end(builder),
746 		g_variant_new_strv(NULL, 0)
747 	};
748 
749 	g_dbus_connection_emit_signal(globalConnection, NULL, OBJECT_NAME, PROPERTIES_INTERFACE, "PropertiesChanged",
750                                   g_variant_new_tuple(signal, 3), NULL);
751 
752 	g_variant_builder_unref(builder);
753 }
754 
onBusAcquiredHandler(GDBusConnection * connection,const char * name,void * userData)755 static void onBusAcquiredHandler(GDBusConnection *connection, const char *name, void *userData) {
756 	globalConnection = connection;
757 	debug("Bus accquired");
758 }
759 
onNameAcquiredHandler(GDBusConnection * connection,const char * name,void * userData)760 static void onNameAcquiredHandler(GDBusConnection *connection, const char *name, void *userData) {
761 	debug("name accquired: %s", name);
762 	GDBusInterfaceInfo **interfaces = ((struct MprisData*)userData)->gdbusNodeInfo->interfaces;
763 
764 	debug("Registering" OBJECT_NAME "object...");
765 	g_dbus_connection_register_object(connection, OBJECT_NAME, interfaces[0], &rootInterfaceVTable, userData, NULL,
766                                       NULL);
767 
768 	g_dbus_connection_register_object(connection, OBJECT_NAME, interfaces[1], &playerInterfaceVTable, userData, NULL,
769                                       NULL);
770 }
771 
onConnotConnectToBus(GDBusConnection * connection,const char * name,void * user_data)772 static void onConnotConnectToBus(GDBusConnection *connection, const char *name, void *user_data){
773 	error("cannot connect to bus");
774 }
775 
startServer(void * data)776 void* startServer(void *data) {
777 	int ownerId;
778 	GMainContext *context = g_main_context_new();
779 	struct MprisData *mprisData = data;
780 
781 
782 	g_main_context_push_thread_default(context);
783 
784 	mprisData->gdbusNodeInfo = g_dbus_node_info_new_for_xml(xmlForNode, NULL);
785 
786 	ownerId = g_bus_own_name(G_BUS_TYPE_SESSION, BUS_NAME, G_BUS_NAME_OWNER_FLAGS_REPLACE,
787                              onBusAcquiredHandler, onNameAcquiredHandler, onConnotConnectToBus,
788                              (void *)mprisData, NULL);
789 
790 	loop = g_main_loop_new(context, FALSE);
791 	g_main_loop_run(loop);
792 
793 	g_bus_unown_name(ownerId);
794 	g_dbus_node_info_unref(mprisData->gdbusNodeInfo);
795 	g_main_loop_unref(loop);
796 
797 	return 0;
798 }
799 
stopServer()800 void stopServer() {
801 	debug("Stopping...");
802 	g_main_loop_quit(loop);
803 }
804