~audio-recorder/audio-recorder/trunk

« back to all changes in this revision

Viewing changes to src/dbus-mpris2.c

  • Committer: Osmo Antero
  • Date: 2012-04-18 19:22:01 UTC
  • Revision ID: osmoma@gmail.com-20120418192201-ejjs6ikv7o4aznbi
New media-player interface that's based on the MediaPlayer2 standard. See src/dbus-mpris2.c.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (c) Linux community.
 
3
 *
 
4
 * This library is free software; you can redistribute it and/or
 
5
 * modify it under the terms of the GNU Library General Public
 
6
 * License as published by the Free Software Foundation; either
 
7
 * version 2 of the License, or (at your option) any later version.
 
8
 *
 
9
 * This library is distributed in the hope that it will be useful,
 
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
12
 * Library General Public License for more details.
 
13
 *
 
14
 * You should have received a copy of the GNU Library General Public
 
15
 * License along with this library; if not, write to the
 
16
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 
17
 * Boston, MA 02111-1307, USA.
 
18
 */
 
19
#include <string.h>
 
20
#include <glib.h>
 
21
#include <gdk/gdk.h>
 
22
#include <dbus/dbus.h>
 
23
#include "log.h"
 
24
#include "utility.h"
 
25
#include "dbus-player.h"
 
26
#include "dbus-mpris2.h"
 
27
 
 
28
// This is a MPRIS (org.mpris.MediaPlayer2) interface to various media-players.
 
29
// Please see: http://www.mpris.org/2.1/spec/
 
30
 
 
31
// Glib/DBus connection
 
32
static GDBusConnection *g_dbus_conn = NULL;
 
33
 
 
34
static GDBusConnection *mpris2_connect_to_dbus();
 
35
static void mpris2_disconnect_from_dbus();
 
36
 
 
37
static gboolean mpris2_check_proxy(MediaPlayerRec *player);
 
38
static GVariant *mpris2_get_property(MediaPlayerRec *player, gchar *prop_name);
 
39
 
 
40
void mpris2_module_init() {
 
41
    LOG_DEBUG("Init dbus_mpris2.c.\n");
 
42
 
 
43
    g_dbus_conn = NULL;
 
44
}
 
45
 
 
46
void mpris2_module_exit() {
 
47
    LOG_DEBUG("Clean up dbus_mpris2.c.\n");
 
48
 
 
49
    mpris2_disconnect_from_dbus();
 
50
}
 
51
 
 
52
GDBusConnection *mpris2_connect_to_dbus() {
 
53
    // Connect to glib/DBus
 
54
    GError *error = NULL;
 
55
 
 
56
    if (!G_IS_DBUS_CONNECTION(g_dbus_conn)) {
 
57
        g_dbus_conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
 
58
 
 
59
        if (!G_IS_DBUS_CONNECTION(g_dbus_conn)) {
 
60
            LOG_ERROR("mpris2_connect_to_dbus: Cannot connect to DBus: %s\n", 
 
61
                       error ? error->message : "");
 
62
 
 
63
            if (error)
 
64
                g_error_free(error);
 
65
 
 
66
            return NULL;
 
67
        }
 
68
    }
 
69
    return g_dbus_conn;
 
70
}
 
71
 
 
72
void mpris2_disconnect_from_dbus() {
 
73
    // Disconnect from glib/DBus
 
74
    if (G_IS_DBUS_CONNECTION(g_dbus_conn)) {
 
75
        g_object_unref(g_dbus_conn);
 
76
    }
 
77
    g_dbus_conn = NULL;
 
78
}
 
79
 
 
80
void mpris2_player_track_changed(gpointer player_rec) {
 
81
    // Sound/audio track changed. 
 
82
    MediaPlayerRec *player = (MediaPlayerRec*)player_rec;
 
83
    if (!player) return;
 
84
 
 
85
    // Stop current recording first. Send PLAYER_STATUS_STOPPED to the rec-manager.c
 
86
    // Notice: Some players send NEITHER "{'PlaybackStatus': 'Stopped'}" NOR  
 
87
    // "{'PlaybackStatus': 'Playing'}" signals before/after the track change. 
 
88
    // We have to stop/start recording ourselves.
 
89
    TrackInfo *tr = &player->track;
 
90
    tr->status = PLAYER_STATUS_STOPPED;
 
91
    dbus_player_process_data(player);
 
92
 
 
93
    // Re-read track-data. Status should be PLAYER_STATUS_PLAYING.
 
94
    mpris2_get_metadata(player_rec);
 
95
 
 
96
    // Debug:
 
97
    // dbus_player_debug_print(player);
 
98
    
 
99
    // Send data to the queue (rec-manager.c)
 
100
    dbus_player_process_data(player);
 
101
}
 
