/* * Copyright (c) Linux community. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include "log.h" #include "utility.h" #include "dbus-player.h" #include "dbus-mpris2.h" // This is a MPRIS2 (org.mpris.MediaPlayer2) compliant media-player interface. // Client side implementation. // Please see: http://www.mpris.org/2.1/spec/ // Glib/DBus connection static GDBusConnection *g_dbus_conn = NULL; static GDBusConnection *mpris2_connect_to_dbus(); static void mpris2_disconnect_from_dbus(); static gboolean mpris2_check_proxy(MediaPlayerRec *player); static GVariant *mpris2_get_property(MediaPlayerRec *player, gchar *prop_name); void mpris2_module_init() { LOG_DEBUG("Init dbus_mpris2.c.\n"); g_dbus_conn = NULL; } void mpris2_module_exit() { LOG_DEBUG("Clean up dbus_mpris2.c.\n"); mpris2_disconnect_from_dbus(); } GDBusConnection *mpris2_connect_to_dbus() { // Connect to glib/DBus GError *error = NULL; if (!G_IS_DBUS_CONNECTION(g_dbus_conn)) { g_dbus_conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); if (!G_IS_DBUS_CONNECTION(g_dbus_conn)) { LOG_ERROR("mpris2_connect_to_dbus: Cannot connect to DBus: %s\n", error ? error->message : ""); if (error) g_error_free(error); return NULL; } } return g_dbus_conn; } void mpris2_disconnect_from_dbus() { // Disconnect from glib/DBus if (G_IS_DBUS_CONNECTION(g_dbus_conn)) { g_object_unref(g_dbus_conn); } g_dbus_conn = NULL; } static void mpris2_player_track_changed(gpointer player_rec) { // Sound/audio track changed. MediaPlayerRec *player = (MediaPlayerRec*)player_rec; if (!player) return; // Stop current recording first. Send PLAYER_STATUS_STOPPED to the rec-manager.c // Notice: Some players send NEITHER "{'PlaybackStatus': 'Stopped'}" NOR // "{'PlaybackStatus': 'Playing'}" signals before/after the track change. // We have to stop/start recording ourselves. TrackInfo *tr = &player->track; tr->status = PLAYER_STATUS_STOPPED; dbus_player_process_data(player); // Re-read track-data. Status should be PLAYER_STATUS_PLAYING. mpris2_get_metadata(player_rec); // Debug: // dbus_player_debug_print(player); // Send data to the queue (rec-manager.c) dbus_player_process_data(player); } static void mpris2_player_state_changed(gpointer player_rec) { // Player's state changed (Palying/Paused/Stopped). MediaPlayerRec *player = (MediaPlayerRec*)player_rec; if (!player) return; // Re-read status data. mpris2_get_metadata(player_rec); // Debug: // dbus_player_debug_print(player); // Send data to the queue (rec-manager.c) dbus_player_process_data(player); } void debug_hash_table(MediaPlayerRec *player, GHashTable *table) { LOG_PLAYER("------------------------\n"); } MediaPlayerRec *mpris2_player_new(const gchar *service_name) { // New MediaPlayerRec record MediaPlayerRec *player = g_malloc0(sizeof(MediaPlayerRec)); player->service_name = g_strdup(service_name); player->app_name = NULL; return player; } static gboolean mpris2_check_proxy(MediaPlayerRec *player) { // Create and return proxy for player->service_name so we can read it's Player properties. // Please see: http://www.mpris.org/2.1/spec/ if (!player) return FALSE; // Already created? if (G_IS_DBUS_PROXY(player->proxy)) { return TRUE; } GDBusConnection *dbus_conn = mpris2_connect_to_dbus(); // Proxy that points to "org.mpris.MediaPlayer2.Player" object. GError *error = NULL; player->proxy = g_dbus_proxy_new_sync(dbus_conn, G_DBUS_PROXY_FLAGS_NONE, NULL, player->service_name, /* name */ "/org/mpris/MediaPlayer2", /* object path */ "org.mpris.MediaPlayer2.Player", /* interface */ NULL, &error); if (error) { g_printerr("Cannot create proxy for %s. %s.\n", player->service_name, error->message); g_error_free(error); player->proxy = NULL; } return (G_IS_DBUS_PROXY(player->proxy)); } gchar *mpris2_get_property_str(MediaPlayerRec *player, gchar *prop_name) { // Read a string property from the player's "org.mpris.MediaPlayer2" interface. GVariant *result = mpris2_get_property(player, prop_name); gchar *str = NULL; if (!result) { return NULL; } if (g_variant_is_of_type(result, G_VARIANT_TYPE_STRING)) { g_variant_get(result, "s", &str); } g_variant_unref(result); // Return a string. // Caller should g_free() this value. return str; } static GVariant *mpris2_get_property(MediaPlayerRec *player, gchar *prop_name) { // Read a property from the player's "org.mpris.MediaPlayer2" interface. if (!player) return FALSE; GDBusConnection *dbus_conn = mpris2_connect_to_dbus(); // Proxy that points to "org.mpris.MediaPlayer2" object. GError *error = NULL; GDBusProxy *proxy = g_dbus_proxy_new_sync(dbus_conn, G_DBUS_PROXY_FLAGS_NONE, NULL, player->service_name, /* service name */ "/org/mpris/MediaPlayer2", /* object path */ "org.mpris.MediaPlayer2", /* base interface */ NULL, &error); if (error) { g_printerr("Cannot create proxy for %s. %s.\n", player->service_name, error->message); g_error_free(error); return NULL; } // List of valid properties: // http://www.mpris.org/2.1/spec/ // Read property GVariant *result = g_dbus_proxy_get_cached_property(proxy, prop_name); // Unref proxy g_object_unref(proxy); // Caller should unref this value. return result; } static void mpris2_prop_signal(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data) { // Handle "PropertiesChanged" signal. MediaPlayerRec *player = (MediaPlayerRec*)user_data; if (!player) return; // Got "PropertiesChanged" signal? if (g_strcmp0(signal_name, "PropertiesChanged")) return; // Debug: #if defined(DEBUG_PLAYER) || defined(DEBUG_ALL) gchar *str = g_variant_print(parameters, TRUE); LOG_PLAYER("Received %s signal from %s.\n", signal_name, player->service_name); LOG_PLAYER("Data is:%s\n\n", str); g_free(str); #endif // The data must have format "(sa{sv}as)" if (g_strcmp0(g_variant_get_type_string(parameters), "(sa{sv}as)")) { return; } GVariantIter *iter = NULL; GVariantIter *invalidated_iter = NULL; gchar *iface = NULL; // Ref: http://developer.gnome.org/gio/2.26/ch27s05.html g_variant_get(parameters, "(sa{sv}as)", &iface, &iter, &invalidated_iter); // Debug: // LOG_PLAYER("The interface is %s.\n", iface); // Check if Metadata(sound track) or PlaybackStatus has changed. // We are NOT interested in the data itself, just which properties changed. gboolean track_changed = FALSE; gboolean player_state_changed = FALSE; gchar *key = NULL; GVariant *value = NULL; while (g_variant_iter_next(iter, "{sv}", &key, &value)) { // Sound/audio track changed? if (!g_ascii_strcasecmp(key, "Metadata")) { track_changed = TRUE; // Player's state changed; Playing/Stopped/Paused? } else if (!g_ascii_strcasecmp(key, "PlaybackStatus")) { player_state_changed = TRUE; } g_free(key); g_variant_unref(value); } if (track_changed) { // Track changed. // Stop current recording. Then re-read data and re-start recording from a new track. mpris2_player_track_changed(player); } else if (player_state_changed) { // Player's state changed. // Send status change to the recorder. mpris2_player_state_changed(player); } /* *** Sample data for the PropertiesChanged signal: Signal PropertiesChanged: ('org.mpris.MediaPlayer2.Player', {'PlaybackStatus': <'Playing'>}, @as []) Signal PropertiesChanged: ('org.mpris.MediaPlayer2.Player', {'Volume': <1.0>}, @as []) Signal PropertiesChanged: ('org.mpris.MediaPlayer2.Player', {'CanGoNext': , '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': , 'xesam:trackNumber': <3>, 'xesam:discNumber': <1>, 'xesam:useCount': <0>, 'xesam:userRating': <0.0>, 'mpris:artUrl': < 'file:///home/moma/.cache/rhythmbox/album-art/00000098'>}>, 'CanSeek': , 'CanGoPrevious': , 'PlaybackStatus': <'Playing'>}, @as []) Signal PropertiesChanged: ('org.mpris.MediaPlayer2.Player', {'PlaybackStatus': <'Paused'>}, @as []) *** */ } static void mpris2_prop_changed(GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, gpointer user_data) { ; //Ref: http://developer.gnome.org/gio/unstable/GDBusProxy.html#GDBusProxy-g-properties-changd } void mpris2_set_signals(gpointer player_rec, gboolean do_connect) { // Connect/disconnct signals for this player. MediaPlayerRec *player = (MediaPlayerRec*)player_rec; // Connect event signals if (do_connect) { GDBusConnection *dbus_conn = mpris2_connect_to_dbus(); GError *error = NULL; // Proxy for org.freedesktop.DBus.Properties player->prop_proxy = g_dbus_proxy_new_sync(dbus_conn, G_DBUS_PROXY_FLAGS_NONE, NULL, player->service_name, /* name */ "/org/mpris/MediaPlayer2", /* object path */ "org.freedesktop.DBus.Properties", /* interface */ NULL, &error); if (error) { g_printerr("Cannot create proxy for org.freedesktop.DBus.Properties. %s.\n", error->message); g_error_free(error); player->prop_proxy = NULL; return; } // Ref: http://developer.gnome.org/gio/2.28/GDBusProxy.html g_signal_connect(player->prop_proxy, "g-properties-changed", G_CALLBACK(mpris2_prop_changed), (gpointer)player/*user data*/); g_signal_connect(player->prop_proxy, "g-signal", G_CALLBACK(mpris2_prop_signal), (gpointer)player/*user data*/); // Disconnect signals } else { // Delete player->prop_proxy. This should unset the above signals. if (G_IS_DBUS_PROXY(player->prop_proxy)) { g_object_unref(player->prop_proxy); } player->prop_proxy = NULL; // Delete also player->proxy if (G_IS_DBUS_PROXY(player->proxy)) { g_object_unref(player->proxy); } player->proxy = NULL; } } gboolean mpris2_service_is_running(gpointer player_rec) { // Check if the application is running. MediaPlayerRec *player = (MediaPlayerRec*)player_rec; if (!player_rec) return FALSE; return mpris2_service_is_running_by_name(player->service_name); } gboolean mpris2_service_is_running_by_name(const char *service_name) { // Check if the service_name/application is running. // Return TRUE/FALSE. // Connect to glib/DBus GDBusConnection *dbus_conn = mpris2_connect_to_dbus(); if (!dbus_conn) return FALSE; // Proxy for DBUS_SERVICE_DBUS GError *error = NULL; GDBusProxy *proxy = g_dbus_proxy_new_sync(dbus_conn, G_DBUS_PROXY_FLAGS_NONE, NULL, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, NULL, &error); if (error) { g_printerr("Cannot create proxy for %s. %s.\n", DBUS_INTERFACE_DBUS, error->message); g_error_free(error); return FALSE; } // Call "NameHasOwner" method error = NULL; GVariant *result = g_dbus_proxy_call_sync(proxy, "NameHasOwner", g_variant_new("(s)", service_name), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (error) { g_printerr("Cannot get NameHasOwner for %s. %s\n", service_name, error->message); g_error_free(error); g_object_unref(proxy); return FALSE; } // The result has format "(boolean,)" // Debug: // gchar *str = g_variant_print(result, TRUE); // LOG_PLAYER("Received data for HasName:<%s>\n", str); // g_free(str); // Take the boolean value gboolean running = FALSE; g_variant_get_child(result, 0, "b", &running); g_variant_unref(result); g_object_unref(proxy); // Return TRUE/FALSE return running; } static gchar *get_string_val(GVariant *v) { // Read and return a string value. The v can be either "s" or "as". // If array then return the first string. gchar *s = NULL; // Is it a string array "as"? if (g_variant_is_of_type(v, G_VARIANT_TYPE_STRING_ARRAY)) { g_variant_get_child(v, 0, "s", &s); // Is of string type "s"? } if (g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) { g_variant_get(v, "s", &s); } return s; } void mpris2_get_metadata(gpointer player_rec) { // Get track information (=metadata) and state for the given media player. // Ref: http://www.mpris.org/2.1/spec/Player_Node.html#Property:Metadata MediaPlayerRec *player = (MediaPlayerRec*)player_rec; if (!player) return; // Reset track info TrackInfo *tr = &player->track; tr->status = PLAYER_STATUS_STOPPED; tr->flags = 0; tr->track[0] = tr->artist[0] = tr->album[0] = '\0'; // Connect to glib/DBus GDBusConnection *dbus_conn = mpris2_connect_to_dbus(); if (!dbus_conn) return; // Proxy that points to "org.mpris.MediaPlayer2.Player" // Ref: http://www.mpris.org/2.1/spec/ if (!mpris2_check_proxy(player)) { return; } // Read "PlaybackStatus" from player's "org.mpris.MediaPlayer2.Player" interface // // Test: // $ dbus-send --print-reply --session --dest=org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 // org.freedesktop.DBus.Properties.Get string:'org.mpris.MediaPlayer2.Player' string:'PlaybackStatus' // GError *error = NULL; GVariant *result = g_dbus_proxy_call_sync(player->proxy, "org.freedesktop.DBus.Properties.Get", g_variant_new("(ss)", "org.mpris.MediaPlayer2.Player", "PlaybackStatus"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (error) { // Cannot contact player (it has quit)? tr->status = PLAYER_STATUS_CLOSED; g_error_free(error); return; } // So result != NULL. // Notice: The MPRIS standard defines result as "v" (variant that contains one string). // I think some players return an array type "(v)". GVariant *peek = result; // Is "(v)"? if (g_variant_is_container(result)) { // Peek to "v" g_variant_get(result, "(v)", &peek); } gchar *s = NULL; g_variant_get(peek, "s", &s); // Set tr->status if (!g_ascii_strcasecmp(s, "Playing")) tr->status = PLAYER_STATUS_PLAYING; else if (!g_ascii_strcasecmp(s, "Paused")) tr->status = PLAYER_STATUS_PAUSED; else if (!g_ascii_strcasecmp(s, "Stopped")) tr->status = PLAYER_STATUS_STOPPED; g_variant_unref(result); result = NULL; g_variant_unref(peek); peek = NULL; g_free(s); // Should we continue? if (tr->status != PLAYER_STATUS_PLAYING) { return; } // The tr->status must be PLAYER_STATUS_PLAYING. // Get track info (Metadata) from player's "org.mpris.MediaPlayer2.Player" interface. // The dict has type "a{sv}". // // Test: // $ dbus-send --print-reply --session --dest=org.mpris.MediaPlayer2.rhythmbox /org/mpris/MediaPlayer2 // org.freedesktop.DBus.Properties.Get string:'org.mpris.MediaPlayer2.Player' string:'Metadata' error = NULL; GVariant *dict = g_dbus_proxy_call_sync(player->proxy, "org.freedesktop.DBus.Properties.Get", g_variant_new("(ss)", "org.mpris.MediaPlayer2.Player", "Metadata"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (error) { // Cannot contact player (it has quit)? tr->status = PLAYER_STATUS_CLOSED; g_error_free(error); return; } /* Some essential key names for the returned "a{sv}" dictionary: 'mpris:trackid' has type 's' 'xesam:url' has type 's' 'xesam:title' has type 's' 'xesam:artist' has type 'as' 'xesam:genre' has type 'as' 'rhythmbox:streamTitle' has type 's' 'xesam:audioBitrate' has type 'i' 'mpris:length' has type 'x' 'xesam:trackNumber' has type 'i' 'xesam:useCount' has type 'i' 'xesam:userRating' has type 'd' Here is example data from Rhythmbox: ('org.mpris.MediaPlayer2.Player', {'CanSeek': , 'Metadata': <{ 'mpris:trackid': <'/org/mpris/MediaPlayer2/Track/2'>, 'xesam:url': <'file:///home/moma/Music/Bruce Springsteen.mp3'>, 'xesam:title': <'Land Of Hope'>, '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': , 'xesam:trackNumber': <10>, 'xesam:discNumber': <1>, 'xesam:useCount': <0>, 'xesam:userRating': <0.0>, 'mpris:artUrl': <'file:///home/moma/.cache/rhythmbox/album-art/00000098'>}>, 'Volume': <1.0>, 'PlaybackStatus': <'Playing'>}, @as [])" */ peek = dict; // Is "(v)" ? if (g_variant_is_container(dict)) { // Peek to "v" g_variant_get(dict, "(v)", &peek); } GVariantIter iter; GVariant *value = NULL; gchar *key = NULL; // Ref: http://developer.gnome.org/glib/2.30/glib-GVariant.html#g-variant-get-va g_variant_iter_init(&iter, peek); while (g_variant_iter_next(&iter, "{sv}", &key, &value)) { // Debug #if defined(DEBUG_PLAYER) || defined(DEBUG_ALL) gchar *sval = g_variant_print(value, TRUE); const gchar *stype = g_variant_get_type_string(value); LOG_PLAYER("Metdata key \"%s\" has type:%s and value:%s\n", key, stype, sval); g_free(sval); #endif gchar *str = NULL; GVariantIter *iter2 = NULL; // xesam:title? if (g_str_has_suffix(key, ":title")) { str = get_string_val(value); str_copy(tr->track, str, MPRIS_STRLEN-1); } // xesam::artist? Notice: This has data type "as" (VLC media player sets this to "s") else if (g_str_has_suffix(key, ":artist")) { str = get_string_val(value); str_copy(tr->artist, str, MPRIS_STRLEN-1); } // xesam:albumArtist? Notice: This has data type "as". (VLC media player sets this to "s") else if (g_str_has_suffix(key, ":albumArtist")) { // Already set by ":artist"? if (tr->artist[0] == '\0') { str = get_string_val(value); str_copy(tr->artist, str, MPRIS_STRLEN-1); } } // xesam:album? else if (g_str_has_suffix(key, ":album")) { str = get_string_val(value); str_copy(tr->album, str, MPRIS_STRLEN-1); } if (iter2) g_variant_iter_free(iter2); g_free(str); g_variant_unref(value); g_free(key); } g_variant_unref(dict); g_variant_unref(peek); } void mpris2_start_app(gpointer player_rec) { // Start player application /* This StartSeviceByName command does not work: $ dbus-send --session --dest="org.freedesktop.DBus" \ "/org/freedesktop/DBus" \ "org.freedesktop.DBus.StartServiceByName" \ "string:org.gnome.Rhythmbox3" \ "int32:0" // The following code works fine for "org.gnome.Rhythmbox3" but there is // no service file for "org.mpris.MediaPlayer2.rhythmbox". gchar *service_name = "org.gnome.Rhythmbox3"; GDBusProxy *proxy = g_dbus_proxy_new_sync(dbus_conn, G_DBUS_PROXY_FLAGS_NONE, NULL, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, NULL, &error); // Call StartServiceByName method. error = NULL; GVariant *result = g_dbus_proxy_call_sync(proxy, "StartServiceByName", g_variant_new("(su)", service_name, 0), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); */ // Let's do it the hard way. MediaPlayerRec *player = (MediaPlayerRec*)player_rec; if (!player_rec) return; // Already running? if (mpris2_service_is_running(player_rec)) { return; } if (!player->exec_name) { g_printerr("Executable name for %s is not set. Start the application manually.\n", player->app_name); return; } // Find path for exec_name gchar *path = find_command_path(player->exec_name); if (!path) { g_printerr("Cannot run %s. Start the application %s manually.\n", player->exec_name, player->app_name); return; } // Run the application. // Build argv[] list. gchar **argv = g_new(gchar*, 2); argv[0] = g_strdup(path); argv[1] = NULL; // Run the command. It will return immediately because it's asynchronous. GError *error = NULL; GPid __attribute__((unused)) pid = exec_command_async(argv, &error); // Free the values if (error) g_error_free(error); g_strfreev(argv); g_free(path); } void mpris2_detect_players() { // Get list of MPRIS2 (org.mpris.MediaPlayer2.*) compliant programs. // Connect to glib/DBus GDBusConnection *dbus_conn = mpris2_connect_to_dbus(); if (!dbus_conn) return; // Get player list GHashTable *player_list = dbus_player_get_list_ref(); #define DBUS_MPRIS2_NAMESPACE "org.mpris.MediaPlayer2." GError *error = NULL; // Ref: http://dbus.freedesktop.org/doc/api/html/group__DBusShared.html // Create proxy for org.freedesktop.DBus. // And execute "ListNames" GVariant *result = g_dbus_connection_call_sync (dbus_conn, "org.freedesktop.DBus", /* name */ "/org/freedesktop/DBus", /* path */ "org.freedesktop.DBus", /* interface */ "ListNames", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); #if 0 // This does the same thing. GDBusProxy *proxy = g_dbus_proxy_new_sync(dbus_conn, G_DBUS_PROXY_FLAGS_NONE, NULL, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, NULL, &error); if (error) { g_print("Cannot create proxy for ListNames. %s\n", error->message); g_error_free(error); return; } // Call ListNames method, wait for reply error = NULL; GVariant *result = g_dbus_proxy_call_sync(proxy, "ListNames", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); // Unref the proxy g_object_unref(proxy); #endif if (error) { g_printerr("Cannot read service names from org.freedesktop.DBus. %s\n", error->message); g_error_free(error); return; } // The result is an array of strings, "(as)". GVariantIter *iter = NULL; gchar *service_name = NULL; // Get iter to a string array g_variant_get(result, "(as)", &iter); // Iter over all service_names service_name = NULL; while (g_variant_iter_next(iter, "s", &service_name)) { // Drop names that begin with ':' if (service_name && *service_name == ':') { g_free(service_name); continue; } MediaPlayerRec *player = NULL; // Belongs to org.mpris.MediaPlayer2.* namespace? if (!strncmp(DBUS_MPRIS2_NAMESPACE, service_name, strlen(DBUS_MPRIS2_NAMESPACE))) { LOG_DEBUG("Detected service name %s.\n", service_name); // New MediaPlayer record player = mpris2_player_new(service_name); // Set application name // Ref: http://specifications.freedesktop.org/mpris-spec/latest/Root_Node.html player->app_name = mpris2_get_property_str(player, "Identity"); // Set executable name (from it's .desktop file) player->exec_name = mpris2_get_property_str(player, "DesktopEntry"); // Function to connect/disconnect event signals player->func_set_signals = mpris2_set_signals; // Function to check if this player is running player->func_check_is_running = mpris2_service_is_running; // Function to get track-info (track/song name/title/album/artist, length, etc.) player->func_get_info = mpris2_get_metadata; // Function to start/run the application player->func_start_app = mpris2_start_app; // Set icon name (notice: this is not a perfect solution but works in most cases!). // Find last "." in the service_name. gchar *pos = g_strrstr(player->service_name, "."); player->icon_name = NULL; if (pos) { player->icon_name = g_strdup(pos+1); } } // Has a valid player record? if (player && player->app_name) { // Add player to the g_player_list. // Lookup the record by its app_name (like: "Amarok 2.3.2") if (!dbus_player_lookup_app_name(player->app_name)) { // Add it to the list g_hash_table_insert(player_list, g_strdup(player->service_name), player); } else { // Duplicate or bad record. Free it. dbus_player_delete_item(player); } } g_free(service_name); } g_variant_iter_free(iter); g_variant_unref(result); }