File: imstatus.py

package info (click to toggle)
exaile 0.2.11.1%2Bdebian-2
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 3,464 kB
  • ctags: 2,403
  • sloc: python: 17,416; ansic: 224; makefile: 163; perl: 153; sh: 125; sql: 74
file content (406 lines) | stat: -rw-r--r-- 14,722 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import dbus, gobject, gtk, os
from gettext import gettext as _
import xl.plugins as plugins


# Plugin description
PLUGIN_NAME        = _('IM Status')
PLUGIN_ICON        = None
PLUGIN_ENABLED     = False
PLUGIN_AUTHORS     = ['Ingelrest François <Athropos@gmail.com>']
PLUGIN_VERSION     = '0.12.1'
PLUGIN_DESCRIPTION = _(r"""Sets the online status of your instant messenger client according to the music you are listening to.\n\nSupported IM clients are:\n * Gaim (>= 2.0 beta6)\n * Gajim\n * Gossip\n * Pidgin""")

# Possible actions when Exaile quits or stops playing
(
    DO_NOTHING,
    CHANGE_STATUS
) = range(2)

# Constants
PID                     = plugins.name(__file__)
TRACK_FIELDS            = ('album', 'artist', 'bitrate', 'genre', 'length', 'rating', 'title', 'track', 'year')
DEFAULT_STOP_ACTION     = CHANGE_STATUS
DEFAULT_STOP_STATUS     = _('Exaile is stopped')
# TRANSLATORS: IMStatus plugin default status format
DEFAULT_STATUS_FORMAT   = _('♫ {artist} - {album} ♫')
DEFAULT_UPDATE_ON_PAUSE = False

# Global variables
mHandlers           = plugins.SignalContainer()  # Handlers used by this plugin
mIMClients          = []                         # Active IM clients
mNowPlaying         = ''                         # The currently used status message
mCurrentTrack       = None                       # The track currently being played
mSupportedIMClients = []                         # Supported IM clients: this list is populated later in the code

##############################################################################

class Pidgin :

    def __init__(self, dbusInterface) :
        """
            Constructor
        """
        self.dbusInterface = dbusInterface

    def listAccounts(self) :
        """
            Purple merges all accounts, so we return a default one
            Each account is associated with:
                * A boolean -> True if the status of this account was changed on the previous track change
        """
        return {'GenericAccount' : False}

    def setAccountStatusMsg(self, account, msg) :
        """
            Change the status message of the given account
            Why is it so complex with Purple??
            Return true if the message is successfully updated
        """
        try :
            current    = self.dbusInterface.PurpleSavedstatusGetCurrent()
            statusType = self.dbusInterface.PurpleSavedstatusGetType(current)
            statusId   = self.dbusInterface.PurplePrimitiveGetIdFromType(statusType)
            if statusId in ['available'] :
                saved = self.dbusInterface.PurpleSavedstatusNew('', statusType)
                self.dbusInterface.PurpleSavedstatusSetMessage(saved, msg)
                self.dbusInterface.PurpleSavedstatusActivate(saved)
                return True
        except :
            pass

        return False

##############################################################################

class Gaim :

    def __init__(self, dbusInterface) :
        """
            Constructor
        """
        self.dbusInterface = dbusInterface

    def listAccounts(self) :
        """
            Gaim merges all accounts, so we return a default one
            Each account is associated with:
                * A boolean -> True if the status of this account was changed on the previous track change
        """
        return {'GenericAccount' : False}

    def setAccountStatusMsg(self, account, msg) :
        """
            Change the status message of the given account
            Why is it so complex with Gaim??
            Return true if the message is successfully updated
        """
        try :
            current    = self.dbusInterface.GaimSavedstatusGetCurrent()
            statusType = self.dbusInterface.GaimSavedstatusGetType(current)
            statusId   = self.dbusInterface.GaimPrimitiveGetIdFromType(statusType)
            if statusId in ['available'] :
                saved = self.dbusInterface.GaimSavedstatusNew('', statusType)
                self.dbusInterface.GaimSavedstatusSetMessage(saved, msg)
                self.dbusInterface.GaimSavedstatusActivate(saved)
                return True
        except :
            pass

        return False

##############################################################################

class Gajim :

    def __init__(self, dbusInterface) :
        """
            Constructor
        """
        self.dbusInterface = dbusInterface

    def listAccounts(self) :
        """
            Return a list of existing accounts
            Each account is associated with:
                * A boolean -> True if the status of this account was changed on the previous track change
        """
        try :
            return dict([(account, False) for account in self.dbusInterface.list_accounts()])
        except :
            return {}

    def setAccountStatusMsg(self, account, msg) :
        """
            Change the status message of the given account
            Return true if the message is successfully updated
        """
        try :
            currentStatus = self.dbusInterface.get_status(account)
            if currentStatus in ['online', 'chat'] :
                self.dbusInterface.change_status(currentStatus, msg, account)
                return True
        except :
            pass

        return False

##############################################################################