102
 
 
103
void mpris2_player_state_changed(gpointer player_rec) {
 
104
    // Player's state changed (Palying/Paused/Stopped).  
 
105
    MediaPlayerRec *player = (MediaPlayerRec*)player_rec;
 
106
    if (!player) return;
 
107
 
 
108
    // Re-read status data.
 
109
    mpris2_get_metadata(player_rec);
 
110
    
 
111
    // Debug:
 
112
    // dbus_player_debug_print(player);
 
113
 
 
114
    // Send data to the queue (rec-manager.c)
 
115
    dbus_player_process_data(player);
 
116
}
 
117
 
 
118
void debug_hash_table(MediaPlayerRec *player, GHashTable *table) {
 
119
    LOG_PLAYER("------------------------\n");
 
120
}
 
121
 
 
122
MediaPlayerRec *mpris2_player_new(const gchar *service_name) {
 
123
    // New MediaPlayerRec record
 
124
    MediaPlayerRec *player = g_malloc0(sizeof(MediaPlayerRec));
 
125
    player->service_name = g_strdup(service_name);
 
126
    player->app_name = NULL;
 
127
    return player;
 
128
}
 
129
 
 
130
static gboolean mpris2_check_proxy(MediaPlayerRec *player) {
 
131
    // Create and return proxy for player->service_name. 
 
132
    // Noice: This proxy points to the base interface "org.mpris.MediaPlayer2".  
 
133
    // Please see: http://www.mpris.org/2.1/spec/
 
134
 
 
135
    if (!player) return FALSE;
 
136
 
 
137
    // Already created?
 
138
    if (G_IS_DBUS_PROXY(player->proxy)) {
 
139
        return TRUE;
 
140
    }   
 
141
 
 
142
    GDBusConnection *dbus_conn = mpris2_connect_to_dbus();
 
143
 
 
144
    GError *error = NULL;
 
145
    player->proxy = g_dbus_proxy_new_sync(dbus_conn,
 
146
                                 G_DBUS_PROXY_FLAGS_NONE,
 
147
                                 NULL,
 
148
                                 player->service_name, /* service name */
 
149
                                 "/org/mpris/MediaPlayer2", /* object path */
 
150
                                 "org.mpris.MediaPlayer2", /* interface */
 
151
                                 NULL,
 
152
                                 &error);
 
153
 
 
154
    if (error) {
 
155
        g_printerr("Cannot create proxy for %s. %s.\n", player->service_name, error->message);
 
156
        g_error_free(error);
 
157
        player->proxy = NULL;
 
158
    }
 
159
 
 
160
    return (G_IS_DBUS_PROXY(player->proxy)); 
 
161
}
 
162
 
 
163
gchar *mpris2_get_property_str(MediaPlayerRec *player, gchar *prop_name) {
 
164
    // Return (string) value for the given property name.
 
165
    GVariant *result = mpris2_get_property(player, prop_name);
 
166
    if (!result) return NULL;
 
167
 
 
168
    gchar *str = NULL;
 
169
    g_variant_get(result, "s", &str);
 
170
 
 
171
    g_variant_unref(result);
 
172
 
 
173
    // Caller should g_free() this value
 
174
    return str; 
 
175
}
 
176
 
 
177
GVariant *mpris2_get_property(MediaPlayerRec *player, gchar *prop_name) {
 
178
    // Return (GVariant) value for the given property name.
 
179
 
 
180
    if (!mpris2_check_proxy(player)) return NULL;
 
181
 
 
182
    // List of valid properties: http://www.mpris.org/2.1/spec/
 
183
    GVariant *result = g_dbus_proxy_get_cached_property(player->proxy, prop_name);  
 
184
 
 
185
    // Caller should unref this value
 
186
    return result; 
 
187
}
 
