[hackers] [wmii] Add python wmiirc/9P client library || Kris Maglione

From: <hg_AT_suckless.org>
Date: Sun, 17 May 2009 18:15:12 +0000 (UTC)

changeset: 2455:c6e4f5df5678
tag: tip
user: Kris Maglione <jg_AT_suckless.org>
date: Sun May 17 14:15:08 2009 -0400
files: .hgignore alternative_wmiircs/python/pygmi/__init__.py alternative_wmiircs/python/pygmi/events.py alternative_wmiircs/python/pygmi/fs.py alternative_wmiircs/python/pygmi/menu.py alternative_wmiircs/python/pygmi/monitor.py alternative_wmiircs/python/pyxp/__init__.py alternative_wmiircs/python/pyxp/client.py alternative_wmiircs/python/pyxp/dial.py alternative_wmiircs/python/pyxp/fcall.py alternative_wmiircs/python/pyxp/fields.py alternative_wmiircs/python/pyxp/messages.py alternative_wmiircs/python/pyxp/mux.py alternative_wmiircs/python/pyxp/types.py alternative_wmiircs/python/wmiirc cmd/wmii.rc.rc cmd/wmii/bar.c cmd/wmii/dat.h cmd/wmii/ewmh.c cmd/wmii/fns.h cmd/wmii/fs.c cmd/wmii/main.c cmd/wmii/mouse.c cmd/wmii/x11.c include/x11.h
description:
Add python wmiirc/9P client library

diff -r 5d035a83e42f -r c6e4f5df5678 .hgignore
--- a/.hgignore Sat May 16 11:14:33 2009 -0400
+++ b/.hgignore Sun May 17 14:15:08 2009 -0400
@@ -1,7 +1,7 @@
 syntax: regexp
 (^|/)\.((.*\.)?sw.|depend|hgignore)$
 (^|/)(tags|mkfile)$