class Gossip :

    def __init__(self, dbusInterface) :
        """
            Constructor
        """
        self.dbusInterface = dbusInterface

    def listAccounts(self) :
        """
            Gossip merges all accounts, so we return a default one
            Each account is associated with:
                * A boolean -> True if the status of this account was changed on the previous track change
        """
        return {'GenericAccount' : False}

    def setAccountStatusMsg(self, account, msg) :
        """
            Change the status message of the given account
            Return true if the message is successfully updated
        """
        try :
            currentStatus, currentMsg = self.dbusInterface.GetPresence('')
            if currentStatus in ['available'] :
                self.dbusInterface.SetPresence(currentStatus, msg)
                return True
        except :
            pass

        return False

##############################################################################

# Elements associated with each supported IM clients
(
    IM_DBUS_SERVICE_NAME,
    IM_DBUS_OBJECT_NAME,
    IM_DBUS_INTERFACE_NAME,
    IM_CLASS,
    IM_INSTANCE,
    IM_ACCOUNTS
) = range(6)


# All specific classes have been defined, so we can now populate the list of supported IM clients
mSupportedIMClients = [
    ['im.pidgin.purple.PurpleService', '/im/pidgin/purple/PurpleObject',      'im.pidgin.purple.PurpleInterface',      Pidgin,   None, {}],  # Pidgin
    ['net.sf.gaim.GaimService',        '/net/sf/gaim/GaimObject',             'net.sf.gaim.GaimInterface',             Gaim,   None, {}],  # Gaim
    ['org.gajim.dbus',                 '/org/gajim/dbus/RemoteObject',        'org.gajim.dbus.RemoteInterface',        Gajim,  None, {}],  # Gajim
    ['org.gnome.Gossip',               '/org/gnome/Gossip',                   'org.gnome.Gossip',                      Gossip, None, {}]   # Gossip
]


def setStatusMsg(nowPlaying) :
    """
        Try to update the status of all accounts of all detected IM clients
    """
    global mNowPlaying

    for client in mIMClients :
        for account,wasUpdated in client[IM_ACCOUNTS].iteritems() :
            if nowPlaying != mNowPlaying or not wasUpdated :
                client[IM_ACCOUNTS][account] = client[IM_INSTANCE].setAccountStatusMsg(account, nowPlaying)
    mNowPlaying = nowPlaying


def updateStatus() :
    """
        Information about the current track has changed
    """
    global mCurrentTrack

    mCurrentTrack = APP.player.current

    # Construct the new status message
    nowPlaying = APP.settings.get_str('format', DEFAULT_STATUS_FORMAT, PID)
    for field in TRACK_FIELDS :
        nowPlaying = nowPlaying.replace('{%s}' % field, unicode(getattr(mCurrentTrack, field)))

    if APP.player.is_paused() and APP.settings.get_boolean('updateOnPause', DEFAULT_UPDATE_ON_PAUSE, PID) :
        nowPlaying = nowPlaying + ' [paused]'

    # And try to update the status of all IM clients
    setStatusMsg(nowPlaying)


def stop_track(exaile, track) :
    """
        Called when Exaile quits or when playback stops
    """
    global mCurrentTrack

    mCurrentTrack = None

    # Stop event is sent also when a track is finished, even if Exaile is going to play the next one
    # Since we don't want to change the status between each track, we add a delay of 1 sec. before doing anything
    gobject.timeout_add(1000, onStopTimer)


def onStopTimer() :
    """
        Called 1 sec. after the reception of a stop event
    """
    global mNowPlaying

    # If mCurrentTrack is still None, it means that Exaile has not started to play another track
    # In this case, we may assume that it is really stopped
    if mCurrentTrack is None :
        mNowPlaying = ''
        if APP.settings.get_int('stopAction', DEFAULT_STOP_ACTION, PID) == CHANGE_STATUS :
            setStatusMsg(APP.settings.get_str('stopStatus', DEFAULT_STOP_STATUS, PID))
    return False


def initialize(self = None):
    """
        Called when the plugin is enabled
    """
    global mIMClients
    oldNumber      = len(mIMClients) 
    mIMClients     = []
    activeServices = dbus.SessionBus().get_object('org.freedesktop.DBus', '/org/freedesktop/DBus').ListNames()

    for client in mSupportedIMClients :
        if client[IM_DBUS_SERVICE_NAME] in activeServices :
            obj       = dbus.SessionBus().get_object(client[IM_DBUS_SERVICE_NAME], client[IM_DBUS_OBJECT_NAME])
            interface = dbus.Interface(obj, client[IM_DBUS_INTERFACE_NAME])

            client[IM_INSTANCE] = client[IM_CLASS](interface)
            client[IM_ACCOUNTS] = client[IM_INSTANCE].listAccounts()

            mIMClients.append(client)
            
            
    # If there is at least one active IM client, we can connect our handlers
    if oldNumber == 0 :
        if len(mIMClients) != 0 :
            mHandlers.connect(APP.player, 'stop-track',                 stop_track)
            mHandlers.connect(APP.player, 'pause-toggled',              lambda exaile, track : updateStatus())
            mHandlers.connect(APP,        'track-information-updated',  lambda arg : updateStatus())
    else :
        if len(mIMClients) == 0 :
            mHandlers.disconnect_all()
    
    #print "Working clients: ", mIMClients

    return True