188
 
 
189
static void mpris2_prop_signal(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, 
 
190
                                  GVariant *parameters, gpointer user_data) {
 
191
    // Handle "PropertiesChanged" signal.
 
192
 
 
193
    MediaPlayerRec *player = (MediaPlayerRec*)user_data;
 
194
    if (!player) return; 
 
195
    
 
196
    // Got "PropertiesChanged" signal?
 
197
    if (g_strcmp0(signal_name, "PropertiesChanged")) return;
 
198
 
 
199
    // Debug:
 
200
    gchar *str = g_variant_print(parameters, TRUE);
 
201
    LOG_PLAYER("Received %s signal from %s.\n", signal_name, player->service_name);
 
202
    LOG_PLAYER("Data is:%s\n\n", str); 
 
203
    g_free(str);
 
204
 
 
205
    // The data must have format "(sa{sv}as)"
 
206
    if (g_strcmp0(g_variant_get_type_string(parameters), "(sa{sv}as)")) {
 
207
        return;
 
208
    }
 
209
      
 
210
    GVariantIter *iter = NULL;
 
211
    GVariantIter *invalidated_iter = NULL;
 
212
    gchar *iface = NULL;
 
213
 
 
214
    // Ref: http://developer.gnome.org/gio/2.26/ch27s05.html
 
215
    g_variant_get(parameters, "(sa{sv}as)", &iface, &iter, &invalidated_iter);    
 
216
 
 
217
    // Debug:
 
218
    // LOG_DEBUG("The interface is %s.\n", iface);
 
219
 
 
220
    // Check if Metadata(sound track) or PlaybackStatus has changed.
 
221
    // We are NOT interested in the data itself, just which properties changed.
 
222
    gboolean track_changed = FALSE;
 
223
    gboolean player_state_changed = FALSE;
 
224
 
 
225
    gchar *key = NULL;
 
226
    GVariant *value = NULL; 
 
227
    while (g_variant_iter_next(iter, "{sv}", &key, &value)) {
 
228
 
 
229
        // Sound/audio track changed? 
 
230
        if (!g_ascii_strcasecmp(key, "Metadata")) {
 
231
            track_changed = TRUE;
 
232
 
 
233
        // Player's state changed; Playing/Stopped/Paused?
 
234
        } else if (!g_ascii_strcasecmp(key, "PlaybackStatus")) {
 
235
            player_state_changed = TRUE;
 
236
        }            
 
237
 
 
238
        g_free(key);
 
239
        g_variant_unref(value);
 
240
    }
 
241
 
 
242
    if (track_changed) {
 
243
        // Audio track changed. 
 
244
        // Stop current recording. Then re-read data and re-start recording from a new track.
 
245
        mpris2_player_track_changed(player);
 
246
 
 
247
    } else if (player_state_changed) {
 
248
        // Player's state changed. 
 
249
        // Send status change to the recorder.
 
250
        mpris2_player_state_changed(player);
 
251
    }
 
252
    
 
253
/* ****
 
254
Typical data for the PropertiesChanged signal: 
 
255
 
 
256
Signal PropertiesChanged: ('org.mpris.MediaPlayer2.Player', {'PlaybackStatus': <'Playing'>}, @as [])
 
257
 
 
258
Signal PropertiesChanged: ('org.mpris.MediaPlayer2.Player', {'Volume': <1.0>}, @as [])
 
259
 
 
260
Signal PropertiesChanged: ('org.mpris.MediaPlayer2.Player', {'CanGoNext': <true>, 'Metadata': <{'mpris:trackid': <'/org/mpris/MediaPlayer2/Track/9'>, 'xesam:url': <'file:///home/moma/Music/Bruce%20Springsteen%20-%20%20Wrecking%20Ball%20%5Bmp3-256-2012%5D/03%20-%20Shackled%20And%20Drawn.mp3'>, 'xesam:title': <'Shackled And Drawn'>, 'xesam:artist': <['Bruce Springsteen']>, 'xesam:album': <'Wrecking Ball'>, 'xesam:genre': <['Rock']>, 'xesam:albumArtist': <['Bruce Springsteen']>, 'xesam:audioBitrate': <262144>, 'xesam:contentCreated': <'2012-01-01T00:00:00Z'>, 'mpris:length': <int64 226000000>, 'xesam:trackNumber': <3>, 'xesam:discNumber': <1>, 'xesam:useCount': <0>, 'xesam:userRating': <0.0>, 'mpris:artUrl': <'file:///home/moma/.cache/rhythmbox/album-art/00000098'>}>, 'CanSeek': <true>, 'CanGoPrevious': <true>, 'PlaybackStatus': <'Playing'>}, @as [])
 
261
 
 
262
Signal PropertiesChanged: ('org.mpris.MediaPlayer2.Player', {'PlaybackStatus': <'Paused'>}, @as [])
 
263
**** */
 
264
 
 
265
}
 
