mpris.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #!/usr/bin/env python
  2. # -*- coding: utf8 -*-
  3. #
  4. # Copyright © 2006-2011 Rafaël Carré <funman at videolanorg>
  5. #
  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.
  10. #
  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.
  15. #
  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.
  19. #
  20. #
  21. # NOTE: This controller is a SAMPLE, and thus doesn't use all the
  22. # Media Player Remote Interface Specification (MPRIS for short) capabilities
  23. #
  24. # MPRIS: http://www.mpris.org/2.1/spec/
  25. #
  26. # You'll need pygtk >= 2.12
  27. #
  28. # TODO
  29. # Ability to choose the Media Player if several are connected to the bus
  30. # core dbus stuff
  31. import dbus
  32. import dbus.glib
  33. # core interface stuff
  34. import gtk
  35. # timer
  36. from gobject import timeout_add
  37. # file loading
  38. import os
  39. global win_position # store the window position on the screen
  40. global playing
  41. playing = False
  42. global shuffle
  43. global root
  44. global player
  45. global tracklist
  46. global props
  47. global bus # Connection to the session bus
  48. mpris='org.mpris.MediaPlayer2'
  49. # If a Media Player connects to the bus, we'll use it
  50. # Note that we forget the previous Media Player we were connected to
  51. def NameOwnerChanged(name, new, old):
  52. if old != '' and mpris in name:
  53. Connect(name)
  54. def PropGet(prop):
  55. global props
  56. return props.Get(mpris + '.Player', prop)
  57. def PropSet(prop, val):
  58. global props
  59. props.Set(mpris + '.Player', prop, val)
  60. # Callback for when 'TrackChange' signal is emitted
  61. def TrackChange(Track):
  62. try:
  63. a = Track['xesam:artist']
  64. except:
  65. a = ''
  66. try:
  67. t = Track['xesam:title']
  68. except:
  69. t = Track['xesam:url']
  70. try:
  71. length = Track['mpris:length']
  72. except:
  73. length = 0
  74. if length > 0:
  75. time_s.set_range(0, length)
  76. time_s.set_sensitive(True)
  77. else:
  78. # disable the position scale if length isn't available
  79. time_s.set_sensitive(False)
  80. # update the labels
  81. l_artist.set_text(a)
  82. l_title.set_text(t)
  83. # Connects to the Media Player we detected
  84. def Connect(name):
  85. global root, player, tracklist, props
  86. global playing, shuffle
  87. root_o = bus.get_object(name, '/org/mpris/MediaPlayer2')
  88. root = dbus.Interface(root_o, mpris)
  89. tracklist = dbus.Interface(root_o, mpris + '.TrackList')
  90. player = dbus.Interface(root_o, mpris + '.Player')
  91. props = dbus.Interface(root_o, dbus.PROPERTIES_IFACE)
  92. # FIXME : doesn't exist anymore in mpris 2.1
  93. # connect to the TrackChange signal
  94. # root_o.connect_to_signal('TrackChange', TrackChange, dbus_interface=mpris)
  95. # determine if the Media Player is playing something
  96. if PropGet('PlaybackStatus') == 'Playing':
  97. playing = True
  98. TrackChange(PropGet('Metadata'))
  99. window.set_title(props.Get(mpris, 'Identity'))
  100. #plays an element
  101. def AddTrack(widget):
  102. mrl = e_mrl.get_text()
  103. if mrl != None and mrl != '':
  104. tracklist.AddTrack(mrl, '/', True)
  105. e_mrl.set_text('')
  106. else:
  107. mrl = bt_file.get_filename()
  108. if mrl != None and mrl != '':
  109. tracklist.AddTrack('directory://' + mrl, '/', True)
  110. update(0)
  111. # basic control
  112. def Next(widget):
  113. player.Next(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
  114. update(0)
  115. def Prev(widget):
  116. player.Prev(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
  117. update(0)
  118. def Stop(widget):
  119. player.Stop(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
  120. update(0)
  121. def Quit(widget):
  122. global props
  123. if props.Get(mpris, 'CanQuit'):
  124. root.Quit(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
  125. l_title.set_text('')
  126. window.set_title('')
  127. def Pause(widget):
  128. player.PlayPause()
  129. if PropGet('PlaybackStatus') == 'Playing':
  130. icon = gtk.STOCK_MEDIA_PAUSE
  131. else:
  132. icon = gtk.STOCK_MEDIA_PLAY
  133. img_bt_toggle.set_from_stock(icon, gtk.ICON_SIZE_SMALL_TOOLBAR)
  134. update(0)
  135. def Shuffle(widget):
  136. global shuffle
  137. shuffle = not shuffle
  138. PropSet('Shuffle', shuffle)
  139. # update status display
  140. def update(widget):
  141. Track = PropGet('Metadata')
  142. vol.set_value(PropGet('Volume') * 100.0)
  143. TrackChange(Track)
  144. GetPlayStatus(0)
  145. # callback for volume change
  146. def volchange(widget):
  147. PropSet('Volume', vol.get_value_as_int() / 100.0)
  148. # callback for position change
  149. def timechange(widget, x=None, y=None):
  150. player.SetPosition(PropGet('Metadata')['mpris:trackid'],
  151. time_s.get_value(),
  152. reply_handler=(lambda *args: None),
  153. error_handler=(lambda *args: None))
  154. # refresh position change
  155. def timeset():
  156. global playing
  157. if playing == True:
  158. try:
  159. time_s.set_value(PropGet('Position'))
  160. except:
  161. playing = False
  162. return True
  163. # toggle simple/full display
  164. def expander(widget):
  165. if exp.get_expanded() == False:
  166. exp.set_label('Less')
  167. else:
  168. exp.set_label('More')
  169. # close event : hide in the systray
  170. def delete_event(self, widget):
  171. self.hide()
  172. return True
  173. # shouldn't happen
  174. def destroy(widget):
  175. gtk.main_quit()
  176. # hide the controller when 'Esc' is pressed
  177. def key_release(widget, event):
  178. if event.keyval == gtk.keysyms.Escape:
  179. global win_position
  180. win_position = window.get_position()
  181. widget.hide()
  182. # callback for click on the tray icon
  183. def tray_button(widget):
  184. global win_position
  185. if window.get_property('visible'):
  186. # store position
  187. win_position = window.get_position()
  188. window.hide()
  189. else:
  190. # restore position
  191. window.move(win_position[0], win_position[1])
  192. window.show()
  193. # hack: update position, volume, and metadata
  194. def icon_clicked(widget, event):
  195. update(0)
  196. # get playing status, modify the Play/Pause button accordingly
  197. def GetPlayStatus(widget):
  198. global playing
  199. global shuffle
  200. playing = PropGet('PlaybackStatus') == 'Playing'
  201. if playing:
  202. img_bt_toggle.set_from_stock('gtk-media-pause', gtk.ICON_SIZE_SMALL_TOOLBAR)
  203. else:
  204. img_bt_toggle.set_from_stock('gtk-media-play', gtk.ICON_SIZE_SMALL_TOOLBAR)
  205. shuffle = PropGet('Shuffle')
  206. bt_shuffle.set_active( shuffle )
  207. # loads UI file from the directory where the script is,
  208. # so we can use /path/to/mpris.py to execute it.
  209. import sys
  210. xml = gtk.Builder()
  211. gtk.Builder.add_from_file(xml, os.path.join(os.path.dirname(sys.argv[0]) , 'mpris.xml'))
  212. # ui setup
  213. bt_close = xml.get_object('close')
  214. bt_quit = xml.get_object('quit')
  215. bt_file = xml.get_object('ChooseFile')
  216. bt_next = xml.get_object('next')
  217. bt_prev = xml.get_object('prev')
  218. bt_stop = xml.get_object('stop')
  219. bt_toggle = xml.get_object('toggle')
  220. bt_mrl = xml.get_object('AddMRL')
  221. bt_shuffle = xml.get_object('shuffle')
  222. l_artist = xml.get_object('l_artist')
  223. l_title = xml.get_object('l_title')
  224. e_mrl = xml.get_object('mrl')
  225. window = xml.get_object('window1')
  226. img_bt_toggle=xml.get_object('image6')
  227. exp = xml.get_object('expander2')
  228. expvbox = xml.get_object('expandvbox')
  229. audioicon = xml.get_object('eventicon')
  230. vol = xml.get_object('vol')
  231. time_s = xml.get_object('time_s')
  232. time_l = xml.get_object('time_l')
  233. # connect to the different callbacks
  234. window.connect('delete_event', delete_event)
  235. window.connect('destroy', destroy)
  236. window.connect('key_release_event', key_release)
  237. tray = gtk.status_icon_new_from_icon_name('audio-x-generic')
  238. tray.connect('activate', tray_button)
  239. bt_close.connect('clicked', destroy)
  240. bt_quit.connect('clicked', Quit)
  241. bt_mrl.connect('clicked', AddTrack)
  242. bt_toggle.connect('clicked', Pause)
  243. bt_next.connect('clicked', Next)
  244. bt_prev.connect('clicked', Prev)
  245. bt_stop.connect('clicked', Stop)
  246. bt_shuffle.connect('clicked', Shuffle)
  247. exp.connect('activate', expander)
  248. vol.connect('changed', volchange)
  249. time_s.connect('adjust-bounds', timechange)
  250. audioicon.set_events(gtk.gdk.BUTTON_PRESS_MASK) # hack for the bottom right icon
  251. audioicon.connect('button_press_event', icon_clicked)
  252. time_s.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
  253. library = '/media/mp3' # editme
  254. # set the Directory chooser to a default location
  255. try:
  256. os.chdir(library)
  257. bt_file.set_current_folder(library)
  258. except:
  259. bt_file.set_current_folder(os.path.expanduser('~'))
  260. # connect to the bus
  261. bus = dbus.SessionBus()
  262. dbus_names = bus.get_object( 'org.freedesktop.DBus', '/org/freedesktop/DBus' )
  263. dbus_names.connect_to_signal('NameOwnerChanged', NameOwnerChanged, dbus_interface='org.freedesktop.DBus') # to detect new Media Players
  264. dbus_o = bus.get_object('org.freedesktop.DBus', '/')
  265. dbus_intf = dbus.Interface(dbus_o, 'org.freedesktop.DBus')
  266. # connect to the first Media Player found
  267. for n in dbus_intf.ListNames():
  268. if mpris in n:
  269. Connect(n)
  270. vol.set_value(PropGet('Volume') * 100.0)
  271. update(0)
  272. break
  273. # run a timer to update position
  274. timeout_add( 1000, timeset)
  275. window.set_icon_name('audio-x-generic')
  276. window.show()
  277. window.set_icon(gtk.icon_theme_get_default().load_icon('audio-x-generic',24,0))
  278. win_position = window.get_position()
  279. gtk.main() # execute the main loop