4 # Copyright © 2006-2007 Rafaël Carré <funman at videolanorg>
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.
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.
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.
24 # NOTE: This controller is a SAMPLE, and thus doesn't use all the
25 # Media Player Remote Interface Specification (MPRIS for short) capabilities
27 # MPRIS: http://wiki.xmms2.xmms.se/index.php/Media_Player_Interfaces
29 # You'll need pygtk >= 2.10 to use gtk.StatusIcon
32 # Ability to choose the Media Player if several are connected to the bus
38 # core interface stuff
48 global win_position
# store the window position on the screen
53 global shuffle
# playlist will play randomly
54 global repeat
# repeat the playlist
55 global loop
# loop the current element
57 # mpris doesn't support getting the status of these (at the moment)
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
67 global bus
# Connection to the session bus
68 global identity
# MediaPlayer Identity
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
:
77 # Callback for when "TrackChange" signal is emitted
78 def TrackChange(Track
):
79 # the only mandatory metadata is "URI"
89 length
= Track
["length"]
93 time_s
.set_range(0,Track
["length"])
94 time_s
.set_sensitive(True)
96 # disable the position scale if length isn't available
97 time_s
.set_sensitive(False)
102 # Connects to the Media Player we detected
104 global root
, player
, tracklist
105 global playing
, identity
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")
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")
117 # connect to the TrackChange signal
118 player_o
.connect_to_signal("TrackChange", TrackChange
, dbus_interface
="org.freedesktop.MediaPlayer")
120 # determine if the Media Player is playing something
121 if player
.GetStatus() == 0:
123 TrackChange(player
.GetMetadata())
125 # gets its identity (name and version)
126 identity
= root
.Identity()
127 window
.set_title(identity
)
130 def AddTrack(widget
):
131 mrl
= e_mrl
.get_text()
132 if mrl
!= None and mrl
!= "":
133 tracklist
.AddTrack(mrl
, True)
136 mrl
= bt_file
.get_filename()
137 if mrl
!= None and mrl
!= "":
138 tracklist
.AddTrack("directory://" + mrl
, True)
144 player
.Next(reply_handler
=(lambda *args
: None), error_handler
=(lambda *args
: None))
148 player
.Prev(reply_handler
=(lambda *args
: None), error_handler
=(lambda *args
: None))
152 player
.Stop(reply_handler
=(lambda *args
: None), error_handler
=(lambda *args
: None))
156 player
.Quit(reply_handler
=(lambda *args
: None), error_handler
=(lambda *args
: None))
161 status
= player
.GetStatus()
163 img_bt_toggle
.set_from_stock(gtk
.STOCK_MEDIA_PAUSE
, gtk
.ICON_SIZE_SMALL_TOOLBAR
)
165 img_bt_toggle
.set_from_stock(gtk
.STOCK_MEDIA_PLAY
, gtk
.ICON_SIZE_SMALL_TOOLBAR
)
171 player
.Repeat(repeat
)
175 shuffle
= not shuffle
176 tracklist
.Random(shuffle
)
183 # update status display
185 Track
= player
.GetMetadata()
186 vol
.set_value(player
.VolumeGet())
203 length
= Track
["length"]
207 time_s
.set_range(0,Track
["length"])
208 time_s
.set_sensitive(True)
210 # disable the position scale if length isn't available
211 time_s
.set_sensitive(False)
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))
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))
222 # refresh position change
227 time_s
.set_value(player
.PositionGet())
232 # toggle simple/full display
233 def expander(widget
):
234 if exp
.get_expanded() == False:
235 exp
.set_label("Less")
237 exp
.set_label("More")
239 # close event : hide in the systray
240 def delete_event(self
, widget
):
248 # hide the controller when 'Esc' is pressed
249 def key_release(widget
, event
):
250 if event
.keyval
== gtk
.keysyms
.Escape
:
252 win_position
= window
.get_position()
255 # callback for click on the tray icon
256 def tray_button(widget
):
258 if window
.get_property('visible'):
260 win_position
= window
.get_position()
264 window
.move(win_position
[0], win_position
[1])
267 # hack: update position, volume, and metadata
268 def icon_clicked(widget
, event
):
271 # get playing status, modify the Play/Pause button accordingly
272 def GetPlayStatus(widget
):
277 status
= player
.GetStatus()
279 playing
= status
[0] == 0
281 img_bt_toggle
.set_from_stock("gtk-media-pause", gtk
.ICON_SIZE_SMALL_TOOLBAR
)
283 img_bt_toggle
.set_from_stock("gtk-media-play", gtk
.ICON_SIZE_SMALL_TOOLBAR
)
284 shuffle
= status
[1] == 1
285 bt_shuffle
.set_active( shuffle
)
286 loop
= status
[2] == 1
287 bt_loop
.set_active( loop
)
288 repeat
= status
[3] == 1
289 bt_repeat
.set_active( repeat
)
290 # loads glade file from the directory where the script is,
291 # so we can use /path/to/mpris.py to execute it.
293 xml
= gtk
.glade
.XML(os
.path
.dirname(sys
.argv
[0]) + '/mpris.glade')
296 bt_close
= xml
.get_widget('close')
297 bt_quit
= xml
.get_widget('quit')
298 bt_file
= xml
.get_widget('ChooseFile')
299 bt_next
= xml
.get_widget('next')
300 bt_prev
= xml
.get_widget('prev')
301 bt_stop
= xml
.get_widget('stop')
302 bt_toggle
= xml
.get_widget('toggle')
303 bt_mrl
= xml
.get_widget('AddMRL')
304 bt_shuffle
= xml
.get_widget('shuffle')
305 bt_repeat
= xml
.get_widget('repeat')
306 bt_loop
= xml
.get_widget('loop')
307 l_artist
= xml
.get_widget('l_artist')
308 l_title
= xml
.get_widget('l_title')
309 e_mrl
= xml
.get_widget('mrl')
310 window
= xml
.get_widget('window1')
311 img_bt_toggle
=xml
.get_widget('image6')
312 exp
= xml
.get_widget('expander2')
313 expvbox
= xml
.get_widget('expandvbox')
314 audioicon
= xml
.get_widget('eventicon')
315 vol
= xml
.get_widget('vol')
316 time_s
= xml
.get_widget('time_s')
317 time_l
= xml
.get_widget('time_l')
319 # connect to the different callbacks
321 window
.connect('delete_event', delete_event
)
322 window
.connect('destroy', destroy
)
323 window
.connect('key_release_event', key_release
)
325 tray
= gtk
.status_icon_new_from_icon_name("audio-x-generic")
326 tray
.connect('activate', tray_button
)
328 bt_close
.connect('clicked', destroy
)
329 bt_quit
.connect('clicked', Quit
)
330 bt_mrl
.connect('clicked', AddTrack
)
331 bt_toggle
.connect('clicked', Pause
)
332 bt_next
.connect('clicked', Next
)
333 bt_prev
.connect('clicked', Prev
)
334 bt_stop
.connect('clicked', Stop
)
335 bt_loop
.connect('clicked', Loop
)
336 bt_repeat
.connect('clicked', Repeat
)
337 bt_shuffle
.connect('clicked', Shuffle
)
338 exp
.connect('activate', expander
)
339 vol
.connect('change-value', volchange
)
340 vol
.connect('scroll-event', volchange
)
341 time_s
.connect('adjust-bounds', timechange
)
342 audioicon
.set_events(gtk
.gdk
.BUTTON_PRESS_MASK
) # hack for the bottom right icon
343 audioicon
.connect('button_press_event', icon_clicked
)
344 time_s
.set_update_policy(gtk
.UPDATE_DISCONTINUOUS
)
346 library
= "/media/mp3" # editme
348 # set the Directory chooser to a default location
351 bt_file
.set_current_folder(library
)
353 bt_file
.set_current_folder(os
.path
.expanduser("~"))
356 bus
= dbus
.SessionBus()
357 dbus_names
= bus
.get_object( "org.freedesktop.DBus", "/org/freedesktop/DBus" )
358 dbus_names
.connect_to_signal("NameOwnerChanged", NameOwnerChanged
, dbus_interface
="org.freedesktop.DBus") # to detect new Media Players
360 dbus_o
= bus
.get_object("org.freedesktop.DBus", "/")
361 dbus_intf
= dbus
.Interface(dbus_o
, "org.freedesktop.DBus")
362 name_list
= dbus_intf
.ListNames()
364 # connect to the first Media Player found
366 if "org.mpris." in n
:
368 window
.set_title(identity
)
369 vol
.set_value(player
.VolumeGet())
373 # run a timer to update position
374 gobject
.timeout_add( 1000, timeset
)
376 window
.set_icon_name('audio-x-generic')
379 icon_theme
= gtk
.icon_theme_get_default()
381 pix
= icon_theme
.load_icon("audio-x-generic",24,0)
386 win_position
= window
.get_position()
388 gtk
.main() # execute the main loop