aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Mason Larobina <mason.larobina@gmail.com>2010-06-26 00:00:46 +0800
committerGravatar Mason Larobina <mason.larobina@gmail.com>2010-06-26 00:00:46 +0800
commitafc0f873e873839da75a54e8ca8095d335527786 (patch)
treeedf0951a5d8bfa11e5ba5877973a0c56de6aff16
parent44c80387726672abdec0108e7ae1791a8bed693c (diff)
parent08d4098f075b5dc8f2a174ec4e824b26fd81c110 (diff)
Merge branch 'experimental'
-rw-r--r--AUTHORS4
-rw-r--r--Makefile4
-rw-r--r--examples/config/config195
-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/formfiller.sh175
-rwxr-xr-xexamples/data/scripts/uzbl-event-manager1132
-rw-r--r--extras/vim/ftplugin/uzbl.vim29
-rw-r--r--extras/vim/syntax/uzbl.vim16
-rw-r--r--src/uzbl-core.c9
18 files changed, 1262 insertions, 1343 deletions
diff --git a/AUTHORS b/AUTHORS
index 7b2522c..7629d49 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -39,7 +39,7 @@ In alphabetical order:
Dmytro Milinevskyy - uzbl-tabbed useability patches
Dusan Popovic (dusanx) - many contributions to early uzbl
Evgeny Grablyk - libsoup settings
- Gregor Uhlenheuer (kongo2002) - uzbl vim syntax
+ Gregor Uhlenheuer (kongo2002) <kongo2002@googlemail.com> - uzbl vim syntax & related files
Helmut Grohne (helmut) - move void **ptr to union, various fixes
Henri Kemppainen (DuClare) <email is akarinotengoku AT THE DOMAIN OF gmail.com> - many contributions, mostly old handler code
Igor Bogomazov - mouse ptr events
@@ -58,7 +58,7 @@ In alphabetical order:
Nicolas Pouillard - refactored scroll command
Olivier Schwander - auto file:// prepend
Paul Tomak - formfiller.sh script, uzbl vim syntax
- Paweł Zuzelski (pawelz) - http auth handler, misc patches
+ Paweł Zuzelski (pawelz) <pawelz@pld-linux.org> - http auth handler, misc patches
Peter Suschlik - backwards searching
Přemysl Hrubý (anydot) <email is dfenze AT gmail.com> - several C contributions and cleanups
Robert Manea (robm) <email is rob DOT manea AT gmail DOT com> - C code all over the place
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 bb5acc6..cff13ad 100644
--- a/examples/config/config
+++ b/examples/config/config
@@ -1,43 +1,42 @@
-# example uzbl config.
-# all settings are optional. you can use uzbl without any config at all (but it won't do much)
+# Example uzbl config. All settings are optional. You can use uzbl without
+# any config at all (but it won't do much).
+# === Core settings ==========================================================
+
+# Install location prefix.
set prefix = /usr/local
-# === Shortcuts / Aliases ===================================================
+# Interface paths.
+set fifo_dir = /tmp
+set socket_dir = /tmp
+
+set shell_cmd = sh -c
+
+# === General config aliases =================================================
# Config related events (use the request function):
-# request BIND <bind cmd> = <command>
-set bind = request BIND
-# request MODE_BIND <mode> <bind cmd> = <command>
-set mode_bind = request MODE_BIND
# request MODE_CONFIG <mode> <key> = <value>
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>
set ignore_key = request IGNORE_KEY
# request MODKEY_ADDITION <key1> <key2> <keyn> <result>
set modkey_addition = request MODKEY_ADDITION
-
-# Action related events (use the event function):
-# event TOGGLE_MODES <mode1> <mode2> ... <moden>
-set toggle_modes = event TOGGLE_MODES
+# request TOGGLE_MODES <mode1> <mode2> ... <moden>
+set toggle_modes = request TOGGLE_MODES
set set_mode = set mode =
set set_status = set status_message =
-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:scripts
-
-# === Handlers ===============================================================
-
-# --- Hardcoded event handlers -----------------------------------------------
+# === Hardcoded handlers =====================================================
# These handlers can't be moved to the new event system yet as we don't
# support events that can wait for a response from a script.
@@ -45,7 +44,7 @@ set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_sock
set scheme_handler = sync_spawn @scripts_dir/scheme.py
set authentication_handler = sync_spawn @scripts_dir/auth.py
-# --- Optional dynamic event handlers ----------------------------------------
+# === Dynamic event handlers =================================================
# Open link in new window
@on_event NEW_WINDOW sh 'uzbl-browser -u "$8"' %r
@@ -77,7 +76,6 @@ set authentication_handler = sync_spawn @scripts_dir/auth.py
# Example CONFIG_CHANGED event handler
#@on_event CONFIG_CHANGED print Config changed: %1 = %2
-
# === Behaviour and appearance ===============================================
set show_status = 1
@@ -93,7 +91,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>
@@ -105,28 +103,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
# %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 =
+set progress.width = 8
+set progress.format = [%d>%p]%c
+set progress.done = =
+set progress.pending =
-# Or ride those spinnas'
-#@progress format = [%d%s%p]
-#@progress spinner = -\\|/
-#@progress done = -
-#@progress pending =
-
-
-# === Core settings ==========================================================
+# === Useragent setup ========================================================
set useragent = Uzbl (Webkit @{WEBKIT_MAJOR}.@{WEBKIT_MINOR}.@{WEBKIT_MICRO}) (@(+uname -sm)@ [@ARCH_UZBL]) (Commit @COMMIT)
-set fifo_dir = /tmp
-set socket_dir = /tmp
-
-# === Key modmapping and ignoring ============================================
+# === Key binding configuration ==============================================
+# --- Internal modmapping and ignoring ---------------------------------------
#modmap <From> <To>
@modmap <Control> <Ctrl>
@@ -142,23 +131,24 @@ set socket_dir = /tmp
@ignore_key <ISO_*>
@ignore_key <Shift>
+# --- Bind aliases -----------------------------------------------------------
-# === Mode bind aliases ======================================================
+# request BIND <bind cmd> = <command>
+set bind = request MODE_BIND global
-# Global binding alias (this is done automatically inside the bind plugin).
-#set bind = @mode_bind global
+# request MODE_BIND <mode> <bind cmd> = <command>
+set mode_bind = request MODE_BIND
# Insert mode binding alias
-set ibind = @mode_bind insert
+set ibind = @mode_bind insert
# Command mode binding alias
-set cbind = @mode_bind command
+set cbind = @mode_bind command
# Non-insert mode bindings alias (ebind for edit-bind).
-set ebind = @mode_bind global,-insert
+set ebind = @mode_bind global,-insert
-
-# === Global & keycmd editing binds ==========================================
+# --- Global & keycmd editing binds ------------------------------------------
# Resets keycmd and returns to default mode.
@on_event ESCAPE @set_mode
@@ -180,26 +170,18 @@ set ebind = @mode_bind global,-insert
@ebind <Ctrl>a = event SET_CURSOR_POS 0
@ebind <Ctrl>e = event SET_CURSOR_POS -1
-# Keycmd injection/append examples.
-#@ebind <Ctrl>su = event INJECT_KEYCMD \@uri
-#@ebind <Ctrl>st = event INJECT_KEYCMD \@title
-#@ebind <Ctrl>du = event APPEND_KEYCMD \@uri
-#@ebind <Ctrl>dt = event APPEND_KEYCMD \@title
-
-
-# === Mouse bindings =========================================================
+# --- Mouse bindings ---------------------------------------------------------
# Middle click open in new window
@bind <Button2> = sh 'if [ "\@SELECTED_URI" ]; then uzbl-browser -u "\@SELECTED_URI"; else echo "uri $(xclip -o | sed s/\\\@/%40/g)" > $4; fi'
-
-# === Keyboard bindings ======================================================
+# --- Keyboard bindings ------------------------------------------------------
# With this command you can enter in any command at runtime when prefixed with
# a colon.
@cbind :_ = %s
-# --- Page movement binds ---
+# Page movement binds
@cbind j = scroll vertical 20
@cbind k = scroll vertical -20
@cbind h = scroll horizontal -20
@@ -212,59 +194,36 @@ set ebind = @mode_bind global,-insert
@cbind $ = scroll horizontal end
@cbind <Space> = scroll vertical end
-# --- Navigation binds ---
+# Navigation binds
@cbind b = back
@cbind m = forward
@cbind S = stop
@cbind r = reload
@cbind R = reload_ign_cache
-# --- Zoom binds ---
+# Zoom binds
@cbind + = zoom_in
@cbind - = zoom_out
@cbind T = toggle_zoom_type
@cbind 1 = set zoom_level = 1.0
@cbind 2 = set zoom_level = 2.0
-# --- Appearance binds ---
+# Appearance binds
@cbind t = toggle_status
-# --- Page searching binds ---
+# Page searching binds
@cbind /* = search %s
@cbind ?* = search_reverse %s
# Jump to next and previous items
@cbind n = search
@cbind N = search_reverse
-# --- Uzbl tabbed binds ---
-# Tab opening
-@cbind gn = event NEW_TAB
-@cbind gN = event NEW_TAB_NEXT
-@cbind go<uri:>_ = event NEW_TAB %s
-@cbind gO<uri:>_ = event NEW_TAB_NEXT %s
-@cbind gY = sh 'echo "event NEW_TAB `xclip -selection primary -o | sed s/\\\@/%40/g`" > $4'
-# Closing / resting
-@cbind gC = exit
-@cbind gQ = event CLEAN_TABS
-# Tab navigating
-@cbind g< = event FIRST_TAB
-@cbind g> = event LAST_TAB
-@cbind gt = event NEXT_TAB
-@cbind gT = event PREV_TAB
-@cbind gi<index:>_ = event GOTO_TAB %s
-# Preset loading
-set preset = event PRESET_TABS
-@cbind gs<preset save:>_ = @preset save %s
-@cbind glo<preset load:>_ = @preset load %s
-@cbind gd<preset del:>_ = @preset del %s
-@cbind gli = @preset list
-
-# --- Web searching binds ---
+# Web searching binds
@cbind gg<Google:>_ = uri http://www.google.com/search?q=\@<encodeURIComponent(%r)>\@
@cbind \\awiki<Archwiki:>_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=\@<encodeURIComponent(%r)>\@&go=Go
@cbind \\wiki<Wikipedia:>_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=\@<encodeURIComponent(%r)>\@&go=Go
-# --- Handy binds ---
+# Handy binds
# Set function shortcut
@cbind s<var:>_<value:>_ = set %1 = %2
# Exit binding
@@ -279,24 +238,19 @@ set preset = event PRESET_TABS
@cbind <Ctrl><Alt>t = sh 'xterm -e "socat unix-connect:$5 -"'
#@cbind <Ctrl><Alt>t = sh 'urxvt -e socat unix-connect:$5 -'
-# --- Uri opening prompts ---
+# Uri opening prompts
@cbind o<uri:>_ = uri %s
# Or have it load the current uri into the keycmd for editing
@cbind O<uri:\@uri>_ = uri %s
-# --- Mode setting binds ---
-# Changing mode via set.
-@cbind I = @set_mode insert
-# Or toggle between modes by raising the toggle event.
-set toggle_cmd_ins = @toggle_modes command insert
-@cbind i = @toggle_cmd_ins
-# And the global toggle bind.
-@bind <Ctrl>i = @toggle_cmd_ins
+# Mode setting binds
+@cbind i = @set_mode insert
+@bind <Ctrl>i = @set_mode insert
-# --- Hard-bound bookmarks ---
+# Hard-bound bookmarks
@cbind gh = uri http://www.uzbl.org
-# --- Yanking & pasting binds ---
+# Yanking & pasting binds
@cbind yu = sh 'echo -n $6 | xclip'
@cbind yy = sh 'echo -n $7 | xclip'
@@ -309,16 +263,16 @@ set toggle_cmd_ins = @toggle_modes command insert
# paste primary selection into keycmd at the cursor position
@bind <Shift-Insert> = sh 'echo "event INJECT_KEYCMD `xclip -o | sed s/\\\@/%40/g`" > $4'
-# --- Bookmark inserting binds ---
+# Bookmark inserting binds
@cbind <Ctrl>b<tags:>_ = sh 'echo -e "$6 %s" >> $XDG_DATA_HOME/uzbl/bookmarks'
# Or use a script to insert a bookmark.
@cbind B = spawn @scripts_dir/insert_bookmark.sh
-# --- Bookmark/history loading ---
+# Bookmark/history loading
@cbind U = spawn @scripts_dir/load_url_from_history.sh
@cbind u = spawn @scripts_dir/load_url_from_bookmarks.sh
-# --- Link following (similar to vimperator and konqueror) ---
+# Link following (similar to vimperator and konqueror)
# Set custom keys you wish to use for navigation. Some common examples:
set follow_hint_keys = 0123456789
#set follow_hint_keys = qwerty
@@ -326,7 +280,7 @@ set follow_hint_keys = 0123456789
#set follow_hint_keys = thsnd-rcgmvwb/;789aefijkopquxyz234
@cbind fl* = script @scripts_dir/follow.js '@follow_hint_keys %s'
-# --- Form filler binds ---
+# Form filler binds
# This script allows you to configure (per domain) values to fill in form
# fields (eg login information) and to fill in these values automatically.
# This implementation allows you to save multiple profiles for each form
@@ -337,11 +291,32 @@ set formfiller = spawn @scripts_dir/formfiller.sh
@cbind zn = @formfiller new
@cbind zl = @formfiller load
-# --- Examples ---
-# Example showing how to use uzbl's fifo to execute a command.
-#@bind X1 = sh 'echo "set zoom_level = 1.0" > "$4"'
-#@bind X2 = sh 'echo "js alert (\\"This is sent by the shell via a fifo\\")" > "$4"'
+# --- Uzbl tabbed binds ------------------------------------------------------
+
+# Tab opening
+@cbind gn = event NEW_TAB
+@cbind gN = event NEW_TAB_NEXT
+@cbind go<uri:>_ = event NEW_TAB %s
+@cbind gO<uri:>_ = event NEW_TAB_NEXT %s
+@cbind gY = sh 'echo "event NEW_TAB `xclip -selection primary -o | sed s/\\\@/%40/g`" > $4'
+# Closing / resting
+@cbind gC = exit
+@cbind gQ = event CLEAN_TABS
+
+# Tab navigating
+@cbind g< = event FIRST_TAB
+@cbind g> = event LAST_TAB
+@cbind gt = event NEXT_TAB
+@cbind gT = event PREV_TAB
+@cbind gi<index:>_ = event GOTO_TAB %s
+
+# Preset loading
+set preset = event PRESET_TABS
+@cbind gs<preset save:>_ = @preset save %s
+@cbind glo<preset load:>_ = @preset load %s
+@cbind gd<preset del:>_ = @preset del %s
+@cbind gli = @preset list
# === Context menu items =====================================================
@@ -354,7 +329,6 @@ menu_add Quit uzbl = exit
# Link context menu
menu_link_add Print Link = print \@SELECTED_URI
-
# === Mode configuration =====================================================
# Define some mode specific uzbl configurations.
@@ -366,10 +340,16 @@ set stack = @mode_config stack
@command keycmd_style = foreground="red"
@command status_background = #202020
@command mode_indicator = Cmd
+@command keycmd_events = 1
+@command forward_keys = 0
+@command modcmd_updates = 1
# Insert mode config.
@insert status_background = #303030
@insert mode_indicator = Ins
+@insert forward_keys = 1
+@insert keycmd_events = 0
+@insert modcmd_updates = 0
# Multi-stage-binding mode config.
@stack keycmd_events = 1
@@ -382,8 +362,9 @@ set stack = @mode_config stack
set default_mode = command
-
-# === Post-load misc commands ===============================================
+# === Post-load misc commands ================================================
# Set the "home" page.
set uri = uzbl.org/doesitwork/@COMMIT
+
+# vim: set fdm=syntax:
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/formfiller.sh b/examples/data/scripts/formfiller.sh
index 1408006..69eca17 100755
--- a/examples/data/scripts/formfiller.sh
+++ b/examples/data/scripts/formfiller.sh
@@ -5,16 +5,37 @@
# uses settings files like: $keydir/<domain>
# files contain lines like: !profile=<profile_name>
# <fieldname>(fieldtype): <value>
-# profile_name should be replaced with a name that will tell sth about that profile
-# fieldtype can be text or password - only for information pupropse (auto-generated) - don't change that
+# profile_name should be replaced with a name that will tell sth about that
+# profile
+# fieldtype can be checkbox, text or password, textarea - only for information
+# pupropse (auto-generated) - don't change that
+#
+# Texteares: for textareas edited text can be now splitted into more lines.
+# If there will be text, that doesn't match key line:
+# <fieldname>(fieldtype):<value>
+# then it will be considered as a multiline for the first field above it
+# Keep in mind, that if you make more than one line for fileds like input
+# text fields, then all lines will be inserted into as one line
+#
+# Checkboxes/radio-buttons: to uncheck it type on of the following after the
+# colon:
+# no
+# off
+# 0
+# unchecked
+# false
+# or leave it blank, even without spaces
+# otherwise it will be considered as checked
#
# user arg 1:
# edit: force editing the file (falls back to new if not found)
# new: start with a new file.
# load: try to load from file into form
# add: try to add another profile to an existing file
+# once: edit form using external editor
#
# something else (or empty): if file not available: new, otherwise load.
+#
# config dmenu colors and prompt
NB="#0f0f0f"
@@ -30,15 +51,16 @@ else
fi
PROMPT="Choose profile"
+MODELINE="> vim:ft=formfiller"
keydir=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/dforms
[ -d "`dirname $keydir`" ] || exit 1
[ -d "$keydir" ] || mkdir "$keydir"
-editor=${VISUAL}
-if [ -z ${editor} ]; then
- if [ -z ${EDITOR} ]; then
+editor="${VISUAL}"
+if [ -z "${editor}" ]; then
+ if [ -z "${EDITOR}" ]; then
editor='xterm -e vim'
else
editor="xterm -e ${EDITOR}"
@@ -69,11 +91,67 @@ if [ "$action" != 'edit' -a "$action" != 'new' -a "$action" != 'load' -a "$acti
then
action="new"
[ -e "$keydir/$domain" ] && action="load"
-elif [ "$action" == 'edit' ] && [ ! -e "$keydir/$domain" ]
+elif [ "$action" = 'edit' ] && [ ! -e "$keydir/$domain" ]
then
action="new"
fi
+dumpFunction="function dump() { \
+ var rv=''; \
+ var allFrames = new Array(window); \
+ for(f=0;f<window.frames.length;f=f+1) { \
+ allFrames.push(window.frames[f]); \
+ } \
+ for(j=0;j<allFrames.length;j=j+1) { \
+ try { \
+ var xp_res=allFrames[j].document.evaluate('//input', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null); \
+ var input; \
+ while(input=xp_res.iterateNext()) { \
+ var type=(input.type?input.type:text); \
+ if(type == 'text' || type == 'password' || type == 'search') { \
+ rv += input.name + '(' + type + '):' + input.value + '\\\\n'; \
+ } \
+ else if(type == 'checkbox' || type == 'radio') { \
+ rv += input.name + '{' + input.value + '}(' + type + '):' + (input.checked?'ON':'OFF') + '\\\\n'; \
+ } \
+ } \
+ xp_res=allFrames[j].document.evaluate('//textarea', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null); \
+ var input; \
+ while(input=xp_res.iterateNext()) { \
+ rv += input.name + '(textarea):' + input.value + '\\\\n'; \
+ } \
+ } \
+ catch(err) { } \
+ } \
+ return rv; \
+}; "
+
+insertFunction="function insert(fname, ftype, fvalue, fchecked) { \
+ var allFrames = new Array(window); \
+ for(f=0;f<window.frames.length;f=f+1) { \
+ allFrames.push(window.frames[f]); \
+ } \
+ for(j=0;j<allFrames.length;j=j+1) { \
+ try { \
+ if(ftype == 'text' || ftype == 'password' || ftype == 'search' || ftype == 'textarea') { \
+ allFrames[j].document.getElementsByName(fname)[0].value = fvalue; \
+ } \
+ else if(ftype == 'checkbox') { \
+ allFrames[j].document.getElementsByName(fname)[0].checked = fchecked;\
+ } \
+ else if(ftype == 'radio') { \
+ var radios = allFrames[j].document.getElementsByName(fname); \
+ for(r=0;r<radios.length;r+=1) { \
+ if(radios[r].value == fvalue) { \
+ radios[r].checked = fchecked; \
+ } \
+ } \
+ } \
+ } \
+ catch(err) { } \
+ } \
+}; "
+
if [ "$action" = 'load' ]
then
[ -e $keydir/$domain ] || exit 2
@@ -84,45 +162,55 @@ then
option=`echo -e -n "$menu"| dmenu ${LINES} -nb "${NB}" -nf "${NF}" -sb "${SB}" -sf "${SF}" -p "${PROMPT}"`
fi
- cat $keydir/$domain | \
- sed -n -e "/^!profile=${option}/,/^!profile=/p" | \
- sed -n -e 's/\([^(]\+\)([^)]\+):[ ]*\(.\+\)$/js if(window.frames.length > 0) { for(i=0;i<window.frames.length;i=i+1) { try { var e = window.frames[i].document.getElementsByName("\1"); if(e.length > 0) { e[0].value="\2"; } } catch(err) { } } }; document.getElementsByName("\1")[0].value="\2"/p' | \
- sed -e 's/@/\\@/g' >> $fifo
+ # Remove comments
+ sed '/^>/d' -i $tmpfile
+
+ sed 's/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):\(off\|no\|false\|unchecked\|0\|$\)/\1{\2}(\3):0/I;s/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[^0]\+/\1{\2}(\3):1/I' -i $keydir/$domain
+ fields=`cat $keydir/$domain | \
+ sed -n "/^!profile=${option}/,/^!profile=/p" | \
+ sed '/^!profile=/d' | \
+ sed 's/^\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):/%{>\1\2):<}%/' | \
+ sed 's/^\(.\+\)$/<{br}>\1/' | \
+ tr -d '\n' | \
+ sed 's/<{br}>%{>\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):<}%/\\n\1\2):/g'`
+ printf '%s\n' "${fields}" | \
+ sed -n -e "s/\([^(]\+\)(\(password\|text\|search\|textarea\)\+):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\2', '\3', 0);/p" | \
+ sed -e 's/@/\\@/g;s/<{br}>/\\\\n/g' > $fifo
+ printf '%s\n' "${fields}" | \
+ sed -n -e "s/\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\3', '\2', \4);/p" | \
+ sed -e 's/@/\\@/g' > $fifo
elif [ "$action" = "once" ]
then
tmpfile=`mktemp`
- html=`echo 'js if(window.frames.length > 0) { for(j=0;j<window.frames.length;j=j+1) { try { window.frames[j].document.documentElement.outerHTML; } catch(err) { } } }' | \
- socat - unix-connect:$socket`
- html=${html}" "`echo 'js document.documentElement.outerHTML' | \
- socat - unix-connect:$socket`
- html=`echo ${html} | \
- tr -d '\n' | \
- sed 's/>/>\n/g' | \
- sed 's/<input/<input type="text"/g' | \
- sed 's/type="text"\(.*\)type="\([^"]\+\)"/type="\2" \1 /g'`
- echo "${html}" | \
- sed -n 's/.*\(<input[^>]\+>\).*/\1/;/type="\(password\|text\)"/Ip' | \
- sed 's/\(.*\)\(type="[^"]\+"\)\(.*\)\(name="[^"]\+"\)\(.*\)/\1\4\3\2\5/I' | \
- sed 's/.*name="\([^"]\+\)".*type="\([^"]\+\)".*/\1(\2):/I' >> $tmpfile
- echo "${html}" | \
- sed -n 's/.*<textarea.*name="\([^"]\+\)".*/\1(textarea):/Ip' >> $tmpfile
+ printf 'js %s dump(); \n' "$dumpFunction" | \
+ socat - unix-connect:$socket | \
+ sed -n '/^[^(]\+([^)]\+):/p' > $tmpfile
+ echo "$MODELINE" >> $tmpfile
${editor} $tmpfile
[ -e $tmpfile ] || exit 2
- cat $tmpfile | \
- sed -n -e 's/\([^(]\+\)([^)]\+):[ ]*\(.\+\)/js if(window.frames.length > 0) { for(i=0;i<window.frames.length;i=i+1) { try { var e = window.frames[i].document.getElementsByName("\1"); if(e.length > 0) { e[0].value="\2" } } catch(err) { } } }; document.getElementsByName("\1")[0].value="\2"/p' | \
- sed -e 's/@/\\@/g' >> $fifo
+ # Remove comments
+ sed '/^>/d' -i $tmpfile
+
+ sed 's/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):\(off\|no\|false\|unchecked\|0\|$\)/\1{\2}(\3):0/I;s/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[^0]\+/\1{\2}(\3):1/I' -i $tmpfile
+ fields=`cat $tmpfile | \
+ sed 's/^\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):/%{>\1\2):<}%/' | \
+ sed 's/^\(.\+\)$/<{br}>\1/' | \
+ tr -d '\n' | \
+ sed 's/<{br}>%{>\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):<}%/\\n\1\2):/g'`
+ printf '%s\n' "${fields}" | \
+ sed -n -e "s/\([^(]\+\)(\(password\|text\|search\|textarea\)\+):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\2', '\3', 0);/p" | \
+ sed -e 's/@/\\@/g;s/<{br}>/\\\\n/g' > $fifo
+ printf '%s\n' "${fields}" | \
+ sed -n -e "s/\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\3', '\2', \4);/p" | \
+ sed -e 's/@/\\@/g' > $fifo
rm -f $tmpfile
else
- if [ "$action" == 'new' -o "$action" == 'add' ]
+ if [ "$action" = 'new' -o "$action" = 'add' ]
then
- if [ "$action" == 'new' ]
- then
- echo "!profile=NAME_THIS_PROFILE$RANDOM" > $keydir/$domain
- else
- echo "!profile=NAME_THIS_PROFILE$RANDOM" >> $keydir/$domain
- fi
+ [ "$action" = 'new' ] && echo "$MODELINE" > $keydir/$domain
+ echo "!profile=NAME_THIS_PROFILE$RANDOM" >> $keydir/$domain
#
# 2. and 3. line (tr -d and sed) are because, on gmail login for example,
# <input > tag is splited into lines
@@ -131,7 +219,7 @@ else
# type="text"
# value="">
# So, tr removes all new lines, and sed inserts new line after each >
- # Next sed selects only <input> tags and only with type == "text" or == "password"
+ # Next sed selects only <input> tags and only with type = "text" or = "password"
# If type is first and name is second, then another sed will change their order
# so the last sed will make output
# text_from_the_name_attr(text or password):
@@ -139,18 +227,9 @@ else
# login(text):
# passwd(password):
#
- html=`echo 'js if(window.frames.length > 0) { for(i=0;i<window.frames.length;i=i+1) { try { window.frames[i].document.documentElement.outerHTML; } catch(err) { } } }' | \
- socat - unix-connect:$socket`
- html=${html}" "`echo 'js document.documentElement.outerHTML' | \
- socat - unix-connect:$socket`
- echo ${html} | \
- tr -d '\n' | \
- sed 's/>/>\n/g' | \
- sed 's/<input/<input type="text"/g' | \
- sed 's/type="text"\(.*\)type="\([^"]\+\)"/type="\2" \1 /g' | \
- sed -n 's/.*\(<input[^>]\+>\).*/\1/;/type="\(password\|text\)"/Ip' | \
- sed 's/\(.*\)\(type="[^"]\+"\)\(.*\)\(name="[^"]\+"\)\(.*\)/\1\4\3\2\5/I' | \
- sed 's/.*name="\([^"]\+\)".*type="\([^"]\+\)".*/\1(\2):/I' >> $keydir/$domain
+ printf 'js %s dump(); \n' "$dumpFunction" | \
+ socat - unix-connect:$socket | \
+ sed -n '/^[^(]\+([^)]\+):/p' >> $keydir/$domain
fi
[ -e "$keydir/$domain" ] || exit 3 #this should never happen, but you never know.
$editor "$keydir/$domain" #TODO: if user aborts save in editor, the file is already overwritten
diff --git a/examples/data/scripts/uzbl-event-manager b/examples/data/scripts/uzbl-event-manager
index 7fa4a09..ab13fbb 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,847 @@ 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
+ init = getattr(plugin, 'init', None)
+ self.init = init if callable(init) else None
- 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)
+ assert init or after or cleanup, "missing hooks in plugin"
+ # 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.'''
- if not msg:
- return
+ def __repr__(self):
+ return u'<plugin(%r)>' % self.plugin
- cmd = FINDSPACES.split(msg, 3)
- if not cmd or cmd[0] != 'EVENT':
- # Not an event message.
- print '---', msg.encode('utf-8')
- return
- while len(cmd) < 4:
- cmd.append('')
+ 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.
- event, args = cmd[2], cmd[3]
- if not event:
- return
+ 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.
+ '''
- try:
- uzbl.event(event, args)
+ assert attr not in uzbl.exports, "attr %r already exported by %r" %\
+ (attr, uzbl.exports[attr][0])
- except:
- print_exc()
+ 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))
-class EventHandler(object):
+ def export_dict(self, uzbl, exports):
+ for (attr, object) in exports.items():
+ self.export(uzbl, attr, object)
- nexthid = count().next
- 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)
+ def find_handler(self, event, callback, args, kwargs):
+ '''Check if a handler with the identical callback and arguments
+ exists and return it.'''
- self.function = handler
- self.args = args
- self.kargs = kargs
- self.event = event
- self.hid = self.nexthid()
+ # Remove dead refs
+ self.handlers -= set(filter(lambda ref: not ref(), self.handlers))
+ # 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]
- if self.args:
- args.append(u"args=%r" % unicode(self.args))
+ def connect(self, uzbl, event, callback, *args, **kwargs):
+ '''Create an event handler object which handles `event` events.
- if self.kargs:
- args.append(u"kargs=%r" % unicode(self.kargs))
+ Arguments passed to the connect function (`args` and `kwargs`) are
+ stored in the handler object and merged with the event arguments
+ come handler execution.
- return u"<EventHandler(%s)>" % ', '.join(args)
+ All handler functions must behave like a `uzbl` instance-method (that
+ means `uzbl` is prepended to the callback call arguments).'''
+ # Sanitise and check event name
+ event = event.upper().strip()
+ assert event and ' ' not in event
-class UzblInstance(object):
+ assert callable(callback), 'callback must be callable'
- # Give all plugins access to the main config dict.
- global_config = CONFIG
+ # 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)
- def __init__(self, parent, client_socket):
+ uzbl.handlers[event].append(handler)
+ uzbl.logger.info('connected %r' % handler)
+ return handler
- # Internal variables.
- self.exports = {}
- self.handlers = {}
- self.parent = parent
- self.client_socket = client_socket
- self.depth = 0
- self.buffer = ''
- self.pid = None
+ def connect_dict(self, uzbl, connects):
+ for (event, callback) in connects.items():
+ self.connect(uzbl, event, callback)
- # 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)
- except:
- raise
+ def require(self, plugin):
+ '''Check that plugin with name `plugin` has been loaded. Use this to
+ ensure that your plugins dependencies have been met.'''
+ 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.'''
- 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..
- '''
+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
- if prepend and callable(object):
- object = partial(object, self)
+ # Flag if the instance has raised the INSTANCE_START event.
+ self.instance_start = False
- self.__dict__.__setitem__(attr, object)
+ # Use name "unknown" until name is discovered.
+ self.logger = get_logger('uzbl-instance[]')
+ # Track plugin event handlers and exported functions.
+ self.exports = {}
+ self.handlers = defaultdict(list)
- def export_dict(self, export_dict):
- '''Export multiple (attr, object)'s at once inside a dict of the
- form `{attr1: object1, attr2: object2, ...}`.'''
+ # Internal vars
+ self._depth = 0
+ self._buffer = ''
- 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 init_plugins(self):
+ '''Call the init and after hooks in all loaded plugins for this
+ instance.'''
+
+ # Initialise each plugin with the current uzbl instance.
+ for plugin in self.parent.plugins.values():
+ if plugin.init:
+ self.logger.debug('calling %r plugin init hook' % plugin.name)
+ plugin.init(self)
- 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.'''
+ # 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)
- event = event.upper().strip()
- assert event and ' ' not in event
- if event not in self.handlers.keys():
- self.handlers[event] = []
+ def send(self, msg):
+ '''Send a command to the uzbl instance via the child socket
+ instance.'''
+
+ msg = msg.strip()
+ assert self.child_socket, "socket inactive"
- handlerobj = EventHandler(event, handler, *args, **kargs)
- self.handlers[event].append(handlerobj)
- print handlerobj
+ if opts.print_events:
+ print ascii(u'%s<-- %s' % (' ' * self._depth, msg))
+ self.child_socket.send(ascii("%s\n" % msg))
- def connect_dict(self, connect_dict):
- '''Connect a dictionary comprising of {"EVENT_NAME": handler, ..} to
- the event handler stack.
- If you need to supply args or kargs to an event use the normal connect
+ def read(self):
+ '''Read data from the child socket and pass lines to the parse_msg
function.'''
- for (event, handler) in connect_dict.items():
- self.connect(event, handler)
-
+ try:
+ raw = unicode(self.child_socket.recv(8192), 'utf-8', 'ignore')
+ if not raw:
+ self.logger.debug('read null byte')
+ return self.close()
- def remove_by_id(self, hid):
- '''Remove connected event handler by unique handler id.'''
+ except:
+ self.logger.error(get_exc())
+ return self.close()
- for (event, handlers) in self.handlers.items():
- for handler in list(handlers):
- if hid != handler.hid:
- continue
+ lines = (self._buffer + raw).split('\n')
+ self._buffer = lines.pop()
- echo("removed %r" % handler)
- handlers.remove(handler)
- return
+ for line in filter(None, map(unicode.strip, lines)):
+ try:
+ self.parse_msg(line.strip())
- echo('unable to find & remove handler with id: %d' % hid)
+ except:
+ self.logger.error(get_exc())
+ self.logger.error('erroneous event: %r' % line)
- def remove(self, handler):
- '''Remove connected event handler.'''
+ def parse_msg(self, line):
+ '''Parse an incoming message from a uzbl instance. Event strings
+ will be parsed into `self.event(event, args)`.'''
- for (event, handlers) in self.handlers.items():
- if handler in handlers:
- echo("removed %r" % handler)
- handlers.remove(handler)
- return
+ # Split by spaces (and fill missing with nulls)
+ elems = (line.split(' ', 3) + ['',]*3)[:4]
- echo('unable to find & remove handler: %r' % handler)
+ # Ignore non-event messages.
+ if elems[0] != 'EVENT':
+ logger.info('non-event message: %r' % line)
+ if opts.print_events:
+ print '--- %s' % ascii(line)
+ return
+ # 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)
- def exec_handler(self, handler, *args, **kargs):
- '''Execute event handler function.'''
+ assert self.name == name, 'instance name mismatch'
- args += handler.args
- kargs = dict(handler.kargs.items()+kargs.items())
- handler.function(self, *args, **kargs)
+ # 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)
- self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- self.server_socket.bind(server_socket)
- self.server_socket.listen(5)
+ # Check if the plugin has a callable hook.
+ hooks = filter(callable, [getattr(module, attr, None) \
+ for attr in ['init', 'after', 'cleanup']])
+ assert hooks, "no hooks in plugin %r" % module
+ 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.'''
- try:
- self.server_socket.close()
- self.server_socket = None
+ def create_server_socket(self):
+ '''Create the event manager daemon socket for uzbl instance duplex
+ communication.'''
- if os.path.exists(self.socket_location):
- os.remove(self.socket_location)
+ # Close old socket.
+ self.close_server_socket()
- except:
- pass
+ sock = socket(AF_UNIX, SOCK_STREAM)
+ sock.bind(opts.server_socket)
+ sock.listen(5)
+
+ 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)
- # Clean up.
+ try:
+ # Accept incoming connections and listen for incoming data
+ self.listen()
+
+ except:
+ if not self._quit:
+ logger.critical(get_exc())
+
+ # 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.'''
+ 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
- def quit(self):
- '''Close all instance socket objects, server socket and delete the
- pid file.'''
+ except (IOError, ValueError):
+ logger.error(get_exc())
+ return None
- echo('shutting down event manager.')
- for client in self['uzbls'].keys():
- self.close_connection(client)
+def pid_running(pid):
+ '''Checks if a process with a pid `pid` is running.'''
+
+ 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
- echo('unlinking: %r' % self.socket_location)
- self._close_server_socket()
+ if (time.time()-start) > 10:
+ logger.critical('unable to kill process with pid %d' % pid)
+ raise OSError
- echo('deleting pid file: %r' % CONFIG['pid_file'])
- del_pid_file(CONFIG['pid_file'])
+ 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())
diff --git a/extras/vim/ftplugin/uzbl.vim b/extras/vim/ftplugin/uzbl.vim
new file mode 100644
index 0000000..513eaa4
--- /dev/null
+++ b/extras/vim/ftplugin/uzbl.vim
@@ -0,0 +1,29 @@
+" Vim filetype file
+" Filename: uzbl.vim
+" Maintainer: Gregor Uhlenheuer
+" Last Change: Sun 04 Apr 2010 01:37:49 PM CEST
+
+if exists('b:did_ftplugin')
+ finish
+endif
+
+let b:did_ftplugin = 1
+
+" enable syntax based folding
+setlocal foldmethod=syntax
+
+" correctly format comments
+setlocal formatoptions=croql
+setlocal comments=:#
+setlocal commentstring=#%s
+
+" define config testing commands and mappings
+if executable('uzbl-core')
+ com! -buffer UzblCoreTest !uzbl-core -c %
+ nmap <buffer> <Leader>uc :UzblCoreTest<CR>
+endif
+
+if executable('uzbl-browser')
+ com! -buffer UzblBrowserTest !uzbl-browser -c %
+ nmap <buffer> <Leader>ub :UzblBrowserTest<CR>
+endif
diff --git a/extras/vim/syntax/uzbl.vim b/extras/vim/syntax/uzbl.vim
index 39f2495..b8572c9 100644
--- a/extras/vim/syntax/uzbl.vim
+++ b/extras/vim/syntax/uzbl.vim
@@ -26,7 +26,7 @@ elseif exists("b:current_syntax")
endif
" Don't match keywords inside strings
-set iskeyword=!-~,192-255
+setl iskeyword=!-~,192-255
syn keyword uzblKeyword back forward scroll reload reload_ign_cache stop
syn keyword uzblKeyword zoom_in zoom_out toggle_zoom_type uri script
@@ -44,13 +44,13 @@ syn match uzblTodo /TODO:/ contained
syn region uzblComment display start=/^#/ end=/$/ contains=uzblTodo
" Comment headings
-syn region uzblSection display start=/^# ===/ end=/$/
-syn region uzblSubSection display start=/^# ---/ end=/$/
+syn region uzblSec matchgroup=uzblSection start=/^# ===.*$/ end=/^# ===/me=e-5 contains=ALL fold
+syn region uzblSSec matchgroup=uzblSubSection start=/^# ---.*$/ end=/^# [=-]\{3}/me=e-5 contains=ALLBUT,uzblSec,uzblSSec fold
" Integer and float matching
-syn match uzblPercent display /\s\(+\|-\|\)\(\d\+.\d\+\|\d\+\)%\(\s\|\n\)/
-syn match uzblInt display /\s\(+\|-\|\)\d\+\(\s\|\n\)/
-syn match uzblFloat display /\s\(+\|-\|\)\d\+.\d\+\(\s\|\n\)/
+syn match uzblPercent display /\s[+-]\=\%(\d\+\.\)\=\d\+%\_s/
+syn match uzblInt display /\s[+-]\=\d\+\_s/
+syn match uzblFloat display /\s[+-]\=\d\+\.\d\+\_s/
" Handler arguments
syn match uzblArgs display /$\d\+/
@@ -63,7 +63,7 @@ syn match uzblInternalExpand display /@[A-Z_]\+/
syn match uzblInternalExpand display /@{[A-Z_]\+}/
" Matches $ENVIRON_VAR
-syn match uzblEnvironVariable display /$[A-Za-z0-9_]\+/
+syn match uzblEnvironVariable display /$\a\+\w*/
" Matches @some_var and @{some_var}
syn match uzblExpand display /@[A-Za-z0-9_\.]\+/
@@ -106,7 +106,7 @@ if version >= 508 || !exists("did_uzbl_syn_inits")
HiLink uzblComment Comment
HiLink uzblTodo Todo
- HiLink uzblSection Folded
+ HiLink uzblSection SpecialComment
HiLink uzblSubSection SpecialComment
HiLink uzblKeyword Keyword
diff --git a/src/uzbl-core.c b/src/uzbl-core.c
index 227e8c6..7be98c3 100644
--- a/src/uzbl-core.c
+++ b/src/uzbl-core.c
@@ -2398,15 +2398,18 @@ void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data)
char *cookies = (char *) g_malloc(len+1);
strncpy(cookies, uzbl.comm.sync_stdout, len+1);
- /* Disconnect to avoid recusion */
+ /* Disconnect to avoid recursion */
g_object_disconnect(G_OBJECT(uzbl.net.soup_cookie_jar), "any_signal", G_CALLBACK(save_cookies_js), NULL, NULL);
p = cookies - 1;
while(p != NULL) {
p = p + 1;
soup_cookie = soup_cookie_parse((const char *) p, soup_uri);
- if(soup_cookie->domain == NULL) soup_cookie->domain = soup_uri->host;
- soup_cookie_jar_add_cookie(uzbl.net.soup_cookie_jar, soup_cookie);
+ if (soup_cookie) {
+ if(soup_cookie->domain == NULL)
+ soup_cookie->domain = soup_uri->host;
+ soup_cookie_jar_add_cookie(uzbl.net.soup_cookie_jar, soup_cookie);
+ }
p = strchr(p, ';');
}