=== added directory 'src/quodlibet'
=== added file 'src/quodlibet/Makefile.am'
--- src/quodlibet/Makefile.am 1970-01-01 00:00:00 +0000
+++ src/quodlibet/Makefile.am 2008-02-04 17:38:05 +0000
@@ -0,0 +1,9 @@
+SUBDIRS = icons
+APPLET_NAME = quodlibet
+APPLET_MAIN_FILE = qlapplet.py
+include $(top_srcdir)/Makefile.python-applet
+
+dist_applet_DATA = \
+ qlinterface.py \
+ imageproducer.py \
+ $(NULL)
=== added directory 'src/quodlibet/icons'
=== added file 'src/quodlibet/icons/Makefile.am'
--- src/quodlibet/icons/Makefile.am 1970-01-01 00:00:00 +0000
+++ src/quodlibet/icons/Makefile.am 2008-02-04 17:38:05 +0000
@@ -0,0 +1,5 @@
+dist_mediaiconplay_DATA = \
+ cover.svg \
+ top.png
+
+mediaiconplaydir = $(libdir)/awn/applets/quodlibet/icons
=== added file 'src/quodlibet/icons/cover.svg'
--- src/quodlibet/icons/cover.svg 1970-01-01 00:00:00 +0000
+++ src/quodlibet/icons/cover.svg 2008-02-04 17:38:05 +0000
@@ -0,0 +1,663 @@
+
+
+
=== added file 'src/quodlibet/icons/top.png'
Binary files src/quodlibet/icons/top.png 1970-01-01 00:00:00 +0000 and src/quodlibet/icons/top.png 2008-02-04 17:38:05 +0000 differ
=== added file 'src/quodlibet/imageproducer.py'
--- src/quodlibet/imageproducer.py 1970-01-01 00:00:00 +0000
+++ src/quodlibet/imageproducer.py 2008-02-04 17:38:05 +0000
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+
+#Taken from Last.fm AWN Applet
+# 2007 Jonathan Rauprich
+# 2007 Tomas Kramar
+# modified: 2008 David Horsley
+
+import sys
+import gtk
+import cairo
+import os
+
+class ImageProducer:
+
+ # dimensions of the cover picture
+ WIDTH = 128.0
+ HEIGHT = 112.0
+ # look at the image "icons/top.png"
+ # it is an image of empty cd cover
+ # the cover image is put in the area between borders
+ BORDER_TOP = 3.0
+ BORDER_LEFT = 18.0
+ BORDER_BOTTOM = 8.0
+ BORDER_RIGHT = 6.0
+
+ def __init__(self, height):
+ self.path = os.path.dirname(__file__)
+
+ self.calculate_cover_dimensions(height)
+
+ def calculate_cover_dimensions(self, height):
+ #this is needed to make the generated cover fit the height of awn bar
+ #cover dimensions written in uppercase are default - the borders have been measured
+ #now we need to scale it, recalculate new borders and width - the height is not calculated - it is the height of awn bar
+
+ scale_factor = height / self.HEIGHT
+ self.HEIGHT = float(height) # casting it to float is really important due to the way how python division works
+ self.WIDTH = round(self.WIDTH * scale_factor)
+ self.BORDER_TOP = round(self.BORDER_TOP * scale_factor)
+ self.BORDER_LEFT = round(self.BORDER_LEFT * scale_factor)
+ self.BORDER_BOTTOM = round(self.BORDER_BOTTOM * scale_factor)
+ self.BORDER_RIGHT = round(self.BORDER_RIGHT * scale_factor)
+
+
+ def generate_cover(self, pixbuf):
+ # first step is to create a surface from jpg image and scale it down
+
+ #pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
+
+ image_width = self.WIDTH - self.BORDER_RIGHT - self.BORDER_LEFT
+ image_height = self.HEIGHT - self.BORDER_TOP - self.BORDER_BOTTOM
+
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(image_width), int(image_height))
+ ctx = cairo.Context(surface)
+
+ x_scale = image_width / pixbuf.get_width()
+ y_scale = image_height / pixbuf.get_height()
+ ctx.scale(x_scale,y_scale)
+
+ ct = gtk.gdk.CairoContext(ctx)
+ ct.set_source_pixbuf(pixbuf,0,0)
+ ctx.paint()
+
+ # we want to create an effect of cd in a box,
+ # so the cover go first, and then the cd box on top
+ f_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(self.WIDTH), int(self.HEIGHT))
+ f_ctx = cairo.Context(f_surface)
+ f_ctx.set_source_surface(surface, self.BORDER_LEFT, self.BORDER_TOP)
+ f_ctx.rectangle(self.BORDER_LEFT, self.BORDER_TOP, self.WIDTH, self.HEIGHT)
+
+ f_ctx.fill()
+
+ base = cairo.ImageSurface.create_from_png(self.path + "/icons/top.png")
+ x_scale = self.WIDTH / base.get_width()
+ y_scale = self.HEIGHT / base.get_height()
+
+ f_ctx.scale(x_scale, y_scale)
+
+ #f_ctx.new_path()
+ f_ctx.set_source_surface(base)
+ f_ctx.paint()
+
+ return self.get_pixbuf_from_surface(f_surface)
+
+ def get_default_cover(self):
+ return "icons/default.png"
+
+ # Stolen from diogodivision's "BlingSwitcher"
+ def get_pixbuf_from_surface(self, surface):
+ from StringIO import StringIO
+ sio = StringIO()
+ surface.write_to_png(sio)
+ sio.seek(0)
+ loader = gtk.gdk.PixbufLoader()
+ loader.write(sio.getvalue())
+ loader.close()
+ return loader.get_pixbuf()
+
+if __name__ == '__main__':
+ app = ImageProducer(40)
+ app.generate_cover("icons/sample_cover.jpg")
=== added file 'src/quodlibet/qlapplet.py'
--- src/quodlibet/qlapplet.py 1970-01-01 00:00:00 +0000
+++ src/quodlibet/qlapplet.py 2008-02-04 17:38:05 +0000
@@ -0,0 +1,206 @@
+#!/usr/bin/python
+
+#Quod Libet applet or AWN
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA
+#
+#Author:
+# 2008 David Horsley widdma@gmail.com
+
+import sys, os
+import os.path
+import awn
+import gobject
+import pygtk
+import gtk
+from gtk import gdk
+from xml.sax.saxutils import escape
+from imageproducer import ImageProducer #from the wonderful last.fm AWN applet
+
+from qlinterface import QLInterface
+
+class QLApplet(awn.AppletSimple, QLInterface):
+ """
+ AWN Applet for Quod Libet
+ """
+
+ labeltext = """Now Playing:
+%(title)s
+%(artist)s
+%(album)s"""
+ path = os.path.dirname(__file__)
+
+ def __init__(self, uid, orient, height):
+ awn.AppletSimple.__init__(self, uid, orient, height)
+ QLInterface.__init__(self)
+
+
+ self.height = height
+
+ self.theme = gtk.IconTheme()
+ self.hide_window()
+
+ self.title = awn.awn_title_get_default()
+
+ if self.ql_running():
+ self.update_title(self.CurrentSong())
+ else:
+ self.title_text = "Player not running (middle click to start)"
+ #Dialogs
+ self.__build_main_dialog()
+ self.__build_not_running_dialog()
+ self.update_album_art()
+
+
+ #Applet Button
+ self.connect ("button-press-event", self.applet_button_press)
+ self.connect ("enter-notify-event", lambda widget, event: self.title.show(self, self.title_text))
+ self.connect ("leave-notify-event", lambda widget, event: self.title.hide(self))
+
+ #Events
+ def applet_button_press(self, widget, event):
+ if self.ql_running():
+ if event.button == 2:
+ self.toggle_window()
+ else:
+ self.main_dialog.show_all()
+ else:
+ if event.button == 2:
+ os.popen2("quodlibet")
+ else:
+ self.not_running_dialog.show_all()
+
+ def onQLExited(self):
+ self.title_text = "Player not running (middle click to start)"
+ self.update_album_art()
+
+ def onQLStarted(self):
+ gobject.timeout_add(2000, self.hide_window)
+
+ def onSongStarted(self, song):
+ self.update_title(song)
+ self.update_album_art()
+
+ song = self.escape(song)
+ self.songlabel.set_label(self.labeltext % song)
+
+ def onSongStoped(self, song, stopped):
+ self.title_text = "Quod Libet"
+ self.update_album_art()
+
+
+
+ #Dialog Builders
+ def __build_not_running_dialog(self):
+
+ self.not_running_dialog = awn.AppletDialog(self)
+ start_button = gtk.Button()
+ start_button.set_label("Start")
+ start_button.connect("clicked", lambda widget: os.popen2("quodlibet"))
+
+ label = gtk.Label("Quod Libet Not Running")
+ label.set_use_markup(True)
+
+ vbox = gtk.VBox(False, 10)
+ vbox.pack_start(label)
+ vbox.pack_start(start_button)
+
+ align = gtk.Alignment()
+ align.set_padding(10,10,10,10)
+ align.add(vbox)
+
+ self.not_running_dialog.add(align)
+ self.not_running_dialog.connect("focus-out-event", lambda widget, event: self.not_running_dialog.hide())
+
+ def __build_secondry_dialog(self):
+ self.secondry_dialog = awn.AppletDialog(self)
+
+
+ def __build_main_dialog(self):
+ self.main_dialog = awn.AppletDialog(self)
+ self.main_dialog.set_title("Quod Libet")
+ song_box = gtk.VBox(spacing = 5)
+ button_box = gtk.HBox()
+
+ self.songlabel = gtk.Label()
+ self.songlabel.set_use_markup(True)
+ self.songlabel.set_padding(5,0)
+ song_box.pack_start(self.songlabel)
+
+ if self.ql_running():
+ song = self.escape(self.CurrentSong())
+ self.songlabel.set_label(self.labeltext % song)
+
+ button_clicked = lambda widget: self.control(widget.action)
+
+ for name in ("previous","play-pause","next"):
+ button = gtk.ToolButton ("gtk-media-%s"%name.split("-")[0])
+ button.connect("clicked", button_clicked)
+ button_box.pack_start(button, False)
+ button.action = name
+
+ align = gtk.Alignment(0.5,0.5,0,0)
+ align.add(button_box)
+ song_box.pack_start(align, False)
+
+ self.albumArt = gtk.Image()
+ self.update_album_art()
+
+ album_box = gtk.HBox(False, 10)
+ album_box.pack_start(song_box)
+ album_box.pack_start(self.albumArt)
+ self.main_dialog.add(album_box)
+
+ self.main_dialog.connect("focus-out-event", lambda widget, event: self.main_dialog.hide())
+
+ def update_title(self, song):
+ #TODO: make this an option
+ try:
+ self.title_text = "%(tracknumber)s: %(title)s - %(artist)s" % song
+ except:
+ pass
+
+ def update_album_art(self):
+
+ if self.ql_running():
+ try:
+ pixbuf = gdk.pixbuf_new_from_file(os.path.expanduser("~/.quodlibet/current.cover"))
+ except:
+ pixbuf = gdk.pixbuf_new_from_file(self.path + "/icons/cover.svg")
+
+ else:
+ pixbuf = gdk.pixbuf_new_from_file(self.path + "/icons/cover.svg")
+
+ self.albumArt.set_from_pixbuf(pixbuf.scale_simple(128,128,gtk.gdk.INTERP_HYPER))
+
+ glossy_album = ImageProducer(128).generate_cover(pixbuf)
+ if self.height != glossy_album.get_height():
+ glossy_album = glossy_album.scale_simple(glossy_album.get_width()*(self.height.__float__() / glossy_album.get_height().__float__()),self.height,gtk.gdk.INTERP_HYPER)
+ self.set_icon (glossy_album)
+
+ def escape(self, dict_str):
+ out= {}
+ for key, value in dict_str.items():
+ out[key] = escape(value)
+ return out
+
+
+if __name__ == "__main__":
+ awn.init (sys.argv[1:])
+ applet = QLApplet(awn.uid, awn.orient, awn.height)
+ awn.init_applet (applet)
+ applet.show_all ()
+ gtk.main ()
=== added file 'src/quodlibet/qlinterface.py'
--- src/quodlibet/qlinterface.py 1970-01-01 00:00:00 +0000
+++ src/quodlibet/qlinterface.py 2008-02-04 17:38:05 +0000
@@ -0,0 +1,144 @@
+#!/usr/bin/python
+
+#Quod Libet DBus and FIFO interface wrapper
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA
+#
+#Author:
+# 2008 David Horsley widdma@gmail.com
+
+import os
+import os.path
+import tempfile
+import dbus
+import dbus.mainloop.glib
+
+class QLInterface():
+ """
+ An interface to control and retrive infomation from Quodlibet
+ """
+ #QL's DBus interface doesn't suport as many commands as it's FIFO
+ #But FIFO doesn't have signals, so we use both.
+
+ controlfile = os.path.expanduser("~/.quodlibet/control")
+ controls = ["next", "previous", "play", "pause", "play-pause",
+ "hide-window", "show-window", "toggle-window",
+ "focus", "quit", "unfilter"]
+
+ controls_opt = ["seek", "order", "repeat", "query", "volume", "filter",
+ "set-rating", "set-browser", "open-browser", "random",
+ "song-list", "queue", "enqueue", "unqueue"]
+
+ dbus_methods = ["GetPostion", "IsPlaying", "CurrentSong", "Next",
+ "Previous", "Pause", "Play", "PlayPause"]
+
+ def __init__(self):
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ self.bus = dbus.SessionBus()
+ self.bus.add_signal_receiver(self.sig_catch, dbus_interface="org.freedesktop.DBus", signal_name = "NameOwnerChanged")
+
+ if self.ql_running():
+ self.setup_dbus()
+
+ def sig_catch(self, name, old, new):
+ if name == "net.sacredchao.QuodLibet":
+ if old == "":
+ self.setup_dbus()
+ self.onQLStarted()
+ else:
+ self.onQLExited()
+
+ def setup_dbus(self):
+ self.QL = self.bus.get_object("net.sacredchao.QuodLibet", "/net/sacredchao/QuodLibet")
+ signals = ("SongStarted","SongEnded","Paused","Unpaused")
+ for signal in signals:
+ self.QL.connect_to_signal(signal, getattr(self, "on%s" % signal), dbus_interface="net.sacredchao.QuodLibet")
+
+
+ def onQLStarted(self):
+ pass
+
+ def onQLExited(self):
+ pass
+
+ def onPaused(self):
+ pass
+
+ def onUnpaused(self):
+ pass
+
+ def onSongStarted(self, song):
+ pass
+
+ def onSongEnded(self, song, stop):
+ pass
+
+ def control(self, action, arg=""):
+ if self.ql_running():
+ if action in self.controls:
+ try:
+ f = file(self.controlfile, "w")
+ f.write(action)
+ f.close()
+ except:
+ pass
+ elif action in self.controls_opt:
+ actstr = action + " " + arg
+ try:
+ f = file(self.controlfile, "w")
+ f.write(actstr)
+ f.close()
+ except:
+ pass
+
+ def retrive(command):
+ #Shamelessly hooked from quodlibet.py
+ fd, filename = tempfile.mkstemp()
+ try:
+ os.unlink(filename)
+ # mkfifo fails if the file exists, so this is safe.
+ os.mkfifo(filename, 0600)
+ f = file(const.CONTROL, "w")
+ f.write(command + " " + filename)
+ f.close()
+
+ f = file(filename, "r")
+ out = f.read()
+ try: os.unlink(filename)
+ except EnvironmentError: pass
+ f.close()
+ return out
+ except TypeError:
+ try: os.unlink(filename)
+ except EnvironmentError: pass
+
+
+ def __getattr__(self,name): #Mostly to prove how cool python is to myself
+ name = name.replace("_","-")
+ if name in self.controls:
+ return lambda: self.control(name)
+ if name in self.controls_opt:
+ return lambda x: self.control(name, x)
+ if name in self.dbus_methods:
+ return getattr(self.QL, name)
+ else:
+ raise AttributeError
+
+
+ def ql_running(self):
+ return ( os.path.exists(self.controlfile) and self.bus.name_has_owner("net.sacredchao.QuodLibet") )
+
=== added file 'src/quodlibet/quodlibet.desktop.in.in'
--- src/quodlibet/quodlibet.desktop.in.in 1970-01-01 00:00:00 +0000
+++ src/quodlibet/quodlibet.desktop.in.in 2008-02-04 17:38:05 +0000
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Version=1.0
+Name=Quod Libet Control
+Type=Application
+Comment=A media-control applet
+Exec=quodlibet/qlapplet.py
+Icon=quodlibet
+X-AWN-AppletType=Python
+Name[en_US]=Quod Libet Control
=== modified file 'configure.ac'
--- configure.ac 2008-01-28 21:15:59 +0000
+++ configure.ac 2008-02-04 17:37:53 +0000
@@ -327,6 +327,9 @@
src/quit-applet/Makefile
src/quit-applet/icons/Makefile
src/quit-applet/quit-applet.desktop.in
+src/quodlibet/Makefile
+src/quodlibet/icons/Makefile
+src/quodlibet/quodlibet.desktop.in
src/separator/Makefile
src/separator/separator.desktop.in
src/shiny-switcher/Makefile