266
 
 
267
void mpris2_prop_changed(GDBusProxy *proxy, GVariant *changed_properties,
 
268
                         GStrv invalidated_properties, gpointer user_data) {
 
269
    ;                         
 
270
    //Ref: http://developer.gnome.org/gio/unstable/GDBusProxy.html#GDBusProxy-g-properties-changd
 
271
 
 
272
}
 
273
                                                        
 
274
void mpris2_set_signals(gpointer player_rec, gboolean do_connect) {
 
275
    // Connect/disconnct signals for this player.
 
276
 
 
277
    MediaPlayerRec *player = (MediaPlayerRec*)player_rec; 
 
278
 
 
279
    // Connect event signals
 
280
    if (do_connect) {
 
281
 
 
282
        GDBusConnection *dbus_conn = mpris2_connect_to_dbus();
 
283
 
 
284
        GError *error = NULL;
 
285
        // This is a proxy for org.freedesktop.DBus.Properties
 
286
        player->prop_proxy = g_dbus_proxy_new_sync(dbus_conn,
 
287
                                     G_DBUS_PROXY_FLAGS_NONE,
 
288
                                     NULL,
 
289
                                     player->service_name,  /* name */
 
290
                                     "/org/mpris/MediaPlayer2",  /* object path */
 
291
                                     "org.freedesktop.DBus.Properties",  /* interface */
 
292
                                     NULL,
 
293
                                     &error);
 
294
 
 
295
        if (error) {
 
296
            g_printerr("Cannot create proxy for org.freedesktop.DBus.Properties. %s.\n", error->message);
 
297
            g_error_free(error);
 
298
            player->prop_proxy = NULL;
 
299
            return;
 
300
        }
 
301
 
 
302
        // Ref: http://developer.gnome.org/gio/2.28/GDBusProxy.html
 
303
        g_signal_connect(player->prop_proxy, "g-properties-changed", 
 
304
                         G_CALLBACK(mpris2_prop_changed), (gpointer)player/*user data*/);
 
305
 
 
306
        g_signal_connect(player->prop_proxy, "g-signal", 
 
307
                         G_CALLBACK(mpris2_prop_signal), (gpointer)player/*user data*/);
 
308
    
 
309
    // Disconnect signals
 
310
    } else {
 
311
 
 
312
        // Delete proxy    
 
313
        if (G_IS_DBUS_PROXY(player->prop_proxy)) {
 
314
            g_object_unref(player->prop_proxy);    
 
315
        }   
 
316
        player->prop_proxy = NULL;
 
317
    }
 
318
}    
 
319
 
 
320
gboolean mpris2_service_is_running(gpointer player_rec) {
 
321
    // Check if the application is running.
 
322
    MediaPlayerRec *player = (MediaPlayerRec*)player_rec;
 
323
    if (!player_rec) return FALSE;
 
324
    
 
325
    return mpris2_service_is_running_by_name(player->service_name);
 
326
}
 
