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