/* * 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 #include "dconf.h" #include "log.h" #include "utility.h" #include "support.h" #include "dbus-player.h" #include "audio-sources.h" // MPRIS2 compliant players. #include "dbus-mpris2.h" // Handcrafted DBus-interface for Skype. #include "dbus-skype.h" // Send commands to rec-manager.c #include "rec-manager-struct.h" // List of players static GHashTable *g_player_list = NULL; static void dbus_player_disconnect_signals(); static void dbus_player_clear_list(); static void dbus_player_get_saved(); static void dbus_player_save(MediaPlayerRec *pl); static void dbus_player_delete_saved(gchar *app_name, gchar *service_name); gboolean add_player_to_list(gchar *app_name, gchar *exec_name, gchar *service_name); void add_skype(); void dbus_player_init() { LOG_DEBUG("Init dbus-player.c.\n"); g_player_list = NULL; skype_module_init(); mpris2_module_init(); } void dbus_player_exit() { LOG_DEBUG("Clean up dbus-player.c.\n"); // Disconnect DBus signals for all Media Players dbus_player_disconnect_signals(); // Clear the player list dbus_player_clear_list(); mpris2_module_exit(); skype_module_exit(); } static RecorderCommand *convert_data(MediaPlayerRec *pl) { // Convert MediaPlayerRec to RecorderCommand if (!pl) return NULL; TrackInfo *tr = &pl->track; RecorderCommand *cmd = g_malloc0(sizeof(RecorderCommand)); cmd->track = g_strndup(tr->track, MPRIS_STRLEN); cmd->artist = g_strndup(tr->artist, MPRIS_STRLEN); cmd->album = g_strndup(tr->album, MPRIS_STRLEN); cmd->track_len = tr->track_len; cmd->track_pos = tr->track_pos; cmd->flags = tr->flags; if (tr->status == PLAYER_STATUS_PAUSED) { cmd->type = RECORDING_PAUSE; } else if (tr->status == PLAYER_STATUS_PLAYING) { cmd->type = RECORDING_START; } else if (tr->status == PLAYER_STATUS_NOTIFY_MSG) { cmd->type = RECORDING_NOTIFY_MSG; } else { // tr.status == PLAYER_STATUS_CLOSED || // tr.status == PLAYER_STATUS_STOPPED cmd->type = RECORDING_STOP; } return cmd; } void dbus_player_process_data(gpointer player) { // Send message to the rec-manager.c if (!player) return; // Convert MediaPlayerRec to RecorderCommand RecorderCommand *cmd = convert_data(player); // Send the command. Rec-manager will free the cmd structure after processing. rec_manager_send_command(cmd); } void dbus_player_player_changed(gchar *service_name) { // Re-connect DBus signals/methods for the given service_name (Media Player or Skype, etc) // Disconnect all signals/object methods dbus_player_disconnect_signals(); // Get MediaPlayerRec for this service_name MediaPlayerRec *player = dbus_player_lookup_service_name(service_name); if (player && player->func_set_signals) { LOG_PLAYER("Connect DBus signals for %s (%s).\n", player->app_name, player->service_name); // Start application (Media Player, Skype, etc) if (player->func_start_app) { player->func_start_app(player); } // Connect signals so we receive track-changed/start/stop messages from this app (over DBus) player->func_set_signals(player, TRUE); // TRUE=connect/register, FALSE=disconnect/unregister // Save this player in GSettings so user doesn't need to refresh the combo manually. dbus_player_save(player); } } static void dbus_player_disconnect_signals() { // Disconnect all signal-functions from the DBus GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, g_player_list); while (g_hash_table_iter_next(&iter, &key, &value)) { MediaPlayerRec *player = (MediaPlayerRec*)value; if (player && player->func_set_signals) { // Disconnect signals player->func_set_signals(player, FALSE); // FALSE=disconnect/unregister } } } GHashTable *dbus_player_get_list_ref() { if (!g_player_list) g_player_list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, dbus_player_delete_item); return g_player_list; } static gboolean dbus_palyer_remove_node(gpointer key, gpointer value, gpointer user_data) { // Return TRUE so this (key, value) pair gets removed and deleted. return TRUE; } static void dbus_player_clear_list() { // Delete the entire g_player_list if (g_player_list) { g_hash_table_foreach_remove(g_player_list, dbus_palyer_remove_node, NULL); g_hash_table_unref(g_player_list); } g_player_list = NULL; } void dbus_player_delete_item(gpointer data) { MediaPlayerRec *player = (MediaPlayerRec*)data; if (!player) return; LOG_PLAYER("dbus_player_delete_item: %s (%s).\n", player->app_name, player->service_name); if (player->func_set_signals) { // Disconnect signals player->func_set_signals(player, FALSE); // TRUE=connect, FALSE=disconnect } if (G_IS_DBUS_PROXY(player->proxy)) { g_object_unref(player->proxy); } player->proxy = NULL; if (G_IS_DBUS_PROXY(player->prop_proxy)) { g_object_unref(player->prop_proxy); } player->prop_proxy = NULL; g_free(player->service_name); g_free(player->exec_name); g_free(player->app_name); g_free(player->icon_name); g_free(player); } MediaPlayerRec *dbus_player_lookup_app_name(gchar *app_name) { // Lookup player by its application name (p->app_name). // Typical app_names are "Amarok 2.3.2", "RhythmBox 2.3" and "Skype 2.1.0". GHashTableIter iter; gpointer key, value; if (str_length(app_name, 1024) < 1) return NULL; g_hash_table_iter_init(&iter, g_player_list); while (g_hash_table_iter_next(&iter, &key, &value)) { MediaPlayerRec *p = (MediaPlayerRec*)value; if (!g_strcmp0(p->app_name, app_name)) { return p; } } return NULL; } MediaPlayerRec *dbus_player_lookup_service_name(gchar *service_name) { // Lookup player by its service name (p->service_name). // "org.mpris.MediaPlayer2.Player.banshee" is a typical service_name. GHashTableIter iter; gpointer key, value; if (str_length(service_name, 1024) < 1) return NULL; g_hash_table_iter_init(&iter, g_player_list); while (g_hash_table_iter_next(&iter, &key, &value)) { MediaPlayerRec *p = (MediaPlayerRec*)value; if (!g_strcmp0(p->service_name, service_name)) { return p; } } return NULL; } void dbus_player_debug_print(MediaPlayerRec *p) { if (!p) return; LOG_PLAYER("------------------------------\n"); LOG_PLAYER("Player app name:%s\n", p->app_name); LOG_PLAYER("Player exec name:%s\n", p->exec_name); LOG_PLAYER("Service name:%s\n", p->service_name); TrackInfo *tr = &p->track; switch (tr->status) { case PLAYER_STATUS_CLOSED: LOG_PLAYER("Status:%d PLAYER_STATUS_CLOSED (not running)\n", tr->status); break; case PLAYER_STATUS_STOPPED: LOG_PLAYER("Status:%d PLAYER_STATUS_STOPPED\n", tr->status); break; case PLAYER_STATUS_PAUSED: LOG_PLAYER("Status:%d PLAYER_STATUS_PAUSED\n", tr->status); break; case PLAYER_STATUS_PLAYING: LOG_PLAYER("Status:%d PLAYER_STATUS_PLAYING\n", tr->status); break; case PLAYER_STATUS_NOTIFY_MSG: // Simply a msg to the GUI. LOG_PLAYER("Status:%d PLAYER_STATUS_NOTIFY_MSG\n", tr->status); break; default: LOG_PLAYER("Unknown status:%d\n", tr->status); } if (tr->status != PLAYER_STATUS_NOTIFY_MSG) { LOG_PLAYER("Track:%s\n", tr->track); LOG_PLAYER("Artist:%s\n", tr->artist); LOG_PLAYER("Album:%s\n", tr->album); LOG_PLAYER("Track length in microsecs:%ld\n", tr->track_len); LOG_PLAYER("Track pos in microsecs:%ld\n", tr->track_pos); LOG_PLAYER("Flags:%d\n", tr->flags); } else { // Simply a msg to the GUI. LOG_PLAYER("Message:%s\n", tr->track); } LOG_PLAYER("------------------------------\n"); } GHashTable *dbus_player_get_player_list() { // Clear the old list dbus_player_clear_list(); // Populate the list. // Detect players that follow the org.mpris.MediaPlayer2.* standard. mpris2_detect_players(); // Add lastly used and saved players to the list dbus_player_get_saved(); // Make sure the most popular players are in the list (on our target distros). // Add Rhythmbox manually (check if installed) // TODO: Should we translate the app-names? add_player_to_list("Rhythmbox", "rhythmbox", "org.mpris.MediaPlayer2.rhythmbox"); // Add Banshee manually (check if installed) add_player_to_list("Banshee", "banshee", "org.mpris.MediaPlayer2.banshee"); // Add Skype manually (check if installed) add_skype(); return g_player_list; } gboolean add_player_to_list(gchar *app_name, gchar *exec_name, gchar *service_name) { // Add player manually to the list. Check if it's installed. // Already in the list? if (dbus_player_lookup_service_name(service_name)) return TRUE; // Find executable /usr/bin/exec_name gchar *path = find_command_path(exec_name); if (!path) { // Not installed. return FALSE; } g_free(path); // New MediaPlayer record MediaPlayerRec *player = mpris2_player_new(service_name); // Set executable player->exec_name = g_strdup(exec_name); // Set application name. This is shown in the listbox player->app_name = g_strdup(app_name); // Function to connect/disconnect event signals for this player player->func_set_signals = mpris2_set_signals; // Function to get track-info (album, track/song name/title, genre, etc.) player->func_get_info = mpris2_get_metadata; // Function to start/run the media player player->func_start_app = mpris2_start_app; // Function to check if this player is running player->func_check_is_running = mpris2_service_is_running; // Set icon name player->icon_name = g_strdup(exec_name); if (!dbus_player_lookup_app_name(player->app_name)) { // Add to list GHashTable *player_list = dbus_player_get_list_ref(); g_hash_table_insert(player_list, g_strdup(player->service_name), player); } else { // Duplicate or bad record. Free it. dbus_player_delete_item(player); } return TRUE; } void add_skype() { // Add Skype gchar *service_name = "com.Skype.API"; // Already in the list? if (dbus_player_lookup_service_name(service_name)) return; // Find /usr/bin/skype gchar *path = find_command_path("skype"); if (!path) { // Not installed. return; } g_free(path); // New MediaPlayer record (yep, we store Skype data in a MediaPlayer record) MediaPlayerRec *player = mpris2_player_new(service_name); player->type = COMM_PROGRAM; // Set application name player->app_name = skype_get_app_name(); // Function to register/unregister notification methods player->func_set_signals = skype_setup; // Function to get track-info (strictly, Skype does not have "track" data, though it has target filename) player->func_get_info = skype_get_info; // Function to start/run Skype player->func_start_app = skype_start_app; // Set icon player->icon_name = g_strdup("skype"); if (!dbus_player_lookup_app_name(player->app_name)) { // Add to list GHashTable *player_list = dbus_player_get_list_ref(); g_hash_table_insert(player_list, g_strdup(player->service_name), player); } else { // Duplicate or bad record. Free it. dbus_player_delete_item(player); } } void dbus_player_send_notification(gchar *msg) { RecorderCommand *cmd = g_malloc0(sizeof(RecorderCommand)); cmd->type = RECORDING_NOTIFY_MSG; cmd->track = g_strndup(msg, MPRIS_STRLEN); // Send command to rec-manager.c. // It will free the cmd structure after processing. rec_manager_send_command(cmd); } // -------------------------------------------------------------------- // Support functions to read/write values to "players/saved-player-list" in GSettings. // -------------------------------------------------------------------- static void split_value(gchar *value, gchar **app_name, gchar **service_name) { // Split value on "=" and return the parts. *app_name = NULL; *service_name = NULL; if (!value) return; // Split on '=' gchar **args = g_strsplit(value, "=", 2); if (args && args[0]) *app_name = g_strdup(args[0]); if (args && args[0] && args[1]) *service_name = g_strdup(args[1]); // Delete args g_strfreev(args); } static gchar *make_exec_name(gchar *service_name) { // Take last part of service_name and consider it to be the executable. // This is not accurate science, but should work in most cases. // Samples: // "org.mpris.MediaPlayer2.rhythmbox" gives us "rhythmbox". // "org.mpris.MediaPlayer2.vlc" gives "vlc". // Find last "." gchar *pos = g_strrstr(service_name, "."); if (pos) { return g_strdup(pos+1); } return NULL; } static void dbus_player_delete_saved(gchar *app_name, gchar *service_name) { // Delete app_name/service_name from GSettings. // See dconf-editor, key: /apps/audio-recorder/players/players/saved-player-list GList *list = NULL; GList *delete_list = NULL; g_strdelimit(app_name, "=\n\t\r", ' '); g_strstrip(app_name); g_strstrip(service_name); // Get saved-player-list from GSettings. const gchar *conf_key = "players/saved-player-list"; conf_get_string_list((gchar*)conf_key, &list); GList *item = g_list_first(list); while (item) { // Take value. // E.g "VLC 3.1=org.mpris.MediaPlayer2.vlc" gchar *value = (gchar*)item->data; gchar *a_name = NULL; gchar *s_name = NULL; // Split on '=' split_value(value, &a_name, &s_name); // Match app_name if (!g_strcmp0(app_name, a_name)) { // Add to delete_list delete_list = g_list_append(delete_list, value); // Match service_name } else if (!g_strcmp0(service_name, s_name)) { // Add to delete_list delete_list = g_list_append(delete_list, value); } g_free(a_name); g_free(s_name); item = g_list_next(item); } // Delete all found nodes item = g_list_first(delete_list); while (item) { gchar *value = item->data; // Remove node list = g_list_remove_all(list, value); // Free string g_free(value); item = g_list_next(item); } if (g_list_length(delete_list) > 0) { // Save changes to Gsettings conf_save_string_list((gchar*)conf_key, list); } // Free delete_list (notice: data already freed) g_list_free(delete_list); delete_list = NULL; // Free list str_list_free(list); list = NULL; } static void dbus_player_save(MediaPlayerRec *pl) { // Save pl->app_name/pl->service_name to GSettings. // See dconf-editor, key: /apps/audio-recorder/players/players/saved-player-list GList *list = NULL; if (!(pl->app_name && pl->service_name)) return; gchar *app_name = g_strdup(pl->app_name); g_strdelimit(app_name, "=\n\t\r", ' '); g_strstrip(app_name); gchar *service_name = g_strdup(pl->service_name); g_strstrip(service_name); // Delete old value dbus_player_delete_saved(app_name, service_name); gchar *value = g_strdup_printf("%s=%s", app_name, service_name); g_free(app_name); g_free(service_name); // Get saved-player-list from Gsettings. const gchar *conf_key = "players/saved-player-list"; conf_get_string_list((gchar*)conf_key, &list); // Save list in Gsettings/DConf list = g_list_prepend(list, g_strdup(value)); conf_save_string_list((gchar*)conf_key, list); #if defined(DEBUG_PLAYER) || defined(DEBUG_ALL) LOG_PLAYER("----------------------------\n"); str_list_print("New, saved saved-player-list", list); LOG_PLAYER("----------------------------\n"); #endif // Free list str_list_free(list); list = NULL; g_free(value); } static void dbus_player_get_saved() { // Get saved-player-list from GSettings. // See dconf-editor, key: /apps/audio-recorder/players/players/saved-player-list const gchar *conf_key = "players/saved-player-list"; // Get list GList *list = NULL; conf_get_string_list((gchar*)conf_key, &list); #if defined(DEBUG_PLAYER) || defined(DEBUG_ALL) LOG_PLAYER("----------------------------\n"); str_list_print("Get saved-player-list", list); LOG_PLAYER("----------------------------\n"); #endif // Add saved & still existing media-players to the list GList *item = g_list_first(list); while (item) { // Take value. // E.g "VLC 3.1=org.mpris.MediaPlayer2.vlc" gchar *value = (gchar*)item->data; gchar *app_name = NULL; gchar *service_name = NULL; // Split on '=' split_value(value, &app_name, &service_name); // Make exec_name from service_name. // TODO: Find a better method for this. Eg. find its .desktop file? gchar *exec_name = make_exec_name(service_name); // Add media-player to list (to be shown in the "Sources:" combo). if (!add_player_to_list(app_name, exec_name, service_name)) { // It's probably uninstalled. Delete form Gsettings too. LOG_PLAYER("Player %s, %s (%s) removed from the list. It's probably uninstalled.\n", app_name, exec_name, service_name); dbus_player_delete_saved(app_name, service_name); } g_free(app_name); g_free(service_name); g_free(exec_name); item = g_list_next(item); } // Free list str_list_free(list); list = NULL; }