4 # Copyright © 2006-2011 Rafaël Carré <funman at videolanorg>
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 # NOTE: This controller is a SAMPLE, and thus doesn't use all the
23 # Media Player Remote Interface Specification (MPRIS for short) capabilities
25 # MPRIS: http://www.mpris.org/2.1/spec/
27 # You'll need pygtk >= 2.12
30 # Ability to choose the Media Player if several are connected to the bus
36 # core interface stuff
40 from gobject
import timeout_add
45 global win_position
# store the window position on the screen
57 global bus
# Connection to the session bus
59 mpris
='org.mpris.MediaPlayer2'
61 # If a Media Player connects to the bus, we'll use it
62 # Note that we forget the previous Media Player we were connected to
63 def NameOwnerChanged(name
, new
, old
):
64 if old
!= '' and mpris
in name
:
69 return props
.Get(mpris
+ '.Player', prop
)
71 def PropSet(prop
, val
):
73 props
.Set(mpris
+ '.Player', prop
, val
)
75 # Callback for when 'TrackChange' signal is emitted
76 def TrackChange(Track
):
78 a
= Track
['xesam:artist']
82 t
= Track
['xesam:title']
84 t
= Track
['xesam:url']
86 length
= Track
['mpris:length']
90 time_s
.set_range(0, length
)
91 time_s
.set_sensitive(True)
93 # disable the position scale if length isn't available
94 time_s
.set_sensitive(False)
99 # Connects to the Media Player we detected
101 global root
, player
, tracklist
, props
102 global playing
, shuffle
104 root_o
= bus
.get_object(name
, '/org/mpris/MediaPlayer2')
105 root
= dbus
.Interface(root_o
, mpris
)
106 tracklist
= dbus
.Interface(root_o
, mpris
+ '.TrackList')
107 player
= dbus
.Interface(root_o
, mpris
+ '.Player')
108 props
= dbus
.Interface(root_o
, dbus
.PROPERTIES_IFACE
)
110 # FIXME : doesn't exist anymore in mpris 2.1
111 # connect to the TrackChange signal
112 # root_o.connect_to_signal('TrackChange', TrackChange, dbus_interface=mpris)
114 # determine if the Media Player is playing something
115 if PropGet('PlaybackStatus') == 'Playing':
117 TrackChange(PropGet('Metadata'))
119 window
.set_title(props
.Get(mpris
, 'Identity'))
122 def AddTrack(widget
):
123 mrl
= e_mrl
.get_text()
124 if mrl
!= None and mrl
!= '':
125 tracklist
.AddTrack(mrl
, '/', True)
128 mrl
= bt_file
.get_filename()
129 if mrl
!= None and mrl
!= '':
130 tracklist
.AddTrack('directory://' + mrl
, '/', True)
136 player
.Next(reply_handler
=(lambda *args
: None), error_handler
=(lambda *args
: None))
140 player
.Prev(reply_handler
=(lambda *args
: None), error_handler
=(lambda *args
: None))
144 player
.Stop(reply_handler
=(lambda *args
: None), error_handler
=(lambda *args
: None))
149 if props
.Get(mpris
, 'CanQuit'):
150 root
.Quit(reply_handler
=(lambda *args
: None), error_handler
=(lambda *args
: None))
156 if PropGet('PlaybackStatus') == 'Playing':
157 icon
= gtk
.STOCK_MEDIA_PAUSE
159 icon
= gtk
.STOCK_MEDIA_PLAY
160 img_bt_toggle
.set_from_stock(icon
, gtk
.ICON_SIZE_SMALL_TOOLBAR
)
165 shuffle
= not shuffle
166 PropSet('Shuffle', shuffle
)
168 # update status display
170 Track
= PropGet('Metadata')
171 vol
.set_value(PropGet('Volume') * 100.0)
175 # callback for volume change
176 def volchange(widget
):
177 PropSet('Volume', vol
.get_value_as_int() / 100.0)
179 # callback for position change
180 def timechange(widget
, x
=None, y
=None):
181 player
.SetPosition(PropGet('Metadata')['mpris:trackid'],
183 reply_handler
=(lambda *args
: None),
184 error_handler
=(lambda *args
: None))
186 # refresh position change
191 time_s
.set_value(PropGet('Position'))
196 # toggle simple/full display
197 def expander(widget
):
198 if exp
.get_expanded() == False:
199 exp
.set_label('Less')
201 exp
.set_label('More')
203 # close event : hide in the systray
204 def delete_event(self
, widget
):
212 # hide the controller when 'Esc' is pressed
213 def key_release(widget
, event
):
214 if event
.keyval
== gtk
.keysyms
.Escape
:
216 win_position
= window
.get_position()
219 # callback for click on the tray icon
220 def tray_button(widget
):
222 if window
.get_property('visible'):
224 win_position
= window
.get_position()
228 window
.move(win_position
[0], win_position
[1])
231 # hack: update position, volume, and metadata
232 def icon_clicked(widget
, event
):
235 # get playing status, modify the Play/Pause button accordingly
236 def GetPlayStatus(widget
):
240 playing
= PropGet('PlaybackStatus') == 'Playing'
242 img_bt_toggle
.set_from_stock('gtk-media-pause', gtk
.ICON_SIZE_SMALL_TOOLBAR
)
244 img_bt_toggle
.set_from_stock('gtk-media-play', gtk
.ICON_SIZE_SMALL_TOOLBAR
)
245 shuffle
= PropGet('Shuffle')
246 bt_shuffle
.set_active( shuffle
)
248 # loads UI file from the directory where the script is,
249 # so we can use /path/to/mpris.py to execute it.
252 gtk
.Builder
.add_from_file(xml
, os
.path
.join(os
.path
.dirname(sys
.argv
[0]) , 'mpris.xml'))
255 bt_close
= xml
.get_object('close')
256 bt_quit
= xml
.get_object('quit')
257 bt_file
= xml
.get_object('ChooseFile')
258 bt_next
= xml
.get_object('next')
259 bt_prev
= xml
.get_object('prev')
260 bt_stop
= xml
.get_object('stop')
261 bt_toggle
= xml
.get_object('toggle')
262 bt_mrl
= xml
.get_object('AddMRL')
263 bt_shuffle
= xml
.get_object('shuffle')
264 l_artist
= xml
.get_object('l_artist')
265 l_title
= xml
.get_object('l_title')
266 e_mrl
= xml
.get_object('mrl')
267 window
= xml
.get_object('window1')
268 img_bt_toggle
=xml
.get_object('image6')
269 exp
= xml
.get_object('expander2')
270 expvbox
= xml
.get_object('expandvbox')
271 audioicon
= xml
.get_object('eventicon')
272 vol
= xml
.get_object('vol')
273 time_s
= xml
.get_object('time_s')
274 time_l
= xml
.get_object('time_l')
276 # connect to the different callbacks
278 window
.connect('delete_event', delete_event
)
279 window
.connect('destroy', destroy
)
280 window
.connect('key_release_event', key_release
)
282 tray
= gtk
.status_icon_new_from_icon_name('audio-x-generic')
283 tray
.connect('activate', tray_button
)
285 bt_close
.connect('clicked', destroy
)
286 bt_quit
.connect('clicked', Quit
)
287 bt_mrl
.connect('clicked', AddTrack
)
288 bt_toggle
.connect('clicked', Pause
)
289 bt_next
.connect('clicked', Next
)
290 bt_prev
.connect('clicked', Prev
)
291 bt_stop
.connect('clicked', Stop
)
292 bt_shuffle
.connect('clicked', Shuffle
)
293 exp
.connect('activate', expander
)
294 vol
.connect('changed', volchange
)
295 time_s
.connect('adjust-bounds', timechange
)
296 audioicon
.set_events(gtk
.gdk
.BUTTON_PRESS_MASK
) # hack for the bottom right icon
297 audioicon
.connect('button_press_event', icon_clicked
)
298 time_s
.set_update_policy(gtk
.UPDATE_DISCONTINUOUS
)
300 library
= '/media/mp3' # editme
302 # set the Directory chooser to a default location
305 bt_file
.set_current_folder(library
)
307 bt_file
.set_current_folder(os
.path
.expanduser('~'))
310 bus
= dbus
.SessionBus()
311 dbus_names
= bus
.get_object( 'org.freedesktop.DBus', '/org/freedesktop/DBus' )
312 dbus_names
.connect_to_signal('NameOwnerChanged', NameOwnerChanged
, dbus_interface
='org.freedesktop.DBus') # to detect new Media Players
314 dbus_o
= bus
.get_object('org.freedesktop.DBus', '/')
315 dbus_intf
= dbus
.Interface(dbus_o
, 'org.freedesktop.DBus')
317 # connect to the first Media Player found
318 for n
in dbus_intf
.ListNames():
321 vol
.set_value(PropGet('Volume') * 100.0)
325 # run a timer to update position
326 timeout_add( 1000, timeset
)
328 window
.set_icon_name('audio-x-generic')
331 window
.set_icon(gtk
.icon_theme_get_default().load_icon('audio-x-generic',24,0))
332 win_position
= window
.get_position()
334 gtk
.main() # execute the main loop