327
 
 
328
gboolean mpris2_service_is_running_by_name(const char *service_name) {
 
329
    // Check if the service_name/application is running.
 
330
 
 
331
    // Connect to glib/DBus
 
332
    GDBusConnection *dbus_conn = mpris2_connect_to_dbus();
 
333
    if (!dbus_conn) return FALSE;
 
334
 
 
335
    // Create proxy for DBUS_SERVICE_DBUS
 
336
    GError *error = NULL;
 
337
    GDBusProxy *proxy = g_dbus_proxy_new_sync(dbus_conn,
 
338
                        G_DBUS_PROXY_FLAGS_NONE,
 
339
                        NULL,
 
340
                        DBUS_SERVICE_DBUS,
 
341
                        DBUS_PATH_DBUS,
 
342
                        DBUS_INTERFACE_DBUS,
 
343
                        NULL,
 
344
                        &error);
 
345
 
 
346
    if (error) {
 
347
        g_printerr("Cannot create proxy for %s. %s.\n", DBUS_INTERFACE_DBUS, error->message);  
 
348
        g_error_free(error);
 
349
        return FALSE;
 
350
    }
 
351
 
 
352
    // Call "NameHasOwner" method
 
353
    error = NULL;
 
354
    GVariant *result = g_dbus_proxy_call_sync(proxy, "NameHasOwner", 
 
355
                       g_variant_new("(s)", service_name),
 
356
                       G_DBUS_CALL_FLAGS_NONE,
 
357
                       -1,
 
358
                       NULL,
 
359
                       &error);
 
360
                       
 
361
    if (error) {
 
362
        g_printerr("Cannot get NameHasOwner for %s. %s\n", service_name, error->message);
 
363
        g_error_free(error);
 
364
        g_object_unref(proxy);
 
365
        return FALSE;
 
366
    }    
 
367
 
 
368
    // The result has format "(boolean,)"
 
369
 
 
370
    // Test:
 
371
    // gchar *str = g_variant_print(result, TRUE);
 
372
    // g_print("Received data for HasName:<%s>\n", str);
 
373
    // g_free(str);
 
374
 
 
375
    // Take the boolean value
 
376
    gboolean running = FALSE;
 
377
    g_variant_get_child(result, 0, "b", &running);
 
378
 
 
379
    g_variant_unref(result);
 
380
    g_object_unref(proxy);
 
381
 
 
382
    // Return TRUE/FALSE
 
383
    return running;
 
384
}
 
385
 
 
386
gchar *get_string_val(GVariant *v) {
 
387
    // Read and return string 
 
388
    gchar *s = NULL;
 
389
 
 
390
    // Is of string type?
 
391
    if (g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) {
 
392
        g_variant_get(v, "s", &s);
 
393
    }
 
394
    return s;
 
395
}
 