def destroy() :
    """
        Called when the plugin is disabled
    """
    mHandlers.disconnect_all()


def showHelp(widget, data=None) :
    """
        Display a dialog box with some help about status format
    """
    msg = _('Any field of the form <b>{field}</b> will be replaced by the '
        'corresponding value.\n\nAvailable fields are ')
    for field in TRACK_FIELDS :
        msg = msg + '<i>' + field + '</i>, '
    dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
    dlg.set_markup(msg[:-2])
    dlg.run()
    dlg.destroy()


def configure() :
    """
        Called when the user wants to configure the plugin
    """
    dlg = plugins.PluginConfigDialog(APP.window, PLUGIN_NAME)

    align = gtk.Alignment()
    align.set_padding(5, 5, 5, 5)
    dlg.get_child().add(align)
    mainBox = gtk.VBox()
    mainBox.set_spacing(5)
    align.add(mainBox)

    # Upper part: status format
    frame = gtk.Frame('')
    frame.get_label_widget().set_markup(_('<b> Status message: </b>'))
    mainBox.pack_start(frame, True, True, 0)
    align = gtk.Alignment(0, 0, 1, 1)
    align.set_padding(5, 5, 5, 5)
    frame.add(align)
    tmpBox = gtk.HBox()
    tmpBox.set_spacing(5)
    entryFormat = gtk.Entry()
    entryFormat.set_text(APP.settings.get_str('format', DEFAULT_STATUS_FORMAT, PID))
    button = gtk.Button(_('Help'), gtk.STOCK_HELP)
    tmpBox.pack_start(entryFormat, True, True)
    tmpBox.pack_start(button)
    button.connect('clicked', showHelp)
    align.add(tmpBox)

    # Middle part: what should be done when Exaile quits of stops playing
    frame = gtk.Frame('')
    frame.get_label_widget().set_markup(_('<b> When Exaile stops playing or quits: </b>'))
    mainBox.pack_start(frame, False, False, 0)
    tmpBox = gtk.VBox()
    tmpBox.set_spacing(5)
    align = gtk.Alignment()
    align.set_padding(5, 5, 5, 5)
    frame.add(align)
    align.add(tmpBox)
    radioDoNothing = gtk.RadioButton(None, _('Do nothing'))
    radioSetStatus = gtk.RadioButton(radioDoNothing, _('Set status to:'))
    if APP.settings.get_int('stopAction', DEFAULT_STOP_ACTION, PID) == DO_NOTHING :
        radioDoNothing.set_active(True)
    else :
        radioSetStatus.set_active(True)
    tmpBox.pack_start(radioDoNothing, False, False, 0)
    box2 = gtk.HBox()
    entryStopStatus = gtk.Entry()
    entryStopStatus.set_text(APP.settings.get_str('stopStatus', DEFAULT_STOP_STATUS, PID))
    box2.pack_start(radioSetStatus, False, False, 0)
    box2.pack_start(entryStopStatus, False, False, 0)
    tmpBox.pack_start(box2, False, False, 0)

    # Lower part: miscellaneous
    frame = gtk.Frame('')
    frame.get_label_widget().set_markup(_('<b> Miscellaneous: </b>'))
    mainBox.pack_start(frame, False, False, 0)
    tmpBox = gtk.VBox()
    tmpBox.set_spacing(5)
    align = gtk.Alignment()
    align.set_padding(5, 5, 5, 5)
    frame.add(align)
    align.add(tmpBox)
    checkUpdateOnPause = gtk.CheckButton(_('Update status when track is paused'))
    tmpBox.pack_start(checkUpdateOnPause, False, False, 0)
    checkUpdateOnPause.set_active(APP.settings.get_boolean('updateOnPause', DEFAULT_UPDATE_ON_PAUSE, PID))
    resetButton = gtk.Button(_('Reset'))
    tmpBox.pack_start(resetButton, False, False, 0)
    resetButton.connect('clicked', initialize)

    dlg.show_all()
    result = dlg.run()
    dlg.hide()

    if result == gtk.RESPONSE_OK :
        if radioDoNothing.get_active() :
            APP.settings.set_int('stopAction', DO_NOTHING, PID)
        else :
            APP.settings.set_int('stopAction', CHANGE_STATUS, PID)
        APP.settings.set_str('stopStatus',        entryStopStatus.get_text(),      PID)
        APP.settings.set_str('format',            entryFormat.get_text(),          PID)
        APP.settings.set_boolean('updateOnPause', checkUpdateOnPause.get_active(), PID)
        if mCurrentTrack is not None :
            updateStatus()