Make python wmiirc's event matching a bit more powerful.

This commit is contained in:
Kris Maglione 2009-05-22 15:32:32 -04:00
parent 63d3123d73
commit 5d8c72952e
9 changed files with 207 additions and 197 deletions

View File

@ -8,53 +8,20 @@ if 'WMII_ADDRESS' in os.environ:
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
def find_script(name):
for path in confpath:
if os.access('%s/%s' % (path, name), os.X_OK):
return '%s/%s' % (path, name)
confpath = os.environ.get('WMII_CONFPATH', '%s/.wmii' % os.environ['HOME']).split(':')
shell = os.environ['SHELL']
sys.path += confpath
from pygmi import events, fs, menu, monitor
from pygmi.util import *
from pygmi.events import *
from pygmi.fs import *
from pygmi.menu import *
from pygmi.monitor import *
from pygmi import util, events, fs, menu, monitor
__all__ = (fs.__all__ + monitor.__all__ + events.__all__ +
menu.__all__ + (
'client', 'call', 'curry', 'program_list',
'find_script', 'confpath', 'shell'))
menu.__all__ + util.__all__ +
('client', 'confpath', 'shell'))
# vim:se sts=4 sw=4 et:

View File

@ -4,17 +4,40 @@ import sys
import traceback
import pygmi
from pygmi import monitor, client, call, program_list
from pygmi import monitor, client, curry, call, program_list, _
__all__ = ('bind_keys', 'bind_events', 'toggle_keys', 'event_loop',
'event')
'event', 'Match')
keydefs = {}
keys = {}
events = {}
eventmatchers = {}
alive = True
class Match(object):
def __init__(self, *args):
self.args = args
self.matchers = []
for a in args:
if a is _:
a = lambda k: True
elif isinstance(a, basestring):
a = a.__eq__
elif isinstance(a, (list, tuple)):
a = curry(lambda ary, k: k in ary, a)
elif hasattr(a, 'search'):
a = a.search
else:
a = str(a).__eq__
self.matchers.append(a)
def match(self, string):
ary = string.split(' ', len(self.matchers))
if all(m(a) for m, a in zip(self.matchers, ary)):
return ary
def flatten(items):
for k, v in items.iteritems():
if not isinstance(k, (list, tuple)):
@ -28,7 +51,10 @@ def bind_keys(items):
def bind_events(items):
for k, v in flatten(items):
events[k] = v
if isinstance(k, Match):
eventmatchers[k] = v
else:
events[k] = v
def event(fn):
bind_events({fn.__name__: fn})
@ -50,11 +76,15 @@ def toggle_keys(on=None, restore=None):
client.write('/keys', restore or ' ')
def dispatch(event, args=''):
if event in events:
try:
try:
if event in events:
events[event](args)
except Exception, e:
traceback.print_exc(sys.stderr)
for matcher, action in eventmatchers.iteritems():
ary = matcher.match(' '.join((event, args)))
if ary is not None:
action(*ary)
except Exception, e:
traceback.print_exc(sys.stderr)
def event_loop():
from pygmi import events

View File

@ -342,12 +342,15 @@ class Button(object):
self.create(colors, label)
def create(self, colors=None, label=None):
def fail(resp, exc, tb):
self.file = None
if not self.file:
self.file = client.create(self.path, ORDWR)
if colors:
self.file.awrite(self.getval(colors, label), offset=0)
self.file.awrite(self.getval(colors, label), offset=0, fail=fail)
elif label:
self.file.awrite(label, offset=24)
self.file.awrite(label, offset=24, fail=fail)
def remove(self):
if self.file:
self.file.aremove()
@ -579,29 +582,4 @@ class Tags(object):
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:

View File

@ -1,4 +1,4 @@
from pygmi import call
from pygmi.util import call
__all__ = 'Menu', 'ClickMenu'

View File

@ -1,3 +1,5 @@
from threading import Timer
from pygmi import client
from pygmi.fs import *
@ -33,7 +35,6 @@ class MonitorBase(type):
return new_cls
class Monitor(object):
side = 'right'
interval = 1.0
@ -57,7 +58,6 @@ class Monitor(object):
if self.timer and mon is not self:
return
if self.active:
from threading import Timer
label = self.getlabel()
if isinstance(label, basestring):
label = None, label

