From 9dd1370d0b7cd876f004f7a822b0357039252184 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 20:11:15 +0100 Subject: remove the 'uzbl' subdirectory in examples/*/, since the sandbox they are no longer needed + update paths everywhere + remove examples/config/enchant (also not needed since sandbox) + bugfix: set /home/dieter in sandbox --- examples/data/plugins/bind.py | 521 ++++++++++++++++++++++++++++ examples/data/plugins/cmd_expand.py | 42 +++ examples/data/plugins/completion.py | 206 +++++++++++ examples/data/plugins/config.py | 97 ++++++ examples/data/plugins/keycmd.py | 571 +++++++++++++++++++++++++++++++ examples/data/plugins/mode.py | 176 ++++++++++ examples/data/plugins/on_event.py | 107 ++++++ examples/data/plugins/plugin_template.py | 76 ++++ examples/data/plugins/progress_bar.py | 159 +++++++++ 9 files changed, 1955 insertions(+) create mode 100644 examples/data/plugins/bind.py create mode 100644 examples/data/plugins/cmd_expand.py create mode 100644 examples/data/plugins/completion.py create mode 100644 examples/data/plugins/config.py create mode 100644 examples/data/plugins/keycmd.py create mode 100644 examples/data/plugins/mode.py create mode 100644 examples/data/plugins/on_event.py create mode 100644 examples/data/plugins/plugin_template.py create mode 100644 examples/data/plugins/progress_bar.py (limited to 'examples/data/plugins') diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py new file mode 100644 index 0000000..9e09337 --- /dev/null +++ b/examples/data/plugins/bind.py @@ -0,0 +1,521 @@ +'''Plugin provides support for binds in uzbl. + +For example: + event BIND ZZ = exit -> bind('ZZ', 'exit') + event BIND o _ = uri %s -> bind('o _', 'uri %s') + event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'") + +And it is also possible to execute a function on activation: + bind('DD', myhandler) +''' + +import sys +import re +import pprint + +# Hold the bind dicts for each uzbl instance. +UZBLS = {} + +# Commonly used regular expressions. +MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match +# Matches , <'x':y>, <:'y'>, , <'x'!y>, ... +PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>' +FIND_PROMPTS = re.compile(PROMPTS).split +VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match + +# For accessing a bind glob stack. +ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) + + +# Custom errors. +class ArgumentError(Exception): pass + + +class Bindlet(object): + '''Per-instance bind status/state tracker.''' + + def __init__(self, uzbl): + self.binds = {'global': {}} + self.uzbl = uzbl + self.depth = 0 + self.args = [] + self.last_mode = None + self.after_cmds = None + self.stack_binds = [] + + # A subset of the global mode binds containing non-stack and modkey + # activiated binds for use in the stack mode. + self.globals = [] + + + def __getitem__(self, key): + return self.get_binds(key) + + + def reset(self): + '''Reset the tracker state and return to last mode.''' + + self.depth = 0 + self.args = [] + self.after_cmds = None + self.stack_binds = [] + + if self.last_mode: + mode, self.last_mode = self.last_mode, None + self.uzbl.set_mode(mode) + + self.uzbl.set('keycmd_prompt') + + + def stack(self, bind, args, depth): + '''Enter or add new bind in the next stack level.''' + + if self.depth != depth: + if bind not in self.stack_binds: + self.stack_binds.append(bind) + + return + + current_mode = self.uzbl.get_mode() + if current_mode != 'stack': + self.last_mode = current_mode + self.uzbl.set_mode('stack') + + self.stack_binds = [bind,] + self.args += args + self.depth += 1 + self.after_cmds = bind.prompts[depth] + + + def after(self): + '''If a stack was triggered then set the prompt and default value.''' + + if self.after_cmds is None: + return + + (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None + + self.uzbl.clear_keycmd() + if prompt: + self.uzbl.set('keycmd_prompt', prompt) + + if set and is_cmd: + self.uzbl.send(set) + + elif set and not is_cmd: + self.uzbl.send('event SET_KEYCMD %s' % set) + + + def get_binds(self, mode=None): + '''Return the mode binds + globals. If we are stacked then return + the filtered stack list and modkey & non-stack globals.''' + + if mode is None: + mode = self.uzbl.get_mode() + + if not mode: + mode = 'global' + + if self.depth: + return self.stack_binds + self.globals + + globals = self.binds['global'] + if mode not in self.binds or mode == 'global': + return filter(None, globals.values()) + + binds = dict(globals.items() + self.binds[mode].items()) + return filter(None, binds.values()) + + + def add_bind(self, mode, glob, bind=None): + '''Insert (or override) a bind into the mode bind dict.''' + + if mode not in self.binds: + self.binds[mode] = {glob: bind} + return + + binds = self.binds[mode] + binds[glob] = bind + + if mode == 'global': + # Regen the global-globals list. + self.globals = [] + for bind in binds.values(): + if bind is not None and bind.is_global: + self.globals.append(bind) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = Bindlet(uzbl) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_bindlet(uzbl): + '''Return the bind tracklet for the given uzbl instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def ismodbind(glob): + '''Return True if the glob specifies a modbind.''' + + return bool(MOD_START(glob)) + + +def split_glob(glob): + '''Take a string of the form "cmd _" and return a list of the + modkeys in the glob and the command.''' + + mods = set() + while True: + match = MOD_START(glob) + if not match: + break + + end = match.span()[1] + mods.add(glob[:end]) + glob = glob[end:] + + return (mods, glob) + + +def unquote(str): + '''Remove quotation marks around string.''' + + if str and str[0] == str[-1] and str[0] in ['"', "'"]: + str = str[1:-1] + + return str + + +class Bind(object): + + # Class attribute to hold the number of Bind classes created. + counter = [0,] + + def __init__(self, glob, handler, *args, **kargs): + self.is_callable = callable(handler) + self._repr_cache = None + + if not glob: + raise ArgumentError('glob cannot be blank') + + if self.is_callable: + self.function = handler + self.args = args + self.kargs = kargs + + elif kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + elif hasattr(handler, '__iter__'): + self.commands = handler + + else: + self.commands = [handler,] + list(args) + + self.glob = glob + + # Assign unique id. + self.counter[0] += 1 + self.bid = self.counter[0] + + self.split = split = FIND_PROMPTS(glob) + self.prompts = [] + for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]): + prompt, set = map(unquote, [prompt, set]) + cmd = True if cmd == '!' else False + if prompt and prompt[-1] != ":": + prompt = "%s:" % prompt + + self.prompts.append((prompt, cmd, set)) + + # Check that there is nothing like: fl** + for glob in split[:-1:4]: + if glob.endswith('*'): + msg = "token '*' not at the end of a prompt bind: %r" % split + raise SyntaxError(msg) + + # Check that there is nothing like: fl_ + for glob in split[4::4]: + if not glob: + msg = 'found null segment after first prompt: %r' % split + raise SyntaxError(msg) + + stack = [] + for (index, glob) in enumerate(reversed(split[::4])): + # Is the binding a MODCMD or KEYCMD: + mod_cmd = ismodbind(glob) + + # Do we execute on UPDATES or EXEC events? + on_exec = True if glob[-1] in ['!', '_'] else False + + # Does the command take arguments? + has_args = True if glob[-1] in ['*', '_'] else False + + glob = glob[:-1] if has_args or on_exec else glob + mods, glob = split_glob(glob) + stack.append((on_exec, has_args, mods, glob, index)) + + self.stack = list(reversed(stack)) + self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD]) + + + def __getitem__(self, depth): + '''Get bind info at a depth.''' + + if self.is_global: + return self.stack[0] + + return self.stack[depth] + + + def __repr__(self): + if self._repr_cache: + return self._repr_cache + + args = ['glob=%r' % self.glob, 'bid=%d' % self.bid] + + if self.is_callable: + args.append('function=%r' % self.function) + if self.args: + args.append('args=%r' % self.args) + + if self.kargs: + args.append('kargs=%r' % self.kargs) + + else: + cmdlen = len(self.commands) + cmds = self.commands[0] if cmdlen == 1 else self.commands + args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) + + self._repr_cache = '' % ', '.join(args) + return self._repr_cache + + +def exec_bind(uzbl, bind, *args, **kargs): + '''Execute bind objects.''' + + uzbl.event("EXEC_BIND", bind, args, kargs) + + if bind.is_callable: + args += bind.args + kargs = dict(bind.kargs.items()+kargs.items()) + bind.function(uzbl, *args, **kargs) + return + + if kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + commands = [] + cmd_expand = uzbl.cmd_expand + for cmd in bind.commands: + cmd = cmd_expand(cmd, args) + uzbl.send(cmd) + + +def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs): + '''Add a mode bind.''' + + bindlet = get_bindlet(uzbl) + + if not hasattr(modes, '__iter__'): + modes = unicode(modes).split(',') + + # Sort and filter binds. + modes = filter(None, map(unicode.strip, modes)) + + if callable(handler) or (handler is not None and handler.strip()): + bind = Bind(glob, handler, *args, **kargs) + + else: + bind = None + + for mode in modes: + if not VALID_MODE(mode): + raise NameError('invalid mode name: %r' % mode) + + for mode in modes: + if mode[0] == '-': + mode, bind = mode[1:], None + + bindlet.add_bind(mode, glob, bind) + uzbl.event('ADDED_MODE_BIND', mode, glob, bind) + + +def bind(uzbl, glob, handler, *args, **kargs): + '''Legacy bind function.''' + + mode_bind(uzbl, 'global', glob, handler, *args, **kargs) + + +def parse_mode_bind(uzbl, args): + '''Parser for the MODE_BIND event. + + Example events: + MODE_BIND = + MODE_BIND command o_ = uri %s + MODE_BIND insert,command = ... + MODE_BIND global ... = ... + MODE_BIND global,-insert ... = ... + ''' + + if not args: + raise ArgumentError('missing bind arguments') + + split = map(unicode.strip, args.split(' ', 1)) + if len(split) != 2: + raise ArgumentError('missing mode or bind section: %r' % args) + + modes, args = split[0].split(','), split[1] + split = map(unicode.strip, args.split('=', 1)) + if len(split) != 2: + raise ArgumentError('missing delimiter in bind section: %r' % args) + + glob, command = split + mode_bind(uzbl, modes, glob, command) + + +def parse_bind(uzbl, args): + '''Legacy parsing of the BIND event and conversion to the new format. + + Example events: + request BIND = + request BIND o_ = uri %s + request BIND = ... + request BIND ... = ... + ''' + + parse_mode_bind(uzbl, "global %s" % args) + + +def mode_changed(uzbl, mode): + '''Clear the stack on all non-stack mode changes.''' + + if mode != 'stack': + get_bindlet(uzbl).reset() + + +def match_and_exec(uzbl, bind, depth, keylet, bindlet): + + (on_exec, has_args, mod_cmd, glob, more) = bind[depth] + cmd = keylet.modcmd if mod_cmd else keylet.keycmd + + if mod_cmd and keylet.held != mod_cmd: + return False + + if has_args: + if not cmd.startswith(glob): + return False + + args = [cmd[len(glob):],] + + elif cmd != glob: + return False + + else: + args = [] + + if bind.is_global or (not more and depth == 0): + exec_bind(uzbl, bind, *args) + if not has_args: + uzbl.clear_current() + + return True + + elif more: + bindlet.stack(bind, args, depth) + return False + + args = bindlet.args + args + exec_bind(uzbl, bind, *args) + uzbl.set_mode() + if not has_args: + bindlet.reset() + uzbl.clear_current() + + return True + + +def keycmd_update(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if t[MOD_CMD] or t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return + + bindlet.after() + + +def keycmd_exec(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if t[MOD_CMD] or not t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return uzbl.clear_keycmd() + + bindlet.after() + + +def modcmd_update(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if not t[MOD_CMD] or t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return + + bindlet.after() + + +def modcmd_exec(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if not t[MOD_CMD] or not t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return uzbl.clear_modcmd() + + bindlet.after() + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'BIND': parse_bind, + 'KEYCMD_EXEC': keycmd_exec, + 'KEYCMD_UPDATE': keycmd_update, + 'MODCMD_EXEC': modcmd_exec, + 'MODCMD_UPDATE': modcmd_update, + 'MODE_BIND': parse_mode_bind, + 'MODE_CHANGED': mode_changed, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'bind': bind, + 'mode_bind': mode_bind, + 'get_bindlet': get_bindlet, + }) diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py new file mode 100644 index 0000000..3f6ae2b --- /dev/null +++ b/examples/data/plugins/cmd_expand.py @@ -0,0 +1,42 @@ +def escape(str): + for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]: + str = str.replace(char, (level * '\\') + char) + + return str + + +def cmd_expand(uzbl, cmd, args): + '''Exports a function that provides the following + expansions in any uzbl command string: + + %s = replace('%s', ' '.join(args)) + %r = replace('%r', "'%s'" % escaped(' '.join(args))) + %1 = replace('%1', arg[0]) + %2 = replace('%2', arg[1]) + %n = replace('%n', arg[n-1]) + ''' + + # Ensure (1) all string representable and (2) correct string encoding. + args = map(unicode, args) + + # Direct string replace. + if '%s' in cmd: + cmd = cmd.replace('%s', ' '.join(args)) + + # Escaped and quoted string replace. + if '%r' in cmd: + cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args))) + + # Arg index string replace. + for (index, arg) in enumerate(args): + index += 1 + if '%%%d' % index in cmd: + cmd = cmd.replace('%%%d' % index, unicode(arg)) + + return cmd + + +def init(uzbl): + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export('cmd_expand', cmd_expand) diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py new file mode 100644 index 0000000..8cea203 --- /dev/null +++ b/examples/data/plugins/completion.py @@ -0,0 +1,206 @@ +'''Keycmd completion.''' + +# A list of functions this plugin exports to be used via uzbl object. +__export__ = ['start_completion', 'get_completion_dict'] + +import re + +# Holds the per-instance completion dicts. +UZBLS = {} + +# Completion level +NONE, ONCE, LIST, COMPLETE = range(4) + +# Default instance dict. +DEFAULTS = {'completions': [], 'level': NONE, 'lock': False} + +# The reverse keyword finding re. +FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall + +# Formats +LIST_FORMAT = " %s " +ITEM_FORMAT = "%s%s" + + +def escape(str): + return str.replace("@", "\@") + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + # Make sure the config keys for all possible completions are known. + uzbl.send('dump_config_as_events') + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_completion_dict(uzbl): + '''Get data stored for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_incomplete_keyword(uzbl): + '''Gets the segment of the keycmd leading up to the cursor position and + uses a regular expression to search backwards finding parially completed + keywords or @variables. Returns a null string if the correct completion + conditions aren't met.''' + + keylet = uzbl.get_keylet() + left_segment = keylet.keycmd[:keylet.cursor] + partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip() + if partial.startswith('set '): + return ('@%s' % partial[4:].lstrip(), True) + + return (partial, False) + + +def stop_completion(uzbl, *args): + '''Stop command completion and return the level to NONE.''' + + d = get_completion_dict(uzbl) + d['level'] = NONE + uzbl.set('completion_list') + + +def complete_completion(uzbl, partial, hint, set_completion=False): + '''Inject the remaining porition of the keyword into the keycmd then stop + the completioning.''' + + if set_completion: + remainder = "%s = " % hint[len(partial):] + + else: + remainder = "%s " % hint[len(partial):] + + uzbl.inject_keycmd(remainder) + stop_completion(uzbl) + + +def partial_completion(uzbl, partial, hint): + '''Inject a common portion of the hints into the keycmd.''' + + remainder = hint[len(partial):] + uzbl.inject_keycmd(remainder) + + +def update_completion_list(uzbl, *args): + '''Checks if the user still has a partially completed keyword under his + cursor then update the completion hints list.''' + + partial = get_incomplete_keyword(uzbl)[0] + if not partial: + return stop_completion(uzbl) + + d = get_completion_dict(uzbl) + if d['level'] < LIST: + return + + hints = [h for h in d['completions'] if h.startswith(partial)] + if not hints: + return uzbl.set('completion_list') + + j = len(partial) + l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)] + uzbl.set('completion_list', LIST_FORMAT % ' '.join(l)) + + +def start_completion(uzbl, *args): + + d = get_completion_dict(uzbl) + if d['lock']: + return + + (partial, set_completion) = get_incomplete_keyword(uzbl) + if not partial: + return stop_completion(uzbl) + + if d['level'] < COMPLETE: + d['level'] += 1 + + hints = [h for h in d['completions'] if h.startswith(partial)] + if not hints: + return + + elif len(hints) == 1: + d['lock'] = True + complete_completion(uzbl, partial, hints[0], set_completion) + d['lock'] = False + return + + elif partial in hints and d['level'] == COMPLETE: + d['lock'] = True + complete_completion(uzbl, partial, partial, set_completion) + d['lock'] = False + return + + smalllen, smallest = sorted([(len(h), h) for h in hints])[0] + common = '' + for i in range(len(partial), smalllen): + char, same = smallest[i], True + for hint in hints: + if hint[i] != char: + same = False + break + + if not same: + break + + common += char + + if common: + d['lock'] = True + partial_completion(uzbl, partial, partial+common) + d['lock'] = False + + update_completion_list(uzbl) + + +def add_builtins(uzbl, args): + '''Pump the space delimited list of builtin commands into the + builtin list.''' + + completions = get_completion_dict(uzbl)['completions'] + builtins = filter(None, map(unicode.strip, args.split(" "))) + for builtin in builtins: + if builtin not in completions: + completions.append(builtin) + + +def add_config_key(uzbl, key, value): + '''Listen on the CONFIG_CHANGED event and add config keys to the variable + list for @var like expansion support.''' + + completions = get_completion_dict(uzbl)['completions'] + key = "@%s" % key + if key not in completions: + completions.append(key) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'BUILTINS': add_builtins, + 'CONFIG_CHANGED': add_config_key, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'KEYCMD_CLEARED': stop_completion, + 'KEYCMD_EXEC': stop_completion, + 'KEYCMD_UPDATE': update_completion_list, + 'START_COMPLETION': start_completion, + 'STOP_COMPLETION': stop_completion, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_completion_dict': get_completion_dict, + 'start_completion': start_completion, + }) diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py new file mode 100644 index 0000000..4a848a3 --- /dev/null +++ b/examples/data/plugins/config.py @@ -0,0 +1,97 @@ +import re +import types + +__export__ = ['set', 'get_config'] + +VALIDKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match +TYPECONVERT = {'int': int, 'float': float, 'str': unicode} + +UZBLS = {} + + +def escape(value): + '''A real escaping function may be required.''' + + return unicode(value) + + +def set(uzbl, key, value='', config=None, force=False): + '''Sends a: "set key = value" command to the uzbl instance. If force is + False then only send a set command if the values aren't equal.''' + + if type(value) == types.BooleanType: + value = int(value) + + else: + value = unicode(value) + + if not VALIDKEY(key): + raise KeyError("%r" % key) + + value = escape(value) + if '\n' in value: + value = value.replace("\n", "\\n") + + if not force: + if config is None: + config = get_config(uzbl) + + if key in config and config[key] == value: + return + + uzbl.send('set %s = %s' % (key, value)) + + +class ConfigDict(dict): + def __init__(self, uzbl): + self._uzbl = uzbl + + def __setitem__(self, key, value): + '''Makes "config[key] = value" a wrapper for the set function.''' + + set(self._uzbl, key, value, config=self) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = ConfigDict(uzbl) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del uzbl + + +def get_config(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def variable_set(uzbl, args): + config = get_config(uzbl) + + key, type, value = list(args.split(' ', 2) + ['',])[:3] + old = config[key] if key in config else None + value = TYPECONVERT[type](value) + + dict.__setitem__(config, key, value) + + if old != value: + uzbl.event("CONFIG_CHANGED", key, value) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'VARIABLE_SET': variable_set, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_config': get_config, + 'set': set, + }) diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py new file mode 100644 index 0000000..c119077 --- /dev/null +++ b/examples/data/plugins/keycmd.py @@ -0,0 +1,571 @@ +import re + +# Hold the keylets. +UZBLS = {} + +# Keycmd format which includes the markup for the cursor. +KEYCMD_FORMAT = "%s%s%s" +MODCMD_FORMAT = " %s " + + +def escape(str): + for char in ['\\', '@']: + str = str.replace(char, '\\'+char) + + return str + + +def uzbl_escape(str): + return "@[%s]@" % escape(str) if str else '' + + +class Keylet(object): + '''Small per-instance object that tracks all the keys held and characters + typed.''' + + def __init__(self): + # Modcmd tracking + self.held = set() + self.ignored = set() + self.modcmd = '' + self.is_modcmd = False + + # Keycmd tracking + self.keycmd = '' + self.cursor = 0 + + self.modmaps = {} + self.ignores = {} + self.additions = {} + + # Keylet string repr cache. + self._repr_cache = None + + + def get_keycmd(self): + '''Get the keycmd-part of the keylet.''' + + return self.keycmd + + + def get_modcmd(self): + '''Get the modcmd-part of the keylet.''' + + if not self.is_modcmd: + return '' + + return ''.join(self.held) + self.modcmd + + + def modmap_key(self, key): + '''Make some obscure names for some keys friendlier.''' + + if key in self.modmaps: + return self.modmaps[key] + + elif key.endswith('_L') or key.endswith('_R'): + # Remove left-right discrimination and try again. + return self.modmap_key(key[:-2]) + + else: + return key + + + def find_addition(self, modkey): + '''Key has just been pressed, check if this key + the held list + results in a modkey addition. Return that addition and remove all + modkeys that created it.''' + + # Intersection of (held list + modkey) and additions. + added = (self.held | set([modkey])) & set(self.additions.keys()) + for key in added: + if key == modkey or modkey in self.additions[key]: + self.held -= self.additions[key] + return key + + # Held list + ignored list + modkey. + modkeys = self.held | self.ignored | set([modkey]) + for (key, value) in self.additions.items(): + if modkeys.issuperset(value): + self.held -= value + return key + + return modkey + + + def key_ignored(self, key): + '''Check if the given key is ignored by any ignore rules.''' + + for (glob, match) in self.ignores.items(): + if match(key): + return True + + return False + + + def __repr__(self): + '''Return a string representation of the keylet.''' + + if self._repr_cache: + return self._repr_cache + + l = [] + if self.is_modcmd: + l.append('modcmd=%r' % self.get_modcmd()) + + elif self.held: + l.append('held=%r' % ''.join(sorted(self.held))) + + if self.keycmd: + l.append('keycmd=%r' % self.get_keycmd()) + + self._repr_cache = '' % ', '.join(l) + return self._repr_cache + + +def add_modmap(uzbl, key, map): + '''Add modmaps. + + Examples: + set modmap = request MODMAP + @modmap + @modmap + ... + + Then: + @bind = + @bind x = + ... + + ''' + + assert len(key) + modmaps = get_keylet(uzbl).modmaps + + if key[0] == "<" and key[-1] == ">": + key = key[1:-1] + + modmaps[key] = map + uzbl.event("NEW_MODMAP", key, map) + + +def modmap_parse(uzbl, map): + '''Parse a modmap definiton.''' + + split = [s.strip() for s in map.split(' ') if s.split()] + + if not split or len(split) > 2: + raise Exception('Invalid modmap arugments: %r' % map) + + add_modmap(uzbl, *split) + + +def add_key_ignore(uzbl, glob): + '''Add an ignore definition. + + Examples: + set ignore_key = request IGNORE_KEY + @ignore_key + @ignore_key + ... + ''' + + assert len(glob) > 1 + ignores = get_keylet(uzbl).ignores + + glob = "<%s>" % glob.strip("<> ") + restr = glob.replace('*', '[^\s]*') + match = re.compile(restr).match + + ignores[glob] = match + uzbl.event('NEW_KEY_IGNORE', glob) + + +def add_modkey_addition(uzbl, modkeys, result): + '''Add a modkey addition definition. + + Examples: + set mod_addition = request MODKEY_ADDITION + @mod_addition + @mod_addition + @mod_addition + ... + + Then: + @bind = + @bind o = + ... + ''' + + additions = get_keylet(uzbl).additions + modkeys = set(modkeys) + + assert len(modkeys) and result and result not in modkeys + + for (existing_result, existing_modkeys) in additions.items(): + if existing_result != result: + assert modkeys != existing_modkeys + + additions[result] = modkeys + uzbl.event('NEW_MODKEY_ADDITION', modkeys, result) + + +def modkey_addition_parse(uzbl, modkeys): + '''Parse modkey addition definition.''' + + keys = filter(None, map(unicode.strip, modkeys.split(" "))) + keys = ['<%s>' % key.strip("<>") for key in keys if key.strip("<>")] + + assert len(keys) > 1 + add_modkey_addition(uzbl, keys[:-1], keys[-1]) + + +def add_instance(uzbl, *args): + '''Create the Keylet object for this uzbl instance.''' + + UZBLS[uzbl] = Keylet() + + +def del_instance(uzbl, *args): + '''Delete the Keylet object for this uzbl instance.''' + + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_keylet(uzbl): + '''Return the corresponding keylet for this uzbl instance.''' + + # Startup events are not correctly captured and sent over the uzbl socket + # yet so this line is needed because the INSTANCE_START event is lost. + if uzbl not in UZBLS: + add_instance(uzbl) + + keylet = UZBLS[uzbl] + keylet._repr_cache = False + return keylet + + +def clear_keycmd(uzbl): + '''Clear the keycmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.keycmd = '' + k.cursor = 0 + k._repr_cache = False + uzbl.set('keycmd') + uzbl.set('raw_keycmd') + uzbl.event('KEYCMD_CLEARED') + + +def clear_modcmd(uzbl, clear_held=False): + '''Clear the modcmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.modcmd = '' + k.is_modcmd = False + k._repr_cache = False + if clear_held: + k.ignored = set() + k.held = set() + + uzbl.set('modcmd') + uzbl.set('raw_modcmd') + uzbl.event('MODCMD_CLEARED') + + +def clear_current(uzbl): + '''Clear the modcmd if is_modcmd else clear keycmd.''' + + k = get_keylet(uzbl) + if k.is_modcmd: + clear_modcmd(uzbl) + + else: + clear_keycmd(uzbl) + + +def focus_changed(uzbl, *args): + '''Focus to the uzbl instance has now been lost which means all currently + held keys in the held list will not get a KEY_RELEASE event so clear the + entire held list.''' + + clear_modcmd(uzbl, clear_held=True) + + +def update_event(uzbl, k, execute=True): + '''Raise keycmd & modcmd update events.''' + + config = uzbl.get_config() + keycmd, modcmd = k.get_keycmd(), k.get_modcmd() + + if k.is_modcmd: + uzbl.event('MODCMD_UPDATE', k) + + else: + uzbl.event('KEYCMD_UPDATE', k) + + if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': + new_modcmd = k.get_modcmd() + if not new_modcmd: + uzbl.set('modcmd', config=config) + uzbl.set('raw_modcmd', config=config) + + elif new_modcmd == modcmd: + uzbl.set('raw_modcmd', escape(modcmd), config=config) + uzbl.set('modcmd', MODCMD_FORMAT % uzbl_escape(modcmd), + config=config) + + if 'keycmd_events' in config and config['keycmd_events'] != '1': + return + + new_keycmd = k.get_keycmd() + if not new_keycmd: + uzbl.set('keycmd', config=config) + uzbl.set('raw_keycmd', config=config) + + elif new_keycmd == keycmd: + # Generate the pango markup for the cursor in the keycmd. + curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' ' + chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]] + value = KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks)) + uzbl.set('keycmd', value, config=config) + uzbl.set('raw_keycmd', escape(keycmd), config=config) + + +def inject_str(str, index, inj): + '''Inject a string into string at at given index.''' + + return "%s%s%s" % (str[:index], inj, str[index:]) + + +def get_keylet_and_key(uzbl, key, add=True): + '''Return the keylet and apply any transformations to the key as defined + by the modmapping or modkey addition rules. Return None if the key is + ignored.''' + + keylet = get_keylet(uzbl) + key = keylet.modmap_key(key) + if len(key) == 1: + return (keylet, key) + + modkey = "<%s>" % key.strip("<>") + + if keylet.key_ignored(modkey): + if add: + keylet.ignored.add(modkey) + + elif modkey in keylet.ignored: + keylet.ignored.remove(modkey) + + modkey = keylet.find_addition(modkey) + + if keylet.key_ignored(modkey): + return (keylet, None) + + return (keylet, modkey) + + +def key_press(uzbl, key): + '''Handle KEY_PRESS events. Things done by this function include: + + 1. Ignore all shift key presses (shift can be detected by capital chars) + 3. In non-modcmd mode: + a. append char to keycmd + 4. If not in modcmd mode and a modkey was pressed set modcmd mode. + 5. If in modcmd mode the pressed key is added to the held keys list. + 6. Keycmd is updated and events raised if anything is changed.''' + + (k, key) = get_keylet_and_key(uzbl, key.strip()) + if not key: + return + + if key.lower() == '' and not k.held and k.keycmd: + k.keycmd = inject_str(k.keycmd, k.cursor, ' ') + k.cursor += 1 + + elif not k.held and len(key) == 1: + config = uzbl.get_config() + if 'keycmd_events' in config and config['keycmd_events'] != '1': + k.keycmd = '' + k.cursor = 0 + uzbl.set('keycmd', config=config) + uzbl.set('raw_keycmd', config=config) + return + + k.keycmd = inject_str(k.keycmd, k.cursor, key) + k.cursor += 1 + + elif len(key) > 1: + k.is_modcmd = True + if key not in k.held: + k.held.add(key) + + else: + k.is_modcmd = True + k.modcmd += key + + update_event(uzbl, k) + + +def key_release(uzbl, key): + '''Respond to KEY_RELEASE event. Things done by this function include: + + 1. Remove the key from the keylet held list. + 2. If in a mod-command then raise a MODCMD_EXEC. + 3. Check if any modkey is held, if so set modcmd mode. + 4. Update the keycmd uzbl variable if anything changed.''' + + (k, key) = get_keylet_and_key(uzbl, key.strip(), add=False) + + if key in k.held: + if k.is_modcmd: + uzbl.event('MODCMD_EXEC', k) + + k.held.remove(key) + clear_modcmd(uzbl) + + +def set_keycmd(uzbl, keycmd): + '''Allow setting of the keycmd externally.''' + + k = get_keylet(uzbl) + k.keycmd = keycmd + k._repr_cache = None + k.cursor = len(keycmd) + update_event(uzbl, k, False) + + +def inject_keycmd(uzbl, keycmd): + '''Allow injecting of a string into the keycmd at the cursor position.''' + + k = get_keylet(uzbl) + k.keycmd = inject_str(k.keycmd, k.cursor, keycmd) + k._repr_cache = None + k.cursor += len(keycmd) + update_event(uzbl, k, False) + + +def append_keycmd(uzbl, keycmd): + '''Allow appening of a string to the keycmd.''' + + k = get_keylet(uzbl) + k.keycmd += keycmd + k._repr_cache = None + k.cursor = len(k.keycmd) + update_event(uzbl, k, False) + + +def keycmd_strip_word(uzbl, sep): + ''' Removes the last word from the keycmd, similar to readline ^W ''' + + sep = sep or ' ' + k = get_keylet(uzbl) + if not k.keycmd: + return + + head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] + rfind = head.rfind(sep) + head = head[:rfind] if rfind + 1 else '' + k.keycmd = head + tail + k.cursor = len(head) + update_event(uzbl, k, False) + + +def keycmd_backspace(uzbl, *args): + '''Removes the character at the cursor position in the keycmd.''' + + k = get_keylet(uzbl) + if not k.keycmd: + return + + k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] + k.cursor -= 1 + update_event(uzbl, k, False) + + +def keycmd_delete(uzbl, *args): + '''Removes the character after the cursor position in the keycmd.''' + + k = get_keylet(uzbl) + if not k.keycmd: + return + + k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] + update_event(uzbl, k, False) + + +def keycmd_exec_current(uzbl, *args): + '''Raise a KEYCMD_EXEC with the current keylet and then clear the + keycmd.''' + + k = get_keylet(uzbl) + uzbl.event('KEYCMD_EXEC', k) + clear_keycmd(uzbl) + + +def set_cursor_pos(uzbl, index): + '''Allow setting of the cursor position externally. Supports negative + indexing and relative stepping with '+' and '-'.''' + + k = get_keylet(uzbl) + if index == '-': + cursor = k.cursor - 1 + + elif index == '+': + cursor = k.cursor + 1 + + else: + cursor = int(index.strip()) + if cursor < 0: + cursor = len(k.keycmd) + cursor + 1 + + if cursor < 0: + cursor = 0 + + if cursor > len(k.keycmd): + cursor = len(k.keycmd) + + k.cursor = cursor + update_event(uzbl, k, False) + + +def init(uzbl): + '''Connect handlers to uzbl events.''' + + # Event handling hooks. + uzbl.connect_dict({ + 'APPEND_KEYCMD': append_keycmd, + 'FOCUS_GAINED': focus_changed, + 'FOCUS_LOST': focus_changed, + 'IGNORE_KEY': add_key_ignore, + 'INJECT_KEYCMD': inject_keycmd, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'KEYCMD_BACKSPACE': keycmd_backspace, + 'KEYCMD_DELETE': keycmd_delete, + 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, + 'KEYCMD_STRIP_WORD': keycmd_strip_word, + 'KEY_PRESS': key_press, + 'KEY_RELEASE': key_release, + 'MODKEY_ADDITION': modkey_addition_parse, + 'MODMAP': modmap_parse, + 'SET_CURSOR_POS': set_cursor_pos, + 'SET_KEYCMD': set_keycmd, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'add_key_ignore': add_key_ignore, + 'add_modkey_addition': add_modkey_addition, + 'add_modmap': add_modmap, + 'append_keycmd': append_keycmd, + 'clear_current': clear_current, + 'clear_keycmd': clear_keycmd, + 'clear_modcmd': clear_modcmd, + 'get_keylet': get_keylet, + 'inject_keycmd': inject_keycmd, + 'set_cursor_pos': set_cursor_pos, + 'set_keycmd': set_keycmd, + }) diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py new file mode 100644 index 0000000..54d865a --- /dev/null +++ b/examples/data/plugins/mode.py @@ -0,0 +1,176 @@ +import sys +import re + +__export__ = ['set_mode', 'get_mode', 'set_mode_config', 'get_mode_config'] + +UZBLS = {} + +DEFAULTS = { + 'mode': '', + 'modes': { + 'insert': { + 'forward_keys': True, + 'keycmd_events': False, + 'modcmd_updates': False, + 'mode_indicator': 'I'}, + 'command': { + 'forward_keys': False, + 'keycmd_events': True, + 'modcmd_updates': True, + 'mode_indicator': 'C'}}} + +FINDSPACES = re.compile("\s+") +VALID_KEY = re.compile("^[\w_]+$").match + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_mode_dict(uzbl): + '''Return the mode dict for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_mode_config(uzbl, mode): + '''Return the mode config for a given mode.''' + + modes = get_mode_dict(uzbl)['modes'] + if mode not in modes: + modes[mode] = {} + + return modes[mode] + + +def get_mode(uzbl): + return get_mode_dict(uzbl)['mode'] + + +def mode_changed(uzbl, mode): + '''The mode has just been changed, now set the per-mode config.''' + + if get_mode(uzbl) != mode: + return + + config = uzbl.get_config() + mode_config = get_mode_config(uzbl, mode) + for (key, value) in mode_config.items(): + uzbl.set(key, value, config=config) + + if 'mode_indicator' not in mode_config: + config['mode_indicator'] = mode + + uzbl.clear_keycmd() + uzbl.clear_modcmd() + + +def set_mode(uzbl, mode=None): + '''Set the mode and raise the MODE_CHANGED event if the mode has changed. + Fallback on the default mode if no mode argument was given and the default + mode is not null.''' + + config = uzbl.get_config() + mode_dict = get_mode_dict(uzbl) + if mode is None: + mode_dict['mode'] = '' + if 'default_mode' in config: + mode = config['default_mode'] + + else: + mode = 'command' + + if not VALID_KEY(mode): + raise KeyError("invalid mode name: %r" % mode) + + if 'mode' not in config or config['mode'] != mode: + config['mode'] = mode + + elif mode_dict['mode'] != mode: + mode_dict['mode'] = mode + uzbl.event("MODE_CHANGED", mode) + + +def config_changed(uzbl, key, value): + '''Check for mode related config changes.''' + + value = None if not value else value + if key == 'default_mode': + if not get_mode(uzbl): + set_mode(uzbl, value) + + elif key == 'mode': + set_mode(uzbl, value) + + +def set_mode_config(uzbl, mode, key, value): + '''Set mode specific configs. If the mode being modified is the current + mode then apply the changes on the go.''' + + assert VALID_KEY(mode) and VALID_KEY(key) + + mode_config = get_mode_config(uzbl, mode) + mode_config[key] = value + + if get_mode(uzbl) == mode: + uzbl.set(key, value) + + +def mode_config(uzbl, args): + '''Parse mode config events.''' + + split = map(unicode.strip, FINDSPACES.split(args.lstrip(), 1)) + if len(split) != 2: + raise SyntaxError('invalid mode config syntax: %r' % args) + + mode, set = split + split = map(unicode.strip, set.split('=', 1)) + if len(split) != 2: + raise SyntaxError('invalid set syntax: %r' % args) + + key, value = split + set_mode_config(uzbl, mode, key, value) + + +def toggle_modes(uzbl, modes): + '''Toggle or cycle between or through a list of modes.''' + + assert len(modes.strip()) + + modelist = filter(None, map(unicode.strip, modes.split(' '))) + mode = get_mode(uzbl) + + index = 0 + if mode in modelist: + index = (modelist.index(mode)+1) % len(modelist) + + set_mode(uzbl, modelist[index]) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'CONFIG_CHANGED': config_changed, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'MODE_CHANGED': mode_changed, + 'MODE_CONFIG': mode_config, + 'TOGGLE_MODES': toggle_modes, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_mode': get_mode, + 'get_mode_config': get_mode_config, + 'set_mode': set_mode, + 'set_mode_config': set_mode_config, + }) diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py new file mode 100644 index 0000000..b9c504a --- /dev/null +++ b/examples/data/plugins/on_event.py @@ -0,0 +1,107 @@ +'''Plugin provides arbitrary binding of uzbl events to uzbl commands. + +Formatting options: + %s = space separated string of the arguments + %r = escaped and quoted version of %s + %1 = argument 1 + %2 = argument 2 + %n = argument n + +Usage: + request ON_EVENT LINK_HOVER set selected_uri = $1 + --> LINK_HOVER http://uzbl.org/ + <-- set selected_uri = http://uzbl.org/ + + request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2 + --> CONFIG_CHANGED selected_uri http://uzbl.org/ + <-- print Config changed: selected_uri = http://uzbl.org/ +''' + +import sys +import re + +__export__ = ['get_on_events', 'on_event'] + +UZBLS = {} + + +def error(msg): + sys.stderr.write('on_event plugin: error: %s\n' % msg) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = {} + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_on_events(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def event_handler(uzbl, *args, **kargs): + '''This function handles all the events being watched by various + on_event definitions and responds accordingly.''' + + events = get_on_events(uzbl) + event = kargs['on_event'] + if event not in events: + return + + commands = events[event] + cmd_expand = uzbl.cmd_expand + for cmd in commands: + cmd = cmd_expand(cmd, args) + uzbl.send(cmd) + + +def on_event(uzbl, event, cmd): + '''Add a new event to watch and respond to.''' + + event = event.upper() + events = get_on_events(uzbl) + if event not in events: + uzbl.connect(event, event_handler, on_event=event) + events[event] = [] + + cmds = events[event] + if cmd not in cmds: + cmds.append(cmd) + + +def parse_on_event(uzbl, args): + '''Parse ON_EVENT events and pass them to the on_event function. + + Syntax: "event ON_EVENT commands".''' + + if not args: + return error("missing on_event arguments") + + split = args.split(' ', 1) + if len(split) != 2: + return error("invalid ON_EVENT syntax: %r" % args) + + event, cmd = split + on_event(uzbl, event, cmd) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'ON_EVENT': parse_on_event, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_on_events': get_on_events, + 'on_event': on_event, + }) diff --git a/examples/data/plugins/plugin_template.py b/examples/data/plugins/plugin_template.py new file mode 100644 index 0000000..565a999 --- /dev/null +++ b/examples/data/plugins/plugin_template.py @@ -0,0 +1,76 @@ +'''Plugin template.''' + +# Holds the per-instance data dict. +UZBLS = {} + +# The default instance dict. +DEFAULTS = {} + + +def add_instance(uzbl, *args): + '''Add a new instance with default config options.''' + + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + '''Delete data stored for an instance.''' + + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_myplugin_dict(uzbl): + '''Get data stored for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def myplugin_function(uzbl, *args, **kargs): + '''Custom plugin function which is exported by the __export__ list at the + top of the file for use by other functions/callbacks.''' + + print "My plugin function arguments:", args, kargs + + # Get the per-instance data object. + data = get_myplugin_dict(uzbl) + + # Function logic goes here. + + +def myplugin_event_parser(uzbl, args): + '''Parses MYPLUGIN_EVENT raised by uzbl or another plugin.''' + + print "Got MYPLUGIN_EVENT with arguments: %r" % args + + # Parsing logic goes here. + + +def init(uzbl): + '''The main function of the plugin which is used to attach all the event + hooks that are going to be used throughout the plugins life. This function + is called each time a UzblInstance() object is created in the event + manager.''' + + # Make a dictionary comprising of {"EVENT_NAME": handler, ..} to the event + # handler stack: + uzbl.connect_dict({ + # event name function + 'INSTANCE_START': add_instance, + 'INSTANCE_EXIT': del_instance, + 'MYPLUGIN_EVENT': myplugin_event_parser, + }) + + # Or connect a handler to an event manually and supply additional optional + # arguments: + #uzbl.connect("MYOTHER_EVENT", myother_event_parser, True, limit=20) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.connect_dict({ + # external name function + 'myplugin_function': myplugin_function, + }) diff --git a/examples/data/plugins/progress_bar.py b/examples/data/plugins/progress_bar.py new file mode 100644 index 0000000..89ba175 --- /dev/null +++ b/examples/data/plugins/progress_bar.py @@ -0,0 +1,159 @@ +import sys + +UZBLS = {} + +DEFAULTS = {'width': 8, + 'done': '=', + 'pending': '.', + 'format': '[%d%a%p]%c', + 'spinner': '-\\|/', + 'sprites': 'loading', + 'updates': 0, + 'progress': 100} + + +def error(msg): + sys.stderr.write("progress_bar plugin: error: %s\n" % msg) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_progress_config(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def update_progress(uzbl, prog=None): + '''Updates the progress_format variable on LOAD_PROGRESS update. + + The current substitution options are: + %d = done char * done + %p = pending char * remaining + %c = percent done + %i = int done + %s = -\|/ spinner + %t = percent pending + %o = int pending + %r = sprites + ''' + + prog_config = get_progress_config(uzbl) + config = uzbl.get_config() + + if prog is None: + prog = prog_config['progress'] + + else: + prog = int(prog) + prog_config['progress'] = prog + + prog_config['updates'] += 1 + format = prog_config['format'] + width = prog_config['width'] + + # Inflate the done and pending bars to stop the progress bar + # jumping around. + if '%c' in format or '%i' in format: + count = format.count('%c') + format.count('%i') + width += (3-len(str(prog))) * count + + if '%t' in format or '%o' in format: + count = format.count('%t') + format.count('%o') + width += (3-len(str(100-prog))) * count + + done = int(((prog/100.0)*width)+0.5) + pending = width - done + + if '%d' in format: + format = format.replace('%d', prog_config['done']*done) + + if '%p' in format: + format = format.replace('%p', prog_config['pending']*pending) + + if '%c' in format: + format = format.replace('%c', '%d%%' % prog) + + if '%i' in format: + format = format.replace('%i', '%d' % prog) + + if '%t' in format: + format = format.replace('%t', '%d%%' % (100-prog)) + + if '%o' in format: + format = format.replace('%o', '%d' % (100-prog)) + + if '%s' in format: + spinner = prog_config['spinner'] + spin = '-' if not spinner else spinner + index = 0 if prog == 100 else prog_config['updates'] % len(spin) + char = '\\\\' if spin[index] == '\\' else spin[index] + format = format.replace('%s', char) + + if '%r' in format: + sprites = prog_config['sprites'] + sprites = '-' if not sprites else sprites + index = int(((prog/100.0)*len(sprites))+0.5)-1 + sprite = '\\\\' if sprites[index] == '\\' else sprites[index] + format = format.replace('%r', sprite) + + if 'progress_format' not in config or config['progress_format'] != format: + config['progress_format'] = format + + +def progress_config(uzbl, args): + '''Parse PROGRESS_CONFIG events from the uzbl instance. + + Syntax: event PROGRESS_CONFIG = + ''' + + split = args.split('=', 1) + if len(split) != 2: + return error("invalid syntax: %r" % args) + + key, value = map(unicode.strip, split) + prog_config = get_progress_config(uzbl) + + if key not in prog_config: + return error("key error: %r" % args) + + if type(prog_config[key]) == type(1): + try: + value = int(value) + + except: + return error("invalid type: %r" % args) + + elif not value: + value = ' ' + + prog_config[key] = value + update_progress(uzbl) + + +def reset_progress(uzbl, args): + '''Reset the spinner counter, reset the progress int and re-draw the + progress bar on LOAD_COMMIT.''' + + prog_dict = get_progress_config(uzbl) + prog_dict['updates'] = prog_dict['progress'] = 0 + update_progress(uzbl) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'LOAD_COMMIT': reset_progress, + 'LOAD_PROGRESS': update_progress, + 'PROGRESS_CONFIG': progress_config, + }) -- cgit v1.2.3