-\.([oOa]|o_pic|so|orig|bak)$
+\.([oOa]|o_pic|so|orig|bak|pyc|pyo)$
 ^cmd/(stfo|osd|wiwarp)(/|$)
 syntax: glob
 config.local.mk
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pygmi/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pygmi/__init__.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,47 @@
+import os
+
+from pyxp import Client
+
+if 'WMII_ADDRESS' in os.environ:
+ client = Client(os.environ['WMII_ADDRESS'])
+else:
+ client = Client(namespace='wmii')
+
+def call(*args, **kwargs):
+ background = kwargs.pop('background', False)
+ input = kwargs.pop('input', None)
+ import subprocess
+ p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, cwd=os.environ['HOME'],
+ **kwargs)
+ if not background:
+ return p.communicate(input)[0].rstrip('\n')
+
+def program_list(path):
+ names = []
+ for d in path:
+ try:
+ for f in os.listdir(d):
+ if f not in names and os.access('%s/%s' % (d, f),
+ os.X_OK):
+ names.append(f)
+ except Exception:
+ pass
+ return sorted(names)
+
+def curry(func, *args, **kwargs):
+ def curried(*newargs, **newkwargs):
+ return func(*(args + newargs), **dict(kwargs, **newkwargs))
+ curried.__name__ = func.__name__ + '__curried__'
+ return curried
+
+from pygmi import events, fs, menu, monitor
+from pygmi.events import *
+from pygmi.fs import *
+from pygmi.menu import *
+from pygmi.monitor import *
+
+__all__ = (fs.__all__ + monitor.__all__ + events.__all__ +
+ menu.__all__ + ('client', 'call', 'curry', 'program_list'))
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pygmi/events.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pygmi/events.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,95 @@
+import os
+import re
+import sys
+import traceback
+
+from pygmi import monitor, client, call, program_list
+
+__all__ = ('bind_keys', 'bind_events', 'toggle_keys', 'event_loop',
+ 'event')
+
+keydefs = {}
+keys = {}
+events = {}
+
+alive = True
+
+def flatten(items):
+ for k, v in items.iteritems():
+ if not isinstance(k, (list, tuple)):
+ k = k,
+ for key in k:
+ yield key, v
+
+def bind_keys(items):
+ for k, v in flatten(items):
+ keys[k % keydefs] = v
+
+def bind_events(items):
+ for k, v in flatten(items):
+ events[k] = v
+
+def event(fn):
+ bind_events({fn.__name__: fn})
+
+@event
+def Key(args):
+ if args in keys:
+ keys[args](args)
+
+keys_enabled = False
+keys_restore = None
+def toggle_keys(on=None, restore=None):
+ if on is None:
+ on = not keys_enabled
+ keys_restore = restore
+ if on:
+ client.write('/keys', '\n'.join(keys.keys()))
+ else:
+ client.write('/keys', restore or ' ')
+
+def dispatch(event, args=''):
+ if event in events:
+ try:
+ events[event](args)
+ except Exception, e:
+ traceback.print_exc(sys.stderr)
+
+def event_loop():
+ from pygmi import events
+ toggle_keys(on=True)
+ for line in client.readlines('/event'):
+ if not events.alive:
+ break
+ dispatch(*line.split(' ', 1))
+ events.alive = False
+
+class Actions(object):
+ which = call('which', 'which')
+
+ def __getattr__(self, name):
+ if name.startswith('_') or name.endswith('_'):
+ raise AttributeError()
+ if hasattr(self, name + '_'):
+ return getattr(self, name + '_')
+ def action(args=''):
+ cmd = call(self.which, name,
+ env=dict(os.environ, PATH=':'.join(confpath)))
+ call(shell, '-c', '$* %s' % args, '--', cmd,
+ background=True)
+ return action
+
+ def _call(self, args):
+ a = args.split(' ')
+ if a:
+ getattr(self, a[0])(*a[1:])
+
+ @property
+ def _choices(self):
+ return sorted(
+ program_list(confpath) +
+ [re.sub('_$', '', k) for k in dir(self)
+ if not re.match('^_', k) and callable(getattr(self, k))])
+
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pygmi/fs.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pygmi/fs.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,588 @@
+import collections
+
+from pyxp import *
+from pyxp.client import *
+from pygmi import *
+
+__all__ = ('wmii', 'Tags', 'Tag', 'Area', 'Frame', 'Client',
+ 'Button', 'Colors', 'Color')
+
+class Ctl(object):
+ sentinel = {}
+ ctl_types = {}
+ ctl_hasid = False
+
+ def __init__(self):
+ pass
+
+ def ctl(self, msg):
+ client.write(self.ctl_path, msg)
+
+ def __getitem__(self, key):
+ for line in self.ctl_lines():
+ key_, rest = line.split(' ', 1)
+ if key_ == key:
+ if key in self.ctl_types:
+ return self.ctl_types[key][0](rest)
+ return rest
+ raise KeyError()
+ def __hasitem__(self, key):
+ return key in self.keys()
+ def __setitem__(self, key, val):
+ assert '\n' not in key
+ if key in self.ctl_types:
+ val = self.ctl_types[key][1](val)
+ self.ctl('%s %s\n' % (key, val))
+
+ def get(self, key, default=sentinel):
+ try:
+ val = self[key]
+ except KeyError, e:
+ if default is not self.sentinel:
+ return default
+ raise e
+ def set(self, key, val):
+ self[key] = val
+
+ def keys(self):
+ return [line.split(' ', 1)[0]
+ for line in self.ctl_lines()]
+ def iteritems(self):
+ return (tuple(line.split(' ', 1))
+ for line in self.ctl_lines())
+ def items(self):
+ return [tuple(line.split(' ', 1))
+ for line in self.ctl_lines()]
+
+ def ctl_lines(self):
+ lines = tuple(client.readlines(self.ctl_path))
+ if self.ctl_hasid:
+ lines = lines[1:]
+ return lines[:-1]
+
+ _id = None
+ @property
+ def id(self):
+ if self._id is None and self.ctl_hasid:
+ return client.read(self.ctl_path).split('\n', 1)[0]
+ return self._id
+
+class Dir(Ctl):
+ ctl_hasid = True
+
+ def __init__(self, id):
+ if id != 'sel':
+ self._id = id
+
+ def __eq__(self, other):
+ return (self.__class__ == other.__class__ and
+ self.id == other.id)
+
+ class ctl_property(object):
+ def __init__(self, key):
+ self.key = key
+ def __get__(self, dir, cls):
+ return dir[self.key]
+ def __set__(self, dir, val):
+ dir[self.key] = val
+ class toggle_property(ctl_property):
+ props = {
+ 'on': True,
+ 'off': False,
+ }
+ def __get__(self, dir, cls):
+ val = dir[self.key]
+ if val in self.props:
+ return self.props[val]
+ return val
+ def __set__(self, dir, val):
+ for k, v in self.props.iteritems():
+ if v == val:
+ val = k
+ break
+ dir[self.key] = val
+
+ class file_property(object):
+ def __init__(self, name, writable=False):
+ self.name = name
+ self.writable = writable
+ def __get__(self, dir, cls):
+ return client.read('%s/%s' % (dir.path, self.name))
+ def __set__(self, dir, val):
+ if not self.writable:
+ raise NotImplementedError('File %s is not writable' % self.name)
+ return client.write('%s/%s' % (dir.path, self.name), val)
+
+ @property
+ def ctl_path(self):
+ return '%s/ctl' % self.path
+
+ @property
+ def path(self):
+ return '%s/%s' % (self.base_path, self._id or 'sel')
+
+ @classmethod
+ def all(cls):
+ return (cls(s.name)
+ for s in client.readdir(cls.base_path)
+ if s.name != 'sel')
+
+ def __repr__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ repr(self._id or 'sel'))
+
+class Client(Dir):
+ base_path = '/client'
+
+ fullscreen = Dir.toggle_property('Fullscreen')
+ urgent = Dir.toggle_property('Urgent')
+
+ label = Dir.file_property('label', writable=True)
+ tags = Dir.file_property('tags', writable=True)
+ props = Dir.file_property('props')
+
+ def kill(self):
+ self.ctl('kill')
+ def slay(self):
+ self.ctl('slay')
+
+class liveprop(object):
+ def __init__(self, get):
+ self.get = get
+ self.attr = str(self)
+ def __get__(self, area, cls):
+ if getattr(area, self.attr, None) is not None:
+ return getattr(area, self.attr)
+ return self.get(area)
+ def __set__(self, area, val):
+ setattr(area, self.attr, val)
+
+class Area(object):
+ def __init__(self, tag, ord, offset=None, width=None, height=None, frames=None):
+ self.tag = tag
+ self.ord = str(ord)
+ self.offset = offset
+ self.width = width
+ self.height = height
+ self.frames = frames
+
+ def prop(key):
+ @liveprop
+ def prop(self):
+ for area in self.tag.index:
+ if str(area.ord) == str(self.ord):
+ return getattr(area, key)
+ return prop
+ offset = prop('offset')
+ width = prop('width')
+ height = prop('height')
+ frames = prop('frames')
+
+ def _get_mode(self):
+ for k, v in self.tag.iteritems():
+ if k == 'colmode':
+ v = v.split(' ')
+ if v[0] == self.ord:
+ return v[1]
+ mode = property(
+ _get_mode,
+ lambda self, val: self.tag.set('colmode %s' % self.ord, val))
+
+ def grow(self, dir, amount=None):
+ self.tag.grow(self, dir, amount)
+ def nudge(self, dir, amount=None):
+ self.tag.nudge(self, dir, amount)
+
+class Frame(object):
+ live = False
+
+ def __init__(self, client, area=None, ord=None, offset=None, height=None):
+ self.client = client
+ self.ord = ord
+ self.offset = offset
+ self.height = height
+
+ @property
+ def width(self):
+ return self.area.width
+
+ def prop(key):
+ @liveprop
+ def prop(self):
+ for area in self.tag.index:
+ for frame in area.frames:
+ if frame.client == self.client:
+ return getattr(frame, key)
+ return prop
+ offset = prop('area')
+ offset = prop('ord')
+ offset = prop('offset')
+ height = prop('height')
+
+ def grow(self, dir, amount=None):
+ self.area.tag.grow(self, dir, amount)
+ def nudge(self, dir, amount=None):
+ self.area.tag.nudge(self, dir, amount)
+
+class Tag(Dir):
+ base_path = '/tag'
+
+ @classmethod
+ def framespec(cls, frame):
+ if isinstance(frame, Frame):
+ frame = frame.client
+ if isinstance(frame, Area):
+ frame = (frame.ord, 'sel')
+ if isinstance(frame, Client):
+ if frame._id is None:
+ return 'sel sel'
+ return 'client %s' % frame.id
+ elif isinstance(frame, basestring):
+ return frame
+ else:
+ return '%s %s' % tuple(map(str, frame))
+ def dirspec(cls, dir):
+ if isinstance(dir, tuple):
+ dir = ' '.join(dir)
+ return dir
+
+ def _set_selected(self, frame):
+ if not isinstance(frame, basestring) or ' ' not in frame:
+ frame = self.framespec(frame)
+ self['select'] = frame
+ selected = property(lambda self: tuple(self['select'].split(' ')),
+ _set_selected)
+
+ def _get_selclient(self):
+ for k, v in self.iteritems():
+ if k == 'select' and 'client' in v:
+ return Client(v.split(' ')[1])
+ return None
+ selclient = property(_get_selclient,
+ lambda self, val: self.set('select',
+ self.framespec(val)))
+
+ @property
+ def selcol(self):
+ return Area(self, self.selected[0])
+
+ @property
+ def index(self):
+ areas = []
+ for l in [l.split(' ')
+ for l in client.readlines('%s/index' % self.path)
+ if l]:
+ if l[0] == '#':
+ if l[1] == '~':
+ area = Area(tag=self, ord=l[1], width=l[2], height=l[3],
+ frames=[])
+ else:
+ area = Area(tag=self, ord=l[1], offset=l[2], width=l[3],
+ frames=[])
+ areas.append(area)
+ i = 0
+ else:
+ area.frames.append(
+ Frame(client=Client(l[1]), area=area, ord=i,
+ offset=l[2], height=l[3]))
+ i += 1
+ return areas
+
+ def delete(self):
+ id = self.id
+ for a in self.index:
+ for f in a.frames:
+ if f.client.tags == id:
+ f.client.kill()
+ else:
+ f.client.tags = '-%s' % id
+ if self == Tag('sel'):
+ Tags.instance.select(Tags.instance.next())
+
+ def select(self, frame, stack=False):
+ self['select'] = '%s %s' % (
+ self.framespec(frame),
+ stack and 'stack' or '')
+
+ def send(self, src, dest, stack=False, cmd='send'):
+ if isinstance(src, tuple):
+ src = ' '.join(src)
+ if isinstance(src, Frame):
+ src = src.client
+ if isinstance(src, Client):
+ src = src._id or 'sel'
+
+ if isinstance(dest, tuple):
+ dest = ' '.join(dest)
+
+ self[cmd] = '%s %s' % (src, dest)
+
+ def swap(self, src, dest):
+ self.send(src, dest, cmd='swap')
+
+ def nudge(self, frame, dir, amount=None):
+ frame = self.framespec(frame)
+ self['nudge'] = '%s %s %s' % (frame, dir, str(amount or ''))
+ def grow(self, frame, dir, amount=None):
+ frame = self.framespec(frame)
+ self['grow'] = '%s %s %s' % (frame, dir, str(amount or ''))
+
+class Button(object):
+ sides = {
+ 'left': 'lbar',
+ 'right': 'rbar',
+ }
+ def __init__(self, side, name, colors=None, label=None):
+ self.side = side
+ self.name = name
+ self.base_path = self.sides[side]
+ self.path = '%s/%s' % (self.base_path, self.name)
+ self.create(colors, label)
+
+ def create(self, colors=None, label=None):
+ with client.create(self.path, OWRITE) as f:
+ if colors or label:
+ f.write(self.getval(colors, label))
+ def remove(self):
+ client.remove(self.path)
+
+ def getval(self, colors=None, label=None):
+ if colors is None:
+ colors = self.colors
+ if label is None:
+ label = self.label
+ return ' '.join([Color(c).hex for c in colors] + [label])
+
+ colors = property(
+ lambda self: tuple(map(Color, client.read(self.path).split(' ')[:3])),
+ lambda self, val: client.write(self.path, self.getval(colors=val)))
+
+ label = property(
+ lambda self: client.read(self.path).split(' ', 3)[3],
+ lambda self, val: client.write(self.path, self.getval(label=val)))
+
+ @classmethod
+ def all(cls, side):
+ return (Button(side, s.name)
+ for s in client.readdir(cls.sides[side])
+ if s.name != 'sel')
+
+class Colors(object):
+ def __init__(self, foreground=None, background=None, border=None):
+ vals = foreground, background, border
+ self.vals = tuple(map(Color, vals))
+
+ @classmethod
+ def from_string(cls, val):
+ return cls(*val.split(' '))
+
+ def __getitem__(self, key):
+ if isinstance(key, basestring):
+ key = {'foreground': 0, 'background': 1, 'border': 2}[key]
+ return self.vals[key]
+
+ def __str__(self):
+ return str(unicode(self))
+ def __unicode__(self):
+ return ' '.join(c.hex for c in self.vals)
+ def __repr__(self):
+ return 'Colors(%s, %s, %s)' % tuple(repr(c.rgb) for c in self.vals)
+
+class Color(object):
+ def __init__(self, colors):
+ if isinstance(colors, Color):
+ colors = colors.rgb
+ elif isinstance(colors, basestring):
+ match = re.match(r'^#(..)(..)(..)$', colors)
+ colors = tuple(int(match.group(group), 16) for group in range(1, 4))
+ def toint(val):
+ if isinstance(val, float):
+ val = int(255 * val)
+ assert 0 <= val <= 255
+ return val
+ self.rgb = tuple(map(toint, colors))
+
+ def __getitem__(self, key):
+ if isinstance(key, basestring):
+ key = {'red': 0, 'green': 1, 'blue': 2}[key]
+ return self.rgb[key]
+
+ @property
+ def hex(self):
+ return '#%02x%02x%02x' % self.rgb
+
+ def __str__(self):
+ return str(unicode(self))
+ def __unicode__(self):
+ return 'rgb(%d, %d, %d)' % self.rgb
+ def __repr__(self):
+ return 'Color(%s)' % repr(self.rgb)
+
+class Rules(collections.MutableMapping):
+ regex = re.compile(r'^\s*/(.*?)/\s*(?:->)?\s*(.*)$')
+
+ def __get__(self, obj, cls):
+ return self
+ def __set__(self, obj, val):
+ self.setitems(val)
+
+ def __init__(self, path, rules=None):
+ self.path = path
+ if rules:
+ self.setitems(rules)
+
+ def __getitem__(self, key):
+ for k, v in self.iteritems():
+ if k == key:
+ return v
+ raise KeyError()
+ def __setitem__(self, key, val):
+ items = []
+ for k, v in self.iteritems():
+ if key == k:
+ v = val
+ key = None
+ items.append((k, v))
+ if key is not None:
+ items.append((key, val))
+ self.setitems(items)
+ def __delitem__(self, key):
+ self.setitems((k, v) for k, v in self.iteritems() if k != key)
+
+ def __len__(self):
+ return len(tuple(self.iteritems()))
+ def __iter__(self):
+ for k, v in self.iteritems():
+ yield k
+ def __list__(self):
+ return list(iter(self))
+ def __tuple__(self):
+ return tuple(iter(self))
+
+ def append(self, item):
+ self.setitems(self + (item,))
+ def __add__(self, items):
+ return tuple(self.iteritems()) + tuple(items)
+
+ def setitems(self, items):
+ lines = []
+ for k, v in items:
+ assert '/' not in k and '\n' not in v
+ lines.append('/%s/ -> %s' % (k, v))
+ lines.append('')
+ client.write(self.path, '\n'.join(lines))
+
+ def iteritems(self):
+ for line in client.readlines(self.path):
+ match = self.regex.match(line)
+ if match:
+ yield match.groups()
+ def items(self):
+ return list(self.iteritems())
+
+@apply
+class wmii(Ctl):
+ ctl_path = '/ctl'
+ ctl_types = {
+ 'normcolors': (Colors.from_string, lambda c: str(Colors(c))),
+ 'focuscolors': (Colors.from_string, lambda c: str(Colors(c))),
+ 'border': (int, str),
+ }
+
+ clients = property(lambda self: Client.all())
+ tags = property(lambda self: Tag.all())
+ lbuttons = property(lambda self: Button.all('left'))
+ rbuttons = property(lambda self: Button.all('right'))
+
+ tagrules = Rules('/tagrules')
+ colrules = Rules('/colrules')
+
+class Tags(object):
+ PREV = []
+ NEXT = []
+
+ def __init__(self, normcol=None, focuscol=None):
+ self.tags = {}
+ self.sel = None
+ self.normcol = normcol or wmii['normcolors']
+ self.focuscol = focuscol or wmii['focuscolors']
+ for t in wmii.tags:
+ self.add(t.id)
+ for b in wmii.lbuttons:
+ if b.name not in self.tags:
+ b.remove()
+ self.focus(Tag('sel').id)
+
+ self.mru = [self.sel.id]
+ self.idx = -1
+ Tags.instance = self
+
+ def add(self, tag):
+ self.tags[tag] = Tag(tag)
+ self.tags[tag].button = Button('left', tag, self.normcol, tag)
+ def delete(self, tag):
+ self.tags.pop(tag).button.remove()
+
+ def focus(self, tag):
+ self.sel = self.tags[tag]
+ self.sel.button.colors = self.focuscol
+ def unfocus(self, tag):
+ self.tags[tag].button.colors = self.normcol
+
+ def set_urgent(self, tag, urgent=True):
+ self.tags[tag].button.label = urgent and '*' + tag or tag
+
+ def next(self, reverse=False):
+ tags = list(wmii.tags)
+ tags.append(tags[0])
+ if reverse:
+ tags.reverse()
+ for i in range(0, len(tags)):
+ if tags[i] == self.sel:
+ return tags[i+1]
+ return self.sel
+
+ def select(self, tag):
+ if tag is self.PREV:
+ self.idx -= 1
+ elif tag is self.NEXT:
+ self.idx += 1
+ else:
+ if isinstance(tag, Tag):
+ tag = tag.id
+ wmii['view'] = tag
+
+ if tag != self.mru[-1]:
+ self.mru.append(tag)
+ self.mru = self.mru[-10:]
+ return
+
+ self.idx = min(-1, max(-len(self.mru), self.idx))
+ wmii['view'] = self.mru[self.idx]
+
+if __name__ == '__main__':
+ c = Client('sel')
+ #print c.id
+ #print c.items()
+ #print c.urgent
+
+ #print list(wmii.clients)
+ #print list(wmii.tags)
+
+ #print [a.frames for a in Tag('sel').index]
+ #print Tag('sel').selclient
+ #print Tag('sel').selclient.label
+ #print Tag('sel').selclient.tags
+ #print Tag('sel').selclient.props
+ #a = Area(Tag('sel'), 1)
+ #print a.width
+ #print a.frames
+
+ #print [[c.hex for c in b.colors] for b in wmii.lbuttons]
+ #print [[c.hex for c in b.colors] for b in wmii.rbuttons]
+ Button('left', '1').colors = ((0., 0., 0.), (1., 1., 1.), (0., 0., 0.))
+ Button('left', '1').label = 'foo'
+ Button('left', '5', label='baz')
+ print repr(wmii['normcolors'])
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pygmi/menu.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pygmi/menu.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,58 @@
+from pygmi import call
+
+__all__ = 'Menu', 'ClickMenu'
+
+def inthread(fn, action):
+ def run():
+ res = fn()
+ if action:
+ return action(res)
+ return res
+ if not action:
+ return run()
+ from threading import Thread
+ Thread(target=run).start()
+
+class Menu(object):
+ def __init__(self, choices=(), action=None,
+ histfile=None, nhist=None):
+ self.choices = choices
+ self.action = action
+ self.histfile = histfile
+ self.nhist = nhist
+
+ def call(self, choices=None):
+ if choices is None:
+ choices = self.choices
+ if callable(choices):
+ choices = choices()
+ def act():
+ args = ['wimenu']
+ if self.histfile:
+ args += ['-h', self.histfile]
+ if self.nhist:
+ args += ['-n', self.nhist]
+ return call(*map(str, args), input='\n'.join(choices))
+ return inthread(act, self.action)
+
+class ClickMenu(object):
+ def __init__(self, choices=(), action=None,
+ histfile=None, nhist=None):
+ self.choices = choices
+ self.action = action
+ self.prev = None
+
+ def call(self, choices=None):
+ if choices is None:
+ choices = self.choices
+ if callable(choices):
+ choices = choices()
+ def act():
+ args = ['wmii9menu']
+ if self.prev:
+ args += ['-i', self.prev]
+ args += ['--'] + list(choices)
+ return call(*map(str, args)).replace('\n', '')
+ return inthread(act, self.action)
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pygmi/monitor.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pygmi/monitor.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,86 @@
+from pygmi import client
+from pygmi.fs import *
+
+__all__ = 'monitors', 'defmonitor', 'Monitor'
+
+monitors = {}
+
+def defmonitor(*args, **kwargs):
+ def monitor(fn):
+ kwargs['action'] = fn
+ if not args and 'name' not in kwargs:
+ kwargs['name'] = fn.__name__
+ monitor = Monitor(*args, **kwargs)
+ monitors[monitor.name] = monitor
+ return monitor
+ if args and callable(args[0]):
+ fn = args[0]
+ args = args[1:]
+ return monitor(fn)
+ return monitor
+
+class MonitorBase(type):
+ def __new__(cls, name, bases, attrs):
+ new_cls = super(MonitorBase, cls).__new__(cls, name, bases, attrs)
+ if name not in attrs:
+ new_cls.name = new_cls.__name__.lower()
+ try:
+ Monitor
+ if new_cls.name not in monitors:
+ monitors[new_cls.name] = new_cls()
+ except Exception, e:
+ pass
+ return new_cls
+
+class Monitor(object):
+
+ side = 'right'
+ interval = 1.0
+
+ def __init__(self, name=None, interval=None, side=None,
+ action=None):
+ if side:
+ self.side = side
+ if name:
+ self.name = name
+ if interval:
+ self.interval = interval
+ if action:
+ self.action = action
+
+ self.button = Button(self.side, self.name)
+ self.tick()
+
+ def tick(self):
+ from pygmi import events
+ if not events.alive:
+ if client:
+ self.button.remove()
+ return
+ if self.active:
+ from threading import Timer
+ label = self.getlabel()
+ if isinstance(label, basestring):
+ label = None, label
+ self.button.create(*label)
+ self.timer = Timer(self.interval, self.tick)
+ self.timer.start()
+
+ def getlabel(self):
+ if self.action:
+ return self.action()
+ return ()
+
+ _active = True
+ def _set_active(self, val):
+ self._active = bool(val)
+ if val:
+ self.tick()
+ else:
+ self.button.remove()
+
+ active = property(
+ lambda self: self._active,
+ _set_active)
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pyxp/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pyxp/__init__.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,7 @@
+from pyxp.client import Client
+from pyxp.dial import dial
+from pyxp.types import Qid, Stat
+
+VERSION = '9P2000'
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pyxp/client.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pyxp/client.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,328 @@
+# Copyright (C) 2007 Kris Maglione
+# See PERMISSIONS
+
+import operator
+import os
+import re
+import sys
+from threading import *
+import traceback
+
+import pyxp
+from pyxp import fcall, fields
+from pyxp.mux import Mux
+from pyxp.types import *
+
+if os.environ.get('NAMESPACE', None):
+ namespace = os.environ['NAMESPACE']
+else:
+ try:
+ namespace = '/tmp/ns.%s.%s' % (
+ os.environ['USER'],
+ re.sub(r'\.0$', '', os.environ['DISPLAY']))
+ except Exception:
+ pass
+NAMESPACE = namespace
+
+OREAD = 0x00
+OWRITE = 0x01
+ORDWR = 0x02
+OEXEC = 0x03
+OEXCL = 0x04
+OTRUNC = 0x10
+OREXEC = 0x20
+ORCLOSE = 0x40
+OAPPEND = 0x80
+
+ROOT_FID = 0
+
+class ProtocolException(Exception):
+ pass
+class RPCError(Exception):
+ pass
+
+class Client(object):
+ ROOT_FID = 0
+
+ def __enter__(self):
+ return self
+ def __exit__(self, *args):
+ self.cleanup()
+
+ def __init__(self, conn=None, namespace=None, root=None):
+ if not conn and namespace:
+ conn = 'unix!%s/%s' % (NAMESPACE, namespace)
+ try:
+ self.lastfid = ROOT_FID
+ self.fids = []
+ self.files = {}
+ self.lock = RLock()
+
+ def process(data):
+ return fcall.Fcall.unmarshall(data)[1]
+ self.mux = Mux(conn, process, maxtag=256)
+
+ resp = self.dorpc(fcall.Tversion(version=pyxp.VERSION, msize=65535))
+ if resp.version != pyxp.VERSION:
+ raise ProtocolException, "Can't speak 9P version '%s'" % resp.version
+ self.msize = resp.msize
+
+ self.dorpc(fcall.Tattach(fid=ROOT_FID, afid=fcall.NO_FID,
+ uname=os.environ['USER'], aname=''))
+
+ if root:
+ path = self.splitpath(root)
+ resp = self.dorpc(fcall.Twalk(fid=ROOT_FID,
+ newfid=ROOT_FID,
+ wname=path))
+ except Exception, e:
+ traceback.print_exc(sys.stdout)
+ if getattr(self, 'mux', None):
+ self.mux.fd.close()
+ raise e
+
+ def cleanup(self):
+ try:
+ for f in self.files:
+ f.close()
+ finally:
+ self.mux.fd.close()
+ self.mux = None
+
+ def dorpc(self, req):
+ resp = self.mux.rpc(req)
+ if isinstance(resp, fcall.Rerror):
+ raise RPCError, "RPC returned error (%s): %s" % (
+ req.__class__.__name__, resp.ename)
+ if req.type != resp.type ^ 1:
+ raise ProtocolException, "Missmatched RPC message types: %s => %s" % (
+ req.__class__.__name__, resp.__class__.__name__)
+ return resp
+
+ def splitpath(self, path):
+ return [v for v in path.split('/') if v != '']
+
+ def getfid(self):
+ with self.lock:
+ if len(self.fids):
+ return self.fids.pop()
+ else:
+ self.lastfid += 1
+ return self.lastfid
+ def putfid(self, fid):
+ with self.lock:
+ self.files.pop(fid)
+ self.fids.append(fid)
+
+ def clunk(self, fid):
+ try:
+ self.dorpc(fcall.Tclunk(fid=fid))
+ finally:
+ self.putfid(fid)
+
+ def walk(self, path):
+ fid = self.getfid()
+ ofid = ROOT_FID
+ while True:
+ self.dorpc(fcall.Twalk(fid=ofid, newfid=fid,
+ wname=path[0:fcall.MAX_WELEM]))
+ path = path[fcall.MAX_WELEM:]
+ ofid = fid
+ if len(path) == 0:
+ break
+
+ @apply
+ class Res:
+ def __enter__(self):
+ return fid
+ def __exit__(self, exc_type, exc_value, traceback):
+ if exc_type:
+ self.clunk(fid)
+ return Res
+
+ def _open(self, path, mode, open):
+ resp = None
+
+ with self.walk(path) as nfid:
+ fid = nfid
+ resp = self.dorpc(open(fid))
+
+ def cleanup():
+ self.clunk(fid)
+ file = File(self, resp, fid, mode, cleanup)
+ self.files[fid] = file
+
+ return file
+
+ def open(self, path, mode = OREAD):
+ path = self.splitpath(path)
+
+ def open(fid):
+ return fcall.Topen(fid=fid, mode=mode)
+ return self._open(path, mode, open)
+
+ def create(self, path, mode = OREAD, perm = 0):
+ path = self.splitpath(path)
+ name = path.pop()
+
+ def open(fid):
+ return fcall.Tcreate(fid=fid, mode=mode, name=name, perm=perm)
+ return self._open(path, mode, open)
+
+ def remove(self, path):
+ path = self.splitpath(path)
+
+ with self.walk(path) as fid:
+ self.dorpc(fcall.Tremove(fid=fid))
+
+ def stat(self, path):
+ path = self.splitpath(path)
+
+ try:
+ with self.walk(path) as fid:
+ resp = self.dorpc(fcall.Tstat(fid= fid))
+ st = resp.stat()
+ self.clunk(fid)
+ return st
+ except RPCError:
+ return None
+
+ def read(self, path, *args, **kwargs):
+ with self.open(path) as f:
+ return f.read(*args, **kwargs)
+ def readlines(self, path, *args, **kwargs):
+ with self.open(path) as f:
+ for l in f.readlines(*args, **kwargs):
+ yield l
+ def readdir(self, path, *args, **kwargs):
+ with self.open(path) as f:
+ for s in f.readdir(*args, **kwargs):
+ yield s
+ def write(self, path, *args, **kwargs):
+ with self.open(path, OWRITE) as f:
+ return f.write(*args, **kwargs)
+
+class File(object):
+
+ def __enter__(self):
+ return self
+ def __exit__(self, *args):
+ self.close()
+
+ def __init__(self, client, fcall, fid, mode, cleanup):
+ self.lock = RLock()
+ self.client = client
+ self.fid = fid
+ self.cleanup = cleanup
+ self.mode = mode
+ self.iounit = fcall.iounit
+ self.qid = fcall.qid
+
+ self.offset = 0
+ self.fd = None
+
+ def dorpc(self, fcall):
+ if hasattr(fcall, 'fid'):
+ fcall.fid = self.fid
+ return self.client.dorpc(fcall)
+
+ def stat(self):
+ resp = self.dorpc(fcall.Tstat())
+ return resp.stat
+
+ def read(self, count=None, offset=None, buf=''):
+ if count is None:
+ count = self.iounit
+ res = []
+ with self.lock:
+ offs = self.offset
+ if offset is not None:
+ offs = offset
+ while count > 0:
+ n = min(count, self.iounit)
+ count -= n
+
+ resp = self.dorpc(fcall.Tread(offset=offs, count=n))
+ data = resp.data
+
+ offs += len(data)
+ res.append(data)
+
+ if len(data) < n:
+ break
+ if offset is None:
+ self.offset = offs
+ res = ''.join(res)
+ if len(res) > 0:
+ return res
+ def readlines(self):
+ last = None
+ while True:
+ data = self.read()
+ if not data:
+ break
+ lines = data.split('\n')
+ for i in range(0, len(lines) - 1):
+ yield lines[i]
+ last = lines[-1]
+ if last:
+ yield last
+ def write(self, data, offset=None):
+ if offset is None:
+ offset = self.offset
+ off = 0
+ with self.lock:
+ offs = self.offset
+ if offset is not None:
+ offs = offset
+ while off < len(data):
+ n = min(len(data), self.iounit)
+
+ resp = self.dorpc(fcall.Twrite(offset=offs,
+ data=data[off:off+n]))
+ off += resp.count
+ offs += resp.count
+ if resp.count < n:
+ break
+ if offset is None:
+ self.offset = offs
+ return off
+ def readdir(self):
+ if not self.qid.type & Qid.QTDIR:
+ raise Exception, "Can only call readdir on a directory"
+ off = 0
+ while True:
+ data = self.read(self.iounit, off)
+ if not data:
+ break
+ off += len(data)
+ for s in Stat.unmarshall_list(data):
+ yield s
+
+ def close(self):
+ try:
+ if self.fd:
+ self.fd_close()
+ finally:
+ self.cleanup()
+ self.tg = None
+ self.fid = None
+ self.client = None
+ self.qid = None
+
+ def remove(self):
+ try:
+ self.dorpc(fcall.Tremove())
+ finally:
+ try:
+ self.close()
+ except Exception:
+ pass
+
+ def fd_close(self):
+ try:
+ self.fd.close()
+ finally:
+ self.fd = None
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pyxp/dial.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pyxp/dial.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,35 @@
+from socket import *
+
+__all__ = 'dial',
+
+def dial_unix(address):
+ sock = socket(AF_UNIX, SOCK_STREAM, 0)
+ sock.connect(address)
+ return sock
+
+def dial_tcp(host):
+ host = host.split('!')
+ if len(host) != 2:
+ return
+ host, port = host
+
+ res = getaddrinfo(host, port, AF_INET, SOCK_STREAM, 0, AI_PASSIVE)
+ for family, socktype, protocol, name, addr in res:
+ try:
+ sock = socket(family, socktype, protocol)
+ sock.connect(addr)
+ return sock
+ except error:
+ if sock:
+ sock.close()
+
+def dial(address):
+ proto, address = address.split('!', 1)
+ if proto == 'unix':
+ return dial_unix(address)
+ elif proto == 'tcp':
+ return dial_tcp(address)
+ else:
+ raise Exception('invalid protocol')
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pyxp/fcall.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pyxp/fcall.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,131 @@
+from pyxp.messages import MessageBase, Message
+from pyxp.fields import *
+from types import Qid, Stat
+
+__all__ = 'Fcall',
+
+NO_FID = 1<<32 - 1
+MAX_WELEM = 16
+
+class FcallBase(MessageBase):
+ idx = 99
+ def __new__(cls, name, bases, attrs):
+ new_cls = super(FcallBase, cls).__new__(cls, name, bases, attrs)
+ new_cls.type = FcallBase.idx
+ if new_cls.type > 99:
+ new_cls.types[new_cls.type] = new_cls
+ FcallBase.idx += 1
+ return new_cls
+
+class Fcall(Message):
+ __metaclass__ = FcallBase
+ types = {}
+
+ def response(self, *args, **kwargs):
+ assert self.type % 2 == 0, "No respense type for response fcalls"
+ kwargs['tag'] = self.tag
+ return self.types[self.type + 1]()
+
+ @classmethod
+ def unmarshall(cls, data, offset=0):
+ res = super(Fcall, cls).unmarshall(data, offset)
+ if cls.type < 100:
+ res = cls.types[res[1].type].unmarshall(data, offset)
+ return res
+
+ size = Size(4, 4)
+ type = Int(1)
+ tag = Int(2)
+
+class Tversion(Fcall):
+ msize = Int(4)
+ version = String()
+class Rversion(Fcall):
+ msize = Int(4)
+ version = String()
+
+class Tauth(Fcall):
+ afid = Int(4)
+ uname = String()
+ aname = String()
+class Rauth(Fcall):
+ aqid = Qid.field()
+
+class Tattach(Fcall):
+ fid = Int(4)
+ afid = Int(4)
+ uname = String()
+ aname = String()
+class Rattach(Fcall):
+ qid = Qid.field()
+
+class Terror(Fcall):
+ def __init__(self):
+ raise Error("Illegal 9P tag 'Terror' encountered")
+class Rerror(Fcall):
+ ename = String()
+
+class Tflush(Fcall):
+ oldtag = Int(2)
+class Rflush(Fcall):
+ pass
+
+class Twalk(Fcall):
+ fid = Int(4)
+ newfid = Int(4)
+ wname = Array(2, String())
+class Rwalk(Fcall):
+ wqid = Array(2, Qid.field())
+
+class Topen(Fcall):
+ fid = Int(4)
+ mode = Int(1)
+class Ropen(Fcall):
+ qid = Qid.field()
+ iounit = Int(4)
+
+class Tcreate(Fcall):
+ fid = Int(4)
+ name = String()
+ perm = Int(4)
+ mode = Int(1)
+class Rcreate(Fcall):
+ qid = Qid.field()
+ iounit = Int(4)
+
+class Tread(Fcall):
+ fid = Int(4)
+ offset = Int(8)
+ count = Int(4)
+class Rread(Fcall):
+ data = Data(4)
+
+class Twrite(Fcall):
+ fid = Int(4)
+ offset = Int(8)
+ data = Data(4)
+class Rwrite(Fcall):
+ count = Int(4)
+
+class Tclunk(Fcall):
+ fid = Int(4)
+class Rclunk(Fcall):
+ pass
+
+class Tremove(Tclunk):
+ pass
+class Rremove(Fcall):
+ pass
+
+class Tstat(Tclunk):
+ pass
+class Rstat(Fcall):
+ sstat = Size(2)
+ stat = Stat.field()
+
+class Twstat(Rstat):
+ pass
+class Rwstat(Fcall):
+ pass
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pyxp/fields.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pyxp/fields.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,123 @@
+from datetime import datetime
+import operator
+
+class Field(object):
+ idx = 0
+
+ def __init__(self):
+ Field.idx += 1
+ self.id = Field.idx
+
+ def repr(self):
+ return self.__class__.__name__
+
+ def __repr__(self):
+ if hasattr(self, 'name'):
+ return '<Field %s "%s">' % (self.repr(), self.name)
+ return super(Field, self).__repr__()
+
+class Int(Field):
+ encoders = {}
+ decoders = {}
+ @classmethod
+ def encoder(cls, n):
+ if n not in cls.encoders:
+ exec ('def enc(n):\n' +
+ ' assert n == n & 0x%s, "Arithmetic overflow"\n' % ('ff' * n) +
+ ' return "".join((' + ','.join(
+ 'chr((n >> %d) & 0xff)' % (i * 8)
+ for i in range(0, n)) + ',))\n')
+ cls.encoders[n] = enc
+ return cls.encoders[n]
+ @classmethod
+ def decoder(cls, n):
+ if n not in cls.decoders:
+ cls.decoders[n] = eval('lambda data, offset: ' + '|'.join(
+ 'ord(data[offset + %d]) << %d' % (i, i * 8)
+ for i in range(0, n)))
+ return cls.decoders[n]
+
+ def __init__(self, size):
+ super(Int, self).__init__()
+ self.size = size
+ self.encode = self.encoder(size)
+ self.decode = self.decoder(size)
+ if self.__class__ == Int:
+ self.marshall = self.encode
+
+ def unmarshall(self, data, offset):
+ return self.size, self.decode(data, offset)
+ def marshall(self, val):
+ return self.encode(val)
+
+ def repr(self):
+ return '%s(%d)' % (self.__class__.__name__, self.size)
+
+class Size(Int):
+ def __init__(self, size, extra=0):
+ super(Size, self).__init__(size)
+ self.extra = extra
+
+ def marshall(self, val):
+ return lambda vals, i: self.encode(
+ reduce(lambda n, i: n + len(vals[i]),
+ range(i + 1, len(vals)),
+ self.extra))
+
+class Date(Int):
+ def __init__(self):
+ super(Date, self).__init__(4)
+
+ def unmarshall(self, data, offset):
+ val = self.decode(data, offset)
+ return 4, datetime.fromtimestamp(val)
+ def marshall(self, val):
+ return self.encode(int(val.strftime('%s')))
+
+# To do: use unicode strings, ensure UTF-8.
+# Not a problem in Python 3K, but there the other
+# data blobs are.
+class String(Int):
+ def __init__(self, size=2):
+ super(String, self).__init__(size)
+
+ def unmarshall(self, data, offset):
+ n = self.decode(data, offset)
+ offset += self.size
+ assert offset + n <= len(data), "String too long to unpack"
+ return self.size + n, data[offset:offset + n]
+ def marshall(self, val):
+ return [self.encode(len(val)), val]
+
+class Data(String):
+ pass
+
+class Array(Int):
+ def __init__(self, size, spec):
+ super(Array, self).__init__(size)
+ self.spec = spec
+
+ def unmarshall(self, data, offset):
+ start = offset
+ n = self.decode(data, offset)
+ offset += self.size
+ res = []
+ for i in range(0, n):
+ size, val = self.spec.unmarshall(data, offset)
+ if isinstance(val, list):
+ res += val
+ else:
+ res.append(val)
+ offset += size
+ return offset - start, res
+ def marshall(self, vals):
+ res = [self.encode(len(vals))]
+ for val in vals:
+ val = self.spec.marshall(val)
+ if isinstance(val, list):
+ res += val
+ else:
+ res.append(val)
+ return res
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pyxp/messages.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pyxp/messages.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,73 @@
+from pyxp.fields import *
+
+class MessageBase(type):
+ idx = 0
+
+ def __new__(cls, name, bases, attrs):
+ fields = []
+ fieldmap = {}
+ for k, v in attrs.items():
+ if isinstance(v, Field):
+ attrs[k] = None
+ fields.append(v)
+ fieldmap[k] = v
+ v.name = k
+ fields.sort(lambda a, b: cmp(a.id, b.id))
+
+ new_cls = super(MessageBase, cls).__new__(cls, name, bases, attrs)
+
+ map = getattr(new_cls, 'fieldmap', {})
+ map.update(fieldmap)
+ new_cls.fields = getattr(new_cls, 'fields', ()) + tuple(fields)
+ new_cls.fieldmap = map
+ for f in fields:
+ f.message = new_cls
+ return new_cls
+
+class Message(object):
+ __metaclass__ = MessageBase
+ def __init__(self, *args, **kwargs):
+ if args:
+ args = dict(zip([f.name for f in self.fields], args))
+ args.update(kwargs)
+ kwargs = args;
+ for k, v in kwargs.iteritems():
+ assert k in self.fieldmap, "Invalid keyword argument"
+ setattr(self, k, v)
+
+ @classmethod
+ def field(cls):
+ class MessageField(Field):
+ def repr(self):
+ return cls.__name__
+ def unmarshall(self, data, offset):
+ return cls.unmarshall(data, offset)
+ def marshall(self, val):
+ return val.marshall()
+ return MessageField()
+
+ @classmethod
+ def unmarshall(cls, data, offset=0):
+ vals = {}
+ start = offset
+ for field in cls.fields:
+ size, val = field.unmarshall(data, offset)
+ offset += size
+ vals[field.name] = val
+ return offset - start, cls(**vals)
+ def marshall(self):
+ res = []
+ callbacks = []
+ for field in self.fields:
+ val = field.marshall(getattr(self, field.name, None))
+ if callable(val):
+ callbacks.append((val, len(res)))
+ if isinstance(val, list):
+ res += val
+ else:
+ res.append(val)
+ for fn, i in reversed(callbacks):
+ res[i] = fn(res, i)
+ return res
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pyxp/mux.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pyxp/mux.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,163 @@
+# Derived from libmux, available in Plan 9 under /sys/src/libmux
+# under the following terms:
+#
+# Copyright (C) 2003-2006 Russ Cox, Massachusetts Institute of Technology
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+from pyxp import fields
+from pyxp.dial import dial
+from threading import *
+Condition = Condition().__class__
+
+__all__ = 'Mux',
+
+class Mux(object):
+ def __init__(self, con, process, mintag=0, maxtag=1<<16 - 1):
+ self.queue = set()
+ self.lock = RLock()
+ self.rendez = Condition(self.lock)
+ self.outlock = RLock()
+ self.inlock = RLock()
+ self.process = process
+ self.wait = {}
+ self.free = set(range(mintag, maxtag))
+ self.mintag = mintag
+ self.maxtag = maxtag
+ self.muxer = None
+
+ if isinstance(con, basestring):
+ con = dial(con)
+ self.fd = con
+
+ if self.fd is None:
+ raise Exception("No connection")
+
+ def rpc(self, dat):
+ r = self.newrpc(dat)
+
+ try:
+ self.lock.acquire()
+ while self.muxer and self.muxer != r and r.data is None:
+ r.wait()
+
+ if r.data is None:
+ if self.muxer and self.muxer != r:
+ self.fail()
+ self.muxer = r
+ self.lock.release()
+ try:
+ while r.data is None:
+ data = self.recv()
+ if data is None:
+ self.lock.acquire()
+ self.queue.remove(r)
+ raise Exception("unexpected eof")
+ self.dispatch(data)
+ self.lock.acquire()
+ finally:
+ self.electmuxer()
+ self.puttag(r)
+ except Exception, e:
+ import sys
+ import traceback
+ traceback.print_exc(sys.stdout)
+ print e
+ finally:
+ if self.lock._is_owned():
+ self.lock.release()
+ return r.data
+
+ def electmuxer(self):
+ for rpc in self.queue:
+ if self.muxer != rpc and rpc.async == False:
+ self.muxer = rpc
+ rpc.notify()
+ return
+ self.muxer = None
+
+ def dispatch(self, dat):
+ tag = dat.tag - self.mintag
+ r = None
+ with self.lock:
+ r = self.wait.get(tag, None)
+ if r is None or r not in self.queue:
+ print "bad rpc tag: %u (no one waiting on it)" % dat.tag
+ return
+ self.queue.remove(r)
+ r.data = dat
+ r.notify()
+
+ def gettag(self, r):
+ tag = 0
+
+ while not self.free:
+ self.rendez.wait()
+
+ tag = self.free.pop()
+
+ if tag in self.wait:
+ raise Exception("nwait botch")
+
+ self.wait[tag] = r
+
+ r.tag = tag
+ r.data.tag = r.tag
+ r.data = None
+ return r.tag
+
+ def puttag(self, r):
+ t = r.tag
+ if self.wait.get(t, None) != r:
+ self.fail()
+ del self.wait[t]
+ self.free.add(t)
+ self.rendez.notify()
+
+ def send(self, dat):
+ data = ''.join(dat.marshall())
+ n = self.fd.send(data)
+ return n == len(data)
+ def recv(self):
+ data = self.fd.recv(4)
+ if data:
+ len = fields.Int.decoders[4](data, 0)
+ data += self.fd.recv(len - 4)
+ return self.process(data)
+
+ def fail():
+ raise Exception()
+
+ def newrpc(self, dat):
+ rpc = Rpc(self, dat)
+ tag = None
+
+ with self.lock:
+ self.gettag(rpc)
+ self.queue.add(rpc)
+
+ if rpc.tag >= 0 and self.send(dat):
+ return rpc
+
+ with self.lock:
+ self.queue.remove(rpc)
+ self.puttag(rpc)
+
+class Rpc(Condition):
+ def __init__(self, mux, data):
+ super(Rpc, self).__init__(mux.lock)
+ self.mux = mux
+ self.data = data
+ self.waiting = True
+ self.async = False
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/pyxp/types.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/pyxp/types.py Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,55 @@
+from pyxp.messages import Message
+from pyxp.fields import *
+
+__all__ = 'Qid', 'Stat'
+
+class Qid(Message):
+ QTFILE = 0x00
+ QTLINK = 0x01
+ QTSYMLINK = 0x02
+ QTTMP = 0x04
+ QTAUTH = 0x08
+ QTMOUNT = 0x10
+ QTEXCL = 0x20
+ QTAPPEND = 0x40
+ QTDIR = 0x80
+
+ type = Int(1)
+ version = Int(4)
+ path = Int(8)
+
+class Stat(Message):
+ DMDIR = 0x80000000
+ DMAPPEND = 0x40000000
+ DMEXCL = 0x20000000
+ DMMOUNT = 0x10000000
+ DMAUTH = 0x08000000
+ DMTMP = 0x04000000
+ DMSYMLINK = 0x02000000
+ DMDEVICE = 0x00800000
+ DMNAMEDPIPE = 0x00200000
+ DMSOCKET = 0x00100000
+ DMSETUID = 0x00080000
+ DMSETGID = 0x00040000
+
+ @classmethod
+ def unmarshall_list(cls, data, offset=0):
+ while offset < len(data):
+ n, stat = cls.unmarshall(data, offset)
+ offset += n
+ yield stat
+
+ size = Size(2)
+ type = Int(2)
+ dev = Int(4)
+ qid = Qid.field()
+ mode = Int(4)
+ atime = Date()
+ mtime = Date()
+ length = Int(8)
+ name = String()
+ uid = String()
+ gid = String()
+ muid = String()
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 alternative_wmiircs/python/wmiirc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/alternative_wmiircs/python/wmiirc Sun May 17 14:15:08 2009 -0400
@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+import os
+import re
+import sys
+
+from pygmi import *
+from pygmi import events
+
+identity = lambda k: k
+
+# Keys
+events.keydefs = dict(
+ mod='Mod4',
+ left='h',
+ down='j',
+ up='k',
+ right='l')
+
+# Bars
+noticetimeout=5
+noticebar=('right', '!notice')
+
+# Theme
+background = '#333333'
+floatbackground='#222222'
+
+wmii.font = 'drift,-*-fixed-*-*-*-*-9-*-*-*-*-*-*-*'
+wmii.normcolors = '#000000', '#c1c48b', '#81654f'
+wmii.focuscolors = '#000000', '#81654f', '#000000'
+wmii.grabmod = events.keydefs['mod']
+wmii.border = 2
+
+def setbackground(color):
+ call('xsetroot', '-solid', color)
+setbackground(background)
+
+terminal = 'wmiir', 'setsid', 'xterm'
+shell = os.environ.get('SHELL', 'sh')
+
+@defmonitor
+def load():
+ return re.sub(r'^.*: ', '', call('uptime')).replace(', ', ' ')
+@defmonitor
+def time():
+ from datetime import datetime
+ return datetime.now().strftime('%c')
+
+wmii.colrules = (
+ ('gimp', '17+83+41'),
+ ('.*', '62+38 # Golden Ratio'),
+)
+
+wmii.tagrules = (
+ ('MPlayer|VLC', '~'),
+)
+
+def unresponsive_client(client):
+ msg = 'The following client is not responding. What would you like to do?'
+ resp = call('wihack', '-transient', client.id,
+ 'xmessage', '-nearmouse', '-buttons', 'Kill,Wait', '-print',
+ '%s\n %s' % (client, client.label))
+ if resp == 'Kill':
+ client.slay()
+
+# End Configuration
+
+confpath = os.environ['WMII_CONFPATH'].split(':')
+events.confpath = confpath
+events.shell = shell
+
+client.write('/event', 'Start wmiirc')
+
+tags = Tags()
+bind_events({
+ 'Quit': lambda args: sys.exit(),
+ 'Start': lambda args: args == 'wmiirc' and sys.exit(),
+ 'CreateTag': tags.add,
+ 'DestroyTag': tags.delete,
+ 'FocusTag': tags.focus,
+ 'UnfocusTag': tags.unfocus,
+ 'UrgentTag': lambda args: tags.set_urgent(args.split(' ')[1], True),
+ 'NotUrgentTag': lambda args: tags.set_urgent(args.split(' ')[1], False),
+
+ 'AreaFocus': lambda args: (args == '~' and
+ (setbackground(floatbackground), True) or
+ setbackground(background)),
+
+ 'Unresponsive': lambda args: Thread(target=unresponsive_client,
+ args=(Client(args),)).start(),
+
+ 'Notice': lambda args: notice.show(args),
+
+ ('LeftBarClick', 'LeftBarDND'):
+ lambda args: args.split(' ')[0] == '1' and tags.select(args.split(' ', 1)[1]),
+
+ 'ClientMouseDown': lambda args: menu(*args.split(' '), type='client'),
+ 'LeftBarMouseDown': lambda args: menu(*reversed(args.split(' ')), type='lbar'),
+})
+
+@apply
+class Actions(events.Actions):
+ def rehash(self, args=''):
+ program_menu.choices = program_list(os.environ['PATH'].split(':'))
+ def quit(self, args=''):
+ wmii.ctl('quit')
+ def eval_(self, args=''):
+ exec args
+ def exec_(self, args=''):
+ wmii['exec'] = args
+ def exit(self, args=''):
+ client.write('/event', 'Quit')
+
+program_menu = Menu(histfile='%s/history.prog' % confpath[0], nhist=5000,
+ action=curry(call, 'wmiir', 'setsid',
+ shell, '-c', background=True))
+action_menu = Menu(histfile='%s/history.action' % confpath[0], nhist=500,
+ choices=lambda: Actions._choices,
+ action=Actions._call)
+tag_menu = Menu(histfile='%s/history.tags' % confpath[0], nhist=100,
+ choices=lambda: sorted(tags.tags.keys()))
+
+def menu(target, button, type):
+ MENUS = {
+ ('client', '3'): (
+ ('Delete', lambda c: Client(c).kill()),
+ ('Kill', lambda c: Client(c).slay()),
+ ('Fullscreen', lambda c: Client(c).set('Fullscreen', 'on'))),
+ ('lbar', '3'): (
+ ('Delete', lambda t: Tag(t).delete())),
+ }
+ choices = MENUS.get((type, button), None)
+ if choices:
+ ClickMenu(choices=(k for k, v in choices),
+ action=lambda k: dict(choices).get(k, identity)(target)
+ ).call()
+
+class Notice(Button):
+ def __init__(self):
+ super(Notice, self).__init__(*noticebar)
+ self.timer = None
+
+ def tick(self):
+ self.label = ''
+
+ def show(self, notice):
+ if self.timer:
+ self.timer.stop()
+ self.label = notice
+ from threading import Timer
+ self.timer = Timer(noticetimeout, self.tick)
+ self.timer.start()
+notice = Notice()
+
+bind_keys({
+ '%(mod)s-Control-t': lambda k: events.toggle_keys(restore='%(mod)s-Control-t'),
+
+ '%(mod)s-%(left)s': lambda k: Tag('sel').select('left'),
+ '%(mod)s-%(right)s': lambda k: Tag('sel').select('right'),
+ '%(mod)s-%(up)s': lambda k: Tag('sel').select('up'),
+ '%(mod)s-%(down)s': lambda k: Tag('sel').select('down'),
+
+ '%(mod)s-Control-%(up)s': lambda k: Tag('sel').select('up', stack=True),
+ '%(mod)s-Control-%(down)s': lambda k: Tag('sel').select('down', stack=True),
+
+ '%(mod)s-space': lambda k: Tag('sel').select('toggle'),
+
+ '%(mod)s-Shift-%(left)s': lambda k: Tag('sel').send(Client('sel'), 'left'),
+ '%(mod)s-Shift-%(right)s': lambda k: Tag('sel').send(Client('sel'), 'right'),
+ '%(mod)s-Shift-%(up)s': lambda k: Tag('sel').send(Client('sel'), 'up'),
+ '%(mod)s-Shift-%(down)s': lambda k: Tag('sel').send(Client('sel'), 'down'),
+
+ '%(mod)s-Shift-space': lambda k: Tag('sel').send(Client('sel'), 'toggle'),
+
+ '%(mod)s-d': lambda k: setattr(Tag('sel').selcol, 'mode', 'default-max'),
+ '%(mod)s-s': lambda k: setattr(Tag('sel').selcol, 'mode', 'stack-max'),
+ '%(mod)s-m': lambda k: setattr(Tag('sel').selcol, 'mode', 'stack+max'),
+
+ '%(mod)s-f': lambda k: Client('sel').set('Fullscreen', 'toggle'),
+ '%(mod)s-Shift-c': lambda k: Client('sel').kill(),
+
+ '%(mod)s-a': lambda k: action_menu.call(),
+ '%(mod)s-p': lambda k: program_menu.call(),
+
+ '%(mod)s-Return': lambda k: call(*terminal, background=True),
+
+ '%(mod)s-t': lambda k: tags.select(tag_menu.call()),
+ '%(mod)s-Shift-t': lambda k: setattr(Client('sel'), 'tags', tag_menu.call()),
+
+ '%(mod)s-n': lambda k: tags.select(tags.next()),
+ '%(mod)s-b': lambda k: tags.select(tags.next(True)),
+ '%(mod)s-i': lambda k: tags.select(tags.NEXT),
+ '%(mod)s-o': lambda k: tags.select(tags.PREV),
+})
+def bind_num(i):
+ bind_keys({
+ '%%(mod)s-%d' % i: lambda k: tags.select(str(i)),
+ '%%(mod)s-Shift-%d' % i: lambda k: setattr(Client('sel'), 'tags', i),
+ })
+map(bind_num, range(0, 10))
+
+Actions.rehash()
+
+# Misc Setup
+#progs_file=`{namespace}^/proglist.$pid
+
+event_loop()
+
+# vim:se sts=4 sw=4 et:
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii.rc.rc
--- a/cmd/wmii.rc.rc Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii.rc.rc Sun May 17 14:15:08 2009 -0400
@@ -155,6 +155,10 @@
         Key-$1 $1
 }
 
