aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Mason Larobina <mason.larobina@gmail.com>2010-04-04 04:44:03 +0800
committerGravatar Mason Larobina <mason.larobina@gmail.com>2010-04-04 04:44:03 +0800
commitd3cbe16bf16ff63c0e3db15d645e958712bd02d8 (patch)
tree6bea7bda62f07b73329fdb136a0b907661ca356c
parentae15d257a858fe27090f3e5798357aea2f5a76da (diff)
Huge plugin & event manager upgrades.
1. Removed unused modules 2. Re-use event handlers with identical callbacks and args. 3. Removed plugin exceptions in favour of assertions. 4. Remove useless raw_keycmd and raw_bind config vars. 5. Implemented and use `after` and `cleanup` plugin hooks (correctly) 6. EM & plugins now use the python logging module to output messages 7. Null config items are removed automatically 8. Simpler mode plugin 9. The init plugins function is called after the INSTANCE_START event 10. New optparse option to silence event echoing to stdout 11. Close instance socket on INSTANCE_EXIT before event handling 12. Caught signals are logged 13. Show times on the messages in the log file 14. Refactor bind pluin to use uzbl.bindlet directly. 15. Refactor keycmd plugin to use uzbl.keycmd directly. 16. Refactored on_event plugin to use uzbl.on_events dict over UZBLS dict 17. Refactor completion plugin to use uzbl.completion set object. 18. Modified progress plugin to use config vars instead of `@progress k = v` 19. mode_config now a defaultdict(dict) (I.e. this allows you to `uzbl.mode_config[mode][var] = value` without needing to check `mode` is in the `uzbl.mode_config` dict). 20. Removed all default mode config values. 21. Removed all `get_mode()` and `set_mode(..)` functions (and the like). 22. Setting the mode is now done via the config object directly (I.e. `uzbl.config['mode'] = 'insert'`). 23. Uses the on_set plugin to watch for 'mode' and 'default_mode' config changes. 24. Don't raise the useless NEW_ON_SET event, missing ON_SET connect. 25. Plugin and EventHandler aren't suited as dict objects. 26. Also using collections.defaultdict(list) for uzbl.handlers dict. 27. Plugin `on_set.py` allows you to attach handlers to config var changes 28. Config plugin reduced to one `uzbl.config` dict-like object. 29. Update export and connect calls in plugins. 30. The functions connect, connect_dict, export, export_dict, require, logging are exported directly to the plugin namespace. 31. Moved parse_msg into Uzbl class. 32. Generally improved comments. 33. UzblEventDaemon now an object. 34. Various variable, function & class renames.
-rw-r--r--Makefile4
-rw-r--r--examples/config/config28
-rw-r--r--examples/data/plugins/bind.py63
-rw-r--r--examples/data/plugins/cmd_expand.py6
-rw-r--r--examples/data/plugins/completion.py129
-rw-r--r--examples/data/plugins/config.py129
-rw-r--r--examples/data/plugins/keycmd.py121
-rw-r--r--examples/data/plugins/mode.py204
-rw-r--r--examples/data/plugins/on_event.py69
-rw-r--r--examples/data/plugins/on_set.py92
-rw-r--r--examples/data/plugins/plugin_template.py76
-rw-r--r--examples/data/plugins/progress_bar.py152
-rwxr-xr-xexamples/data/scripts/uzbl-event-manager1125
13 files changed, 1009 insertions, 1189 deletions
diff --git a/Makefile b/Makefile
index 5c97e4d..a995f76 100644
--- a/Makefile
+++ b/Makefile
@@ -63,10 +63,10 @@ test-uzbl-browser-sandbox: uzbl-browser
make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data
cp -np ./misc/env.sh ./sandbox/env.sh
source ./sandbox/env.sh && uzbl-cookie-daemon restart -nv &
- source ./sandbox/env.sh && uzbl-event-manager restart -nav &
+ source ./sandbox/env.sh && uzbl-event-manager restart -navv &
source ./sandbox/env.sh && uzbl-browser --uri http://www.uzbl.org --verbose
source ./sandbox/env.sh && uzbl-cookie-daemon stop -v
- source ./sandbox/env.sh && uzbl-event-manager stop -v
+ source ./sandbox/env.sh && uzbl-event-manager stop -ivv
make DESTDIR=./sandbox uninstall
rm -rf ./sandbox/usr
diff --git a/examples/config/config b/examples/config/config
index 504c7b8..348ad42 100644
--- a/examples/config/config
+++ b/examples/config/config
@@ -14,8 +14,8 @@ set mode_bind = request MODE_BIND
set mode_config = request MODE_CONFIG
# request ON_EVENT <EVENT_NAME> <command>
set on_event = request ON_EVENT
-# request PROGRESS_CONFIG <key> = <value>
-set progress = request PROGRESS_CONFIG
+# request ON_SET <key/glob> <command>
+set on_set = request ON_SET
# request MODMAP <From> <To>
set modmap = request MODMAP
# request IGNORE_KEY <glob>
@@ -93,7 +93,7 @@ set hint_style = weight="bold"
set mode_section = <span background="khaki" foreground="black">[\@[\@mode_indicator]\@]</span>
set keycmd_section = [<span \@prompt_style>\@[\@keycmd_prompt]\@</span><span \@modcmd_style>\@modcmd</span><span \@keycmd_style>\@keycmd</span><span \@completion_style>\@completion_list</span>]
-set progress_section = <span foreground="#606060">\@[\@progress_format]\@</span>
+set progress_section = <span foreground="#606060">\@[\@progress.output]\@</span>
set scroll_section = <span foreground="#606060">\@[\@scroll_message]\@</span>
set uri_section = <span foreground="#99FF66">\@[\@uri]\@</span>
set name_section = <span foreground="khaki">\@[\@NAME]\@</span>
@@ -104,19 +104,19 @@ set status_format = <span font_family="monospace">@mode_section @keycmd_sect
set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI
-# Progress bar config
-@progress width = 8
+# --- Progress bar configuration (progress_bar.py plugin) ---
# %d = done, %p = pending %c = percent done, %i = int done, %s = spinner,
# %t = percent pending, %o = int pending, %r = sprite scroll
-@progress format = [%d>%p]%c
-@progress done = =
-@progress pending =
-
-# Or ride those spinnas'
-#@progress format = [%d%s%p]
-#@progress spinner = -\\|/
-#@progress done = -
-#@progress pending =
+set progress.width = 8
+set progress.format = [%d>%p]%c
+set progress.done = =
+set progress.pending =
+
+# Or using a spinner:
+#set progress.format = [%d%s%p]
+#set progress.spinner = -\\|/
+#set progress.done = -
+#set progress.pending =
# === Core settings ==========================================================
diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py
index a1a5d89..5b13476 100644
--- a/examples/data/plugins/bind.py
+++ b/examples/data/plugins/bind.py
@@ -11,10 +11,6 @@ And it is also possible to execute a function on activation:
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
@@ -62,9 +58,9 @@ class Bindlet(object):
if self.last_mode:
mode, self.last_mode = self.last_mode, None
- self.uzbl.set_mode(mode)
+ self.uzbl.config['mode'] = mode
- self.uzbl.set('keycmd_prompt')
+ del self.uzbl.config['keycmd_prompt']
def stack(self, bind, args, depth):
@@ -76,10 +72,10 @@ class Bindlet(object):
return
- current_mode = self.uzbl.get_mode()
- if current_mode != 'stack':
- self.last_mode = current_mode
- self.uzbl.set_mode('stack')
+ mode = self.uzbl.config.get('mode', None)
+ if mode != 'stack':
+ self.last_mode = mode
+ self.uzbl.config['mode'] = 'stack'
self.stack_binds = [bind,]
self.args += args
@@ -97,7 +93,7 @@ class Bindlet(object):
self.uzbl.clear_keycmd()
if prompt:
- self.uzbl.set('keycmd_prompt', prompt)
+ self.uzbl.config['keycmd_prompt'] = prompt
if set and is_cmd:
self.uzbl.send(set)
@@ -111,7 +107,7 @@ class Bindlet(object):
the filtered stack list and modkey & non-stack globals.'''
if mode is None:
- mode = self.uzbl.get_mode()
+ mode = self.uzbl.config.get('mode', None)
if not mode:
mode = 'global'
@@ -145,24 +141,6 @@ class Bindlet(object):
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.'''
@@ -324,7 +302,7 @@ def exec_bind(uzbl, bind, *args, **kargs):
def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs):
'''Add a mode bind.'''
- bindlet = get_bindlet(uzbl)
+ bindlet = uzbl.bindlet
if not hasattr(modes, '__iter__'):
modes = unicode(modes).split(',')
@@ -400,7 +378,8 @@ def mode_changed(uzbl, mode):
'''Clear the stack on all non-stack mode changes.'''
if mode != 'stack':
- get_bindlet(uzbl).reset()
+ uzbl.bindlet.reset()
+ uzbl.clear_keycmd()
def match_and_exec(uzbl, bind, depth, keylet, bindlet):
@@ -440,7 +419,7 @@ def match_and_exec(uzbl, bind, depth, keylet, bindlet):
args = bindlet.args + args
exec_bind(uzbl, bind, *args)
if not has_args or on_exec:
- uzbl.set_mode()
+ del uzbl.config['mode']
bindlet.reset()
uzbl.clear_current()
@@ -448,7 +427,7 @@ def match_and_exec(uzbl, bind, depth, keylet, bindlet):
def key_event(uzbl, keylet, mod_cmd=False, on_exec=False):
- bindlet = get_bindlet(uzbl)
+ bindlet = uzbl.bindlet
depth = bindlet.depth
for bind in bindlet.get_binds():
t = bind[depth]
@@ -463,12 +442,14 @@ def key_event(uzbl, keylet, mod_cmd=False, on_exec=False):
# Return to the previous mode if the KEYCMD_EXEC keycmd doesn't match any
# binds in the stack mode.
if on_exec and not mod_cmd and depth and depth == bindlet.depth:
- uzbl.set_mode()
+ del uzbl.config['mode']
+# plugin init hook
def init(uzbl):
- # Event handling hooks.
- uzbl.connect_dict({
+ '''Export functions and connect handlers to events.'''
+
+ connect_dict(uzbl, {
'BIND': parse_bind,
'MODE_BIND': parse_mode_bind,
'MODE_CHANGED': mode_changed,
@@ -481,12 +462,10 @@ def init(uzbl):
for mod_cmd in range(2):
for on_exec in range(2):
event = events[mod_cmd][on_exec]
- uzbl.connect(event, key_event, bool(mod_cmd), bool(on_exec))
+ connect(uzbl, event, key_event, bool(mod_cmd), bool(on_exec))
- # Function exports to the uzbl object, `function(uzbl, *args, ..)`
- # becomes `uzbl.function(*args, ..)`.
- uzbl.export_dict({
+ export_dict(uzbl, {
'bind': bind,
'mode_bind': mode_bind,
- 'get_bindlet': get_bindlet,
+ 'bindlet': Bindlet(uzbl),
})
diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py
index 3f6ae2b..b007975 100644
--- a/examples/data/plugins/cmd_expand.py
+++ b/examples/data/plugins/cmd_expand.py
@@ -35,8 +35,6 @@ def cmd_expand(uzbl, cmd, args):
return cmd
-
+# plugin init hook
def init(uzbl):
- # Function exports to the uzbl object, `function(uzbl, *args, ..)`
- # becomes `uzbl.function(*args, ..)`.
- uzbl.export('cmd_expand', cmd_expand)
+ export(uzbl, 'cmd_expand', cmd_expand)
diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py
index 8cea203..e8c7f34 100644
--- a/examples/data/plugins/completion.py
+++ b/examples/data/plugins/completion.py
@@ -1,19 +1,10 @@
'''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
@@ -21,39 +12,17 @@ FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall
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()
+ keylet = uzbl.keylet
left_segment = keylet.keycmd[:keylet.cursor]
partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip()
if partial.startswith('set '):
@@ -65,9 +34,8 @@ def get_incomplete_keyword(uzbl):
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')
+ uzbl.completion.level = NONE
+ del uzbl.config['completion_list']
def complete_completion(uzbl, partial, hint, set_completion=False):
@@ -99,46 +67,46 @@ def update_completion_list(uzbl, *args):
if not partial:
return stop_completion(uzbl)
- d = get_completion_dict(uzbl)
- if d['level'] < LIST:
+ if uzbl.completion.level < LIST:
return
- hints = [h for h in d['completions'] if h.startswith(partial)]
+ hints = filter(lambda h: h.startswith(partial), uzbl.completion)
if not hints:
- return uzbl.set('completion_list')
+ del uzbl.config['completion_list']
+ return
j = len(partial)
l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)]
- uzbl.set('completion_list', LIST_FORMAT % ' '.join(l))
+ uzbl.config['completion_list'] = LIST_FORMAT % ' '.join(l)
def start_completion(uzbl, *args):
- d = get_completion_dict(uzbl)
- if d['lock']:
+ comp = uzbl.completion
+ if comp.locked:
return
(partial, set_completion) = get_incomplete_keyword(uzbl)
if not partial:
return stop_completion(uzbl)
- if d['level'] < COMPLETE:
- d['level'] += 1
+ if comp.level < COMPLETE:
+ comp.level += 1
- hints = [h for h in d['completions'] if h.startswith(partial)]
+ hints = filter(lambda h: h.startswith(partial), comp)
if not hints:
return
elif len(hints) == 1:
- d['lock'] = True
+ comp.lock()
complete_completion(uzbl, partial, hints[0], set_completion)
- d['lock'] = False
+ comp.unlock()
return
- elif partial in hints and d['level'] == COMPLETE:
- d['lock'] = True
+ elif partial in hints and comp.level == COMPLETE:
+ comp.lock()
complete_completion(uzbl, partial, partial, set_completion)
- d['lock'] = False
+ comp.unlock()
return
smalllen, smallest = sorted([(len(h), h) for h in hints])[0]
@@ -156,51 +124,56 @@ def start_completion(uzbl, *args):
common += char
if common:
- d['lock'] = True
+ comp.lock()
partial_completion(uzbl, partial, partial+common)
- d['lock'] = False
+ comp.unlock()
update_completion_list(uzbl)
-def add_builtins(uzbl, args):
+def add_builtins(uzbl, builtins):
'''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)
+ uzbl.completion.update(builtins.split())
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)
+ uzbl.completion.add("@%s" % key)
+
+
+class Completions(set):
+ def __init__(self):
+ set.__init__(self)
+ self.locked = False
+ self.level = NONE
+
+ def lock(self):
+ self.locked = True
+
+ def unlock(self):
+ self.locked = False
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,
+ '''Export functions and connect handlers to events.'''
+
+ export_dict(uzbl, {
+ 'completion': Completions(),
+ 'start_completion': start_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,
+ connect_dict(uzbl, {
+ 'BUILTINS': add_builtins,
+ 'CONFIG_CHANGED': add_config_key,
+ 'KEYCMD_CLEARED': stop_completion,
+ 'KEYCMD_EXEC': stop_completion,
+ 'KEYCMD_UPDATE': update_completion_list,
+ 'START_COMPLETION': start_completion,
+ 'STOP_COMPLETION': stop_completion,
})
+
+ uzbl.send('dump_config_as_events')
diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py
index 4a848a3..ed2d761 100644
--- a/examples/data/plugins/config.py
+++ b/examples/data/plugins/config.py
@@ -1,97 +1,90 @@
-import re
-import types
+from re import compile
+from types import BooleanType
+from UserDict import DictMixin
-__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)
+valid_key = compile('^[A-Za-z0-9_\.]+$').match
+types = {'int': int, 'float': float, 'str': unicode}
+escape = lambda s: unicode(s).replace('\n', '\\n')
+class Config(DictMixin):
+ def __init__(self, uzbl):
+ self.uzbl = uzbl
-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.'''
+ # Create the base dict and map allowed methods to `self`.
+ self.data = data = {}
- if type(value) == types.BooleanType:
- value = int(value)
+ methods = ['__contains__', '__getitem__', '__iter__',
+ '__len__', 'get', 'has_key', 'items', 'iteritems',
+ 'iterkeys', 'itervalues', 'values']
- else:
- value = unicode(value)
+ for method in methods:
+ setattr(self, method, getattr(data, method))
- if not VALIDKEY(key):
- raise KeyError("%r" % key)
- value = escape(value)
- if '\n' in value:
- value = value.replace("\n", "\\n")
+ def __setitem__(self, key, value):
+ self.set(key, value)
- if not force:
- if config is None:
- config = get_config(uzbl)
+ def __delitem__(self, key):
+ self.set(key)
- if key in config and config[key] == value:
- return
+ def update(self, other=None, **kwargs):
+ if other is None:
+ other = {}
- uzbl.send('set %s = %s' % (key, value))
+ for (key, value) in dict(other).items() + kwargs.items():
+ self[key] = value
-class ConfigDict(dict):
- def __init__(self, uzbl):
- self._uzbl = uzbl
+ def set(self, key, value='', force=False):
+ '''Generates a `set <key> = <value>` command string to send to the
+ current uzbl instance.
- def __setitem__(self, key, value):
- '''Makes "config[key] = value" a wrapper for the set function.'''
+ Note that the config dict isn't updated by this function. The config
+ dict is only updated after a successful `VARIABLE_SET ..` event
+ returns from the uzbl instance.'''
- set(self._uzbl, key, value, config=self)
+ assert valid_key(key)
+ if type(value) == BooleanType:
+ value = int(value)
-def add_instance(uzbl, *args):
- UZBLS[uzbl] = ConfigDict(uzbl)
+ else:
+ value = escape(value)
+ if not force and key in self and self[key] == value:
+ return
-def del_instance(uzbl, *args):
- if uzbl in UZBLS:
- del uzbl
+ self.uzbl.send(u'set %s = %s' % (key, value))
-def get_config(uzbl):
- if uzbl not in UZBLS:
- add_instance(uzbl)
+def parse_set_event(uzbl, args):
+ '''Parse `VARIABLE_SET <var> <type> <value>` event and load the
+ (key, value) pair into the `uzbl.config` dict.'''
- return UZBLS[uzbl]
+ (key, type, raw_value) = (args.split(' ', 2) + ['',])[:3]
+ assert valid_key(key)
+ assert type in types
-def variable_set(uzbl, args):
- config = get_config(uzbl)
+ new_value = types[type](raw_value)
+ old_value = uzbl.config.get(key, None)
- key, type, value = list(args.split(' ', 2) + ['',])[:3]
- old = config[key] if key in config else None
- value = TYPECONVERT[type](value)
+ # Update new value.
+ uzbl.config.data[key] = new_value
- dict.__setitem__(config, key, value)
+ if old_value != new_value:
+ uzbl.event('CONFIG_CHANGED', key, new_value)
- if old != value:
- uzbl.event("CONFIG_CHANGED", key, value)
+ # Cleanup null config values.
+ if type == 'str' and not new_value:
+ del uzbl.config.data[key]
+# plugin init hook
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,
- })
+ export(uzbl, 'config', Config(uzbl))
+ connect(uzbl, 'VARIABLE_SET', parse_set_event)
+
+# plugin cleanup hook
+def cleanup(uzbl):
+ uzbl.config.data.clear()
diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py
index c119077..b600afe 100644
--- a/examples/data/plugins/keycmd.py
+++ b/examples/data/plugins/keycmd.py
@@ -1,8 +1,5 @@
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>"
@@ -38,9 +35,6 @@ class Keylet(object):
self.ignores = {}
self.additions = {}
- # Keylet string repr cache.
- self._repr_cache = None
-
def get_keycmd(self):
'''Get the keycmd-part of the keylet.'''
@@ -106,9 +100,6 @@ class Keylet(object):
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())
@@ -119,8 +110,7 @@ class Keylet(object):
if self.keycmd:
l.append('keycmd=%r' % self.get_keycmd())
- self._repr_cache = '<Keylet(%s)>' % ', '.join(l)
- return self._repr_cache
+ return '<keylet(%s)>' % ', '.join(l)
def add_modmap(uzbl, key, map):
@@ -140,7 +130,7 @@ def add_modmap(uzbl, key, map):
'''
assert len(key)
- modmaps = get_keylet(uzbl).modmaps
+ modmaps = uzbl.keylet.modmaps
if key[0] == "<" and key[-1] == ">":
key = key[1:-1]
@@ -171,7 +161,7 @@ def add_key_ignore(uzbl, glob):
'''
assert len(glob) > 1
- ignores = get_keylet(uzbl).ignores
+ ignores = uzbl.keylet.ignores
glob = "<%s>" % glob.strip("<> ")
restr = glob.replace('*', '[^\s]*')
@@ -197,7 +187,7 @@ def add_modkey_addition(uzbl, modkeys, result):
...
'''
- additions = get_keylet(uzbl).additions
+ additions = uzbl.keylet.additions
modkeys = set(modkeys)
assert len(modkeys) and result and result not in modkeys
@@ -220,65 +210,34 @@ def modkey_addition_parse(uzbl, modkeys):
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 = uzbl.keylet
k.keycmd = ''
k.cursor = 0
- k._repr_cache = False
- uzbl.set('keycmd')
- uzbl.set('raw_keycmd')
+ del uzbl.config['keycmd']
uzbl.event('KEYCMD_CLEARED')
def clear_modcmd(uzbl, clear_held=False):
'''Clear the modcmd for this uzbl instance.'''
- k = get_keylet(uzbl)
+ k = uzbl.keylet
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')
+ del uzbl.config['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:
+ if uzbl.keylet.is_modcmd:
clear_modcmd(uzbl)
else:
@@ -296,7 +255,6 @@ def focus_changed(uzbl, *args):
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:
@@ -305,32 +263,28 @@ def update_event(uzbl, k, execute=True):
else:
uzbl.event('KEYCMD_UPDATE', k)
- if 'modcmd_updates' not in config or config['modcmd_updates'] == '1':
+ if uzbl.config.get('modcmd_updates', '1') == '1':
new_modcmd = k.get_modcmd()
if not new_modcmd:
- uzbl.set('modcmd', config=config)
- uzbl.set('raw_modcmd', config=config)
+ del uzbl.config['modcmd']
elif new_modcmd == modcmd:
- uzbl.set('raw_modcmd', escape(modcmd), config=config)
- uzbl.set('modcmd', MODCMD_FORMAT % uzbl_escape(modcmd),
- config=config)
+ uzbl.config['modcmd'] = MODCMD_FORMAT % uzbl_escape(modcmd)
- if 'keycmd_events' in config and config['keycmd_events'] != '1':
+ if uzbl.config.get('keycmd_events', '1') != '1':
return
new_keycmd = k.get_keycmd()
if not new_keycmd:
- uzbl.set('keycmd', config=config)
- uzbl.set('raw_keycmd', config=config)
+ del uzbl.config['keycmd']
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)
+
+ uzbl.config['keycmd'] = value
def inject_str(str, index, inj):
@@ -344,7 +298,7 @@ def get_keylet_and_key(uzbl, key, add=True):
by the modmapping or modkey addition rules. Return None if the key is
ignored.'''
- keylet = get_keylet(uzbl)
+ keylet = uzbl.keylet
key = keylet.modmap_key(key)
if len(key) == 1:
return (keylet, key)
@@ -385,12 +339,11 @@ def key_press(uzbl, key):
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':
+
+ if uzbl.config.get('keycmd_events', '1') != '1':
k.keycmd = ''
k.cursor = 0
- uzbl.set('keycmd', config=config)
- uzbl.set('raw_keycmd', config=config)
+ del uzbl.config['keycmd']
return
k.keycmd = inject_str(k.keycmd, k.cursor, key)
@@ -429,9 +382,8 @@ def key_release(uzbl, key):
def set_keycmd(uzbl, keycmd):
'''Allow setting of the keycmd externally.'''
- k = get_keylet(uzbl)
+ k = uzbl.keylet
k.keycmd = keycmd
- k._repr_cache = None
k.cursor = len(keycmd)
update_event(uzbl, k, False)
@@ -439,9 +391,8 @@ def set_keycmd(uzbl, keycmd):
def inject_keycmd(uzbl, keycmd):
'''Allow injecting of a string into the keycmd at the cursor position.'''
- k = get_keylet(uzbl)
+ k = uzbl.keylet
k.keycmd = inject_str(k.keycmd, k.cursor, keycmd)
- k._repr_cache = None
k.cursor += len(keycmd)
update_event(uzbl, k, False)
@@ -449,9 +400,8 @@ def inject_keycmd(uzbl, keycmd):
def append_keycmd(uzbl, keycmd):
'''Allow appening of a string to the keycmd.'''
- k = get_keylet(uzbl)
+ k = uzbl.keylet
k.keycmd += keycmd
- k._repr_cache = None
k.cursor = len(k.keycmd)
update_event(uzbl, k, False)
@@ -460,7 +410,7 @@ def keycmd_strip_word(uzbl, sep):
''' Removes the last word from the keycmd, similar to readline ^W '''
sep = sep or ' '
- k = get_keylet(uzbl)
+ k = uzbl.keylet
if not k.keycmd:
return
@@ -475,7 +425,7 @@ def keycmd_strip_word(uzbl, sep):
def keycmd_backspace(uzbl, *args):
'''Removes the character at the cursor position in the keycmd.'''
- k = get_keylet(uzbl)
+ k = uzbl.keylet
if not k.keycmd:
return
@@ -487,7 +437,7 @@ def keycmd_backspace(uzbl, *args):
def keycmd_delete(uzbl, *args):
'''Removes the character after the cursor position in the keycmd.'''
- k = get_keylet(uzbl)
+ k = uzbl.keylet
if not k.keycmd:
return
@@ -499,8 +449,7 @@ 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)
+ uzbl.event('KEYCMD_EXEC', uzbl.keylet)
clear_keycmd(uzbl)
@@ -508,7 +457,7 @@ 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)
+ k = uzbl.keylet
if index == '-':
cursor = k.cursor - 1
@@ -530,18 +479,16 @@ def set_cursor_pos(uzbl, index):
update_event(uzbl, k, False)
+# plugin init hook
def init(uzbl):
- '''Connect handlers to uzbl events.'''
+ '''Export functions and connect handlers to events.'''
- # Event handling hooks.
- uzbl.connect_dict({
+ connect_dict(uzbl, {
'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,
@@ -554,9 +501,7 @@ def init(uzbl):
'SET_KEYCMD': set_keycmd,
})
- # Function exports to the uzbl object, `function(uzbl, *args, ..)`
- # becomes `uzbl.function(*args, ..)`.
- uzbl.export_dict({
+ export_dict(uzbl, {
'add_key_ignore': add_key_ignore,
'add_modkey_addition': add_modkey_addition,
'add_modmap': add_modmap,
@@ -564,8 +509,8 @@ def init(uzbl):
'clear_current': clear_current,
'clear_keycmd': clear_keycmd,
'clear_modcmd': clear_modcmd,
- 'get_keylet': get_keylet,
'inject_keycmd': inject_keycmd,
+ 'keylet': Keylet(),
'set_cursor_pos': set_cursor_pos,
'set_keycmd': set_keycmd,
})
diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py
index 54d865a..e0de706 100644
--- a/examples/data/plugins/mode.py
+++ b/examples/data/plugins/mode.py
@@ -1,176 +1,68 @@
-import sys
-import re
+from collections import defaultdict
-__export__ = ['set_mode', 'get_mode', 'set_mode_config', 'get_mode_config']
+def parse_mode_config(uzbl, args):
+ '''Parse `MODE_CONFIG <mode> <var> = <value>` event and update config if
+ the `<mode>` is the current mode.'''
-UZBLS = {}
+ ustrip = unicode.strip
+ args = unicode(args)
-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'}}}
+ assert args.strip(), "missing mode config args"
+ (mode, args) = map(ustrip, (args.strip().split(' ', 1) + ['',])[:2])
-FINDSPACES = re.compile("\s+")
-VALID_KEY = re.compile("^[\w_]+$").match
+ assert args.strip(), "missing mode config set arg"
+ (key, value) = map(ustrip, (args.strip().split('=', 1) + [None,])[:2])
+ assert key and value is not None, "invalid mode config set syntax"
+ uzbl.mode_config[mode][key] = value
+ if uzbl.config.get('mode', None) == mode:
+ uzbl.config[key] = value
-def add_instance(uzbl, *args):
- UZBLS[uzbl] = dict(DEFAULTS)
+def default_mode_updated(uzbl, var, mode):
+ if mode and not uzbl.config.get('mode', None):
+ logger.debug('setting mode to default %r' % mode)
+ uzbl.config['mode'] = mode
-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:
+def mode_updated(uzbl, var, mode):
+ if not mode:
+ mode = uzbl.config.get('default_mode', 'command')
+ logger.debug('setting mode to default %r' % mode)
+ uzbl.config['mode'] = 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
+ # Load mode config
+ mode_config = uzbl.mode_config.get(mode, None)
+ if mode_config:
+ uzbl.config.update(mode_config)
- elif mode_dict['mode'] != mode:
- mode_dict['mode'] = mode
- uzbl.event("MODE_CHANGED", mode)
+ uzbl.send('event MODE_CONFIRM %s' % mode)
-def config_changed(uzbl, key, value):
- '''Check for mode related config changes.'''
+def confirm_change(uzbl, mode):
+ if mode and uzbl.config.get('mode', None) == mode:
+ uzbl.event('MODE_CHANGED', mode)
- 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])
+# plugin init hook
+def init(uzbl):
+ require('config')
+ require('on_set')
+ # Usage `uzbl.mode_config[mode][key] = value`
+ export(uzbl, 'mode_config', defaultdict(dict))
-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,
+ connect_dict(uzbl, {
+ 'MODE_CONFIG': parse_mode_config,
+ 'MODE_CONFIRM': confirm_change,
})
- # 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,
- })
+# plugin after hook
+def after(uzbl):
+ uzbl.on_set('mode', mode_updated)
+ uzbl.on_set('default_mode', default_mode_updated)
+
+# plugin cleanup hook
+def cleanup(uzbl):
+ uzbl.mode_config.clear()
diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py
index b9c504a..5142275 100644
--- a/examples/data/plugins/on_event.py
+++ b/examples/data/plugins/on_event.py
@@ -20,36 +20,11 @@ Usage:
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)
+ events = uzbl.on_events
event = kargs['on_event']
if event not in events:
return
@@ -65,9 +40,9 @@ def on_event(uzbl, event, cmd):
'''Add a new event to watch and respond to.'''
event = event.upper()
- events = get_on_events(uzbl)
+ events = uzbl.on_events
if event not in events:
- uzbl.connect(event, event_handler, on_event=event)
+ connect(uzbl, event, event_handler, on_event=event)
events[event] = []
cmds = events[event]
@@ -80,28 +55,28 @@ def parse_on_event(uzbl, args):
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)
+ args = args.strip()
+ assert args, 'missing on event arguments'
- event, cmd = split
- on_event(uzbl, event, cmd)
+ (event, command) = (args.split(' ', 1) + ['',])[:2]
+ assert event and command, 'missing on event command'
+ on_event(uzbl, event, command)
+# plugin init hook
def init(uzbl):
- # Event handling hooks.
- uzbl.connect_dict({
- 'INSTANCE_EXIT': del_instance,
- 'INSTANCE_START': add_instance,
- 'ON_EVENT': parse_on_event,
- })
+ '''Export functions and connect handlers to events.'''
+
+ connect(uzbl, '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,
+ export_dict(uzbl, {
+ 'on_event': on_event,
+ 'on_events': {},
})
+
+# plugin cleanup hook
+def cleanup(uzbl):
+ for handlers in uzbl.on_events.values():
+ del handlers[:]
+
+ uzbl.on_events.clear()
diff --git a/examples/data/plugins/on_set.py b/examples/data/plugins/on_set.py
new file mode 100644
index 0000000..130b816
--- /dev/null
+++ b/examples/data/plugins/on_set.py
@@ -0,0 +1,92 @@
+from re import compile
+from functools import partial
+
+valid_glob = compile('^[A-Za-z0-9_\*\.]+$').match
+
+def make_matcher(glob):
+ '''Make matcher function from simple glob.'''
+
+ pattern = "^%s$" % glob.replace('*', '[^\s]*')
+ return compile(pattern).match
+
+
+def exec_handlers(uzbl, handlers, key, arg):
+ '''Execute the on_set handlers that matched the key.'''
+
+ for handler in handlers:
+ if callable(handler):
+ handler(key, arg)
+
+ else:
+ uzbl.send(uzbl.cmd_expand(handler, [key, arg]))
+
+
+def check_for_handlers(uzbl, key, arg):
+ '''Check for handlers for the current key.'''
+
+ for (matcher, handlers) in uzbl.on_sets.values():
+ if matcher(key):
+ exec_handlers(uzbl, handlers, key, arg)
+
+
+def on_set(uzbl, glob, handler, prepend=True):
+ '''Add a new handler for a config key change.
+
+ Structure of the `uzbl.on_sets` dict:
+ { glob : ( glob matcher function, handlers list ), .. }
+ '''
+
+ assert valid_glob(glob)
+
+ while '**' in glob:
+ glob = glob.replace('**', '*')
+
+ if callable(handler):
+ orig_handler = handler
+ if prepend:
+ handler = partial(handler, uzbl)
+
+ else:
+ orig_handler = handler = unicode(handler)
+
+ if glob in uzbl.on_sets:
+ (matcher, handlers) = uzbl.on_sets[glob]
+ handlers.append(handler)
+
+ else:
+ matcher = make_matcher(glob)
+ uzbl.on_sets[glob] = (matcher, [handler,])
+
+ uzbl.logger.info('on set %r call %r' % (glob, orig_handler))
+
+
+def parse_on_set(uzbl, args):
+ '''Parse `ON_SET <glob> <command>` event then pass arguments to the
+ `on_set(..)` function.'''
+
+ (glob, command) = (args.split(' ', 1) + [None,])[:2]
+ assert glob and command and valid_glob(glob)
+ on_set(uzbl, glob, command)
+
+
+# plugins init hook
+def init(uzbl):
+ require('config')
+ require('cmd_expand')
+
+ export_dict(uzbl, {
+ 'on_sets': {},
+ 'on_set': on_set,
+ })
+
+ connect_dict(uzbl, {
+ 'ON_SET': parse_on_set,
+ 'CONFIG_CHANGED': check_for_handlers,
+ })
+
+# plugins cleanup hook
+def cleanup(uzbl):
+ for (matcher, handlers) in uzbl.on_sets.values():
+ del handlers[:]
+
+ uzbl.on_sets.clear()
diff --git a/examples/data/plugins/plugin_template.py b/examples/data/plugins/plugin_template.py
deleted file mode 100644
index 565a999..0000000
--- a/examples/data/plugins/plugin_template.py
+++ /dev/null
@@ -1,76 +0,0 @@
-'''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
index 89ba175..b2edffc 100644
--- a/examples/data/plugins/progress_bar.py
+++ b/examples/data/plugins/progress_bar.py
@@ -1,39 +1,7 @@
-import sys
+UPDATES = 0
-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.
+def update_progress(uzbl, progress=None):
+ '''Updates the progress.output variable on LOAD_PROGRESS update.
The current substitution options are:
%d = done char * done
@@ -44,116 +12,82 @@ def update_progress(uzbl, prog=None):
%t = percent pending
%o = int pending
%r = sprites
+
+ Default configuration options:
+ progress.format = [%d>%p]%c
+ progress.width = 8
+ progress.done = =
+ progress.pending =
+ progress.spinner = -\|/
+ progress.sprites = loading
'''
- prog_config = get_progress_config(uzbl)
- config = uzbl.get_config()
+ global UPDATES
- if prog is None:
- prog = prog_config['progress']
+ if progress is None:
+ UPDATES = 0
+ progress = 100
else:
- prog = int(prog)
- prog_config['progress'] = prog
+ UPDATES += 1
+ progress = int(progress)
- prog_config['updates'] += 1
- format = prog_config['format']
- width = prog_config['width']
+ # Get progress config vars.
+ format = uzbl.config.get('progress.format', '[%d>%p]%c')
+ width = int(uzbl.config.get('progress.width', 8))
+ done_symbol = uzbl.config.get('progress.done', '=')
+ pend = uzbl.config.get('progress.pending', None)
+ pending_symbol = pend if pend else ' '
# 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
+ width += (3-len(str(progress))) * count
if '%t' in format or '%o' in format:
count = format.count('%t') + format.count('%o')
- width += (3-len(str(100-prog))) * count
+ width += (3-len(str(100-progress))) * count
- done = int(((prog/100.0)*width)+0.5)
+ done = int(((progress/100.0)*width)+0.5)
pending = width - done
if '%d' in format:
- format = format.replace('%d', prog_config['done']*done)
+ format = format.replace('%d', done_symbol * done)
if '%p' in format:
- format = format.replace('%p', prog_config['pending']*pending)
+ format = format.replace('%p', pending_symbol * pending)
if '%c' in format:
- format = format.replace('%c', '%d%%' % prog)
+ format = format.replace('%c', '%d%%' % progress)
if '%i' in format:
- format = format.replace('%i', '%d' % prog)
+ format = format.replace('%i', '%d' % progress)
if '%t' in format:
- format = format.replace('%t', '%d%%' % (100-prog))
+ format = format.replace('%t', '%d%%' % (100-progress))
if '%o' in format:
- format = format.replace('%o', '%d' % (100-prog))
+ format = format.replace('%o', '%d' % (100-progress))
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)
+ spinner = uzbl.config.get('progress.spinner', '-\\|/')
+ index = 0 if progress == 100 else UPDATES % len(spinner)
+ spin = '\\\\' if spinner[index] == '\\' else spinner[index]
+ format = format.replace('%s', spin)
if '%r' in format:
- sprites = prog_config['sprites']
- sprites = '-' if not sprites else sprites
- index = int(((prog/100.0)*len(sprites))+0.5)-1
+ sprites = uzbl.config.get('progress.sprites', 'loading')
+ index = int(((progress/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)
-
+ if uzbl.config.get('progress.output', None) != format:
+ uzbl.config['progress.output'] = format
+# plugin init hook
def init(uzbl):
- # Event handling hooks.
- uzbl.connect_dict({
- 'INSTANCE_EXIT': del_instance,
- 'INSTANCE_START': add_instance,
- 'LOAD_COMMIT': reset_progress,
+ connect_dict(uzbl, {
+ 'LOAD_COMMIT': lambda uzbl, uri: update_progress(uzbl),
'LOAD_PROGRESS': update_progress,
- 'PROGRESS_CONFIG': progress_config,
})
diff --git a/examples/data/scripts/uzbl-event-manager b/examples/data/scripts/uzbl-event-manager
index 7fa4a09..75a1c13 100755
--- a/examples/data/scripts/uzbl-event-manager
+++ b/examples/data/scripts/uzbl-event-manager
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# Event Manager for Uzbl
-# Copyright (c) 2009, Mason Larobina <mason.larobina@gmail.com>
+# Copyright (c) 2009-2010, Mason Larobina <mason.larobina@gmail.com>
# Copyright (c) 2009, Dieter Plaetinck <dieter@plaetinck.be>
#
# This program is free software: you can redistribute it and/or modify
@@ -26,21 +26,23 @@ Event manager for uzbl written in python.
'''
+import atexit
import imp
+import logging
import os
-import sys
-import re
import socket
-import pprint
+import sys
import time
-import atexit
-from select import select
-from signal import signal, SIGTERM
-from optparse import OptionParser
-from traceback import print_exc
+import weakref
+from collections import defaultdict
from functools import partial
+from glob import glob
from itertools import count
-
+from optparse import OptionParser
+from select import select
+from signal import signal, SIGTERM, SIGINT
+from socket import socket, AF_UNIX, SOCK_STREAM
+from traceback import format_exc
def xdghome(key, default):
'''Attempts to use the environ XDG_*_HOME paths if they exist otherwise
@@ -52,11 +54,6 @@ def xdghome(key, default):
return os.path.join(os.environ['HOME'], default)
-
-# ============================================================================
-# ::: Default configuration section ::::::::::::::::::::::::::::::::::::::::::
-# ============================================================================
-
# `make install` will put the correct value here for your system
PREFIX = '/usr/local/'
@@ -64,120 +61,34 @@ PREFIX = '/usr/local/'
DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
-# Event manager config dictionary. This is not to be confused with the config
-# dict that tracks variables in the uzbl instance.
-CONFIG = {
- 'verbose': False,
- 'daemon_mode': True,
- 'auto_close': False,
-
- 'plugins_load': [],
- 'plugins_ignore': [],
-
- 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'),
- os.path.join(PREFIX, 'share/uzbl/examples/data/plugins/')],
-
- 'server_socket': os.path.join(CACHE_DIR, 'event_daemon'),
- 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'),
-}
-
-# ============================================================================
-# ::: End of configuration section :::::::::::::::::::::::::::::::::::::::::::
-# ============================================================================
-
-
# Define some globals.
SCRIPTNAME = os.path.basename(sys.argv[0])
-FINDSPACES = re.compile("\s+")
-
-
-class ArgumentError(Exception):
- pass
-
-
-def echo(msg):
- '''Prints only if the verbose flag has been set.'''
-
- if CONFIG['verbose']:
- sys.stdout.write("%s: %s\n" % (SCRIPTNAME, msg))
-
-
-def error(msg):
- '''Prints error messages to stderr.'''
-
- sys.stderr.write("%s: error: %s\n" % (SCRIPTNAME, msg))
-
-
-def find_plugins(plugin_dirs):
- '''Find all event manager plugins in the plugin dirs and return a
- dictionary of {'plugin-name.py': '/full/path/to/plugin-name.py', ...}'''
-
- plugins = {}
-
- for plugin_dir in plugin_dirs:
- plugin_dir = os.path.realpath(os.path.expandvars(plugin_dir))
- if not os.path.isdir(plugin_dir):
- continue
-
- for filename in os.listdir(plugin_dir):
- if not filename.lower().endswith('.py'):
- continue
-
- path = os.path.join(plugin_dir, filename)
- if not os.path.isfile(path):
- continue
-
- if filename not in plugins:
- plugins[filename] = plugin_dir
-
- return plugins
+def get_exc():
+ '''Format `format_exc` for logging.'''
+ return "\n%s" % format_exc().rstrip()
-def load_plugins(plugin_dirs, load=None, ignore=None):
- '''Load event manager plugins found in the plugin_dirs.'''
+def expandpath(path):
+ '''Expand and realpath paths.'''
+ return os.path.realpath(os.path.expandvars(path))
- load = [] if load is None else load
- ignore = [] if ignore is None else ignore
-
- # Find the plugins in the plugin_dirs.
- found = find_plugins(plugin_dirs)
-
- if load:
- # Ignore anything not in the load list.
- for plugin in found.keys():
- if plugin not in load:
- del found[plugin]
-
- if ignore:
- # Ignore anything in the ignore list.
- for plugin in found.keys():
- if plugin in ignore:
- del found[plugin]
-
- # Print plugin list to be loaded.
- pprint.pprint(found)
-
- loaded = {}
- # Load all found plugins into the loaded dict.
- for (filename, plugin_dir) in found.items():
- name = filename[:-3]
- info = imp.find_module(name, [plugin_dir])
- plugin = imp.load_module(name, *info)
- loaded[(plugin_dir, filename)] = plugin
-
- return loaded
+def ascii(u):
+ '''Convert unicode strings into ascii for transmission over
+ ascii-only streams/sockets/devices.'''
+ return u.encode('utf-8')
def daemonize():
'''Daemonize the process using the Stevens' double-fork magic.'''
+ logger.info('entering daemon mode')
+
try:
if os.fork():
os._exit(0)
except OSError:
- print_exc()
- sys.stderr.write("fork #1 failed")
+ logger.critical(get_exc())
sys.exit(1)
os.chdir('/')
@@ -189,8 +100,7 @@ def daemonize():
os._exit(0)
except OSError:
- print_exc()
- sys.stderr.write("fork #2 failed")
+ logger.critical(get_exc())
sys.exit(1)
if sys.stdout.isatty():
@@ -206,6 +116,8 @@ def daemonize():
os.dup2(stdout.fileno(), sys.stdout.fileno())
os.dup2(stderr.fileno(), sys.stderr.fileno())
+ logger.info('entered daemon mode')
+
def make_dirs(path):
'''Make all basedirs recursively as required.'''
@@ -213,637 +125,840 @@ def make_dirs(path):
try:
dirname = os.path.dirname(path)
if not os.path.isdir(dirname):
+ logger.debug('creating directories %r' % dirname)
os.makedirs(dirname)
except OSError:
- print_exc()
+ logger.error(get_exc())
-def make_pid_file(pid_file):
- '''Make pid file at given pid_file location.'''
+class EventHandler(object):
+ '''Event handler class. Used to store args and kwargs which are merged
+ come time to call the callback with the event args and kwargs.'''
- make_dirs(pid_file)
- fileobj = open(pid_file, 'w')
- fileobj.write('%d' % os.getpid())
- fileobj.close()
+ nextid = count().next
+ def __init__(self, plugin, event, callback, args, kwargs):
+ self.id = self.nextid()
+ self.plugin = plugin
+ self.event = event
+ self.callback = callback
+ self.args = args
+ self.kwargs = kwargs
-def del_pid_file(pid_file):
- '''Delete pid file at given pid_file location.'''
- if os.path.isfile(pid_file):
- os.remove(pid_file)
+ def __repr__(self):
+ elems = ['id=%d' % self.id, 'event=%s' % self.event,
+ 'callback=%r' % self.callback]
+ if self.args:
+ elems.append('args=%s' % repr(self.args))
-def get_pid(pid_file):
- '''Read pid from pid_file.'''
+ if self.kwargs:
+ elems.append('kwargs=%s' % repr(self.kwargs))
- try:
- fileobj = open(pid_file, 'r')
- pid = int(fileobj.read())
- fileobj.close()
- return pid
+ elems.append('plugin=%s' % self.plugin.name)
+ return u'<handler(%s)>' % ', '.join(elems)
- except IOError, ValueError:
- print_exc()
- return None
+ def call(self, uzbl, *args, **kwargs):
+ '''Execute the handler function and merge argument lists.'''
-def pid_running(pid):
- '''Returns True if a process with the given pid is running.'''
+ args = args + self.args
+ kwargs = dict(self.kwargs.items() + kwargs.items())
+ self.callback(uzbl, *args, **kwargs)
- try:
- os.kill(pid, 0)
- except OSError:
- return False
+class Plugin(object):
+ '''Plugin module wrapper object.'''
- else:
- return True
+ # Special functions exported from the Plugin instance to the
+ # plugin namespace.
+ special_functions = ['require', 'export', 'export_dict', 'connect',
+ 'connect_dict', 'logger']
-def term_process(pid):
- '''Send a SIGTERM signal to the process with the given pid.'''
+ def __init__(self, parent, name, path, plugin):
+ self.parent = parent
+ self.name = name
+ self.path = path
+ self.plugin = plugin
+ self.logger = get_logger('plugin.%s' % name)
- if not pid_running(pid):
- return False
+ # Weakrefs to all handlers created by this plugin
+ self.handlers = set([])
- os.kill(pid, SIGTERM)
+ # Plugins init hook
+ self.init = getattr(plugin, 'init')
- start = time.time()
- while True:
- if not pid_running(pid):
- return True
+ # Plugins optional after hook
+ after = getattr(plugin, 'after', None)
+ self.after = after if callable(after) else None
- if time.time() - start > 5:
- raise OSError('failed to stop process with pid: %d' % pid)
+ # Plugins optional cleanup hook
+ cleanup = getattr(plugin, 'cleanup', None)
+ self.cleanup = cleanup if callable(cleanup) else None
- time.sleep(0.25)
+ # Export plugin's instance methods to plugin namespace
+ for attr in self.special_functions:
+ plugin.__dict__[attr] = getattr(self, attr)
-def parse_msg(uzbl, msg):
- '''Parse an incoming msg from a uzbl instance. All non-event messages
- will be printed here and not be passed to the uzbl instance event
- handler function.'''
+ def __repr__(self):
+ return u'<plugin(%r)>' % self.plugin
- if not msg:
- return
- cmd = FINDSPACES.split(msg, 3)
- if not cmd or cmd[0] != 'EVENT':
- # Not an event message.
- print '---', msg.encode('utf-8')
- return
+ def export(self, uzbl, attr, object, prepend=True):
+ '''Attach `object` to `uzbl` instance. This is the preferred method
+ of sharing functionality, functions, data and objects between
+ plugins.
- while len(cmd) < 4:
- cmd.append('')
+ If the object is callable you may wish to turn the callable object
+ in to a meta-instance-method by prepending `uzbl` to the call stack.
+ You can change this behaviour with the `prepend` argument.
+ '''
- event, args = cmd[2], cmd[3]
- if not event:
- return
+ assert attr not in uzbl.exports, "attr %r already exported by %r" %\
+ (attr, uzbl.exports[attr][0])
- try:
- uzbl.event(event, args)
+ prepend = True if prepend and callable(object) else False
+ uzbl.__dict__[attr] = partial(object, uzbl) if prepend else object
+ uzbl.exports[attr] = (self, object, prepend)
+ uzbl.logger.info('exported %r to %r by plugin %r, prepended %r'
+ % (object, 'uzbl.%s' % attr, self.name, prepend))
- except:
- print_exc()
+ def export_dict(self, uzbl, exports):
+ for (attr, object) in exports.items():
+ self.export(uzbl, attr, object)
-class EventHandler(object):
- nexthid = count().next
+ def find_handler(self, event, callback, args, kwargs):
+ '''Check if a handler with the identical callback and arguments
+ exists and return it.'''
- def __init__(self, event, handler, *args, **kargs):
- if not callable(handler):
- raise ArgumentError("EventHandler object requires a callable "
- "object function for the handler argument not: %r" % handler)
+ # Remove dead refs
+ self.handlers -= set(filter(lambda ref: not ref(), self.handlers))
- self.function = handler
- self.args = args
- self.kargs = kargs
- self.event = event
- self.hid = self.nexthid()
+ # Find existing identical handler
+ for handler in [ref() for ref in self.handlers]:
+ if handler.event == event and handler.callback == callback \
+ and handler.args == args and handler.kwargs == kwargs:
+ return handler
- def __repr__(self):
- args = ["event=%s" % self.event, "hid=%d" % self.hid,
- "function=%r" % self.function]
+ def connect(self, uzbl, event, callback, *args, **kwargs):
+ '''Create an event handler object which handles `event` events.
- if self.args:
- args.append(u"args=%r" % unicode(self.args))
+ Arguments passed to the connect function (`args` and `kwargs`) are
+ stored in the handler object and merged with the event arguments
+ come handler execution.
- if self.kargs:
- args.append(u"kargs=%r" % unicode(self.kargs))
+ All handler functions must behave like a `uzbl` instance-method (that
+ means `uzbl` is prepended to the callback call arguments).'''
- return u"<EventHandler(%s)>" % ', '.join(args)
+ # Sanitise and check event name
+ event = event.upper().strip()
+ assert event and ' ' not in event
+ assert callable(callback), 'callback must be callable'
-class UzblInstance(object):
+ # Check if an identical handler already exists
+ handler = self.find_handler(event, callback, args, kwargs)
+ if not handler:
+ # Create a new handler
+ handler = EventHandler(self, event, callback, args, kwargs)
+ self.handlers.add(weakref.ref(handler))
+ self.logger.info('new %r' % handler)
- # Give all plugins access to the main config dict.
- global_config = CONFIG
+ uzbl.handlers[event].append(handler)
+ uzbl.logger.info('connected %r' % handler)
+ return handler
- def __init__(self, parent, client_socket):
- # Internal variables.
- self.exports = {}
- self.handlers = {}
- self.parent = parent
- self.client_socket = client_socket
+ def connect_dict(self, uzbl, connects):
+ for (event, callback) in connects.items():
+ self.connect(uzbl, event, callback)
- self.depth = 0
- self.buffer = ''
- self.pid = None
- # Call the init function in every plugin. The init function in each
- # plugin is where that plugin connects functions to events and exports
- # functions to the uzbl object.
- for plugin in self.parent['plugins'].values():
- try:
- plugin.init(self)
+ def require(self, plugin):
+ '''Check that plugin with name `plugin` has been loaded. Use this to
+ ensure that your plugins dependencies have been met.'''
- except:
- raise
+ assert plugin in self.parent.plugins, self.logger.critical(
+ 'plugin %r required by plugin %r' (plugin, self.name))
- def send(self, msg):
- '''Send a command to the uzbl instance via the socket file.'''
+class Uzbl(object):
+ def __init__(self, parent, child_socket):
+ self.opts = opts
+ self.parent = parent
+ self.child_socket = child_socket
+ self.time = time.time()
+ self.pid = None
+ self.name = None
- msg = msg.strip()
- if self.client_socket:
- print (u'%s<-- %s' % (' ' * self.depth, msg)).encode('utf-8')
- self.client_socket.send(("%s\n" % msg).encode('utf-8'))
-
- else:
- print (u'%s!-- %s' % (' ' * self.depth, msg)).encode('utf-8')
-
-
- def export(self, attr, object, prepend=True):
- '''Attach an object to the current class instance. This is the
- preferred method of sharing functionality, functions and objects
- between plugins.
-
- If the object is callable you may wish to turn the callable object in
- to an "instance method call" by using the `functools.partial(..)`
- tool to prepend the `self` object to the callable objects argument
- list.
-
- Example session from a plugins POV:
- >>> config_dict = {'foo': 'data..', 'bar': 'other data..'}
- >>> uzbl.export('config', config_dict)
- >>> uzbl.config is config_dict
- True
- >>> print uzbl.config['foo']
- data..
- >>> uzbl.export('get', lambda uzbl, key: uzbl.config[key])
- >>> print uzbl.get('bar')
- other data..
- '''
+ # Flag if the instance has raised the INSTANCE_START event.
+ self.instance_start = False
- if prepend and callable(object):
- object = partial(object, self)
+ # Use name "unknown" until name is discovered.
+ self.logger = get_logger('uzbl-instance[]')
- self.__dict__.__setitem__(attr, object)
+ # Track plugin event handlers and exported functions.
+ self.exports = {}
+ self.handlers = defaultdict(list)
+ # Internal vars
+ self._depth = 0
+ self._buffer = ''
- def export_dict(self, export_dict):
- '''Export multiple (attr, object)'s at once inside a dict of the
- form `{attr1: object1, attr2: object2, ...}`.'''
- for (attr, object) in export_dict.items():
- self.export(attr, object)
+ def __repr__(self):
+ return '<uzbl(%s)>' % ', '.join([
+ 'pid=%s' % (self.pid if self.pid else "Unknown"),
+ 'name=%s' % ('%r' % self.name if self.name else "Unknown"),
+ 'uptime=%f' % (time.time()-self.time),
+ '%d exports' % len(self.exports.keys()),
+ '%d handlers' % sum([len(l) for l in self.handlers.values()])])
- def connect(self, event, handler, *args, **kargs):
- '''Connect a uzbl event with a handler. Handlers can either be a
- function or a uzbl command string.'''
+ def init_plugins(self):
+ '''Call the init and after hooks in all loaded plugins for this
+ instance.'''
- event = event.upper().strip()
- assert event and ' ' not in event
+ # Initialise each plugin with the current uzbl instance.
+ for plugin in self.parent.plugins.values():
+ self.logger.debug('calling %r plugin init hook' % plugin.name)
+ plugin.init(self)
- if event not in self.handlers.keys():
- self.handlers[event] = []
+ # Allow plugins to use exported features of other plugins by calling an
+ # optional `after` function in the plugins namespace.
+ for plugin in self.parent.plugins.values():
+ if plugin.after:
+ self.logger.debug('calling %r plugin after hook'%plugin.name)
+ plugin.after(self)
- handlerobj = EventHandler(event, handler, *args, **kargs)
- self.handlers[event].append(handlerobj)
- print handlerobj
+ def send(self, msg):
+ '''Send a command to the uzbl instance via the child socket
+ instance.'''
- def connect_dict(self, connect_dict):
- '''Connect a dictionary comprising of {"EVENT_NAME": handler, ..} to
- the event handler stack.
+ msg = msg.strip()
+ assert self.child_socket, "socket inactive"
- If you need to supply args or kargs to an event use the normal connect
- function.'''
+ if opts.print_events:
+ print ascii(u'%s<-- %s' % (' ' * self._depth, msg))
- for (event, handler) in connect_dict.items():
- self.connect(event, handler)
+ self.child_socket.send(ascii("%s\n" % msg))
- def remove_by_id(self, hid):
- '''Remove connected event handler by unique handler id.'''
+ def read(self):
+ '''Read data from the child socket and pass lines to the parse_msg
+ function.'''
- for (event, handlers) in self.handlers.items():
- for handler in list(handlers):
- if hid != handler.hid:
- continue
+ try:
+ raw = unicode(self.child_socket.recv(8192), 'utf-8', 'ignore')
+ if not raw:
+ self.logger.debug('read null byte')
+ return self.close()
- echo("removed %r" % handler)
- handlers.remove(handler)
- return
+ except:
+ self.logger.error(get_exc())
+ return self.close()
- echo('unable to find & remove handler with id: %d' % hid)
+ lines = (self._buffer + raw).split('\n')
+ self._buffer = lines.pop()
+ for line in filter(None, map(unicode.strip, lines)):
+ try:
+ self.parse_msg(line.strip())
+
+ except:
+ self.logger.error(get_exc())
+ self.logger.error('erroneous event: %r' % line)
- def remove(self, handler):
- '''Remove connected event handler.'''
- for (event, handlers) in self.handlers.items():
- if handler in handlers:
- echo("removed %r" % handler)
- handlers.remove(handler)
- return
+ def parse_msg(self, line):
+ '''Parse an incoming message from a uzbl instance. Event strings
+ will be parsed into `self.event(event, args)`.'''
- echo('unable to find & remove handler: %r' % handler)
+ # Split by spaces (and fill missing with nulls)
+ elems = (line.split(' ', 3) + ['',]*3)[:4]
+ # Ignore non-event messages.
+ if elems[0] != 'EVENT':
+ logger.info('non-event message: %r' % line)
+ if opts.print_events:
+ print '--- %s' % ascii(line)
+ return
- def exec_handler(self, handler, *args, **kargs):
- '''Execute event handler function.'''
+ # Check event string elements
+ (name, event, args) = elems[1:]
+ assert name and event, 'event string missing elements'
+ if not self.name:
+ self.name = name
+ self.logger = get_logger('uzbl-instance%s' % name)
+ self.logger.info('found instance name %r' % name)
- args += handler.args
- kargs = dict(handler.kargs.items()+kargs.items())
- handler.function(self, *args, **kargs)
+ assert self.name == name, 'instance name mismatch'
+
+ # Handle the event with the event handlers through the event method
+ self.event(event, args)
def event(self, event, *args, **kargs):
'''Raise an event.'''
event = event.upper()
- elems = [event,]
- if args: elems.append(unicode(args))
- if kargs: elems.append(unicode(kargs))
- print (u'%s--> %s' % (' ' * self.depth, ' '.join(elems))).encode('utf-8')
+
+ if not opts.daemon_mode and opts.print_events:
+ elems = [event,]
+ if args: elems.append(unicode(args))
+ if kargs: elems.append(unicode(kargs))
+ print ascii(u'%s--> %s' % (' ' * self._depth, ' '.join(elems)))
if event == "INSTANCE_START" and args:
+ assert not self.instance_start, 'instance already started'
+
self.pid = int(args[0])
+ self.logger.info('found instance pid %r' % self.pid)
+
+ self.init_plugins()
+
+ elif event == "INSTANCE_EXIT":
+ self.logger.info('uzbl instance exit')
+ self.close()
if event not in self.handlers:
return
for handler in self.handlers[event]:
- self.depth += 1
+ self._depth += 1
try:
- self.exec_handler(handler, *args, **kargs)
+ handler.call(self, *args, **kargs)
except:
- print_exc()
+ self.logger.error(get_exc())
+
+ self._depth -= 1
+
- self.depth -= 1
+ def close_connection(self, child_socket):
+ '''Close child socket and delete the uzbl instance created for that
+ child socket connection.'''
def close(self):
- '''Close the client socket and clean up.'''
+ '''Close the client socket and call the plugin cleanup hooks.'''
+
+ self.logger.debug('called close method')
+
+ # Remove self from parent uzbls dict.
+ if self.child_socket in self.parent.uzbls:
+ self.logger.debug('removing self from uzbls list')
+ del self.parent.uzbls[self.child_socket]
try:
- self.client_socket.close()
+ if self.child_socket:
+ self.logger.debug('closing child socket')
+ self.child_socket.close()
except:
- pass
+ self.logger.error(get_exc())
+
+ finally:
+ self.child_socket = None
- for (name, plugin) in self.parent['plugins'].items():
- if hasattr(plugin, 'cleanup'):
+ # Call plugins cleanup hooks.
+ for plugin in self.parent.plugins.values():
+ if plugin.cleanup:
+ self.logger.debug('calling %r plugin cleanup hook'
+ % plugin.name)
plugin.cleanup(self)
+ logger.info('removed %r' % self)
-class UzblEventDaemon(dict):
- def __init__(self):
- # Init variables and dict keys.
- dict.__init__(self, {'uzbls': {}})
- self.running = None
+class UzblEventDaemon(object):
+ def __init__(self):
+ self.opts = opts
self.server_socket = None
- self.socket_location = None
+ self._quit = False
+
+ # Hold uzbl instances
+ # {child socket: Uzbl instance, ..}
+ self.uzbls = {}
+
+ # Hold plugins
+ # {plugin name: Plugin instance, ..}
+ self.plugins = {}
# Register that the event daemon server has started by creating the
# pid file.
- make_pid_file(CONFIG['pid_file'])
+ make_pid_file(opts.pid_file)
# Register a function to clean up the socket and pid file on exit.
atexit.register(self.quit)
- # Make SIGTERM act orderly.
- signal(SIGTERM, lambda signum, stack_frame: sys.exit(1))
+ # Add signal handlers.
+ for sigint in [SIGTERM, SIGINT]:
+ signal(sigint, self.quit)
- # Load plugins, first-build of the plugins may be a costly operation.
- self['plugins'] = load_plugins(CONFIG['plugin_dirs'],
- CONFIG['plugins_load'], CONFIG['plugins_ignore'])
+ # Load plugins into self.plugins
+ self.load_plugins(opts.plugins)
- def _create_server_socket(self):
- '''Create the event manager daemon socket for uzbl instance duplex
- communication.'''
+ def load_plugins(self, plugins):
+ '''Load event manager plugins.'''
- server_socket = CONFIG['server_socket']
- server_socket = os.path.realpath(os.path.expandvars(server_socket))
- self.socket_location = server_socket
+ for path in plugins:
+ logger.debug('loading plugin %r' % path)
+ (dir, file) = os.path.split(path)
+ name = file[:-3] if file.lower().endswith('.py') else file
- # Delete socket if it exists.
- if os.path.exists(server_socket):
- os.remove(server_socket)
+ info = imp.find_module(name, [dir,])
+ module = imp.load_module(name, *info)
+ assert callable(getattr(module, 'init', None)),\
+ "plugin missing init function: %r" % module
- self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- self.server_socket.bind(server_socket)
- self.server_socket.listen(5)
+ logger.debug('creating plugin instance for %r plugin' % name)
+ plugin = Plugin(self, name, path, module)
+ self.plugins[name] = plugin
+ logger.info('new %r' % plugin)
- def _close_server_socket(self):
- '''Close and delete the server socket.'''
+ def create_server_socket(self):
+ '''Create the event manager daemon socket for uzbl instance duplex
+ communication.'''
- try:
- self.server_socket.close()
- self.server_socket = None
+ # Close old socket.
+ self.close_server_socket()
- if os.path.exists(self.socket_location):
- os.remove(self.socket_location)
+ sock = socket(AF_UNIX, SOCK_STREAM)
+ sock.bind(opts.server_socket)
+ sock.listen(5)
- except:
- pass
+ self.server_socket = sock
+ logger.debug('bound server socket to %r' % opts.server_socket)
def run(self):
'''Main event daemon loop.'''
- # Create event daemon socket.
- self._create_server_socket()
- echo('listening on: %s' % self.socket_location)
+ logger.debug('entering main loop')
+
+ # Create and listen on the server socket
+ self.create_server_socket()
- if CONFIG['daemon_mode']:
- echo('entering daemon mode.')
+ if opts.daemon_mode:
+ # Daemonize the process
daemonize()
- # The pid has changed so update the pid file.
- make_pid_file(CONFIG['pid_file'])
- # Now listen for incoming connections and or data.
- self.listen()
+ # Update the pid file
+ make_pid_file(opts.pid_file)
+
+ try:
+ # Accept incoming connections and listen for incoming data
+ self.listen()
+
+ except:
+ if not self._quit:
+ logger.critical(get_exc())
- # Clean up.
+ # Clean up and exit
self.quit()
+ logger.debug('exiting main loop')
+
def listen(self):
'''Accept incoming connections and constantly poll instance sockets
for incoming data.'''
- self.running = True
- while self.running:
+ logger.info('listening on %r' % opts.server_socket)
- sockets = [self.server_socket] + self['uzbls'].keys()
+ # Count accepted connections
+ connections = 0
- reads, _, errors = select(sockets, [], sockets, 1)
+ while (self.uzbls or not connections) or (not opts.auto_close):
+ socks = [self.server_socket] + self.uzbls.keys()
+ reads, _, errors = select(socks, [], socks, 1)
if self.server_socket in reads:
- self.accept_connection()
reads.remove(self.server_socket)
- for client in reads:
- self.read_socket(client)
+ # Accept connection and create uzbl instance.
+ child_socket = self.server_socket.accept()[0]
+ self.uzbls[child_socket] = Uzbl(self, child_socket)
+ connections += 1
+
+ for uzbl in [self.uzbls[s] for s in reads]:
+ uzbl.read()
+
+ for uzbl in [self.uzbls[s] for s in errors]:
+ uzbl.logger.error('socket read error')
+ uzbl.close()
- for client in errors:
- error('Unknown error on socket: %r' % client)
- self.close_connection(client)
+ logger.info('auto closing')
- def read_socket(self, client):
- '''Read data from an instance socket and pass to the uzbl objects
- event handler function.'''
+ def close_server_socket(self):
+ '''Close and delete the server socket.'''
- uzbl = self['uzbls'][client]
try:
- raw = unicode(client.recv(8192), 'utf-8', 'ignore')
+ if self.server_socket:
+ logger.debug('closing server socket')
+ self.server_socket.close()
+ self.server_socket = None
+
+ if os.path.exists(opts.server_socket):
+ logger.info('unlinking %r' % opts.server_socket)
+ os.unlink(opts.server_socket)
except:
- print_exc()
- raw = None
+ logger.error(get_exc())
- if not raw:
- # Read null byte, close socket.
- return self.close_connection(client)
- uzbl.buffer += raw
- msgs = uzbl.buffer.split('\n')
- uzbl.buffer = msgs.pop()
+ def quit(self, sigint=None, *args):
+ '''Close all instance socket objects, server socket and delete the
+ pid file.'''
- for msg in msgs:
- try:
- parse_msg(uzbl, msg.strip())
+ if sigint == SIGTERM:
+ logger.critical('caught SIGTERM, exiting')
- except:
- print_exc()
+ elif sigint == SIGINT:
+ logger.critical('caught SIGINT, exiting')
+ elif not self._quit:
+ logger.debug('shutting down event manager')
- def accept_connection(self):
- '''Accept incoming connection to the server socket.'''
+ self.close_server_socket()
- client_socket = self.server_socket.accept()[0]
+ for uzbl in self.uzbls.values():
+ uzbl.close()
- uzbl = UzblInstance(self, client_socket)
- self['uzbls'][client_socket] = uzbl
+ del_pid_file(opts.pid_file)
+ if not self._quit:
+ logger.info('event manager shut down')
+ self._quit = True
- def close_connection(self, client):
- '''Clean up after instance close.'''
+def make_pid_file(pid_file):
+ '''Creates a pid file at `pid_file`, fails silently.'''
+
+ try:
+ logger.debug('creating pid file %r' % pid_file)
+ make_dirs(pid_file)
+ pid = os.getpid()
+ fileobj = open(pid_file, 'w')
+ fileobj.write('%d' % pid)
+ fileobj.close()
+ logger.info('created pid file %r with pid %d' % (pid_file, pid))
+
+ except:
+ logger.error(get_exc())
+
+
+def del_pid_file(pid_file):
+ '''Deletes a pid file at `pid_file`, fails silently.'''
+
+ if os.path.isfile(pid_file):
try:
- if client in self['uzbls']:
- uzbl = self['uzbls'][client]
- uzbl.close()
- del self['uzbls'][client]
+ logger.debug('deleting pid file %r' % pid_file)
+ os.remove(pid_file)
+ logger.info('deleted pid file %r' % pid_file)
except:
- print_exc()
+ logger.error(get_exc())
- if not len(self['uzbls']) and CONFIG['auto_close']:
- echo('auto closing event manager.')
- self.running = False
+def get_pid(pid_file):
+ '''Reads a pid from pid file `pid_file`, fails None.'''
- def quit(self):
- '''Close all instance socket objects, server socket and delete the
- pid file.'''
+ try:
+ logger.debug('reading pid file %r' % pid_file)
+ fileobj = open(pid_file, 'r')
+ pid = int(fileobj.read())
+ fileobj.close()
+ logger.info('read pid %d from pid file %r' % (pid, pid_file))
+ return pid
- echo('shutting down event manager.')
+ except (IOError, ValueError):
+ logger.error(get_exc())
+ return None
- for client in self['uzbls'].keys():
- self.close_connection(client)
- echo('unlinking: %r' % self.socket_location)
- self._close_server_socket()
+def pid_running(pid):
+ '''Checks if a process with a pid `pid` is running.'''
- echo('deleting pid file: %r' % CONFIG['pid_file'])
- del_pid_file(CONFIG['pid_file'])
+ try:
+ os.kill(pid, 0)
+ except OSError:
+ return False
+ else:
+ return True
+
+
+def term_process(pid):
+ '''Asks nicely then forces process with pid `pid` to exit.'''
+
+ try:
+ logger.info('sending SIGTERM to process with pid %r' % pid)
+ os.kill(pid, SIGTERM)
+
+ except OSError:
+ logger.error(get_exc())
+
+ logger.debug('waiting for process with pid %r to exit' % pid)
+ start = time.time()
+ while True:
+ if not pid_running(pid):
+ logger.debug('process with pid %d exit' % pid)
+ return True
+
+ if (time.time()-start) > 5:
+ logger.warning('process with pid %d failed to exit' % pid)
+ logger.info('sending SIGKILL to process with pid %d' % pid)
+ try:
+ os.kill(pid, SIGKILL)
+ except:
+ logger.critical(get_exc())
+ raise
+
+ if (time.time()-start) > 10:
+ logger.critical('unable to kill process with pid %d' % pid)
+ raise OSError
+
+ time.sleep(0.25)
def stop_action():
'''Stop the event manager daemon.'''
- pid_file = CONFIG['pid_file']
+ pid_file = opts.pid_file
if not os.path.isfile(pid_file):
- return echo('no running daemon found.')
+ logger.error('could not find running event manager with pid file %r'
+ % opts.pid_file)
+ return
- echo('found pid file: %r' % pid_file)
pid = get_pid(pid_file)
if not pid_running(pid):
- echo('no process with pid: %d' % pid)
- return os.remove(pid_file)
+ logger.debug('no process with pid %r' % pid)
+ del_pid_file(pid_file)
+ return
- echo("terminating process with pid: %d" % pid)
+ logger.debug('terminating process with pid %r' % pid)
term_process(pid)
- if os.path.isfile(pid_file):
- os.remove(pid_file)
-
- echo('stopped event daemon.')
+ del_pid_file(pid_file)
+ logger.info('stopped event manager process with pid %d' % pid)
def start_action():
'''Start the event manager daemon.'''
- pid_file = CONFIG['pid_file']
+ pid_file = opts.pid_file
if os.path.isfile(pid_file):
- echo('found pid file: %r' % pid_file)
pid = get_pid(pid_file)
if pid_running(pid):
- return echo('event daemon already started with pid: %d' % pid)
+ logger.error('event manager already started with pid %d' % pid)
+ return
- echo('no process with pid: %d' % pid)
- os.remove(pid_file)
+ logger.info('no process with pid %d' % pid)
+ del_pid_file(pid_file)
- echo('starting event manager.')
UzblEventDaemon().run()
def restart_action():
'''Restart the event manager daemon.'''
- echo('restarting event manager daemon.')
stop_action()
start_action()
def list_action():
- '''List all the plugins being loaded by the event daemon.'''
+ '''List all the plugins that would be loaded in the current search
+ dirs.'''
- plugins = find_plugins(CONFIG['plugin_dirs'])
- dirs = {}
+ names = {}
+ for plugin in opts.plugins:
+ (head, tail) = os.path.split(plugin)
+ if tail not in names:
+ names[tail] = plugin
- for (plugin, plugin_dir) in plugins.items():
- if plugin_dir not in dirs:
- dirs[plugin_dir] = []
+ for plugin in sorted(names.values()):
+ print plugin
- dirs[plugin_dir].append(plugin)
- for (index, (plugin_dir, plugin_list)) in enumerate(sorted(dirs.items())):
- if index:
- print
+if __name__ == "__main__":
+ parser = OptionParser('usage: %prog [options] {start|stop|restart|list}')
+ add = parser.add_option
- print "%s:" % plugin_dir
- for plugin in sorted(plugin_list):
- print " %s" % plugin
+ add('-v', '--verbose',
+ dest='verbose', default=2, action='count',
+ help='increase verbosity')
+ add('-d', '--plugin-dir',
+ dest='plugin_dirs', action='append', metavar="DIR", default=[],
+ help='add extra plugin search dir, same as `-l "DIR/*.py"`')
-if __name__ == "__main__":
- USAGE = "usage: %prog [options] {start|stop|restart|list}"
- PARSER = OptionParser(usage=USAGE)
- PARSER.add_option('-v', '--verbose', dest='verbose', action="store_true",
- help="print verbose output.")
+ add('-l', '--load-plugin',
+ dest='load_plugins', action='append', metavar="PLUGIN", default=[],
+ help='load plugin, loads before plugins in search dirs')
- PARSER.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store",
- metavar="DIRS", help="Specify plugin directories in the form of "\
- "'dir1:dir2:dir3'.")
+ socket_location = os.path.join(CACHE_DIR, 'event_daemon')
+ add('-s', '--server-socket',
+ dest='server_socket', metavar="SOCKET", default=socket_location,
+ help='server AF_UNIX socket location')
- PARSER.add_option('-l', '--load-plugins', dest="load", action="store",
- metavar="PLUGINS", help="comma separated list of plugins to load")
+ add('-p', '--pid-file',
+ metavar="FILE", dest='pid_file',
+ help='pid file location, defaults to server socket + .pid')
- PARSER.add_option('-i', '--ignore-plugins', dest="ignore", action="store",
- metavar="PLUGINS", help="comma separated list of plugins to ignore")
+ add('-n', '--no-daemon',
+ dest='daemon_mode', action='store_false', default=True,
+ help='daemonize the process')
- PARSER.add_option('-p', '--pid-file', dest='pid', action='store',
- metavar='FILE', help="specify pid file location")
+ add('-a', '--auto-close',
+ dest='auto_close', action='store_true', default=False,
+ help='auto close after all instances disconnect')
- PARSER.add_option('-s', '--server-socket', dest='socket', action='store',
- metavar='SOCKET', help="specify the daemon socket location")
+ add('-i', '--no-default-dirs',
+ dest='default_dirs', action='store_false', default=True,
+ help='ignore the default plugin search dirs')
- PARSER.add_option('-n', '--no-daemon', dest="daemon",
- action="store_true", help="don't enter daemon mode.")
+ add('-o', '--log-file',
+ dest='log_file', metavar='FILE',
+ help='write logging output to a file, defaults to server socket +'
+ ' .log')
- PARSER.add_option('-a', '--auto-close', dest='autoclose',
- action='store_true', help='auto close after all instances disconnect.')
+ add('-q', '--quiet-events',
+ dest='print_events', action="store_false", default=True,
+ help="silence the printing of events to stdout")
- (OPTIONS, ARGS) = PARSER.parse_args()
+ (opts, args) = parser.parse_args()
- # init like {start|stop|..} daemon actions dict.
- DAEMON_ACTIONS = {'start': start_action, 'stop': stop_action,
- 'restart': restart_action, 'list': list_action}
+ opts.server_socket = expandpath(opts.server_socket)
+
+ # Set default pid file location
+ if not opts.pid_file:
+ opts.pid_file = "%s.pid" % opts.server_socket
- if not ARGS:
- ACTION = 'start'
+ else:
+ opts.pid_file = expandpath(opts.pid_file)
- elif len(ARGS) == 1:
- ACTION = ARGS[0]
- if ACTION not in DAEMON_ACTIONS:
- raise ArgumentError("unknown argument: %r" % ACTION)
+ # Set default log file location
+ if not opts.log_file:
+ opts.log_file = "%s.log" % opts.server_socket
else:
- raise ArgumentError("too many arguments: %r" % ARGS)
+ opts.log_file = expandpath(opts.log_file)
+
+ # Logging setup
+ log_level = logging.CRITICAL - opts.verbose*10
+
+ # Console logging handler
+ ch = logging.StreamHandler()
+ ch.setLevel(max(log_level+10, 10))
+ ch.setFormatter(logging.Formatter(
+ '%(name)s: %(levelname)s: %(message)s'))
+
+ # File logging handler
+ fh = logging.FileHandler(opts.log_file, 'w', 'utf-8', 1)
+ fh.setLevel(max(log_level, 10))
+ fh.setFormatter(logging.Formatter(
+ '[%(created)f] %(name)s: %(levelname)s: %(message)s'))
+
+ # logging.getLogger wrapper which sets the levels and adds the
+ # file and console handlers automagically
+ def get_logger(name):
+ handlers = [ch, fh]
+ level = [max(log_level, 10),]
+ logger = logging.getLogger(name)
+ logger.setLevel(level[0])
+ for handler in handlers:
+ logger.addHandler(handler)
+
+ return logger
+
+ # Get main logger
+ logger = get_logger(SCRIPTNAME)
+ logger.info('logging to %r' % opts.log_file)
- # parse other flags & options.
- if OPTIONS.verbose:
- CONFIG['verbose'] = True
+ plugins = {}
- if OPTIONS.plugin_dirs:
- PLUGIN_DIRS = []
- for DIR in OPTIONS.plugin_dirs.split(':'):
- if not DIR:
- continue
+ # Load all `opts.load_plugins` into the plugins list
+ for path in opts.load_plugins:
+ path = expandpath(path)
+ matches = glob(path)
+ if not matches:
+ parser.error('cannot find plugin(s): %r' % path)
- PLUGIN_DIRS.append(os.path.realpath(DIR))
+ for plugin in matches:
+ (head, tail) = os.path.split(plugin)
+ if tail not in plugins:
+ logger.debug('found plugin: %r' % plugin)
+ plugins[tail] = plugin
- CONFIG['plugin_dirs'] = PLUGIN_DIRS
- echo("plugin search dirs: %r" % PLUGIN_DIRS)
+ else:
+ logger.debug('ignoring plugin: %r' % plugin)
- if OPTIONS.load and OPTIONS.ignore:
- error("you can't load and ignore at the same time.")
- sys.exit(1)
+ # Add default plugin locations
+ if opts.default_dirs:
+ logger.debug('adding default plugin dirs to plugin dirs list')
+ opts.plugin_dirs += [os.path.join(DATA_DIR, 'plugins/'),
+ os.path.join(PREFIX, 'share/uzbl/examples/data/plugins/')]
+
+ else:
+ logger.debug('ignoring default plugin dirs')
- elif OPTIONS.load:
- LOAD = CONFIG['plugins_load']
- for PLUGIN in OPTIONS.load.split(','):
- if PLUGIN.strip():
- LOAD.append(PLUGIN.strip())
+ # Load all plugins in `opts.plugin_dirs` into the plugins list
+ for dir in opts.plugin_dirs:
+ dir = expandpath(dir)
+ logger.debug('searching plugin dir: %r' % dir)
+ for plugin in glob(os.path.join(dir, '*.py')):
+ (head, tail) = os.path.split(plugin)
+ if tail not in plugins:
+ logger.debug('found plugin: %r' % plugin)
+ plugins[tail] = plugin
- echo('only loading plugin(s): %s' % ', '.join(LOAD))
+ else:
+ logger.debug('ignoring plugin: %r' % plugin)
- elif OPTIONS.ignore:
- IGNORE = CONFIG['plugins_ignore']
- for PLUGIN in OPTIONS.ignore.split(','):
- if PLUGIN.strip():
- IGNORE.append(PLUGIN.strip())
+ plugins = plugins.values()
- echo('ignoring plugin(s): %s' % ', '.join(IGNORE))
+ # Check all the paths in the plugins list are files
+ for plugin in plugins:
+ if not os.path.isfile(plugin):
+ parser.error('plugin not a file: %r' % plugin)
- if OPTIONS.autoclose:
- CONFIG['auto_close'] = True
- echo('will auto close.')
+ if opts.auto_close: logger.debug('will auto close')
+ else: logger.debug('will not auto close')
- if OPTIONS.pid:
- CONFIG['pid_file'] = os.path.realpath(OPTIONS.pid)
- echo("pid file location: %r" % CONFIG['pid_file'])
+ if opts.daemon_mode: logger.debug('will daemonize')
+ else: logger.debug('will not daemonize')
- if OPTIONS.socket:
- CONFIG['server_socket'] = os.path.realpath(OPTIONS.socket)
- echo("daemon socket location: %s" % CONFIG['server_socket'])
+ opts.plugins = plugins
+
+ # init like {start|stop|..} daemon actions
+ daemon_actions = {'start': start_action, 'stop': stop_action,
+ 'restart': restart_action, 'list': list_action}
+
+ if len(args) == 1:
+ action = args[0]
+ if action not in daemon_actions:
+ parser.error('invalid action: %r' % action)
+
+ elif not args:
+ logger.warning('no daemon action given, assuming %r' % 'start')
+ action = 'start'
+
+ else:
+ parser.error('invalid action argument: %r' % args)
- if OPTIONS.daemon:
- CONFIG['daemon_mode'] = False
+ logger.info('daemon action %r' % action)
+ # Do action
+ daemon_actions[action]()
- # Now {start|stop|...}
- DAEMON_ACTIONS[ACTION]()
+ logger.debug('process CPU time: %f' % time.clock())