diff --git a/.hgignore b/.hgignore index d107c6a9..0b9ad02c 100644 --- a/.hgignore +++ b/.hgignore @@ -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 --git a/alternative_wmiircs/python/pygmi/__init__.py b/alternative_wmiircs/python/pygmi/__init__.py new file mode 100644 index 00000000..c9129f8e --- /dev/null +++ b/alternative_wmiircs/python/pygmi/__init__.py @@ -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 --git a/alternative_wmiircs/python/pygmi/events.py b/alternative_wmiircs/python/pygmi/events.py new file mode 100644 index 00000000..0a9c397b --- /dev/null +++ b/alternative_wmiircs/python/pygmi/events.py @@ -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 --git a/alternative_wmiircs/python/pygmi/fs.py b/alternative_wmiircs/python/pygmi/fs.py new file mode 100644 index 00000000..ed465f65 --- /dev/null +++ b/alternative_wmiircs/python/pygmi/fs.py @@ -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 --git a/alternative_wmiircs/python/pygmi/menu.py b/alternative_wmiircs/python/pygmi/menu.py new file mode 100644 index 00000000..7e78a5b9 --- /dev/null +++ b/alternative_wmiircs/python/pygmi/menu.py @@ -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 --git a/alternative_wmiircs/python/pygmi/monitor.py b/alternative_wmiircs/python/pygmi/monitor.py new file mode 100644 index 00000000..7d3fd111 --- /dev/null +++ b/alternative_wmiircs/python/pygmi/monitor.py @@ -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 --git a/alternative_wmiircs/python/pyxp/__init__.py b/alternative_wmiircs/python/pyxp/__init__.py new file mode 100644 index 00000000..2ba74006 --- /dev/null +++ b/alternative_wmiircs/python/pyxp/__init__.py @@ -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 --git a/alternative_wmiircs/python/pyxp/client.py b/alternative_wmiircs/python/pyxp/client.py new file mode 100644 index 00000000..f019d853 --- /dev/null +++ b/alternative_wmiircs/python/pyxp/client.py @@ -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 --git a/alternative_wmiircs/python/pyxp/dial.py b/alternative_wmiircs/python/pyxp/dial.py new file mode 100644 index 00000000..55dcf9d5 --- /dev/null +++ b/alternative_wmiircs/python/pyxp/dial.py @@ -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 --git a/alternative_wmiircs/python/pyxp/fcall.py b/alternative_wmiircs/python/pyxp/fcall.py new file mode 100644 index 00000000..15f4601f --- /dev/null +++ b/alternative_wmiircs/python/pyxp/fcall.py @@ -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 --git a/alternative_wmiircs/python/pyxp/fields.py b/alternative_wmiircs/python/pyxp/fields.py new file mode 100644 index 00000000..9fce5f81 --- /dev/null +++ b/alternative_wmiircs/python/pyxp/fields.py @@ -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 '' % (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 --git a/alternative_wmiircs/python/pyxp/messages.py b/alternative_wmiircs/python/pyxp/messages.py new file mode 100644 index 00000000..8498e50f --- /dev/null +++ b/alternative_wmiircs/python/pyxp/messages.py @@ -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 --git a/alternative_wmiircs/python/pyxp/mux.py b/alternative_wmiircs/python/pyxp/mux.py new file mode 100644 index 00000000..42272df3 --- /dev/null +++ b/alternative_wmiircs/python/pyxp/mux.py @@ -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 --git a/alternative_wmiircs/python/pyxp/types.py b/alternative_wmiircs/python/pyxp/types.py new file mode 100644 index 00000000..a5ca1c02 --- /dev/null +++ b/alternative_wmiircs/python/pyxp/types.py @@ -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 --git a/alternative_wmiircs/python/wmiirc b/alternative_wmiircs/python/wmiirc new file mode 100644 index 00000000..7c80fca9 --- /dev/null +++ b/alternative_wmiircs/python/wmiirc @@ -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 --git a/cmd/wmii.rc.rc b/cmd/wmii.rc.rc index 8ec3f435..6376a267 100755 --- a/cmd/wmii.rc.rc +++ b/cmd/wmii.rc.rc @@ -155,6 +155,10 @@ fn Event-Key { Key-$1 $1 } +fn Event-Quit { + exit +} + fn Event-Start { if(~ $1 $wmiiscript) exit diff --git a/cmd/wmii/bar.c b/cmd/wmii/bar.c index 21dd0b8c..9ff72ebc 100644 --- a/cmd/wmii/bar.c +++ b/cmd/wmii/bar.c @@ -85,6 +85,10 @@ bar_create(Bar **bp, const char *name) { 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 --git a/cmd/wmii/dat.h b/cmd/wmii/dat.h index 3414e6eb..ceb4ed15 100644 --- a/cmd/wmii/dat.h +++ b/cmd/wmii/dat.h @@ -64,9 +64,9 @@ enum { extern char* modes[]; #define TOGGLE(x) \ - (x == On ? "On" : \ - x == Off ? "Off" : \ - x == Toggle ? "Toggle" : \ + (x == On ? "on" : \ + x == Off ? "off" : \ + x == Toggle ? "toggle" : \ "") enum { Off, diff --git a/cmd/wmii/ewmh.c b/cmd/wmii/ewmh.c index 17e6555a..af3c942b 100644 --- a/cmd/wmii/ewmh.c +++ b/cmd/wmii/ewmh.c @@ -11,9 +11,9 @@ static void ewmh_getwinstate(Client*); 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 @@ void 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 @@ ewmh_clientmessage(XClientMessageEvent *e) { 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 --git a/cmd/wmii/fns.h b/cmd/wmii/fns.h index 3bd048b5..803ee2c1 100644 --- a/cmd/wmii/fns.h +++ b/cmd/wmii/fns.h @@ -245,7 +245,7 @@ void mouse_resize(Client*, Align, bool); 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 --git a/cmd/wmii/fs.c b/cmd/wmii/fs.c index 1a52b00a..a8507f15 100644 --- a/cmd/wmii/fs.c +++ b/cmd/wmii/fs.c @@ -535,6 +535,8 @@ fs_write(Ixp9Req *r) { 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 @@ fs_clunk(Ixp9Req *r) { 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 --git a/cmd/wmii/main.c b/cmd/wmii/main.c index c8323a11..565b1440 100644 --- a/cmd/wmii/main.c +++ b/cmd/wmii/main.c @@ -424,6 +424,8 @@ extern int fmtevent(Fmt*); i = ixp_serverloop(&srv); if(i) fprint(2, "%s: error: %r\n", argv0); + else + event("Quit"); cleanup(); diff --git a/cmd/wmii/mouse.c b/cmd/wmii/mouse.c index f924c764..43819838 100644 --- a/cmd/wmii/mouse.c +++ b/cmd/wmii/mouse.c @@ -99,7 +99,7 @@ rect_morph(Rectangle *r, Point d, Align *mask) { /* 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 @@ rect_morph(Rectangle *r, Point d, Align *mask) { 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_vline(Rectangle *rects, int nrect, int dx, Rectangle *r, int x) { * 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 --git a/cmd/wmii/x11.c b/cmd/wmii/x11.c index 53b8ac3d..f6b37753 100644 --- a/cmd/wmii/x11.c +++ b/cmd/wmii/x11.c @@ -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 @@ -12,9 +10,6 @@ #include #include #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 @@ getprop_ulong(Window *w, char *prop, char *type, char** strlistdup(char *list[]) { - char **p, *q; + char **p; + char *q; int i, m, n; n = 0; @@ -1120,11 +1116,11 @@ sethints(Window *w) { 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 @@ sethints(Window *w) { break; } h->grav = p; - h->gravstatic = (xs.win_gravity==StaticGravity); + h->gravstatic = (xs.win_gravity == StaticGravity); } Rectangle diff --git a/include/x11.h b/include/x11.h index 8cafb73d..50d70768 100644 --- a/include/x11.h +++ b/include/x11.h @@ -160,8 +160,8 @@ struct Screen { 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);