+fn Event-Quit {
+ exit
+}
+
 fn Event-Start {
        if(~ $1 $wmiiscript)
                exit
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii/bar.c
--- a/cmd/wmii/bar.c Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii/bar.c Sun May 17 14:15:08 2009 -0400
@@ -85,6 +85,10 @@
         b->id = id++;
         utflcpy(b->name, name, sizeof b->name);
         b->col = def.normcolor;
+
+ strlcat(b->buf, b->col.colstr, sizeof(b->buf));
+ strlcat(b->buf, " ", sizeof(b->buf));
+ strlcat(b->buf, b->text, sizeof(b->buf));
         
         for(sp=screens; (s = *sp); sp++) {
                 i = bp - s->bar;
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii/dat.h
--- a/cmd/wmii/dat.h Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii/dat.h Sun May 17 14:15:08 2009 -0400
@@ -64,9 +64,9 @@
 extern char* modes[];
 
 #define TOGGLE(x) \
- (x == On ? "On" : \
- x == Off ? "Off" : \
- x == Toggle ? "Toggle" : \
+ (x == On ? "on" : \
+ x == Off ? "off" : \
+ x == Toggle ? "toggle" : \
          "<toggle>")
 enum {
         Off,
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii/ewmh.c
--- a/cmd/wmii/ewmh.c Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii/ewmh.c Sun May 17 14:15:08 2009 -0400
@@ -11,9 +11,9 @@
 static void ewmh_setstate(Client*, Atom, int);
 
 #define Net(x) ("_NET_" x)
-#define Action(x) ("_NET_WM_ACTION_" x)
-#define State(x) ("_NET_WM_STATE_" x)
-#define Type(x) ("_NET_WM_WINDOW_TYPE_" x)
+#define Action(x) Net("WM_ACTION_" x)
+#define State(x) Net("WM_STATE_" x)
+#define Type(x) Net("WM_WINDOW_TYPE_" x)
 #define NET(x) xatom(Net(x))
 #define ACTION(x) xatom(Action(x))
 #define STATE(x) xatom(State(x))
@@ -223,13 +223,13 @@
 ewmh_getwintype(Client *c) {
         static Prop props[] = {
                 {Type("DESKTOP"), TypeDesktop},
- {Type("DOCK"), TypeDock},
+ {Type("DOCK"), TypeDock},
                 {Type("TOOLBAR"), TypeToolbar},
- {Type("MENU"), TypeMenu},
+ {Type("MENU"), TypeMenu},
                 {Type("UTILITY"), TypeUtility},
- {Type("SPLASH"), TypeSplash},
- {Type("DIALOG"), TypeDialog},
- {Type("NORMAL"), TypeNormal},
+ {Type("SPLASH"), TypeSplash},
+ {Type("DIALOG"), TypeDialog},
+ {Type("NORMAL"), TypeNormal},
                 {0, }
         };
         long mask;
@@ -356,8 +356,7 @@
                 case StateUnset: action = Off; break;
                 case StateSet: action = On; break;
                 case StateToggle: action = Toggle; break;
- default:
- return -1;
+ default: return -1;
                 }
                 Dprint(DEwmh, "\tAction: %s\n", TOGGLE(action));
                 ewmh_setstate(c, l[1], action);
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii/fns.h
--- a/cmd/wmii/fns.h Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii/fns.h Sun May 17 14:15:08 2009 -0400
@@ -245,7 +245,7 @@
 void mouse_resizecol(Divide*);
 bool readmotion(Point*);
 int readmouse(Point*, uint*);
-Align snap_rect(Rectangle *rects, int num, Rectangle *current, Align *mask, int snapw);
+Align snap_rect(const Rectangle *rects, int num, Rectangle *current, Align *mask, int snapw);
 
 /* printevent.c */
 void printevent(XEvent*);
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii/fs.c
--- a/cmd/wmii/fs.c Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii/fs.c Sun May 17 14:15:08 2009 -0400
@@ -535,6 +535,8 @@
                 return;
         case FsFCtags:
                 ixp_srv_data2cstring(r);
+ print("%d\n", r->ifcall.io.count);
+ print("%s\n", r->ifcall.io.data);
                 apply_tags(f->p.client, r->ifcall.io.data);
                 r->ofcall.io.count = r->ifcall.io.count;
                 respond(r, nil);
@@ -707,6 +709,11 @@
                 q = f->p.bar->text;
                 utflcpy(q, (char*)m.pos, sizeof ((Bar*)0)->text);
 
+ p[0] = '\0';
+ strlcat(p, f->p.bar->col.colstr, sizeof(f->p.bar->buf));
+ strlcat(p, " ", sizeof(f->p.bar->buf));
+ strlcat(p, f->p.bar->text, sizeof(f->p.bar->buf));
+
                 bar_draw(f->p.bar->screen);
                 break;
         }
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii/main.c
--- a/cmd/wmii/main.c Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii/main.c Sun May 17 14:15:08 2009 -0400
@@ -424,6 +424,8 @@
         i = ixp_serverloop(&srv);
         if(i)
                 fprint(2, "%s: error: %r\n", argv0);
+ else
+ event("Quit");
 
         cleanup();
 
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii/mouse.c
--- a/cmd/wmii/mouse.c Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii/mouse.c Sun May 17 14:15:08 2009 -0400
@@ -99,7 +99,7 @@
 
 /* Yes, yes, macros are evil. So are patterns. */
 #define frob(x, y) \
- Rectangle *rp; \
+ const Rectangle *rp; \
         int i, tx; \
                                                                           \
         for(i=0; i < nrect; i++) { \
@@ -117,12 +117,12 @@
         return dx \
 
 static int
-snap_hline(Rectangle *rects, int nrect, int dx, Rectangle *r, int y) {
+snap_hline(const Rectangle *rects, int nrect, int dx, const Rectangle *r, int y) {
         frob(y, x);
 }
 
 static int
-snap_vline(Rectangle *rects, int nrect, int dx, Rectangle *r, int x) {
+snap_vline(const Rectangle *rects, int nrect, int dx, const Rectangle *r, int x) {
         frob(x, y);
 }
 
@@ -134,7 +134,7 @@
  * snap.
  */
 Align
-snap_rect(Rectangle *rects, int num, Rectangle *r, Align *mask, int snap) {
+snap_rect(const Rectangle *rects, int num, Rectangle *r, Align *mask, int snap) {
         Align ret;
         Point d;
         
diff -r 5d035a83e42f -r c6e4f5df5678 cmd/wmii/x11.c
--- a/cmd/wmii/x11.c Sat May 16 11:14:33 2009 -0400
+++ b/cmd/wmii/x11.c Sun May 17 14:15:08 2009 -0400
@@ -2,8 +2,6 @@
  * See LICENSE file for license details.
  */
 #define _X11_VISIBLE
-#define ZP _ZP
-#define ZR _ZR
 #define pointerwin __pointerwin
 #include "dat.h"
 #include <limits.h>
@@ -12,9 +10,6 @@
 #include <unistd.h>
 #include <bio.h>
 #include "fns.h"
-#undef ZP /* These should be allocated in read-only memory, */
-#undef ZR /* but declaring them const causes too much trouble
- * elsewhere. */
 #undef pointerwin
 
 const Point ZP = {0, 0};
@@ -897,7 +892,8 @@
 
 char**
 strlistdup(char *list[]) {
- char **p, *q;
+ char **p;
+ char *q;
         int i, m, n;
 
         n = 0;
@@ -1120,11 +1116,11 @@
                 h->aspect.max.y = xs.max_aspect.y;
         }
 
- h->position = ((xs.flags & (USPosition|PPosition)) != 0);
+ h->position = (xs.flags & (USPosition|PPosition)) != 0;
 
- p = ZP;
         if(!(xs.flags & PWinGravity))
                 xs.win_gravity = NorthWestGravity;
+ p = ZP;
         switch (xs.win_gravity) {
         case EastGravity:
         case CenterGravity:
@@ -1150,7 +1146,7 @@
                 break;
         }
         h->grav = p;
- h->gravstatic = (xs.win_gravity==StaticGravity);
+ h->gravstatic = (xs.win_gravity == StaticGravity);
 }
 
 Rectangle
diff -r 5d035a83e42f -r c6e4f5df5678 include/x11.h
--- a/include/x11.h Sat May 16 11:14:33 2009 -0400
+++ b/include/x11.h Sun May 17 14:15:08 2009 -0400
@@ -160,8 +160,8 @@
 Display *display;
 Screen scr;
 
-extern Point ZP;
-extern Rectangle ZR;
+extern const Point ZP;
+extern const Rectangle ZR;
 extern Window* pointerwin;
 
 Point Pt(int x, int y);
Received on Sun May 17 2009 - 18:15:12 UTC

This archive was generated by hypermail 2.2.0 : Sun May 17 2009 - 18:24:04 UTC