On Sun, Mar 29, 2009 at 7:15 PM, Pat LeSmithe <
qed...@gmail.com> wrote:
>
> Thanks to an unexpected grant of round tuits [1] from the Back Burner
> Foundation for Eventual Development, here's a somewhat improved tray
> icon for Sage.
>
> Besides PyGTK, the main script now uses Python's bindings to
>
freedesktop.org's D-Bus to export several methods for manipulating the
Could you post some screenshots?
> #!/usr/bin/env python
> """
> A Sage system tray / notification area / status icon built on PyGTK
> and
freedesktop.org's D-Bus.
> """
> import math, os, sys
>
> # We pickle only when actually sending pixbuf'd icons from a Sage
> # worksheet, via the bus.
> import cPickle as pickle
>
> # Register for automatic clean-up.
> import sage.interfaces.cleaner as the_wolf
> the_wolf.cleaner(os.getpid())
>
> # We use system-wide installations of PyGTK and D-Bus Python bindings.
> # The package Numeric is used only when pickling icons to transmit
> # over D-Bus. In this case, PyGTK needs to be built with Numeric
> # support. There's probably a way to do it with NumPy, instead. The
> # following additions seem to be sufficient on a x86_64 Fedora 9
> # system. Perhaps it's possible to auto-detect and configure for
> # these dependencies.
> sys.path.append('/usr/lib/python2.5/site-packages/')
> sys.path.append('/usr/lib/python2.5/site-packages/dbus')
> sys.path.append('/usr/lib64/python2.5/site-packages/')
> sys.path.append('/usr/lib64/python2.5/site-packages/gtk-2.0')
> sys.path.append('/usr/lib64/python2.5/site-packages/Numeric')
>
> import Numeric
> import gobject, gtk
> import dbus, dbus.service, dbus.mainloop.glib
>
> # Local dictionary of some available icons.
> icon_home = os.environ['SAGE_ROOT'] + '/data/extcode/notebook/images/'
> sage_icons = { 'default' : 'icon32x32.png',
> 'email' : 'icon_email.gif',
> 'preview' : 'icon_preview.gif',
> 'print' : 'icon_print.gif' }
> for name in sage_icons:
> sage_icons[name] = icon_home + sage_icons[name]
>
> # We'll export a SageTrayIcon object, including several methods for
> # asynchronously "signalling" the tray icon, over the session bus.
> #
> # dbus-python tutorial:
> #
http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html
> # gtk.StatusIcon doc:
> #
http://library.gnome.org/devel/pygtk/stable/class-gtkstatusicon.html
> # gtk.gdk.Pixbuf doc:
> #
http://library.gnome.org/devel/pygtk/stable/class-gdkpixbuf.html
> class SageTrayIcon(dbus.service.Object):
> def __init__(self, conn, object_path):
> dbus.service.Object.__init__(self, conn, object_path)
> self.icon = gtk.StatusIcon()
> self.icon.set_from_file(sage_icons['default'])
> self.icon.set_tooltip('Sage Notebook started')
>
> # Local prep for icon animations.
> self.update_pixbuf_props()
> self.update_anim_params(5.0)
>
> # Store the current icon as a GDK pixbuf and get its parameters.
> # Used to to prepare for animations.
> def update_pixbuf_props(self):
> self.pixbuf = self.icon.get_pixbuf()
> self.w = self.pixbuf.get_width()
> self.h = self.pixbuf.get_height()
> self.pixbuf_mod = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
> self.w, self.h)
>
> # (re)Set various animation parameters.
> def update_anim_params(self, seconds):
> # Duration of an animation, in seconds.
> self.t = seconds
> # Frames per second, in theory.
> self.fps = 24.0
> # Time between frames, in milliseconds.
> self.dt_ms = int(1000 / self.fps)
> # Number of frames.
> self.frames = int(self.t * self.fps)
> # Number of animation periods.
> self.periods = self.t
> # Period = a * pi. We convert the unit of time from seconds
> # to frames. See below.
> self.a = self.frames / self.periods / math.pi
>
> # The dbus.service.method decorator exports a method in the
> # indicated "interface" via D-Bus. The in/out signatures may not
> # be required, but D-Bus' introspection doesn't always work.
> # Default and variable-length arguments also don't appear to work,
> # unfortunately. These issues may be surmountable.
>
> # Return a list of names of available icons.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='', out_signature='as')
> def get_icon_names(self):
> return [name for name in sage_icons]
>
> # Set the icon by keying into the local dictionary.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='s', out_signature='')
> def set_icon_by_name(self, name):
> self.icon.set_from_file(sage_icons[name])
>
> # Set the icon using the given pickled pixbuf. This currently
> # requires PyGTK to be built with Numeric support.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='s', out_signature='')
> def send_pickled_icon(self, pickled_icon):
> new_icon_pixbuf = gtk.gdk.pixbuf_new_from_array(
> pickle.loads(str(pickled_icon)), gtk.gdk.COLORSPACE_RGB, 8 )
> self.icon.set_from_pixbuf(new_icon_pixbuf)
>
> # Set the icon tooltip.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='s', out_signature='')
> def set_icon_tooltip(self, text):
> self.icon.set_tooltip(text)
>
> # Toggle icon blinking. The period seems to be one second.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='', out_signature='')
> def toggle_blinking(self):
> if self.icon.get_blinking():
> self.icon.set_blinking(False)
> else:
> self.icon.set_blinking(True)
>
> # Blink the icon for an interval, unless it's already blinking.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='d', out_signature='')
> def blink(self, seconds):
> if not self.icon.get_blinking():
> self.icon.set_blinking(True)
> gobject.timeout_add(int(seconds * 1000), self.toggle_blinking)
>
> # Transform the icon via self.* and a given function. Afterwards,
> # restore the original icon. Note: This is not re-entrant, so
> # multiple simultaneous calls may be entertaining.
> def transform_shape(self, func):
> self.update_pixbuf_props()
> self.i = 0
> def advance_frame():
> self.icon.set_from_pixbuf(func(self.i))
> if self.i < self.frames:
> self.i += 1
> return True
> self.icon.set_from_pixbuf(self.pixbuf)
> # Note: time.sleep() doesn't suffice here, since it won't
> # properly queue GTK events. Also, gobject.timeout_add()
> # doesn't seem to work with Python generators.
> gobject.timeout_add(self.dt_ms, advance_frame)
>
> # (un)Squeeze the icon horizontally --- make it wink.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='d', out_signature='')
> def squeeze_horizontally(self, seconds):
> self.update_anim_params(seconds)
> def func(t):
> h = max( 1, int( self.h * ( 1.0 - math.sin( t / self.a )**2 ) ) )
> return self.pixbuf.scale_simple(self.w, h, gtk.gdk.INTERP_HYPER)
> self.transform_shape(func)
>
> # (un)Squeeze the icon vertically --- make it wink, sideways.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='d', out_signature='')
> def squeeze_vertically(self, seconds):
> self.update_anim_params(seconds)
> def func(t):
> w = max( 1, int( self.w * ( 1.0 - math.sin( t / self.a )**2 ) ) )
> return self.pixbuf.scale_simple(w, self.h, gtk.gdk.INTERP_HYPER)
> self.transform_shape(func)
>
> # (un)Shrink the icon.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='d', out_signature='')
> def shrink(self, seconds):
> self.update_anim_params(seconds)
> def func(t):
> x = max( 1, int( self.w * ( 1.0 - math.sin( t / self.a )**2 ) ) )
> return self.pixbuf.scale_simple(x, x, gtk.gdk.INTERP_HYPER)
> self.transform_shape(func)
>
> # Modulate the icon's saturation.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='dd', out_signature='')
> def saturate(self, seconds, amplitude):
> self.update_anim_params(seconds)
> phase = math.asin( 1.0 / amplitude - 1.0 )
> def func(t):
> sat = amplitude * ( 1.0 + math.sin( 2.0 * t / self.a + phase ) )
> self.pixbuf.saturate_and_pixelate(self.pixbuf_mod, sat, False)
> return self.pixbuf_mod
> self.transform_shape(func)
>
> # Kill the tray icon by quitting the GTK event loop. This is
> # currently irreversible.
> @dbus.service.method('org.sagemath.TrayIconInterface',
> in_signature='', out_signature='')
> def quit_icon(self):
> loop.quit()
>
> # If we're called as shell script, we make a new Sage tray icon,
> # export selected methods over the session bus, and enter a GTK event
> # loop. Note: A running loop is required for a functioning (e.g.,
> # visible) icon.
> if __name__ == '__main__':
> dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
>
> bus = dbus.SessionBus()
> name = dbus.service.BusName('org.sagemath.TrayIconService', bus)
> exported_icon = SageTrayIcon(bus, '/SageTrayIcon')
>
> loop = gobject.MainLoop()
> loop.run()
>
> tray icon
> system:sage
>
> <p>Use D-Bus' introspection to get an ugly list of methods exported by the tray icon process:</p>
>
> {{{id=41|
> tray_icon.dbus_introspect()
> ///
> }}}
>
> <p>Get the names of the remote icons:</p>
>
> {{{id=44|
> map(str,tray_icon.get_icon_names())
> ///
> }}}
>
> <p>Change the icon a couple of times:</p>
>
> {{{id=36|
> tray_icon.set_icon_by_name('email')
> ///
> }}}
>
> {{{id=10|
> tray_icon.set_icon_by_name('default')
> ///
> }}}
>
> <p>Update the tooltip:</p>
>
> {{{id=46|
> tray_icon.set_icon_tooltip('Sage Notebook is running...')
> ///
> }}}
>
> <p>Sample some effects, not all at once:</p>
>
> {{{id=48|
> tray_icon.toggle_blinking()
> ///
> }}}
>
> {{{id=49|
> tray_icon.toggle_blinking()
> ///
> }}}
>
> {{{id=35|
> tray_icon.blink(3)
> ///
> }}}
>
> {{{id=33|
> tray_icon.squeeze_vertically(3)
> ///
> }}}
>
> {{{id=29|
> tray_icon.squeeze_horizontally(3)
> ///
> }}}
>
> {{{id=31|
> tray_icon.shrink(3)
> ///
> }}}
>
> {{{id=30|
> tray_icon.saturate(3.0, 2.0)
> ///
> }}}
>
> <p>Get the names of our local icons:</p>
>
> {{{id=53|
> tray_icon.get_sendable_icon_names()
> ///
> }}}
>
> <p>Send a local icon to the tray icon process via D-Bus:</p>
>
> {{{id=52|
> tray_icon.send_icon_by_name('alert')
> ///
> }}}
>
> <p>Compose a simple effect, which blocks until finished:</p>
>
> {{{id=54|
> import time
> for i in xrange(10):
> tray_icon.send_icon_by_name('smile')
> time.sleep(0.75)
> tray_icon.send_icon_by_name('tongue')
> time.sleep(0.25)
> ///
> }}}
>
> <p>Quit the tray icon process:</p>
>
> {{{id=47|
> tray_icon.quit_icon()
> ///
> }}}
>
> {{{id=55|
>
> ///
> }}}
> # Change this to access an icon process from command-line Sage. Be
> # sure the icon process is running. See that script for more detail.
> if sage_mode == 'notebook':
> import sys
>
> # Use the system's installation of dbus-python and PyGTK.
> sys.path.append('/usr/lib/python2.5/site-packages/')
> sys.path.append('/usr/lib/python2.5/site-packages/dbus')
> sys.path.append('/usr/lib64/python2.5/site-packages/')
> sys.path.append('/usr/lib64/python2.5/site-packages/gtk-2.0')
>
> import gtk
> import dbus, dbus.mainloop.glib
>
> # Find the exported tray icon and specify an interface.
> dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
> bus = dbus.SessionBus()
> proxy_icon = bus.get_object('org.sagemath.TrayIconService','/SageTrayIcon')
> tray_icon = dbus.Interface(proxy_icon,
> dbus_interface='org.sagemath.TrayIconInterface')
>
> # D-Bus' introspection tells us what's been exported. Perhaps it's
> # possible to get this to work transparently with Sage's
> # intropection. Note: proxy_icon.Introspect() gives the same
> # results.
> def dbus_introspect():
> print str( tray_icon.Introspect(
> dbus_interface='org.freedesktop.DBus.Introspectable' ) )
>
> # We mock up a convenience method for tray_icon.
> tray_icon.dbus_introspect = dbus_introspect
>
> # If Numeric is installed, and PyGTK uses it, we can actually send
> # pickled pixbufs, via D-Bus, to the tray icon process.
> sys.path.append('/usr/lib64/python2.5/site-packages/Numeric')
>
> import os
> import Numeric
> import cPickle as pickle
>
> # The tray icon process has its own dictionary of icons. We make
> # our own dictionary of icons.
> new_icon_home = SAGE_ROOT + '/local/share/moin/htdocs/modern/img/'
> new_sage_icons = { 'alert' : 'alert.png',
> 'attention' : 'attention.png',
> 'smile' : 'smile.png',
> 'tongue' : 'tongue.png' }
> for name in new_sage_icons:
> new_sage_icons[name] = new_icon_home + new_sage_icons[name]
>
> # More convenience methods, analogous to those exported via D-Bus.
> def get_sendable_icon_names():
> return [name for name in new_sage_icons]
>
> tray_icon.get_sendable_icon_names = get_sendable_icon_names
>
> def send_icon_by_name(name):
> pixbuf = gtk.gdk.pixbuf_new_from_file(new_sage_icons[name])
> tray_icon.send_pickled_icon(pickle.dumps(pixbuf.get_pixels_array()))
> tray_icon.send_icon_by_name = send_icon_by_name
>
>
--
William Stein
Associate Professor of Mathematics
University of Washington
http://wstein.org