aboutsummaryrefslogtreecommitdiffhomepage
path: root/examples/data/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'examples/data/plugins')
-rw-r--r--examples/data/plugins/bind.py521
-rw-r--r--examples/data/plugins/cmd_expand.py42
-rw-r--r--examples/data/plugins/completion.py206
-rw-r--r--examples/data/plugins/config.py97
-rw-r--r--examples/data/plugins/keycmd.py571
-rw-r--r--examples/data/plugins/mode.py176
-rw-r--r--examples/data/plugins/on_event.py107
-rw-r--r--examples/data/plugins/plugin_template.py76
-rw-r--r--examples/data/plugins/progress_bar.py159
9 files changed, 1955 insertions, 0 deletions
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>, <'x':y>, <:'y'>, <x!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 "<Mod1><Mod2>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*<int:>*
+ 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<prompt1:><prompt2:>_
+ 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 = '<Bind(%s)>' % ', '.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>
+ MODE_BIND command o<location:>_ = uri %s
+ MODE_BIND insert,command <BackSpace> = ...
+ 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 <bind> = <command>
+ request BIND o<location:>_ = uri %s
+ request BIND <BackSpace> = ...
+ 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 = "<span> %s </span>"
+ITEM_FORMAT = "<span @hint_style>%s</span>%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<Tab> 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<span @cursor_style>%s</span>%s"
+MODCMD_FORMAT = "<span> %s </span>"
+
+
+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 = '<Keylet(%s)>' % ', '.join(l)
+ return self._repr_cache
+
+
+def add_modmap(uzbl, key, map):
+ '''Add modmaps.
+
+ Examples:
+ set modmap = request MODMAP
+ @modmap <Control> <Ctrl>
+ @modmap <ISO_Left_Tab> <Shift-Tab>
+ ...
+
+ Then:
+ @bind <Shift-Tab> = <command1>
+ @bind <Ctrl>x = <command2>
+ ...
+
+ '''
+
+ 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 <Shift>
+ @ignore_key <ISO_*>
+ ...
+ '''
+
+ 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 <Shift> <Control> <Meta>
+ @mod_addition <Left> <Up> <Left-Up>
+ @mod_addition <Right> <Up> <Right-Up>
+ ...
+
+ Then:
+ @bind <Right-Up> = <command1>
+ @bind <Meta>o = <command2>
+ ...
+ '''
+
+ 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() == '<space>' 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 <EVENT_NAME> 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 <key> = <value>
+ '''
+
+ 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,
+ })