396
 
 
397
void mpris2_get_metadata(gpointer player_rec) {
 
398
    // Get track information (=metadata) and state for the given media player.
 
399
    // Ref: http://www.mpris.org/2.1/spec/Player_Node.html#Property:Metadata
 
400
 
 
401
    MediaPlayerRec *player = (MediaPlayerRec*)player_rec;
 
402
    if (!player) return;
 
403
 
 
404
    // Reset track info
 
405
    TrackInfo *tr = &player->track;
 
406
    tr->status = PLAYER_STATUS_STOPPED;
 
407
    tr->flags = 0;
 
408
    tr->track[0] = tr->artist[0] = tr->album[0] = '\0';
 
409
    
 
410
    // Connect to glib/DBus
 
411
    GDBusConnection *dbus_conn = mpris2_connect_to_dbus();
 
412
    if (!dbus_conn) return;
 
413
 
 
414
    // Create proxy for the Player object, "org.mpris.MediaPlayer2.Player"
 
415
    // Ref: http://www.mpris.org/2.1/spec/
 
416
    GError *error = NULL;
 
417
    GDBusProxy *proxy = g_dbus_proxy_new_sync (dbus_conn,
 
418
                                 G_DBUS_PROXY_FLAGS_NONE,
 
419
                                 NULL,
 
420
                                 player->service_name,  /* name */
 
421
                                 "/org/mpris/MediaPlayer2",  /* object path */
 
422
                                 "org.mpris.MediaPlayer2.Player",  /* .Player interface */
 
423
                                 NULL,
 
424
                                 &error);
 
425
    if (error) {
 
426
        g_printerr("Cannot create proxy for %s. %s.\n", player->service_name, error->message);
 
427
        g_error_free(error);
 
428
        return;
 
429
    }
 
430
 
 
431
    // Read playback status
 
432
    // Ref: http://www.mpris.org/2.1/spec/Player_Node.html
 
433
    GVariant *ret = g_dbus_proxy_get_cached_property(proxy, "PlaybackStatus");  
 
434
    if (!ret) {
 
435
        // Cannot contact player (it has quit)?
 
436
        tr->status = PLAYER_STATUS_CLOSED;
 
437
        g_object_unref(proxy);
 
438
        return;
 
439
    }
 
440
 
 
441
    // ret != NULL.
 
442
    gchar *s = NULL;
 
443
    g_variant_get(ret, "s", &s);
 
444
 
 
445
    // Set tr->status
 
446
    if (!g_ascii_strcasecmp(s, "Playing"))
 
447
        tr->status = PLAYER_STATUS_PLAYING;
 
448
 
 
449
    else if (!g_ascii_strcasecmp(s, "Paused"))
 
450
        tr->status = PLAYER_STATUS_PAUSED;
 
451
 
 
452
    else if (!g_ascii_strcasecmp(s, "Stopped"))
 
453
        tr->status = PLAYER_STATUS_STOPPED;
 
454
 
 
455
    g_variant_unref(ret);
 
456
    g_free(s);
 
457
 
 
458
    // Should we continue?
 
459
    if (tr->status != PLAYER_STATUS_PLAYING) {
 
460
        g_object_unref(proxy); 
 
461
        return;
 
462
    }
 
463
 
 
464
    // The state is PLAYER_STATUS_PLAYING.
 
465
    // Read track information (Metadata). The dict has type "a{sv}"  
 
466
    GVariant *dict = g_dbus_proxy_get_cached_property(proxy, "Metadata");  
 
467
 
 
468
    /* Some key names for the returned "a{sv}" dictionary:
 
469
      'mpris:trackid' has type 's'
 
470
      'xesam:url' has type 's'
 
471
      'xesam:title' has type 's'
 
472
      'xesam:artist' has type 'as'
 
473
      'xesam:genre' has type 'as'
 
474
      'rhythmbox:streamTitle' has type 's'
 
475
      'xesam:audioBitrate' has type 'i'
 
476
      'mpris:length' has type 'x'
 
477
      'xesam:trackNumber' has type 'i'
 
478
      'xesam:useCount' has type 'i'
 
479
      'xesam:userRating' has type 'd'
 
480
 
 
481
     Example data from Rhythmbox:
 
482
     ('org.mpris.MediaPlayer2.Player', 
 
483
       {'CanSeek': <true>, 
 
484
       'Metadata': <{
 
485
       'mpris:trackid': <'/org/mpris/MediaPlayer2/Track/2'>, 
 
486
       'xesam:url': <'file:///home/moma/Music/Bruce Springsteen.mp3'>, 
 
487
       'xesam:title': <'Land Of Hope'>, 
 
488
       'xesam:artist': <['Bruce Springsteen']>,
 
489
       'xesam:album': <'Wrecking Ball'>, 
 
490
       'xesam:genre': <['Rock']>, 
 
491
       'xesam:albumArtist': <['Bruce Springsteen']>,
 
492
       'xesam:audioBitrate': <262144>, 
 
493
       'xesam:contentCreated': <'2012-01-01T00:00:00Z'>, 
 
494
       'mpris:length': <int64 418000000>, 
 
495
       'xesam:trackNumber': <10>, 
 
496
       'xesam:discNumber': <1>, 
 
497
       'xesam:useCount': <0>, 
 
498
       'xesam:userRating': <0.0>, 
 
499
       'mpris:artUrl': <'file:///home/moma/.cache/rhythmbox/album-art/00000098'>}>, 
 
500
       'Volume': <1.0>, 
 
501
       'PlaybackStatus': <'Playing'>}, @as [])"
 
502
    */
 
503
    
 
504
    GVariantIter iter;
 
505
    GVariant *value = NULL;
 
506
    gchar *key = NULL;
 
507
 
 
508
    // Ref: http://developer.gnome.org/glib/2.30/glib-GVariant.html#g-variant-get-va
 
509
    g_variant_iter_init(&iter, dict);
 
510
    while (g_variant_iter_next (&iter, "{sv}", &key, &value)) {
 
511
 
 
512
        gchar *str = NULL;
 
513
        GVariantIter *iter = NULL;
 
514
 
 
515
        // xesam:title?
 
516
        if (g_str_has_suffix(key, ":title")) {  
 
517
            str = get_string_val(value);
 
518
            str_copy(tr->track, str, MPRIS_STRLEN-1);
 
519
        }
 
520
 
 
521
        // xesam::artist? Notice: This has data type "as".
 
522
        else if (g_str_has_suffix(key, ":artist")) {  
 
523
            // Take 1.st string of the array
 
524
            g_variant_get(value, "as", &iter);
 
525
            
 
526
            if (iter) {
 
527
                 g_variant_iter_next(iter, "s", &str);
 
528
            }      
 
529
            str_copy(tr->artist, str, MPRIS_STRLEN-1);
 
530
        }
 
531
 
 
532
        // xesam:albumArtist? Notice: This has data type "as".
 
533
        else if (g_str_has_suffix(key, ":albumArtist")) {  
 
534
            // Already set by ":artist"?
 
535
            if (tr->artist[0] == '\0') {
 
536
                // Take 1.st string of the array
 
537
                g_variant_get(value, "as", &iter);
 
538
 
 
539
                if (iter) {
 
540
                     g_variant_iter_next(iter, "s", &str);
 
541
                }      
 
542
                str_copy(tr->artist, str, MPRIS_STRLEN-1);
 
543
            }       
 
544
        }
 
545
 
 
546
        // xesam:album?
 
547
        else if (g_str_has_suffix(key, ":album")) {  
 
548
            str = get_string_val(value);
 
549
            str_copy(tr->album, str, MPRIS_STRLEN-1);
 
550
        }
 
551
 
 
552
        if (iter)
 
553
            g_variant_iter_free(iter);
 
554
 
 
555
        g_free(str);
 
556
 
 
557
        g_variant_unref(value);
 
558
        g_free(key);
 
559
    }
 
560
 
 
561
    g_variant_unref(dict);
 
562
    g_object_unref(proxy); 
 
563
}
 
