/* Copyright 2010 Canonical Ltd. Authors: Conor Curran This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using Dbusmenu; using Gee; using GLib; public class MusicPlayerBridge : GLib.Object { const int DEVICE_ITEMS_COUNT = 3; private SettingsManager settings_manager; private Dbusmenu.Menuitem root_menu; private HashMap registered_clients; private HashMap file_monitors; private HashMap mpris_to_desktop; private Mpris2Watcher watcher; public MusicPlayerBridge() { } construct{ this.registered_clients = new HashMap (); this.file_monitors = new HashMap (); this.mpris_to_desktop = new HashMap (); this.settings_manager = new SettingsManager(); this.settings_manager.blacklist_updates.connect ( this.on_blacklist_update ); this.settings_manager.preferred_updates.connect ( this.on_preferred_update ); } private void on_blacklist_update ( string[] blacklist ) { debug("some blacklist update"); foreach(var desktop_id in blacklist){ string key = desktop_id; if (this.registered_clients.has_key (key)){ debug ("Apparently %s is now blacklisted - remove thy self", key); this.registered_clients[key].remove_from_menu(); this.registered_clients.unset (key); } } // double check present players to ensure dynamic removal/addition this.watcher.check_for_active_clients.begin(); } private void on_preferred_update ( Gee.ArrayList preferred ) { debug ("Preferred players update. Clearing current preferred players..."); foreach (var player_controller in this.registered_clients.values) { player_controller.set_as_preferred (false); } foreach (var desktop_id in preferred) { string key = desktop_id; if (this.registered_clients.has_key (key)) { debug ("Setting %s as preferred player", key); this.registered_clients[key].set_as_preferred (true); } } } private void try_to_add_inactive_familiar_clients() { var preferred_players = this.settings_manager.fetch_preferred (); foreach ( string desktop in this.settings_manager.fetch_interested()){ debug ( "interested client found : %s", desktop ); AppInfo? app_info = create_app_info ( desktop.concat( ".desktop" ) ); if ( app_info == null ){ warning ( "Could not create app_info for path %s \n Getting out of here ", desktop ); continue; } bool is_preferred = desktop in preferred_players; PlayerController ctrl = new PlayerController ( this.root_menu, app_info, null, this.fetch_icon_name(desktop), calculate_menu_position(), null, PlayerController.state.OFFLINE, is_preferred ); var mpris_key = desktop; this.registered_clients.set(mpris_key, ctrl); this.establish_file_monitoring (app_info, mpris_key); } } private void establish_file_monitoring (AppInfo info, string mpris_key){ DesktopAppInfo desktop_info = info as DesktopAppInfo; var file_path = desktop_info.get_filename (); File f = File.new_for_path (file_path); try { FileMonitor monitor = f.monitor (FileMonitorFlags.SEND_MOVED, null); unowned FileMonitor weak_monitor = monitor; monitor.changed.connect ((desktop_file, other_file, event_type) => { this.relevant_desktop_file_changed (desktop_file, other_file, event_type, weak_monitor); }); monitor.ref(); // will be unref()ed by relevant_desktop_file_changed() GLib.debug ("monitoring file '%s'", file_path); this.file_monitors.set (file_path, mpris_key); } catch (Error e){ warning ("Unable to create a file monitor for %s", info.get_name()); return; } } private void relevant_desktop_file_changed (File desktop_file, File? other_file, FileMonitorEvent event_type, FileMonitor monitor) { if (event_type != FileMonitorEvent.DELETED) return; string? path = desktop_file.get_path (); if (path == null){ warning ("relevant_desktop_file_changed is returning a file with no path !"); return; } if (!this.file_monitors.has_key (path)){ warning ("relevant_desktop_file_changed is returning a file which we know nothing about - %s", path); return; } var mpris_key = this.file_monitors[path]; GLib.debug ("file \"%s\" was removed; stopping monitoring \"%s\"", path, mpris_key); this.registered_clients[mpris_key].remove_from_menu(); this.settings_manager.remove_interested (mpris_key); this.registered_clients.unset (mpris_key); monitor.cancel (); monitor.unref(); } private int calculate_menu_position() { if(this.registered_clients.size == 0){ return DEVICE_ITEMS_COUNT; } else{ return (DEVICE_ITEMS_COUNT + (this.registered_clients.size * PlayerController.WIDGET_QUANTITY)); } } public void client_has_become_available ( string desktop, string dbus_name, bool use_playlists ) { if (desktop == null || desktop == ""){ warning("Client %s attempting to register without desktop entry being set on the mpris root", dbus_name); return; } if (desktop in this.settings_manager.fetch_blacklist()) { debug ("Client %s attempting to register but I'm afraid it is blacklisted", desktop); return; } debug ( "client_has_become_available %s", desktop ); AppInfo? app_info = create_app_info ( desktop.concat( ".desktop" ) ); if ( app_info == null ){ warning ( "Could not create app_info for path %s \n Getting out of here ", desktop ); return; } var mpris_key = desktop; bool is_preferred = desktop in this.settings_manager.fetch_preferred (); mpris_to_desktop.set (dbus_name, desktop); if ( this.registered_clients.has_key (mpris_key) == false ){ debug("New client has registered that we have not seen before: %s", dbus_name ); PlayerController ctrl = new PlayerController ( this.root_menu, app_info, dbus_name, this.fetch_icon_name(desktop), this.calculate_menu_position(), use_playlists, PlayerController.state.READY, is_preferred); this.registered_clients.set ( mpris_key, ctrl ); debug ( "Have not seen this %s before, new controller created.", desktop ); this.settings_manager.add_interested ( desktop ); this.establish_file_monitoring (app_info, mpris_key); debug ( "application added to the interested list" ); } else{ this.registered_clients[mpris_key].use_playlists = use_playlists; this.registered_clients[mpris_key].update_state ( PlayerController.state.READY ); this.registered_clients[mpris_key].activate ( dbus_name ); debug("Application has already registered - awaken the hibernation: %s with playlists %s \n", dbus_name, use_playlists.to_string() ); } } public void client_has_vanished ( string mpris_root_interface ) { debug("\n MusicPlayerBridge -> client with dbus interface %s has vanished", mpris_root_interface ); if (root_menu != null){ debug("\n attempt to remove %s", mpris_root_interface); if (mpris_to_desktop.has_key(mpris_root_interface)){ var mpris_key = mpris_to_desktop[mpris_root_interface]; mpris_to_desktop.unset(mpris_root_interface); if ( mpris_key != null && this.registered_clients.has_key(mpris_key)){ registered_clients[mpris_key].hibernate(); debug("\n Successively offlined client %s", mpris_key); } } } } public void set_root_menu_item ( Dbusmenu.Menuitem menu ) { this.root_menu = menu; this.try_to_add_inactive_familiar_clients(); this.watcher = new Mpris2Watcher (); this.watcher.client_appeared.connect (this.client_has_become_available); this.watcher.client_disappeared.connect (this.client_has_vanished); } public void enable_player_specific_items_for_client (string object_path, string desktop_id) { var mpris_key = desktop_id; if (this.registered_clients.has_key (mpris_key) == false){ warning ("we don't have a client with desktop id %s registered", desktop_id); return; } this.registered_clients[mpris_key].enable_player_specific_items(object_path); } public void enable_track_specific_items_for_client (string object_path, string desktop_id) { var mpris_key = desktop_id; if (this.registered_clients.has_key (mpris_key) == false){ warning ("we don't have a client with desktop id %s registered", desktop_id); return; } this.registered_clients[mpris_key].enable_track_specific_items(object_path); } private static AppInfo? create_app_info ( string desktop ) { DesktopAppInfo info = new DesktopAppInfo ( desktop ); if ( desktop == null || info == null ){ warning ( "Could not create a desktopappinfo instance from app: %s", desktop ); return null; } GLib.AppInfo app_info = info as GLib.AppInfo; return app_info; } private static string? fetch_icon_name(string desktop) { // We know the appinfo is good because it was loaded in the previous reg step. DesktopAppInfo info = new DesktopAppInfo ( desktop.concat( ".desktop" ) ) ; KeyFile desktop_keyfile = new KeyFile (); try{ desktop_keyfile.load_from_file (info.get_filename(), KeyFileFlags.NONE); } catch(GLib.FileError error){ warning("Error loading keyfile - FileError"); return null; } catch(GLib.KeyFileError error){ warning("Error loading keyfile - KeyFileError"); return null; } try{ return desktop_keyfile.get_string (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_ICON); } catch(GLib.KeyFileError error){ warning("Error trying to fetch the icon name from the keyfile"); return null; } } }