1# coding: utf-8 2# vim: set et sw=2: 3# 4# Copyright (C) 2007-2008 - Vincent Untz 5# Copyright (C) 2012 - Nirbheek Chauhan <nirbheek@gentoo.org> 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2, or (at your option) 10# any later version. 11# 12# The Rhythmbox authors hereby grant permission for non-GPL compatible 13# GStreamer plugins to be used and distributed together with GStreamer 14# and Rhythmbox. This permission is above and beyond the permissions granted 15# by the GPL license by which Rhythmbox is covered. If you modify this code 16# you may extend this exception to your version of the code, but you are not 17# obligated to do so. If you do not wish to do so, delete this exception 18# statement from your version. 19# 20# This program is distributed in the hope that it will be useful, 21# but WITHOUT ANY WARRANTY; without even the implied warranty of 22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23# GNU General Public License for more details. 24# 25# You should have received a copy of the GNU General Public License 26# along with this program; if not, write to the Free Software 27# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 28 29import rb 30import gi 31from gi.repository import Gio, GLib, GObject, Peas 32from gi.repository import RB 33 34import gettext 35gettext.install('rhythmbox', RB.locale_dir()) 36 37NORMAL_SONG_ARTIST = 'artist' 38NORMAL_SONG_TITLE = 'title' 39NORMAL_SONG_ALBUM = 'album' 40STREAM_SONG_ARTIST = 'rb:stream-song-artist' 41STREAM_SONG_TITLE = 'rb:stream-song-title' 42STREAM_SONG_ALBUM = 'rb:stream-song-album' 43 44PROPERTIES_IFACE_NAME = 'org.freedesktop.DBus.Properties' 45MC5_BUS_NAME = 'org.freedesktop.Telepathy.MissionControl5' 46MC5_AM_OBJ_PATH = '/org/freedesktop/Telepathy/AccountManager' 47MC5_AM_IFACE_NAME = 'org.freedesktop.Telepathy.AccountManager' 48MC5_ACCT_IFACE_NAME = 'org.freedesktop.Telepathy.Account' 49 50PURPLE_BUS_NAME = 'im.pidgin.purple.PurpleService' 51PURPLE_OBJ_PATH = '/im/pidgin/purple/PurpleObject' 52PURPLE_IFACE_NAME = 'im.pidgin.purple.PurpleInterface' 53 54class IMStatusPlugin (GObject.Object, Peas.Activatable): 55 __gtype_name__ = 'IMStatusPlugin' 56 object = GObject.property(type=GObject.Object) 57 58 def __init__ (self): 59 GObject.Object.__init__ (self) 60 61 def _init_dbus_proxies(self): 62 self.proxies = {} 63 bus_type = Gio.BusType.SESSION 64 flags = 0 65 iface_info = None 66 # Creating proxies doesn't do any blocking I/O, and never fails 67 self.proxies["purple"] = Gio.DBusProxy.new_for_bus_sync(bus_type, flags, iface_info, 68 PURPLE_BUS_NAME, PURPLE_OBJ_PATH, PURPLE_IFACE_NAME, None) 69 self.proxies["mc5_props"] = Gio.DBusProxy.new_for_bus_sync(bus_type, flags, iface_info, 70 MC5_BUS_NAME, MC5_AM_OBJ_PATH, PROPERTIES_IFACE_NAME, None) 71 72 def do_activate (self): 73 shell = self.object 74 sp = shell.props.shell_player 75 self.psc_id = sp.connect ('playing-song-changed', 76 self.playing_entry_changed) 77 self.pc_id = sp.connect ('playing-changed', 78 self.playing_changed) 79 self.pspc_id = sp.connect ('playing-song-property-changed', 80 self.playing_song_property_changed) 81 82 self.current_entry = None 83 self.current_artist = None 84 self.current_title = None 85 self.current_album = None 86 87 self._init_dbus_proxies () 88 self.save_status () 89 90 if sp.get_playing (): 91 self.set_entry (sp.get_playing_entry ()) 92 93 def do_deactivate (self): 94 shell = self.object 95 sp = shell.props.shell_player 96 sp.disconnect (self.psc_id) 97 sp.disconnect (self.pc_id) 98 sp.disconnect (self.pspc_id) 99 100 if self.current_entry is not None: 101 self.restore_status () 102 103 def playing_changed (self, sp, playing): 104 if playing: 105 self.set_entry (sp.get_playing_entry ()) 106 else: 107 self.current_entry = None 108 self.restore_status () 109 110 def playing_entry_changed (self, sp, entry): 111 if sp.get_playing (): 112 self.set_entry (entry) 113 114 def playing_song_property_changed (self, sp, uri, property, old, new): 115 relevant = False 116 if sp.get_playing () and property in (NORMAL_SONG_ARTIST, STREAM_SONG_ARTIST): 117 self.current_artist = new 118 relevant = True 119 elif sp.get_playing () and property in (NORMAL_SONG_TITLE, STREAM_SONG_TITLE): 120 self.current_title = new 121 relevant = True 122 elif sp.get_playing () and property in (NORMAL_SONG_ALBUM, STREAM_SONG_ALBUM): 123 self.current_album = new 124 relevant = True 125 126 if relevant: 127 self.set_status () 128 129 def set_entry (self, entry): 130 if rb.entry_equal(entry, self.current_entry): 131 return 132 133 if self.current_entry == None: 134 self.save_status () 135 self.current_entry = entry 136 137 if entry is None: 138 self.restore_status () 139 return 140 141 self.set_status_from_entry () 142 143 def set_status_from_entry (self): 144 shell = self.object 145 db = shell.get_property ("db") 146 self.current_artist = self.current_entry.get_string(RB.RhythmDBPropType.ARTIST) 147 self.current_title = self.current_entry.get_string(RB.RhythmDBPropType.TITLE) 148 self.current_album = self.current_entry.get_string(RB.RhythmDBPropType.ALBUM) 149 150 if self.current_entry.get_entry_type().props.category == RB.RhythmDBEntryCategory.STREAM: 151 if not self.current_artist: 152 self.current_artist = db.entry_request_extra_metadata (self.current_entry, STREAM_SONG_ARTIST) 153 if not self.current_title: 154 self.current_title = db.entry_request_extra_metadata (self.current_entry, STREAM_SONG_TITLE) 155 if not self.current_album: 156 self.current_album = db.entry_request_extra_metadata (self.current_entry, STREAM_SONG_ALBUM) 157 158 self.set_status () 159 160 def set_status (self): 161 subs = { 162 'artist': self.current_artist, 163 'album': self.current_album, 164 'title': self.current_title 165 } 166 if self.current_artist: 167 if self.current_title: 168 # Translators: do not translate %(artist)s or %(title)s, they are 169 # string substitution markers (like %s) for the artist and title of 170 # the current playing song. They can be reordered if necessary. 171 new_status = _(u"♫ %(artist)s - %(title)s ♫") % subs 172 elif self.current_album: 173 # Translators: do not translate %(artist)s or %(album)s, they are 174 # string substitution markers (like %s) for the artist and album name 175 # of the current playing song. They can be reordered if necessary. 176 new_status = _(u"♫ %(artist)s - %(album)s ♫") % subs 177 elif self.current_album: 178 # Translators: do not translate %(album)s, it is a string substitution 179 # marker (like %s) for the album name of the current playing song. 180 new_status = _(u"♫ %(album)s ♫") % subs 181 elif self.current_title: 182 # Translators: do not translate %(title)s, it is a string substitution 183 # marker (like %s) for the title of the current playing song. 184 new_status = _(u"♫ %(title)s ♫") % subs 185 else: 186 new_status = _(u"♫ Listening to music... ♫") 187 188 self.set_mc5_status (new_status) 189 self.set_purple_status (new_status) 190 191 def save_status (self): 192 self.saved_mc5 = self.get_mc5_status () 193 self.saved_purple = self.get_purple_status () 194 195 def restore_status (self): 196 if self.saved_mc5 != None: 197 self.set_mc5_status (self.saved_mc5) 198 if self.saved_purple != None: 199 self.set_purple_status (self.saved_purple) 200 201 def set_mc5_status (self, new_status): 202 try: 203 proxy = self.proxies["mc5_props"] 204 for acct_obj_path in proxy.Get("(ss)", MC5_AM_IFACE_NAME, "ValidAccounts"): 205 # Create a new proxy connected to acct_obj_path 206 acct_proxy = Gio.DBusProxy.new_for_bus_sync(Gio.BusType.SESSION, 0, None, 207 MC5_BUS_NAME, acct_obj_path, 208 PROPERTIES_IFACE_NAME, None) 209 # status = (state, status, status_message) 210 status = acct_proxy.Get("(ss)", MC5_ACCT_IFACE_NAME, "RequestedPresence") 211 # Create the (uss) GVariant to set the new status message 212 vstatus = GLib.Variant("(uss)", (status[0], status[1], new_status)) 213 # Set the status! 214 acct_proxy.Set("(ssv)", MC5_ACCT_IFACE_NAME, "RequestedPresence", vstatus) 215 except GLib.GError as e: 216 print("GError while setting status: " + str(e)) 217 218 def get_mc5_status (self): 219 try: 220 proxy = self.proxies["mc5_props"] 221 got_status = False 222 # a bit awful: this just returns the status text from the first account 223 # that has one. 224 for acct_obj_path in proxy.Get("(ss)", MC5_AM_IFACE_NAME, "ValidAccounts"): 225 # Create a new proxy connected to acct_obj_path 226 acct_proxy = Gio.DBusProxy.new_for_bus_sync (Gio.BusType.SESSION, 0, None, 227 MC5_BUS_NAME, acct_obj_path, 228 PROPERTIES_IFACE_NAME, None) 229 # Get (state, status, status_message) 230 ret = acct_proxy.Get("(ss)", MC5_ACCT_IFACE_NAME, "RequestedPresence") 231 got_status = True 232 if ret[2] != "": 233 return ret[2] 234 # if all accounts have empty status, return that 235 if got_status: 236 return "" 237 except GLib.GError as e: 238 print("GError while setting status: " + str(e)) 239 return None 240 241 def set_purple_status (self, new_status): 242 try: 243 proxy = self.proxies["purple"] 244 status = proxy.PurpleSavedstatusGetCurrent() 245 proxy.PurpleSavedstatusSetMessage("(is)", status, new_status) 246 proxy.PurpleSavedstatusActivate("(i)", status) 247 except GLib.GError as e: 248 print("GError while setting status: " + str(e)) 249 250 def get_purple_status (self): 251 try: 252 proxy = self.proxies["purple"] 253 status = proxy.PurpleSavedstatusGetCurrent() 254 return proxy.PurpleSavedstatusGetMessage("(i)", status) 255 except GLib.GError as e: 256 print("GError while setting status: " + str(e)) 257 return None 258