564
 
 
565
void mpris2_start_app(gpointer player_rec) {
 
566
    // Start player application
 
567
 
 
568
    MediaPlayerRec *player = (MediaPlayerRec*)player_rec;
 
569
    if (!player_rec) return;
 
570
    
 
571
    // Already running?
 
572
    if (mpris2_service_is_running(player_rec)) {
 
573
        return;
 
574
    }
 
575
    
 
576
    if (!player->exec_name) {
 
577
        g_printerr("Executable name for %s is not set. Start the application manually.\n", 
 
578
                   player->app_name); 
 
579
        return;
 
580
    }
 
581
    
 
582
    // Find path for exec_name
 
583
    gchar *path = find_command_path(player->exec_name);
 
584
    if (!path) {
 
585
        g_printerr("Cannot run %s. Start the application %s manually.\n", 
 
586
                   player->exec_name, player->app_name); 
 
587
        return;
 
588
    }
 
589
    
 
590
    // Run the application.
 
591
 
 
592
    // Build argv[] list.
 
593
    gchar **argv = g_new(gchar*, 2);
 
594
    argv[0] = g_strdup(path);
 
595
    argv[1] = NULL;
 
596
 
 
597
    // Run the command. It will return immediately because it's asynchronous.
 
598
    GError *error = NULL;
 
599
    GPid __attribute__((unused)) pid = exec_command_async(argv, &error);
 
600
 
 
601
    // Free the values
 
602
    if (error)
 
603
        g_error_free(error);
 
604
 
 
605
    g_strfreev(argv);
 
606
    g_free(path);
 
607
}
 