View File

@ -0,0 +1,52 @@
import os
import subprocess
import pygmi
__all__ = 'call', 'program_list', 'curry', 'find_script', '_'
def _():
pass
def call(*args, **kwargs):
background = kwargs.pop('background', False)
input = kwargs.pop('input', None)
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):
if _ in args:
blank = [i for i in range(0, len(args)) if args[i] is _]
def curried(*newargs, **newkwargs):
ary = list(args)
for k, v in zip(blank, newargs):
ary[k] = v
ary = tuple(ary) + newargs[len(blank):]
return func(*ary, **dict(kwargs, **newkwargs))
else:
def curried(*newargs, **newkwargs):
return func(*(args + newargs), **dict(kwargs, **newkwargs))
curried.__name__ = func.__name__ + '__curried__'
return curried
def find_script(name):
for path in pygmi.confpath:
if os.access('%s/%s' % (path, name), os.X_OK):
return '%s/%s' % (path, name)
# vim:se sts=4 sw=4 et:

View File

@ -10,72 +10,55 @@ def awithfile(*oargs, **okwargs):
return next
return wrapper
def wrap_callback(fn, file):
file.called = 0
def callback(data, exc, tb):
file.called += 1
file.close()
if callable(fn):
fn(data, exc, tb)
Client.respond(fn, data, exc, tb)
return callback
class Client(client.Client):
ROOT_FID = 0
def awalk(self, path, async, fail=None):
ctxt = dict(path=path, fid=self.getfid(), ofid=ROOT_FID)
def _awalk(self, path, callback, fail=None):
ctxt = dict(path=path, fid=self._getfid(), ofid=ROOT_FID)
def next(resp=None, exc=None, tb=None):
if exc and ctxt['ofid'] != ROOT_FID:
self.aclunk(ctxt['fid'])
self._aclunk(ctxt['fid'])
if not ctxt['path'] and resp or exc:
if exc and fail:
return self.respond(fail, None, exc, tb)
return self.respond(async, ctxt['fid'], exc, tb)
return self.respond(callback, ctxt['fid'], exc, tb)
wname = ctxt['path'][:fcall.MAX_WELEM]
ofid = ctxt['ofid']
ctxt['path'] = ctxt['path'][fcall.MAX_WELEM:]
if resp:
ctxt['ofid'] = ctxt['fid']
self.dorpc(fcall.Twalk(fid=ofid,
self._dorpc(fcall.Twalk(fid=ofid,
newfid=ctxt['fid'],
wname=wname),
next)
next()
def _open(self, path, mode, open, origpath=None):
resp = None
with self.walk(path) as nfid:
fid = nfid
resp = self.dorpc(open(fid))
def cleanup():
self.aclunk(fid)
file = File(self, origpath or '/'.join(path), resp, fid, mode, cleanup)
self.files[fid] = file
return file
def _aopen(self, path, mode, open, callback, origpath=None):
_file = property(lambda self: File)
def _aopen(self, path, mode, open, callback, fail=None, origpath=None):
resp = None
def next(fid, exc, tb):
def next(resp, exc, tb):
def cleanup():
self.clunk(fid)
file = File(self, origpath or '/'.join(path), resp, fid, mode, cleanup)
self.files[fid] = file
self._clunk(fid)
file = self._file(self, origpath or '/'.join(path), resp, fid, mode, cleanup)
self.respond(callback, file)
self.dorpc(open(fid), next, callback)
self.awalk(path, next, callback)
self._dorpc(open(fid), next, fail or callback)
self._awalk(path, next, fail or callback)
def aopen(self, path, callback=True, mode=OREAD):
def aopen(self, path, callback=True, fail=None, mode=OREAD):
assert callable(callback)
path = self.splitpath(path)
path = self._splitpath(path)
def open(fid):
return fcall.Topen(fid=fid, mode=mode)
return self._aopen(path, mode, open, callback)
return self._aopen(path, mode, open, fail or callback)
def acreate(self, path, callback=True, mode=OREAD, perm=0):
path = self.splitpath(path)
def acreate(self, path, callback=True, fail=None, 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)
@ -83,21 +66,21 @@ class Client(client.Client):
def callback(resp, exc, tb):
if resp:
resp.close()
return self._aopen(path, mode, open, async,
return self._aopen(path, mode, open, callback, fail,
origpath='/'.join(path + [name]))
def aremove(self, path, callback=True):
path = self.splitpath(path)
def aremove(self, path, callback=True, fail=None):
path = self._splitpath(path)
def next(fid, exc, tb):
self.dorpc(fcall.Tremove(fid=fid), callback)
self.awalk(path, next, callback)
self._dorpc(fcall.Tremove(fid=fid), callback, fail)
self._awalk(path, next, callback, fail)
def astat(self, path, callback):
path = self.splitpath(path)
def astat(self, path, callback, fail = None):
path = self._splitpath(path)
def next(fid, exc, tb):
def next(resp, exc, tb):
callback(resp.stat, exc, tb)
self.dorpc(fcall.Tstat(fid=fid), next, callback)
self._dorpc(fcall.Tstat(fid=fid), next, callback)
@awithfile()
def aread(self, (file, exc, tb), callback, *args, **kwargs):
@ -133,9 +116,9 @@ class File(client.File):
def stat(self, callback):
def next(resp, exc, tb):
callback(resp.stat, exc, tb)
resp = self.dorpc(fcall.Tstat(), next, callback)
resp = self._dorpc(fcall.Tstat(), next, callback)
def aread(self, callback, count=None, offset=None, buf=''):
def aread(self, callback, fail=None, count=None, offset=None, buf=''):
ctxt = dict(res=[], count=self.iounit, offset=self.offset)
if count is not None:
ctxt['count'] = count
@ -153,8 +136,8 @@ class File(client.File):
n = min(ctxt['count'], self.iounit)
ctxt['count'] -= n
self.dorpc(fcall.Tread(offset=ctxt['offset'], count=n),
next, callback)
self._dorpc(fcall.Tread(offset=ctxt['offset'], count=n),
next, fail or callback)
next()
def areadlines(self, callback):
@ -178,7 +161,7 @@ class File(client.File):
callback(None)
self.aread(next)
def awrite(self, data, callback=True, offset=None):
def awrite(self, data, callback=True, fail=None, offset=None):
ctxt = dict(offset=self.offset, off=0)
if offset is not None:
ctxt['offset'] = offset
@ -186,22 +169,25 @@ class File(client.File):
if resp:
ctxt['off'] += resp.count
ctxt['offset'] += resp.count
if ctxt['off'] < len(data):
if ctxt['off'] < len(data) or not (exc or resp):
n = min(len(data), self.iounit)
self.dorpc(fcall.Twrite(offset=ctxt['offset'],
self._dorpc(fcall.Twrite(offset=ctxt['offset'],
data=data[ctxt['off']:ctxt['off']+n]),
next, callback)
next, fail or callback)
else:
if offset is None:
self.offset = ctxt['offset']
self.respond(callback, ctxt['off'], exc, tb)
next()
def aremove(self, callback=True):
def aremove(self, callback=True, fail=None):
def next(resp, exc, tb):
self.close()
self.respond(resp and True, exc, tb)
self.dorpc(fcall.Tremove(), next)
if exc and fail:
self.respond(fail, resp and True, exc, tb)
else:
self.respond(callback, resp and True, exc, tb)
self._dorpc(fcall.Tremove(), next)
# vim:se sts=4 sw=4 et:

View File

@ -52,32 +52,31 @@ class Client(object):
def __enter__(self):
return self
def __exit__(self, *args):
self.cleanup()
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.fids = set()
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))
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,
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,
path = self._splitpath(root)
resp = self._dorpc(fcall.Twalk(fid=ROOT_FID,
newfid=ROOT_FID,
wname=path))
except Exception, e:
@ -86,7 +85,7 @@ class Client(object):
self.mux.fd.close()
raise e
def cleanup(self):
def _cleanup(self):
try:
for f in self.files:
f.close()
@ -94,7 +93,7 @@ class Client(object):
self.mux.fd.close()
self.mux = None
def dorpc(self, req, callback=None, error=None):
def _dorpc(self, req, callback=None, error=None):
def doresp(resp):
if isinstance(resp, fcall.Rerror):
raise RPCError, "%s[%d] RPC returned error: %s" % (
@ -117,38 +116,37 @@ class Client(object):
return doresp(self.mux.rpc(req))
self.mux.rpc(req, next)
def splitpath(self, path):
def _splitpath(self, path):
return [v for v in path.split('/') if v != '']
def getfid(self):
def _getfid(self):
with self.lock:
if self.fids:
return self.fids.pop()
self.lastfid += 1
return self.lastfid
def putfid(self, fid):
def _putfid(self, fid):
with self.lock:
self.files.pop(fid)
self.fids.append(fid)
self.fids.add(fid)
def aclunk(self, fid, callback=None):
def _aclunk(self, fid, callback=None):
def next(resp, exc, tb):
if resp:
self.putfid(fid)
self._putfid(fid)
self.respond(callback, resp, exc, tb)
self.dorpc(fcall.Tclunk(fid=fid), next)
self._dorpc(fcall.Tclunk(fid=fid), next)
def clunk(self, fid):
def _clunk(self, fid):
try:
self.dorpc(fcall.Tclunk(fid=fid))
self._dorpc(fcall.Tclunk(fid=fid))
finally:
self.putfid(fid)
self._putfid(fid)
def walk(self, path):
fid = self.getfid()
def _walk(self, path):
fid = self._getfid()
ofid = ROOT_FID
while True:
self.dorpc(fcall.Twalk(fid=ofid, newfid=fid,
self._dorpc(fcall.Twalk(fid=ofid, newfid=fid,
wname=path[0:fcall.MAX_WELEM]))
path = path[fcall.MAX_WELEM:]
ofid = fid
@ -161,32 +159,31 @@ class Client(object):
return fid
def __exit__(res, exc_type, exc_value, traceback):
if exc_type:
self.clunk(fid)
self._clunk(fid)
return Res
_file = property(lambda self: File)
def _open(self, path, mode, open, origpath=None):
resp = None
with self.walk(path) as nfid:
with self._walk(path) as nfid:
fid = nfid
resp = self.dorpc(open(fid))
resp = self._dorpc(open(fid))
def cleanup():
self.aclunk(fid)
file = File(self, origpath or '/'.join(path), resp, fid, mode, cleanup)
self.files[fid] = file
self._aclunk(fid)
file = self._file(self, origpath or '/'.join(path), resp, fid, mode, cleanup)
return file
def open(self, path, mode=OREAD):
path = self.splitpath(path)
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)
path = self._splitpath(path)
name = path.pop()
def open(fid):
@ -194,19 +191,19 @@ class Client(object):
return self._open(path, mode, open, origpath='/'.join(path + [name]))
def remove(self, path):
path = self.splitpath(path)
path = self._splitpath(path)
with self.walk(path) as fid:
self.dorpc(fcall.Tremove(fid=fid))
with self._walk(path) as fid:
self._dorpc(fcall.Tremove(fid=fid))
def stat(self, path):
path = self.splitpath(path)
path = self._splitpath(path)
try:
with self.walk(path) as fid:
resp = self.dorpc(fcall.Tstat(fid= fid))
with self._walk(path) as fid:
resp = self._dorpc(fcall.Tstat(fid= fid))
st = resp.stat()
self.clunk(fid)
self._clunk(fid)
return st
except RPCError:
return None
@ -238,7 +235,7 @@ class File(object):
self.client = client
self.path = path
self.fid = fid
self.cleanup = cleanup
self._cleanup = cleanup
self.mode = mode
self.iounit = fcall.iounit
self.qid = fcall.qid
@ -247,15 +244,15 @@ class File(object):
self.offset = 0
def __del__(self):
if not self.closed:
self.cleanup()
self._cleanup()
def dorpc(self, fcall, async=None, error=None):
def _dorpc(self, fcall, async=None, error=None):
if hasattr(fcall, 'fid'):
fcall.fid = self.fid
return self.client.dorpc(fcall, async, error)
return self.client._dorpc(fcall, async, error)
def stat(self):
resp = self.dorpc(fcall.Tstat())
resp = self._dorpc(fcall.Tstat())
return resp.stat
def read(self, count=None, offset=None, buf=''):
@ -270,7 +267,7 @@ class File(object):
n = min(count, self.iounit)
count -= n
resp = self.dorpc(fcall.Tread(offset=offs, count=n))
resp = self._dorpc(fcall.Tread(offset=offs, count=n))
data = resp.data
offs += len(data)
@ -307,7 +304,7 @@ class File(object):
while off < len(data):
n = min(len(data), self.iounit)
resp = self.dorpc(fcall.Twrite(offset=offs,
resp = self._dorpc(fcall.Twrite(offset=offs,
data=data[off:off+n]))
off += resp.count
offs += resp.count
@ -331,7 +328,7 @@ class File(object):
def close(self):
assert not self.closed
self.closed = True
self.cleanup()
self._cleanup()
self.tg = None
self.fid = None
self.client = None
@ -339,7 +336,7 @@ class File(object):
def remove(self):
try:
self.dorpc(fcall.Tremove())
self._dorpc(fcall.Tremove())
finally:
try:
self.close()

View File

@ -70,8 +70,7 @@ client.awrite('/event', 'Start wmiirc')
tags = Tags()
bind_events({
'Quit': lambda args: sys.exit(),
'Start': lambda args: args == 'wmiirc' and sys.exit(),
('Quit', Match('Start', 'wmiirc')): lambda *a: sys.exit(),
'CreateTag': tags.add,
'DestroyTag': tags.delete,
'FocusTag': tags.focus,
@ -88,11 +87,21 @@ bind_events({
'Notice': lambda args: notice.show(args),
('LeftBarClick', 'LeftBarDND'):
lambda args: args.split()[0] == '1' and tags.select(args.split(' ', 1)[1]),
Match(('LeftBarClick', 'LeftBarDND'), '1'): lambda e, b, tag: tags.select(tag),
Match('LeftBarClick', '4'): lambda *a: tags.select(tags.next(True)),
Match('LeftBarClick', '5'): lambda *a: tags.select(tags.next()),
'ClientMouseDown': lambda args: menu(*args.split(), type='client'),
'LeftBarMouseDown': lambda args: menu(*reversed(args.split()), type='lbar'),
Match('LeftBarMouseDown', 3): lambda e, n, tag: clickmenu((
('Delete', lambda t: Tag(t).delete()),
), (tag,)),
Match('ClientMouseDown', _, 3): lambda e, client, n: clickmenu((
('Delete', lambda c: Client(c).kill()),
('Kill', lambda c: Client(c).slay()),
('Fullscreen', lambda c: Client(c).set('Fullscreen', 'on')),
), (client,)),
Match('ClientClick', _, 4): lambda e, c, n: Tag('sel').select('up'),
Match('ClientClick', _, 5): lambda e, c, n: Tag('sel').select('down'),
})
@apply
@ -117,20 +126,10 @@ action_menu = Menu(histfile='%s/history.action' % confpath[0], nhist=500,
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()
def clickmenu(choices, args):
ClickMenu(choices=(k for k, v in choices),
action=lambda choice: dict(choices).get(choice, identity)(*args)) \
.call()
class Notice(Button):
def __init__(self):
@ -138,7 +137,7 @@ class Notice(Button):
self.timer = None
def tick(self):
self.label = ''
self.label = ' '
def write(self, notice):
client.awrite('/event', 'Notice %s' % notice.replace('\n', ' '))
@ -201,7 +200,8 @@ map(bind_num, range(0, 10))
Actions.rehash()
dirs = ('%s/plugins' % dir for dir in confpath if os.access('%s/plugins' % dir, os.R_OK))
dirs = filter(curry(os.access, _, os.R_OK),
('%s/plugins' % dir for dir in confpath))
files = filter(re.compile(r'\.py$').match,
reduce(operator.add, map(os.listdir, dirs), []))
for f in ['wmiirc_local'] + ['plugins.%s' % file[:-3] for file in files]: