Build VLC aliases cleanly (don't need to parse argv[0])
[vlc.git] / extras / mpris.py
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 #
4 # Copyright © 2006-2007 Rafaël Carré <funman at videolanorg>
5 #
6 # $Id$
7
8 #  This program is free software; you can redistribute it and/or modify
9 #  it under the terms of the GNU General Public License as published by
10 #  the Free Software Foundation; either version 2 of the License, or
11 #  (at your option) any later version.
12
13 #  This program is distributed in the hope that it will be useful,
14 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #  GNU General Public License for more details.
17
18 #  You should have received a copy of the GNU General Public License
19 #  along with this program; if not, write to the Free Software
20 #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 #
22
23 #
24 # NOTE: This controller is a SAMPLE, and thus doesn't use all the
25 # Media Player Remote Interface Specification (MPRIS for short) capabilities
26 #
27 # MPRIS:  http://wiki.xmms2.xmms.se/index.php/Media_Player_Interfaces
28 #
29 # You'll need pygtk >= 2.10 to use gtk.StatusIcon
30 #
31 # TODO
32 #   Ability to choose the Media Player if several are connected to the bus
33
34 # core dbus stuff
35 import dbus
36 import dbus.glib
37
38 # core interface stuff
39 import gtk
40 import gtk.glade
41
42 # timer
43 import gobject
44
45 # file loading
46 import os
47
48 global win_position # store the window position on the screen
49
50 global playing
51 playing = False
52
53 global shuffle      # playlist will play randomly
54 global repeat       # repeat the playlist
55 global loop         # loop the current element
56
57 # mpris doesn't support getting the status of these (at the moment)
58 shuffle = False
59 repeat = False
60 loop = False
61
62 # these are defined on the mpris detected unique name
63 global root         # / org.freedesktop.MediaPlayer
64 global player       # /Player org.freedesktop.MediaPlayer
65 global tracklist    # /Tracklist org.freedesktop.MediaPlayer
66
67 global bus          # Connection to the session bus
68 global identity     # MediaPlayer Identity
69
70
71 # If a Media Player connects to the bus, we'll use it
72 # Note that we forget the previous Media Player we were connected to
73 def NameOwnerChanged(name, new, old):
74     if old != "" and "org.mpris." in name:
75         Connect(name)
76
77 # Callback for when "TrackChange" signal is emitted
78 def TrackChange(Track):
79     # the only mandatory metadata is "URI"
80     try:
81         a = Track["artist"]
82     except:
83         a = ""
84     try:
85         t = Track["title"]
86     except:
87         t = Track["URI"]
88     try:
89         length = Track["length"]
90     except:
91         length = 0
92     if length > 0:
93         time_s.set_range(0,Track["length"])
94         time_s.set_sensitive(True)
95     else:
96         # disable the position scale if length isn't available
97         time_s.set_sensitive(False)
98     # update the labels
99     l_artist.set_text(a)
100     l_title.set_text(t)
101
102 # Connects to the Media Player we detected
103 def Connect(name):
104     global root, player, tracklist
105     global playing, identity
106
107     # first we connect to the objects
108     root_o = bus.get_object(name, "/")
109     player_o = bus.get_object(name, "/Player")
110     tracklist_o = bus.get_object(name, "/TrackList")
111
112     # there is only 1 interface per object
113     root = dbus.Interface(root_o, "org.freedesktop.MediaPlayer")
114     tracklist  = dbus.Interface(tracklist_o, "org.freedesktop.MediaPlayer")
115     player = dbus.Interface(player_o, "org.freedesktop.MediaPlayer")
116
117     # connect to the TrackChange signal
118     player_o.connect_to_signal("TrackChange", TrackChange, dbus_interface="org.freedesktop.MediaPlayer")
119
120     # determine if the Media Player is playing something
121     if player.GetStatus() == 0:
122         playing = True
123         TrackChange(player.GetMetadata())
124
125     # gets its identity (name and version)
126     identity = root.Identity()
127     window.set_title(identity)
128
129 #plays an element
130 def AddTrack(widget):
131     mrl = e_mrl.get_text()
132     if mrl != None and mrl != "":
133         tracklist.AddTrack(mrl, True)
134         e_mrl.set_text('')
135     else:
136         mrl = bt_file.get_filename()
137         if mrl != None and mrl != "":
138             tracklist.AddTrack("directory://" + mrl, True)
139     update(0)
140
141 # basic control
142
143 def Next(widget):
144     player.Next(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
145     update(0)
146
147 def Prev(widget):
148     player.Prev(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
149     update(0)
150
151 def Stop(widget):
152     player.Stop(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
153     update(0)
154
155 def Quit(widget):
156     player.Quit(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
157     l_title.set_text("")
158
159 def Pause(widget):
160     player.Pause()
161     status = player.GetStatus()
162     if status == 0:
163         img_bt_toggle.set_from_stock(gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_SMALL_TOOLBAR)
164     else:
165         img_bt_toggle.set_from_stock(gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_SMALL_TOOLBAR)
166     update(0)
167
168 def Repeat(widget):
169     global repeat
170     repeat = not repeat
171     player.Repeat(repeat)
172
173 def Shuffle(widget):
174     global shuffle
175     shuffle = not shuffle
176     tracklist.Random(shuffle)
177
178 def Loop(widget):
179     global loop
180     loop = not loop
181     tracklist.Loop(loop)
182
183 # update status display
184 def update(widget):
185     Track = player.GetMetadata()
186     vol.set_value(player.VolumeGet())
187     try: 
188         a = Track["artist"]
189     except:
190         a = ""
191     try:
192         t = Track["title"]
193     except:        
194         t = ""
195     if t == "":
196         try:
197             t = Track["URI"]
198         except:
199             t = ""
200     l_artist.set_text(a)
201     l_title.set_text(t)
202     try:
203         length = Track["length"]
204     except:
205         length = 0
206     if length > 0:
207         time_s.set_range(0,Track["length"])
208         time_s.set_sensitive(True)
209     else:
210         # disable the position scale if length isn't available
211         time_s.set_sensitive(False)
212     GetPlayStatus(0)
213
214 # callback for volume change
215 def volchange(widget, data):
216     player.VolumeSet(vol.get_value_as_int(), reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
217
218 # callback for position change
219 def timechange(widget, x=None, y=None):
220     player.PositionSet(int(time_s.get_value()), reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
221
222 # refresh position change
223 def timeset():
224     global playing
225     if playing == True:
226         try:
227             time_s.set_value(player.PositionGet())
228         except:
229             playing = False
230     return True
231
232 # toggle simple/full display
233 def expander(widget):
234     if exp.get_expanded() == False:
235         exp.set_label("Less")
236     else:
237         exp.set_label("More")
238
239 # close event : hide in the systray
240 def delete_event(self, widget):
241     self.hide()
242     return True
243
244 # shouldn't happen
245 def destroy(widget):
246     gtk.main_quit()
247
248 # hide the controller when 'Esc' is pressed
249 def key_release(widget, event):
250     if event.keyval == gtk.keysyms.Escape:
251         global win_position
252         win_position = window.get_position()
253         widget.hide()
254
255 # callback for click on the tray icon
256 def tray_button(widget):
257     global win_position
258     if window.get_property('visible'):
259         # store position
260         win_position = window.get_position()
261         window.hide()
262     else:
263         # restore position
264         window.move(win_position[0], win_position[1])
265         window.show()
266
267 # hack: update position, volume, and metadata
268 def icon_clicked(widget, event):
269     update(0)
270
271 # get playing status, modify the Play/Pause button accordingly
272 def GetPlayStatus(widget):
273     global playing
274     status = player.GetStatus()
275     if status == 0:
276         img_bt_toggle.set_from_stock("gtk-media-pause", gtk.ICON_SIZE_SMALL_TOOLBAR)
277         playing = True
278     else:
279         img_bt_toggle.set_from_stock("gtk-media-play", gtk.ICON_SIZE_SMALL_TOOLBAR)
280         playing = False
281
282 # loads glade file from the directory where the script is,
283 # so we can use /path/to/mpris.py to execute it.
284 import sys
285 xml = gtk.glade.XML(os.path.dirname(sys.argv[0]) + '/mpris.glade')
286
287 # ui setup
288 bt_close    = xml.get_widget('close')
289 bt_quit     = xml.get_widget('quit')
290 bt_file     = xml.get_widget('ChooseFile')
291 bt_next     = xml.get_widget('next')
292 bt_prev     = xml.get_widget('prev')
293 bt_stop     = xml.get_widget('stop')
294 bt_toggle   = xml.get_widget('toggle')
295 bt_mrl      = xml.get_widget('AddMRL')
296 bt_shuffle  = xml.get_widget('shuffle')
297 bt_repeat   = xml.get_widget('repeat')
298 bt_loop     = xml.get_widget('loop')
299 l_artist    = xml.get_widget('l_artist')
300 l_title     = xml.get_widget('l_title')
301 e_mrl       = xml.get_widget('mrl')
302 window      = xml.get_widget('window1')
303 img_bt_toggle=xml.get_widget('image6')
304 exp         = xml.get_widget('expander2')
305 expvbox     = xml.get_widget('expandvbox')
306 audioicon   = xml.get_widget('eventicon')
307 vol         = xml.get_widget('vol')
308 time_s      = xml.get_widget('time_s')
309 time_l      = xml.get_widget('time_l')
310
311 # connect to the different callbacks
312
313 window.connect('delete_event',  delete_event)
314 window.connect('destroy',       destroy)
315 window.connect('key_release_event', key_release)
316
317 tray = gtk.status_icon_new_from_icon_name("audio-x-generic")
318 tray.connect('activate', tray_button)
319
320 bt_close.connect('clicked',     destroy)
321 bt_quit.connect('clicked',      Quit)
322 bt_mrl.connect('clicked',       AddTrack)
323 bt_toggle.connect('clicked',    Pause)
324 bt_next.connect('clicked',      Next)
325 bt_prev.connect('clicked',      Prev)
326 bt_stop.connect('clicked',      Stop)
327 bt_loop.connect('clicked',      Loop)
328 bt_repeat.connect('clicked',    Repeat)
329 bt_shuffle.connect('clicked',   Shuffle)
330 exp.connect('activate',         expander)
331 vol.connect('change-value',     volchange)
332 vol.connect('scroll-event',     volchange)
333 time_s.connect('adjust-bounds', timechange)
334 audioicon.set_events(gtk.gdk.BUTTON_PRESS_MASK) # hack for the bottom right icon
335 audioicon.connect('button_press_event', icon_clicked) 
336 time_s.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
337
338 library = "/media/mp3" # editme
339
340 # set the Directory chooser to a default location
341 try:
342     os.chdir(library)
343     bt_file.set_current_folder(library)
344 except:
345     bt_file.set_current_folder(os.path.expanduser("~"))
346
347 # connect to the bus
348 bus = dbus.SessionBus()
349 dbus_names = bus.get_object( "org.freedesktop.DBus", "/org/freedesktop/DBus" )
350 dbus_names.connect_to_signal("NameOwnerChanged", NameOwnerChanged, dbus_interface="org.freedesktop.DBus") # to detect new Media Players
351
352 dbus_o = bus.get_object("org.freedesktop.DBus", "/")
353 dbus_intf = dbus.Interface(dbus_o, "org.freedesktop.DBus")
354 name_list = dbus_intf.ListNames()
355
356 # connect to the first Media Player found
357 for n in name_list:
358     if "org.mpris." in n:
359         Connect(n)
360         window.set_title(identity)
361         vol.set_value(player.VolumeGet())
362         update(0)
363         break
364
365 # run a timer to update position
366 gobject.timeout_add( 1000, timeset)
367
368 window.set_icon_name('audio-x-generic')
369 window.show()
370
371 icon_theme = gtk.icon_theme_get_default()
372 try:
373     pix = icon_theme.load_icon("audio-x-generic",24,0)
374     window.set_icon(pix)
375 except:
376     True
377
378 win_position = window.get_position()
379
380 gtk.main() # execute the main loop