diff options
author | Dieter Plaetinck <dieter@plaetinck.be> | 2009-12-06 18:00:26 +0100 |
---|---|---|
committer | Dieter Plaetinck <dieter@plaetinck.be> | 2009-12-06 18:00:26 +0100 |
commit | f2341aee3860107450b453486336133dafbcdd8b (patch) | |
tree | cbd39d0aee8cd5ec5cf87f065de7b10cace16e28 | |
parent | 0f13fe9e6b71347f67f8374ce0aa7164648e8e51 (diff) | |
parent | 3d2043d8f46855e8c3724e65bd5848c06ed27069 (diff) |
Merge remote branch 'mason/master'
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | examples/config/uzbl/config | 14 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/bind.py | 143 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/completion.py | 2 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/config.py | 41 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/keycmd.py | 68 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/mode.py | 17 | ||||
-rwxr-xr-x | examples/data/uzbl/scripts/uzbl-event-manager | 340 | ||||
-rw-r--r-- | uzbl-core.c | 38 |
10 files changed, 358 insertions, 317 deletions
@@ -1,7 +1,7 @@ # first entries are for gnu make, 2nd for BSD make. see http://lists.uzbl.org/pipermail/uzbl-dev-uzbl.org/2009-July/000177.html -CFLAGS:=-std=c99 $(shell pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0) -ggdb -Wall -W -DARCH="\"$(shell uname -m)\"" -lgthread-2.0 -DCOMMIT="\"$(shell git log | head -n1 | sed "s/.* //")\"" $(CPPFLAGS) -fPIC -W -Wall -Wextra -pedantic -ggdb3 -CFLAGS!=echo -std=c99 `pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0` -ggdb -Wall -W -DARCH='"\""'`uname -m`'"\""' -lgthread-2.0 -DCOMMIT='"\""'`git log | head -n1 | sed "s/.* //"`'"\""' $(CPPFLAGS) -fPIC -W -Wall -Wextra -pedantic -ggdb3 +CFLAGS:=-std=c99 $(shell pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0) -ggdb -Wall -W -DARCH="\"$(shell uname -m)\"" -lgthread-2.0 -DCOMMIT="\"$(shell git log | head -n1 | sed "s/.* //")\"" $(CPPFLAGS) -fPIC -W -Wall -Wextra -pedantic +CFLAGS!=echo -std=c99 `pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0` -ggdb -Wall -W -DARCH='"\""'`uname -m`'"\""' -lgthread-2.0 -DCOMMIT='"\""'`git log | head -n1 | sed "s/.* //"`'"\""' $(CPPFLAGS) -fPIC -W -Wall -Wextra -pedantic LDFLAGS:=$(shell pkg-config --libs gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0) -pthread $(LDFLAGS) LDFLAGS!=echo `pkg-config --libs gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0` -pthread $(LDFLAGS) @@ -63,8 +63,8 @@ test-dev: uzbl-core XDG_DATA_HOME=./examples/data XDG_CONFIG_HOME=./examples/config ./uzbl-core --uri http://www.uzbl.org --verbose test-dev-browser: uzbl-browser - XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/uzbl-cookie-daemon start -nv & - XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/uzbl-event-manager start -nv & + XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/uzbl-cookie-daemon restart -nv & + XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/uzbl-event-manager restart -nav & XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:`pwd`/examples/data/uzbl/scripts/:$$PATH" ./uzbl-browser --uri http://www.uzbl.org --verbose XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/uzbl-cookie-daemon stop -v XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/uzbl-event-manager stop -v @@ -306,6 +306,10 @@ the java script in @< >@. print The currently viewed document contains @<document.links.length>@ links +The @<>@ substitution can also load JavaScript from a file, syntax: @<+filename>@ + + print JS return value from file: @<+/path/to/file.js>@ + Variable expansion also works within a java script substitution. diff --git a/examples/config/uzbl/config b/examples/config/uzbl/config index 9fda268..69a211c 100644 --- a/examples/config/uzbl/config +++ b/examples/config/uzbl/config @@ -10,7 +10,7 @@ set bind = request BIND # request MODE_CONFIG <mode> <key> = <value set mode_config = request MODE_CONFIG # request TOGGLE_MODES <mode1> <mode2> ... <moden> -set toggle_modes = request TOGGLE_MODES +set toggle_modes = event TOGGLE_MODES # request ON_EVENT <EVENT_NAME> <command> set on_event = request ON_EVENT # request PROGRESS_CONFIG <key> = <value> @@ -29,6 +29,8 @@ set shell_cmd = sh -c # Spawn path shortcuts. In spawn the first dir+path match is used in "dir1:dir2:dir3:executable" set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data/uzbl:scripts +# Javascipt helpers. +set jsh = js var run=Uzbl.run; function get(k){return run("print \\\@"+k)}; function set(k, v) {run("set "+k+" = "+v)}; # === Handlers =============================================================== @@ -43,9 +45,11 @@ set new_window = sh 'uzbl-browser -u $8' # equivalent to the default beh # Load start handlers @on_event LOAD_START @set_status <span foreground="khaki">wait</span> -# Load commit handler +# Load commit handlers @on_event LOAD_COMMIT @set_status <span foreground="green">recv</span> @on_event LOAD_COMMIT script @scripts_dir/scroll-percentage.js +# Reset the keycmd on navigation +@on_event LOAD_COMMIT @set_mode # Load finish handlers @on_event LOAD_FINISH @set_status <span foreground="gold">done</span> @@ -63,7 +67,6 @@ set new_window = sh 'uzbl-browser -u $8' # equivalent to the default beh # Misc on_event handlers #@on_event CONFIG_CHANGED print Config changed: %1 = %2 - # === Behaviour and appearance =============================================== set show_status = 1 @@ -137,10 +140,9 @@ set socket_dir = /tmp # otherwise open the selection in the current window set load_from_xclip = sh 'echo "uri $(xclip -o)" > $4' set open_new_window = sh 'uzbl-browser -u \@SELECTED_URI' -@bind <Button2> = js if("\@SELECTED_URI") { Uzbl.run("\@open_new_window"); } else { Uzbl.run("\\\@load_from_xclip"); } +@bind <Button2> = @jsh if(get("SELECTED_URI")) { run("\@open_new_window"); } else { run("\\\@load_from_xclip"); } # Edit HTML forms in external editor -# set external_editor = gvim #set external_editor = xterm -e vim @bind E = script @scripts_dir/extedit.js @@ -312,7 +314,7 @@ set default_mode = command # Changing mode method via set. @bind I = @set_mode insert -# Or toggle between modes by rasing request events. +# Or toggle between modes by rasing the toggle event. set toggle_cmd_ins = @toggle_modes command insert @bind i = @toggle_cmd_ins diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py index 3169b15..9702434 100644 --- a/examples/data/uzbl/plugins/bind.py +++ b/examples/data/uzbl/plugins/bind.py @@ -11,13 +11,15 @@ And it is also possible to execute a function on activation: import sys import re +import pprint # Export these functions to uzbl.<name> __export__ = ['bind', 'del_bind', 'del_bind_by_glob', 'get_binds'] # Hold the bind dicts for each uzbl instance. UZBLS = {} -DEFAULTS = {'binds': [], 'depth': 0, 'stack': [], 'args': [], 'last_mode': ''} +DEFAULTS = {'binds': [], 'depth': 0, 'stack': [], 'args': [], + 'last_mode': '', 'after': None} # Commonly used regular expressions. starts_with_mod = re.compile('^<([A-Z][A-Za-z0-9-_]*)>') @@ -27,6 +29,10 @@ find_prompts = re.compile('<([^:>]*):(\"[^\"]*\"|\'[^\']*\'|[^>]*)>').split ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) +class ArgumentError(Exception): + pass + + def ismodbind(glob): '''Return True if the glob specifies a modbind.''' @@ -74,21 +80,11 @@ def get_binds(uzbl): return get_bind_dict(uzbl)['binds'] -def get_stack_depth(uzbl): - '''Return the stack for the uzbl instance.''' - - return get_bind_dict(uzbl)['depth'] - - -def get_filtered_binds(uzbl): +def get_filtered_binds(uzbl, bd): '''Return the bind list for the uzbl instance or return the filtered bind list thats on the current stack.''' - bind_dict = get_bind_dict(uzbl) - if bind_dict['depth']: - return list(bind_dict['stack']) - - return list(bind_dict['binds']) + return bd['stack'] if bd['depth'] else bd['binds'] def del_bind(uzbl, bind): @@ -220,6 +216,20 @@ class Bind(object): return self._repr_cache +def expand(cmd, args): + '''Replaces "%s %1 %2 %3..." with "<all args> <arg 0> <arg 1>...".''' + + if '%s' in cmd: + cmd = cmd.replace('%s', ' '.join(map(unicode, args))) + + for (index, arg) in enumerate(args): + index += 1 + if '%%%d' % index in cmd: + cmd = cmd.replace('%%%d' % index, unicode(arg)) + + return cmd + + def exec_bind(uzbl, bind, *args, **kargs): '''Execute bind objects.''' @@ -236,14 +246,7 @@ def exec_bind(uzbl, bind, *args, **kargs): commands = [] for cmd in bind.commands: - if '%s' in cmd: - if len(args) > 1: - for arg in args: - cmd = cmd.replace('%s', arg, 1) - - elif len(args) == 1: - cmd = cmd.replace('%s', args[0]) - + cmd = expand(cmd, args) uzbl.send(cmd) @@ -283,43 +286,51 @@ def mode_changed(uzbl, mode): clear_stack(uzbl) -def clear_stack(uzbl): +def clear_stack(uzbl, bd=None): '''Clear everything related to stacked binds.''' - bind_dict = get_bind_dict(uzbl) - bind_dict['stack'] = [] - bind_dict['depth'] = 0 - bind_dict['args'] = [] - if bind_dict['last_mode']: - uzbl.set_mode(bind_dict['last_mode']) - bind_dict['last_mode'] = '' + if bd is None: + bd = get_bind_dict(uzbl) + + bd['stack'] = [] + bd['depth'] = 0 + bd['args'] = [] + bd['after'] = None + if bd['last_mode']: + mode, bd['last_mode'] = bd['last_mode'], '' + uzbl.set_mode(mode) - uzbl.set('keycmd_prompt', force=False) + uzbl.set('keycmd_prompt') -def stack_bind(uzbl, bind, args, depth): +def stack_bind(uzbl, bind, args, depth, bd): '''Increment the stack depth in the bind dict, generate filtered bind list for stack mode and set keycmd prompt.''' - bind_dict = get_bind_dict(uzbl) - if bind_dict['depth'] != depth: - if bind not in bind_dict['stack']: - bind_dict['stack'].append(bind) + if bd['depth'] != depth: + if bind not in bd['stack']: + bd['stack'].append(bind) return if uzbl.get_mode() != 'stack': - bind_dict['last_mode'] = uzbl.get_mode() + bd['last_mode'] = uzbl.get_mode() uzbl.set_mode('stack') - globalcmds = [cmd for cmd in bind_dict['binds'] if cmd.is_global] - bind_dict['stack'] = [bind,] + globalcmds - bind_dict['args'] += args - bind_dict['depth'] = depth + 1 + globalcmds = [cmd for cmd in bd['binds'] if cmd.is_global] + bd['stack'] = [bind,] + globalcmds + bd['args'] += args + bd['depth'] = depth + 1 + bd['after'] = bind.prompts[depth] + - uzbl.send('event BIND_STACK_LEVEL %d' % bind_dict['depth']) +def after_bind(uzbl, bd): + '''Check if there are afte-actions to perform.''' - (prompt, set) = bind.prompts[depth] + if bd['after'] is None: + return + + (prompt, set), bd['after'] = bd['after'], None if prompt: uzbl.set('keycmd_prompt', '%s:' % prompt) @@ -332,9 +343,11 @@ def stack_bind(uzbl, bind, args, depth): else: uzbl.clear_keycmd() + uzbl.send('event BIND_STACK_LEVEL %d' % bd['depth']) + + +def match_and_exec(uzbl, bind, depth, keylet, bd): -def match_and_exec(uzbl, bind, depth, keylet): - bind_dict = get_bind_dict(uzbl) (on_exec, has_args, mod_cmd, glob, more) = bind[depth] held = keylet.held @@ -363,62 +376,74 @@ def match_and_exec(uzbl, bind, depth, keylet): return True elif more: - stack_bind(uzbl, bind, args, depth) + stack_bind(uzbl, bind, args, depth, bd) return False - args = bind_dict['args'] + args + args = bd['args'] + args exec_bind(uzbl, bind, *args) uzbl.set_mode() if not has_args: - clear_stack(uzbl) + clear_stack(uzbl, bd) uzbl.clear_current() return True def keycmd_update(uzbl, keylet): - depth = get_stack_depth(uzbl) - for bind in get_filtered_binds(uzbl): + bd = get_bind_dict(uzbl) + depth = bd['depth'] + for bind in get_filtered_binds(uzbl, bd): t = bind[depth] if t[MOD_CMD] or t[ON_EXEC]: continue - if match_and_exec(uzbl, bind, depth, keylet): + if match_and_exec(uzbl, bind, depth, keylet, bd): return + after_bind(uzbl, bd) + def keycmd_exec(uzbl, keylet): - depth = get_stack_depth(uzbl) - for bind in get_filtered_binds(uzbl): + bd = get_bind_dict(uzbl) + depth = bd['depth'] + for bind in get_filtered_binds(uzbl, bd): t = bind[depth] if t[MOD_CMD] or not t[ON_EXEC]: continue - if match_and_exec(uzbl, bind, depth, keylet): + if match_and_exec(uzbl, bind, depth, keylet, bd): return uzbl.clear_keycmd() + after_bind(uzbl, bd) + def modcmd_update(uzbl, keylet): - depth = get_stack_depth(uzbl) - for bind in get_filtered_binds(uzbl): + bd = get_bind_dict(uzbl) + depth = bd['depth'] + for bind in get_filtered_binds(uzbl, bd): t = bind[depth] if not t[MOD_CMD] or t[ON_EXEC]: continue - if match_and_exec(uzbl, bind, depth, keylet): + if match_and_exec(uzbl, bind, depth, keylet, bd): return + after_bind(uzbl, bd) + def modcmd_exec(uzbl, keylet): - depth = get_stack_depth(uzbl) - for bind in get_filtered_binds(uzbl): + bd = get_bind_dict(uzbl) + depth = bd['depth'] + for bind in get_filtered_binds(uzbl, bd): t = bind[depth] if not t[MOD_CMD] or not t[ON_EXEC]: continue - if match_and_exec(uzbl, bind, depth, keylet): + if match_and_exec(uzbl, bind, depth, keylet, bd): return uzbl.clear_modcmd() + after_bind(uzbl, bd) + def init(uzbl): connects = {'BIND': parse_bind_event, diff --git a/examples/data/uzbl/plugins/completion.py b/examples/data/uzbl/plugins/completion.py index 42e7e17..770f310 100644 --- a/examples/data/uzbl/plugins/completion.py +++ b/examples/data/uzbl/plugins/completion.py @@ -194,5 +194,5 @@ def init(uzbl): # And connect the dicts event handlers to the handler stack. uzbl.connect_dict(connects) - for event in ['STOP_COMPLETION', 'KEYCMD_EXEC', 'KEYCMD_CLEAR']: + for event in ['STOP_COMPLETION', 'KEYCMD_EXEC', 'KEYCMD_CLEARED']: uzbl.connect(event, stop_completion) diff --git a/examples/data/uzbl/plugins/config.py b/examples/data/uzbl/plugins/config.py index 47b59f9..b43161b 100644 --- a/examples/data/uzbl/plugins/config.py +++ b/examples/data/uzbl/plugins/config.py @@ -3,8 +3,8 @@ import types __export__ = ['set', 'get_config'] -_VALIDSETKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match -_TYPECONVERT = {'int': int, 'float': float, 'str': unicode} +VALIDKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match +TYPECONVERT = {'int': int, 'float': float, 'str': unicode} UZBLS = {} @@ -15,14 +15,7 @@ def escape(value): return unicode(value) -def get_config(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def set(uzbl, key, value='', force=True): +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.''' @@ -32,7 +25,7 @@ def set(uzbl, key, value='', force=True): else: value = unicode(value) - if not _VALIDSETKEY(key): + if not VALIDKEY(key): raise KeyError("%r" % key) value = escape(value) @@ -40,13 +33,25 @@ def set(uzbl, key, value='', force=True): value = value.replace("\n", "\\n") if not force: - config = get_config(uzbl) + 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) @@ -63,22 +68,12 @@ def get_config(uzbl): return UZBLS[uzbl] -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, force=False) - - 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) + value = TYPECONVERT[type](value) dict.__setitem__(config, key, value) diff --git a/examples/data/uzbl/plugins/keycmd.py b/examples/data/uzbl/plugins/keycmd.py index 4c88fd8..af6beff 100644 --- a/examples/data/uzbl/plugins/keycmd.py +++ b/examples/data/uzbl/plugins/keycmd.py @@ -33,6 +33,7 @@ class Keylet(object): def __init__(self): # Modcmd tracking self.held = set() + self.ignored = set() self.modcmd = '' self.is_modcmd = False @@ -82,15 +83,18 @@ class Keylet(object): results in a modkey addition. Return that addition and remove all modkeys that created it.''' - already_added = self.held & set(self.additions.keys()) - for key in already_added: - if modkey in self.additions[key]: + # 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 - modkeys = set(list(self.held) + [modkey,]) + # 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 = modkeys ^ value + self.held -= value return key return modkey @@ -256,11 +260,8 @@ def clear_keycmd(uzbl): k.keycmd = '' k.cursor = 0 k._repr_cache = False - config = uzbl.get_config() - if 'keycmd' not in config or config['keycmd']: - uzbl.set('keycmd') - - uzbl.event('KEYCMD_CLEAR') + uzbl.set('keycmd') + uzbl.event('KEYCMD_CLEARED') def clear_modcmd(uzbl, clear_held=False): @@ -271,13 +272,11 @@ def clear_modcmd(uzbl, clear_held=False): k.is_modcmd = False k._repr_cache = False if clear_held: + k.ignored = set() k.held = set() - config = uzbl.get_config() - if 'modcmd' not in config or config['modcmd']: - uzbl.set('modcmd') - - uzbl.event('MODCMD_CLEAR') + uzbl.set('modcmd') + uzbl.event('MODCMD_CLEARED') def clear_current(uzbl): @@ -314,22 +313,25 @@ def update_event(uzbl, k, execute=True): if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': new_modcmd = k.get_modcmd() if not new_modcmd: - uzbl.set('modcmd') + uzbl.set('modcmd', config=config) elif new_modcmd == modcmd: - uzbl.set('modcmd', "<span> %s </span>" % uzbl_escape(new_modcmd)) + uzbl.set('modcmd', '<span> %s </span>' % uzbl_escape(new_modcmd), + config=config) if 'keycmd_events' in config and config['keycmd_events'] != '1': return - keycmd = k.get_keycmd() - if not keycmd: - return uzbl.set('keycmd') + new_keycmd = k.get_keycmd() + if not new_keycmd: + uzbl.set('keycmd', config=config) - # 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:]] - uzbl.set('keycmd', KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks))) + 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) def inject_str(str, index, inj): @@ -338,7 +340,7 @@ def inject_str(str, index, inj): return "%s%s%s" % (str[:index], inj, str[index:]) -def get_keylet_and_key(uzbl, key): +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.''' @@ -349,6 +351,14 @@ def get_keylet_and_key(uzbl, key): 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): @@ -380,9 +390,7 @@ def key_press(uzbl, key): if 'keycmd_events' in config and config['keycmd_events'] != '1': k.keycmd = '' k.cursor = 0 - if config['keycmd']: - uzbl.set('keycmd') - + uzbl.set('keycmd', config=config) return k.keycmd = inject_str(k.keycmd, k.cursor, key) @@ -408,9 +416,7 @@ def key_release(uzbl, key): 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()) - if not key: - return + (k, key) = get_keylet_and_key(uzbl, key.strip(), add=False) if key in k.held: if k.is_modcmd: diff --git a/examples/data/uzbl/plugins/mode.py b/examples/data/uzbl/plugins/mode.py index 52b104a..f85d999 100644 --- a/examples/data/uzbl/plugins/mode.py +++ b/examples/data/uzbl/plugins/mode.py @@ -58,16 +58,13 @@ def get_mode(uzbl): def mode_changed(uzbl, mode): '''The mode has just been changed, now set the per-mode config.''' - get_mode_dict(uzbl)['mode'] = mode + if get_mode(uzbl) != mode: + return config = uzbl.get_config() mode_config = get_mode_config(uzbl, mode) for (key, value) in mode_config.items(): - if key not in config: - config[key] = value - - elif config[key] != value: - config[key] = value + uzbl.set(key, value, config=config) if 'mode_indicator' not in mode_config: config['mode_indicator'] = mode @@ -96,12 +93,10 @@ def set_mode(uzbl, mode=None): if 'mode' not in config or config['mode'] != mode: config['mode'] = mode - return - - elif get_mode(uzbl) == mode: - return - uzbl.event("MODE_CHANGED", mode) + elif mode_dict['mode'] != mode: + mode_dict['mode'] = mode + uzbl.event("MODE_CHANGED", mode) def config_changed(uzbl, key, value): diff --git a/examples/data/uzbl/scripts/uzbl-event-manager b/examples/data/uzbl/scripts/uzbl-event-manager index 0054ff6..6669282 100755 --- a/examples/data/uzbl/scripts/uzbl-event-manager +++ b/examples/data/uzbl/scripts/uzbl-event-manager @@ -30,7 +30,6 @@ import imp import os import sys import re -import types import socket import pprint import time @@ -39,6 +38,7 @@ from select import select from signal import signal, SIGTERM from optparse import OptionParser from traceback import print_exc +from functools import partial # ============================================================================ @@ -63,8 +63,9 @@ DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') -# Config dict (NOT the same as the uzbl.config). -config = { +# 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, @@ -86,20 +87,25 @@ config = { # Define some globals. -_SCRIPTNAME = os.path.basename(sys.argv[0]) -_RE_FINDSPACES = re.compile("\s+") +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)) + 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)) + sys.stderr.write("%s: error: %s\n" % (SCRIPTNAME, msg)) def counter(): @@ -111,18 +117,6 @@ def counter(): yield i -def iscallable(obj): - '''Return true if the object is callable.''' - - return hasattr(obj, "__call__") - - -def isiterable(obj): - '''Return true if you can iterate over the item.''' - - return hasattr(obj, "__iter__") - - 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', ...}''' @@ -134,23 +128,26 @@ def find_plugins(plugin_dirs): if not os.path.isdir(plugin_dir): continue - for file in os.listdir(plugin_dir): - if not file.lower().endswith('.py'): + for filename in os.listdir(plugin_dir): + if not filename.lower().endswith('.py'): continue - path = os.path.join(plugin_dir, file) + path = os.path.join(plugin_dir, filename) if not os.path.isfile(path): continue - if file not in plugins: - plugins[file] = plugin_dir + if filename not in plugins: + plugins[filename] = plugin_dir return plugins -def load_plugins(plugin_dirs, load=[], ignore=[]): +def load_plugins(plugin_dirs, load=None, ignore=None): '''Load event manager plugins found in the plugin_dirs.''' + 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) @@ -171,11 +168,11 @@ def load_plugins(plugin_dirs, load=[], ignore=[]): loaded = {} # Load all found plugins into the loaded dict. - for (filename, dir) in found.items(): + for (filename, plugin_dir) in found.items(): name = filename[:-3] - info = imp.find_module(name, [dir,]) + info = imp.find_module(name, [plugin_dir]) plugin = imp.load_module(name, *info) - loaded[(dir, filename)] = plugin + loaded[(plugin_dir, filename)] = plugin return loaded @@ -230,9 +227,9 @@ def make_pid_file(pid_file): '''Make pid file at given pid_file location.''' make_dirs(pid_file) - file = open(pid_file, 'w') - file.write('%d' % os.getpid()) - file.close() + fileobj = open(pid_file, 'w') + fileobj.write('%d' % os.getpid()) + fileobj.close() def del_pid_file(pid_file): @@ -246,13 +243,12 @@ def get_pid(pid_file): '''Read pid from pid_file.''' try: - file = open(pid_file, 'r') - strpid = file.read() - file.close() - pid = int(strpid.strip()) + fileobj = open(pid_file, 'r') + pid = int(fileobj.read()) + fileobj.close() return pid - except: + except IOError, ValueError: print_exc() return None @@ -289,16 +285,32 @@ def term_process(pid): time.sleep(0.25) -def prepender(function, *pre_args): - '''Creates a wrapper around a callable object injecting a list of - arguments before the called arguments.''' +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.''' - locals = (function, pre_args) - def _prepender(*args, **kargs): - (function, pre_args) = locals - return function(*(pre_args + args), **kargs) + if not msg: + return - return _prepender + cmd = FINDSPACES.split(msg, 3) + if not cmd or cmd[0] != 'EVENT': + # Not an event message. + print '---', msg + return + + while len(cmd) < 4: + cmd.append('') + + event, args = cmd[2], cmd[3] + if not event: + return + + try: + uzbl.event(event, args) + + except: + print_exc() class EventHandler(object): @@ -306,7 +318,7 @@ class EventHandler(object): nexthid = counter().next def __init__(self, event, handler, *args, **kargs): - if not iscallable(handler): + if not callable(handler): raise ArgumentError("EventHandler object requires a callable " "object function for the handler argument not: %r" % handler) @@ -333,7 +345,7 @@ class EventHandler(object): class UzblInstance(object): # Give all plugins access to the main config dict. - config = config + config = CONFIG def __init__(self, parent, client_socket): @@ -378,8 +390,8 @@ class UzblInstance(object): raise KeyError("conflicting export: %r" % export) obj = getattr(plugin, export) - if iscallable(obj): - obj = prepender(obj, self) + if callable(obj): + obj = partial(obj, self) self._exports[export] = obj @@ -442,7 +454,7 @@ class UzblInstance(object): handlers.remove(handler) return - echo('unable to find & remove handler with id: %d' % handler.hid) + echo('unable to find & remove handler with id: %d' % hid) def remove(self, handler): @@ -516,7 +528,7 @@ class UzblEventDaemon(dict): # Register that the event daemon server has started by creating the # pid file. - make_pid_file(config['pid_file']) + make_pid_file(CONFIG['pid_file']) # Register a function to clean up the socket and pid file on exit. atexit.register(self.quit) @@ -525,15 +537,15 @@ class UzblEventDaemon(dict): signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) # 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']) + self['plugins'] = load_plugins(CONFIG['plugin_dirs'], + CONFIG['plugins_load'], CONFIG['plugins_ignore']) def _create_server_socket(self): '''Create the event manager daemon socket for uzbl instance duplex communication.''' - server_socket = config['server_socket'] + server_socket = CONFIG['server_socket'] server_socket = os.path.realpath(os.path.expandvars(server_socket)) self.socket_location = server_socket @@ -563,11 +575,11 @@ class UzblEventDaemon(dict): def run(self): '''Main event daemon loop.''' - if config['daemon_mode']: + if CONFIG['daemon_mode']: echo('entering daemon mode.') daemonize() # The pid has changed so update the pid file. - make_pid_file(config['pid_file']) + make_pid_file(CONFIG['pid_file']) # Create event daemon socket. self._create_server_socket() @@ -587,18 +599,18 @@ class UzblEventDaemon(dict): self.running = True while self.running: - sockets = [self.server_socket,] + self['uzbls'].keys() + sockets = [self.server_socket] + self['uzbls'].keys() - read, _, error = select(sockets, [], sockets, 1) + reads, _, errors = select(sockets, [], sockets, 1) - if self.server_socket in read: + if self.server_socket in reads: self.accept_connection() - read.remove(self.server_socket) + reads.remove(self.server_socket) - for client in read: + for client in reads: self.read_socket(client) - for client in error: + for client in errors: error('Unknown error on socket: %r' % client) self.close_connection(client) @@ -607,55 +619,28 @@ class UzblEventDaemon(dict): '''Read data from an instance socket and pass to the uzbl objects event handler function.''' + uzbl = self['uzbls'][client] try: - uzbl = self['uzbls'][client] - try: - raw = unicode(client.recv(8192), 'utf-8', 'ignore') - - except: - print_exc() - raw = None - - 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() - - for msg in msgs: - self.parse_msg(uzbl, msg) + raw = unicode(client.recv(8192), 'utf-8', 'ignore') except: - raise - - - def parse_msg(self, 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.''' - - msg = msg.strip() - if not msg: - return - - cmd = _RE_FINDSPACES.split(msg, 3) - if not cmd or cmd[0] != 'EVENT': - # Not an event message. - print '---', msg - return + print_exc() + raw = None - if len(cmd) < 4: - cmd.append('') + if not raw: + # Read null byte, close socket. + return self.close_connection(client) - event, args = cmd[2], cmd[3] + uzbl.buffer += raw + msgs = uzbl.buffer.split('\n') + uzbl.buffer = msgs.pop() - try: - uzbl.event(event, args) + for msg in msgs: + try: + parse_msg(uzbl, msg.strip()) - except: - print_exc() + except: + print_exc() def accept_connection(self): @@ -679,7 +664,7 @@ class UzblEventDaemon(dict): except: print_exc() - if not len(self['uzbls']) and config['auto_close']: + if not len(self['uzbls']) and CONFIG['auto_close']: echo('auto closing event manager.') self.running = False @@ -696,14 +681,14 @@ class UzblEventDaemon(dict): echo('unlinking: %r' % self.socket_location) self._close_server_socket() - echo('deleting pid file: %r' % config['pid_file']) - del_pid_file(config['pid_file']) + echo('deleting pid file: %r' % CONFIG['pid_file']) + del_pid_file(CONFIG['pid_file']) -def stop(): +def stop_action(): '''Stop the event manager daemon.''' - pid_file = config['pid_file'] + pid_file = CONFIG['pid_file'] if not os.path.isfile(pid_file): return echo('no running daemon found.') @@ -721,10 +706,10 @@ def stop(): echo('stopped event daemon.') -def start(): +def start_action(): '''Start the event manager daemon.''' - pid_file = config['pid_file'] + pid_file = CONFIG['pid_file'] if os.path.isfile(pid_file): echo('found pid file: %r' % pid_file) pid = get_pid(pid_file) @@ -738,126 +723,129 @@ def start(): UzblEventDaemon().run() -def restart(): +def restart_action(): '''Restart the event manager daemon.''' echo('restarting event manager daemon.') - stop() - start() + stop_action() + start_action() -def list_plugins(): +def list_action(): '''List all the plugins being loaded by the event daemon.''' - plugins = find_plugins(config['plugin_dirs']) + plugins = find_plugins(CONFIG['plugin_dirs']) dirs = {} - for (plugin, dir) in plugins.items(): - if dir not in dirs: - dirs[dir] = [] + for (plugin, plugin_dir) in plugins.items(): + if plugin_dir not in dirs: + dirs[plugin_dir] = [] - dirs[dir].append(plugin) + dirs[plugin_dir].append(plugin) - for (index, (dir, plugin_list)) in enumerate(sorted(dirs.items())): + for (index, (plugin_dir, plugin_list)) in enumerate(sorted(dirs.items())): if index: print - print "%s:" % dir + print "%s:" % plugin_dir for plugin in sorted(plugin_list): print " %s" % plugin 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", + 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.") - parser.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store", + PARSER.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store", metavar="DIRS", help="Specify plugin directories in the form of "\ "'dir1:dir2:dir3'.") - parser.add_option('-l', '--load-plugins', dest="load", action="store", + PARSER.add_option('-l', '--load-plugins', dest="load", action="store", metavar="PLUGINS", help="comma separated list of plugins to load") - parser.add_option('-i', '--ignore-plugins', dest="ignore", action="store", + PARSER.add_option('-i', '--ignore-plugins', dest="ignore", action="store", metavar="PLUGINS", help="comma separated list of plugins to ignore") - parser.add_option('-p', '--pid-file', dest='pid', action='store', + PARSER.add_option('-p', '--pid-file', dest='pid', action='store', metavar='FILE', help="specify pid file location") - parser.add_option('-s', '--server-socket', dest='socket', action='store', + PARSER.add_option('-s', '--server-socket', dest='socket', action='store', metavar='SOCKET', help="specify the daemon socket location") - parser.add_option('-n', '--no-daemon', dest="daemon", + PARSER.add_option('-n', '--no-daemon', dest="daemon", action="store_true", help="don't enter daemon mode.") - parser.add_option('-a', '--auto-close', dest='autoclose', + PARSER.add_option('-a', '--auto-close', dest='autoclose', action='store_true', help='auto close after all instances disconnect.') - (options, args) = parser.parse_args() + (OPTIONS, ARGS) = PARSER.parse_args() - # init like {start|stop|..} daemon control section. - daemon_controls = {'start': start, 'stop': stop, 'restart': restart, - 'list': list_plugins} + # init like {start|stop|..} daemon actions dict. + 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_controls: - error('unknown action: %r' % action) - sys.exit(1) + if not ARGS: + ACTION = 'start' - elif len(args) > 1: - error("too many arguments: %r" % args) - sys.exit(1) + elif len(ARGS) == 1: + ACTION = ARGS[0] + if ACTION not in DAEMON_ACTIONS: + raise ArgumentError("unknown argument: %r" % ACTION) else: - action = 'start' + raise ArgumentError("too many arguments: %r" % ARGS) # parse other flags & options. - if options.verbose: - config['verbose'] = True + if OPTIONS.verbose: + CONFIG['verbose'] = True + + if OPTIONS.plugin_dirs: + PLUGIN_DIRS = [] + for DIR in OPTIONS.plugin_dirs.split(':'): + if not DIR: + continue + + PLUGIN_DIRS.append(os.path.realpath(DIR)) - if options.plugin_dirs: - plugin_dirs = map(os.path.realpath, map(str.strip, - options.plugin_dirs.split(':'))) - config['plugin_dirs'] = plugin_dirs - echo("plugin search dirs: %r" % plugin_dirs) + CONFIG['plugin_dirs'] = PLUGIN_DIRS + echo("plugin search dirs: %r" % PLUGIN_DIRS) - if options.load and options.ignore: + if OPTIONS.load and OPTIONS.ignore: error("you can't load and ignore at the same time.") sys.exit(1) - elif options.load: - plugins_load = config['plugins_load'] - for plugin in options.load.split(','): - if plugin.strip(): - plugins_load.append(plugin.strip()) + elif OPTIONS.load: + LOAD = CONFIG['plugins_load'] + for PLUGIN in OPTIONS.load.split(','): + if PLUGIN.strip(): + LOAD.append(PLUGIN.strip()) - echo('only loading plugin(s): %s' % ', '.join(plugins_load)) + echo('only loading plugin(s): %s' % ', '.join(LOAD)) - elif options.ignore: - plugins_ignore = config['plugins_ignore'] - for plugin in options.ignore.split(','): - if plugin.strip(): - plugins_ignore.append(plugin.strip()) + elif OPTIONS.ignore: + IGNORE = CONFIG['plugins_ignore'] + for PLUGIN in OPTIONS.ignore.split(','): + if PLUGIN.strip(): + IGNORE.append(PLUGIN.strip()) - echo('ignoring plugin(s): %s' % ', '.join(plugins_ignore)) + echo('ignoring plugin(s): %s' % ', '.join(IGNORE)) - if options.autoclose: - config['auto_close'] = True + if OPTIONS.autoclose: + CONFIG['auto_close'] = True echo('will auto close.') - if options.pid: - config['pid_file'] = os.path.realpath(options.pid) - echo("pid file location: %r" % config['pid_file']) + if OPTIONS.pid: + CONFIG['pid_file'] = os.path.realpath(OPTIONS.pid) + echo("pid file location: %r" % CONFIG['pid_file']) - if options.socket: - config['server_socket'] = os.path.realpath(options.socket) - echo("daemon socket location: %s" % config['server_socket']) + if OPTIONS.socket: + CONFIG['server_socket'] = os.path.realpath(OPTIONS.socket) + echo("daemon socket location: %s" % CONFIG['server_socket']) - if options.daemon: - config['daemon_mode'] = False + if OPTIONS.daemon: + CONFIG['daemon_mode'] = False # Now {start|stop|...} - daemon_controls[action]() + DAEMON_ACTIONS[ACTION]() diff --git a/uzbl-core.c b/uzbl-core.c index f224c60..42e274e 100644 --- a/uzbl-core.c +++ b/uzbl-core.c @@ -266,9 +266,16 @@ expand(const char *s, guint recurse) { } else if(recurse != 1 && etype == EXP_EXPR) { + mycmd = expand(ret, 1); - g_spawn_command_line_sync(mycmd, &cmd_stdout, NULL, NULL, &err); + gchar *quoted = g_shell_quote(mycmd); + gchar *tmp = g_strdup_printf("%s %s", + uzbl.behave.shell_cmd?uzbl.behave.shell_cmd:"/bin/sh -c", + quoted); + g_spawn_command_line_sync(tmp, &cmd_stdout, NULL, NULL, &err); g_free(mycmd); + g_free(quoted); + g_free(tmp); if (err) { g_printerr("error on running command: %s\n", err->message); @@ -287,9 +294,22 @@ expand(const char *s, guint recurse) { } else if(recurse != 2 && etype == EXP_JS) { - mycmd = expand(ret, 2); - eval_js(uzbl.gui.web_view, mycmd, js_ret); - g_free(mycmd); + + /* read JS from file */ + if(ret[0] == '+') { + GArray *tmp = g_array_new(TRUE, FALSE, sizeof(gchar *)); + mycmd = expand(ret+1, 2); + g_array_append_val(tmp, mycmd); + + run_external_js(uzbl.gui.web_view, tmp, js_ret); + g_array_free(tmp, TRUE); + } + /* JS from string */ + else { + mycmd = expand(ret, 2); + eval_js(uzbl.gui.web_view, mycmd, js_ret); + g_free(mycmd); + } if(js_ret->str) { g_string_append(buf, js_ret->str); @@ -374,7 +394,9 @@ read_file_by_line (const gchar *path) { g_io_channel_unref (chan); } else { - fprintf(stderr, "File '%s' not be read.\n", path); + gchar *tmp = g_strdup_printf("File %s can not be read.", path); + send_event(COMMAND_ERROR, tmp, NULL); + g_free(tmp); } return lines; @@ -513,13 +535,17 @@ void catch_signal(int s) { if(s == SIGTERM || s == SIGINT || - s == SIGSEGV || s == SIGILL || s == SIGFPE || s == SIGQUIT) { clean_up(); exit(EXIT_SUCCESS); } + else if(s == SIGSEGV) { + clean_up(); + fprintf(stderr, "Program aborted, segmentation fault!\nAttempting to clean up...\n"); + exit(EXIT_FAILURE); + } else if(s == SIGALRM && uzbl.state.event_buffer) { g_ptr_array_free(uzbl.state.event_buffer, TRUE); uzbl.state.event_buffer = NULL; |