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