-#!/usr/bin/python
+#!/usr/bin/env python
# -*- coding: utf8 -*-
#
-# Copyright (C) 2006 Rafaël Carré <funman at videolanorg>
+# Copyright © 2006-2007 Rafaël Carré <funman at videolanorg>
#
# $Id$
#
#
#
-# NOTE: this controller is a SAMPLE, and thus doesn't implement all the D-Bus Media Player specification. http://wiki.videolan.org/index.php/DBus-spec
-# This is an unfinished document (on the 12/06/2006) and has been designed to be as general as possible.
-# So don't expect that much from this, but basic capabilities should work out of the box (Play/Pause/Next/Add)
+# NOTE: This controller is a SAMPLE, and thus doesn't use all the
+# Media Player Remote Interface Specification (MPRIS for short) capabilities
#
-# Also notice it has been designed first for a previous specificaiton, and thus some code may not work/be disabled
+# MPRIS: http://wiki.xmms2.xmms.se/index.php/Media_Player_Interfaces
#
# You'll need pygtk >= 2.10 to use gtk.StatusIcon
#
+# TODO
+# Ability to choose the Media Player if several are connected to the bus
+
+# core dbus stuff
import dbus
import dbus.glib
+
+# core interface stuff
import gtk
import gtk.glade
+
+# timer
import gobject
+
+# file loading
import os
-global position
-global timer
+global win_position # store the window position on the screen
+
global playing
playing = False
-def itemchange_handler(item):
- gobject.timeout_add( 2000, timeset)
+global shuffle # playlist will play randomly
+global repeat # repeat the playlist
+global loop # loop the current element
+
+# mpris doesn't support getting the status of these (at the moment)
+shuffle = False
+repeat = False
+loop = False
+
+# these are defined on the mpris detected unique name
+global root # / org.freedesktop.MediaPlayer
+global player # /Player org.freedesktop.MediaPlayer
+global tracklist # /Tracklist org.freedesktop.MediaPlayer
+
+global bus # Connection to the session bus
+global identity # MediaPlayer Identity
+
+
+# If a Media Player connects to the bus, we'll use it
+# Note that we forget the previous Media Player we were connected to
+def NameOwnerChanged(name, new, old):
+ if old != "" and "org.mpris." in name:
+ Connect(name)
+
+# Callback for when "TrackChange" signal is emitted
+def TrackChange(Track):
+ # the only mandatory metadata is "URI"
try:
- a = item["artist"]
+ a = Track["artist"]
except:
a = ""
try:
- t = item["title"]
+ t = Track["title"]
except:
- t = ""
- if t == "":
- t = item["URI"]
+ t = Track["URI"]
+ try:
+ length = Track["length"]
+ except:
+ length = 0
+ if length > 0:
+ time_s.set_range(0,Track["length"])
+ time_s.set_sensitive(True)
+ else:
+ # disable the position scale if length isn't available
+ time_s.set_sensitive(False)
+ # update the labels
l_artist.set_text(a)
l_title.set_text(t)
-#connect to the bus
-bus = dbus.SessionBus()
-player_o = bus.get_object("org.mpris.vlc", "/Player")
-tracklist_o = bus.get_object("org.mpris.vlc", "/TrackList")
+# Connects to the Media Player we detected
+def Connect(name):
+ global root, player, tracklist
+ global playing, identity
-tracklist = dbus.Interface(tracklist_o, "org.freedesktop.MediaPlayer")
-player = dbus.Interface(player_o, "org.freedesktop.MediaPlayer")
-try:
- player_o.connect_to_signal("TrackChange", itemchange_handler, dbus_interface="org.freedesktop.MediaPlayer")
-except:
- True
+ # first we connect to the objects
+ root_o = bus.get_object(name, "/")
+ player_o = bus.get_object(name, "/Player")
+ tracklist_o = bus.get_object(name, "/TrackList")
+
+ # there is only 1 interface per object
+ root = dbus.Interface(root_o, "org.freedesktop.MediaPlayer")
+ tracklist = dbus.Interface(tracklist_o, "org.freedesktop.MediaPlayer")
+ player = dbus.Interface(player_o, "org.freedesktop.MediaPlayer")
+
+ # connect to the TrackChange signal
+ player_o.connect_to_signal("TrackChange", TrackChange, dbus_interface="org.freedesktop.MediaPlayer")
+
+ # determine if the Media Player is playing something
+ if player.GetStatus() == 0:
+ playing = True
+ TrackChange(player.GetMetadata())
+
+ # gets its identity (name and version)
+ identity = root.Identity()
+ window.set_title(identity)
#plays an element
def AddTrack(widget):
tracklist.AddTrack("directory://" + mrl, True)
update(0)
-#basic control
+# basic control
+
def Next(widget):
player.Next(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
update(0)
player.Stop(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
update(0)
-#update status display
-def update(widget):
- item = tracklist.GetMetadata(tracklist.GetCurrentTrack())
- vol.set_value(player.VolumeGet())
- try:
- a = item["artist"]
- except: a = ""
- try:
- t = item["title"]
- except: t = ""
- if t == "":
- t = item["URI"]
- l_artist.set_text(a)
- l_title.set_text(t)
- GetPlayStatus(0)
-
-#get playing status from remote vlc
-def GetPlayStatus(widget):
- global playing
- status = player.GetStatus()
- if status == 0:
- img_bt_toggle.set_from_stock("gtk-media-pause", gtk.ICON_SIZE_SMALL_TOOLBAR)
- playing = True
- else:
- img_bt_toggle.set_from_stock("gtk-media-play", gtk.ICON_SIZE_SMALL_TOOLBAR)
- playing = False
-
def Quit(widget):
player.Quit(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
l_title.set_text("")
status = player.GetStatus()
if status == 0:
img_bt_toggle.set_from_stock(gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_SMALL_TOOLBAR)
- gobject.timeout_add( 2000, timeset)
else:
img_bt_toggle.set_from_stock(gtk.STOCK_MEDIA_PLAY, gtk.ICON_SIZE_SMALL_TOOLBAR)
update(0)
-#callback for volume
+def Repeat(widget):
+ global repeat
+ repeat = not repeat
+ player.Repeat(repeat)
+
+def Shuffle(widget):
+ global shuffle
+ shuffle = not shuffle
+ tracklist.Random(shuffle)
+
+def Loop(widget):
+ global loop
+ loop = not loop
+ tracklist.Loop(loop)
+
+# update status display
+def update(widget):
+ Track = player.GetMetadata()
+ vol.set_value(player.VolumeGet())
+ try:
+ a = Track["artist"]
+ except:
+ a = ""
+ try:
+ t = Track["title"]
+ except:
+ t = ""
+ if t == "":
+ try:
+ t = Track["URI"]
+ except:
+ t = ""
+ l_artist.set_text(a)
+ l_title.set_text(t)
+ try:
+ length = Track["length"]
+ except:
+ length = 0
+ if length > 0:
+ time_s.set_range(0,Track["length"])
+ time_s.set_sensitive(True)
+ else:
+ # disable the position scale if length isn't available
+ time_s.set_sensitive(False)
+ GetPlayStatus(0)
+
+# callback for volume change
def volchange(widget, data):
player.VolumeSet(vol.get_value_as_int(), reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
-#callback for position change
+# callback for position change
def timechange(widget, x=None, y=None):
player.PositionSet(int(time_s.get_value()), reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
-#refresh position
+# refresh position change
def timeset():
global playing
- time_s.set_value(player.PositionGet())
- return playing
+ if playing == True:
+ try:
+ time_s.set_value(player.PositionGet())
+ except:
+ playing = False
+ return True
-#simple/full display
+# toggle simple/full display
def expander(widget):
if exp.get_expanded() == False:
exp.set_label("Less")
else:
exp.set_label("More")
-#close event
+# close event : hide in the systray
def delete_event(self, widget):
self.hide()
return True
+# shouldn't happen
def destroy(widget):
gtk.main_quit()
+# hide the controller when 'Esc' is pressed
def key_release(widget, event):
- global position
if event.keyval == gtk.keysyms.Escape:
- position = window.get_position()
+ global win_position
+ win_position = window.get_position()
widget.hide()
-#click on the tray icon
+# callback for click on the tray icon
def tray_button(widget):
- global position
+ global win_position
if window.get_property('visible'):
- position = window.get_position()
+ # store position
+ win_position = window.get_position()
window.hide()
else:
- window.move(position[0], position[1])
+ # restore position
+ window.move(win_position[0], win_position[1])
window.show()
-xml = gtk.glade.XML('dbus-vlc.glade')
+# hack: update position, volume, and metadata
+def icon_clicked(widget, event):
+ update(0)
+
+# get playing status, modify the Play/Pause button accordingly
+def GetPlayStatus(widget):
+ global playing
+ global shuffle
+ global loop
+ global repeat
+ status = player.GetStatus()
+ playing = status[0] == 0
+ if playing:
+ img_bt_toggle.set_from_stock("gtk-media-pause", gtk.ICON_SIZE_SMALL_TOOLBAR)
+ else:
+ img_bt_toggle.set_from_stock("gtk-media-play", gtk.ICON_SIZE_SMALL_TOOLBAR)
+ shuffle = status[1] == 1
+ bt_shuffle.set_active( shuffle )
+ loop = status[2] == 1
+ bt_loop.set_active( loop )
+ repeat = status[3] == 1
+ bt_repeat.set_active( repeat )
+# loads glade file from the directory where the script is,
+# so we can use /path/to/mpris.py to execute it.
+import sys
+xml = gtk.glade.XML(os.path.dirname(sys.argv[0]) + '/mpris.glade')
+
+# ui setup
bt_close = xml.get_widget('close')
bt_quit = xml.get_widget('quit')
bt_file = xml.get_widget('ChooseFile')
bt_stop = xml.get_widget('stop')
bt_toggle = xml.get_widget('toggle')
bt_mrl = xml.get_widget('AddMRL')
+bt_shuffle = xml.get_widget('shuffle')
+bt_repeat = xml.get_widget('repeat')
+bt_loop = xml.get_widget('loop')
l_artist = xml.get_widget('l_artist')
l_title = xml.get_widget('l_title')
e_mrl = xml.get_widget('mrl')
img_bt_toggle=xml.get_widget('image6')
exp = xml.get_widget('expander2')
expvbox = xml.get_widget('expandvbox')
-vlcicon = xml.get_widget('eventicon')
+audioicon = xml.get_widget('eventicon')
vol = xml.get_widget('vol')
time_s = xml.get_widget('time_s')
time_l = xml.get_widget('time_l')
+# connect to the different callbacks
+
window.connect('delete_event', delete_event)
window.connect('destroy', destroy)
window.connect('key_release_event', key_release)
-tray = gtk.status_icon_new_from_icon_name("vlc")
+tray = gtk.status_icon_new_from_icon_name("audio-x-generic")
tray.connect('activate', tray_button)
-def icon_clicked(widget, event):
- update(0)
-
bt_close.connect('clicked', destroy)
bt_quit.connect('clicked', Quit)
bt_mrl.connect('clicked', AddTrack)
bt_next.connect('clicked', Next)
bt_prev.connect('clicked', Prev)
bt_stop.connect('clicked', Stop)
+bt_loop.connect('clicked', Loop)
+bt_repeat.connect('clicked', Repeat)
+bt_shuffle.connect('clicked', Shuffle)
exp.connect('activate', expander)
vol.connect('change-value', volchange)
vol.connect('scroll-event', volchange)
time_s.connect('adjust-bounds', timechange)
-vlcicon.set_events(gtk.gdk.BUTTON_PRESS_MASK)
-vlcicon.connect('button_press_event', icon_clicked)
+audioicon.set_events(gtk.gdk.BUTTON_PRESS_MASK) # hack for the bottom right icon
+audioicon.connect('button_press_event', icon_clicked)
time_s.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
-gobject.timeout_add( 2000, timeset)
-library = "/media/mp3"
+library = "/media/mp3" # editme
+# set the Directory chooser to a default location
try:
os.chdir(library)
bt_file.set_current_folder(library)
except:
- print "edit this file to point to your media library"
+ bt_file.set_current_folder(os.path.expanduser("~"))
-window.set_icon_name('vlc')
-window.set_title("VLC - D-Bus ctrl")
+# connect to the bus
+bus = dbus.SessionBus()
+dbus_names = bus.get_object( "org.freedesktop.DBus", "/org/freedesktop/DBus" )
+dbus_names.connect_to_signal("NameOwnerChanged", NameOwnerChanged, dbus_interface="org.freedesktop.DBus") # to detect new Media Players
+
+dbus_o = bus.get_object("org.freedesktop.DBus", "/")
+dbus_intf = dbus.Interface(dbus_o, "org.freedesktop.DBus")
+name_list = dbus_intf.ListNames()
+
+# connect to the first Media Player found
+for n in name_list:
+ if "org.mpris." in n:
+ Connect(n)
+ window.set_title(identity)
+ vol.set_value(player.VolumeGet())
+ update(0)
+ break
+
+# run a timer to update position
+gobject.timeout_add( 1000, timeset)
+
+window.set_icon_name('audio-x-generic')
window.show()
-try:
- update(0)
-except:
- True
-
icon_theme = gtk.icon_theme_get_default()
try:
- pix = icon_theme.load_icon("vlc",24,0)
+ pix = icon_theme.load_icon("audio-x-generic",24,0)
window.set_icon(pix)
except:
True
-position = window.get_position()
-vol.set_value(player.VolumeGet())
-gtk.main()
+win_position = window.get_position()
+
+gtk.main() # execute the main loop