608
 
 
609
void mpris2_detect_players() {
 
610
    // Get list of MPRIS2 (org.mpris.MediaPlayer2.*) compliant programs.
 
611
 
 
612
    // Connect to glib/DBus
 
613
    GDBusConnection *dbus_conn = mpris2_connect_to_dbus();
 
614
    if (!dbus_conn) return;
 
615
 
 
616
    // Get player list
 
617
    GHashTable *player_list = dbus_player_get_list_ref();
 
618
 
 
619
    #define DBUS_MPRIS2_NAMESPACE "org.mpris.MediaPlayer2."
 
620
 
 
621
    GError *error = NULL;
 
622
 
 
623
    // Ref: http://dbus.freedesktop.org/doc/api/html/group__DBusShared.html
 
624
    // Create proxy for org.freedesktop.DBus.
 
625
    // And execute "ListNames" 
 
626
    GVariant *result = g_dbus_connection_call_sync (dbus_conn,
 
627
                                        "org.freedesktop.DBus", /* name */
 
628
                                        "/org/freedesktop/DBus", /* path */
 
629
                                        "org.freedesktop.DBus", /* interface */
 
630
                                        "ListNames",
 
631
                                        NULL,
 
632
                                        NULL,
 
633
                                        G_DBUS_CALL_FLAGS_NONE,
 
634
                                        -1,
 
635
                                        NULL,
 
636
                                        &error);
 
637
 
 
638
#if 0
 
639
    // This does the same thing.
 
640
    GDBusProxy *proxy = g_dbus_proxy_new_sync(dbus_conn,
 
641
                        G_DBUS_PROXY_FLAGS_NONE,
 
642
                        NULL,
 
643
                        DBUS_SERVICE_DBUS,
 
644
                        DBUS_PATH_DBUS,
 
645
                        DBUS_INTERFACE_DBUS,
 
646
                        NULL,
 
647
                        &error);
 
648
 
 
649
    if (error) {
 
650
       g_print("Cannot create proxy for ListNames. %s\n", error->message);
 
651
       g_error_free(error);
 
652
       return;
 
653
    }    
 
654
 
 
655
    // Call ListNames method, wait for reply
 
656
    error = NULL;
 
657
    GVariant *result = g_dbus_proxy_call_sync(proxy, "ListNames", 
 
658
                      NULL,
 
659
                      G_DBUS_CALL_FLAGS_NONE,
 
660
                      -1,
 
661
                      NULL,
 
662
                      &error);
 
663
 
 
664
    // Unref the proxy
 
665
    g_object_unref(proxy);
 
666
 
 
667
#endif
 
668
 
 
669
    if (error) {
 
670
        g_printerr("Cannot read service names from org.freedesktop.DBus. %s\n", error->message);
 
671
        g_error_free(error);
 
672
        return;
 
673
    }    
 
674
 
 
675
    // The result is an array of strings, "(as)".
 
676
 
 
677
    GVariantIter *iter = NULL;
 
678
    gchar *service_name = NULL;
 
679
 
 
680
    // Get iter to the string array
 
681
    g_variant_get(result, "(as)", &iter);
 
682
 
 
683
    // Iter over all service_names
 
684
    service_name = NULL;
 
685
    while (g_variant_iter_next(iter, "s", &service_name)) {
 
686
 
 
687
        // Drop names that begin with ':'
 
688
        if (service_name && *service_name == ':') {
 
689
            g_free(service_name);    
 
690
            continue;
 
691
        }
 
692
 
 
693
        MediaPlayerRec *player = NULL;
 
694
 
 
695
        // Belongs to org.mpris.MediaPlayer2.* namespace?   
 
696
        if (!strncmp(DBUS_MPRIS2_NAMESPACE, service_name, strlen(DBUS_MPRIS2_NAMESPACE))) {
 
697
 
 
698
            LOG_DEBUG("Detected service name %s.\n", service_name);
 
699
 
 
700
            // New MediaPlayer record
 
701
            player = mpris2_player_new(service_name);
 
702
 
 
703
            // Set application name
 
704
            // Ref: http://specifications.freedesktop.org/mpris-spec/latest/Root_Node.html
 
705
            player->app_name = mpris2_get_property_str(player, "Identity");
 
706
 
 
707
            // Set executable name (from it's .desktop file)
 
708
            player->exec_name = mpris2_get_property_str(player, "DesktopEntry");
 
709
 
 
710
            // Function to connect/disconnect event signals
 
711
            player->func_set_signals = mpris2_set_signals;
 
712
 
 
713
            // Function to check if this player is running
 
714
            player->func_check_is_running = mpris2_service_is_running;
 
715
 
 
716
            // Function to get track-info (track/song name/title/album/artist, length, etc.)
 
717
            player->func_get_info = mpris2_get_metadata;
 
718
 
 
719
            // Function to start/run the application
 
720
            player->func_start_app = mpris2_start_app;
 
721
 
 
722
            // Set icon name (this is not a perfect solution!).
 
723
            // Find last "." in the service_name.
 
724
            gchar *pos = g_strrstr(player->service_name, "."); 
 
725
        
 
726
            if (pos) {
 
727
                player->icon_name = g_strdup_printf("%s.png", pos+1);
 
728
            } else {
 
729
                player->icon_name = g_strdup("mediaplayer.png");
 
730
            }
 
731
            
 
732
        }
 
733
 
 
734
        // Has a player record?
 
735
        if (player && player->app_name) {
 
736
 
 
737
            // Add player to the g_player_list.
 
738
 
 
739
            // Lookup the record by its app_name (like: "Amarok 2.3.2")
 
740
            if (!dbus_player_lookup_app_name(player->app_name)) {
 
741
                // Add it to the list
 
742
                g_hash_table_insert(player_list, g_strdup(player->service_name), player);
 
743
            } else {
 
744
                // Duplicate or bad record. Free it.
 
745
                dbus_player_delete_item(player);
 
746
            }
 
747
 
 
748
        }
 
749
 
 
750
        g_free(service_name);
 
751
    }
 
752
        
 
753
    g_variant_iter_free(iter);
 
754
    g_variant_unref(result);
 
755
 
 
756
}
 
757
 
 
758