- up to 2013.12.08 git snap (which doesn't hang on some analytics js)
authorArkadiusz Miśkiewicz <arekm@maven.pl>
Sun, 8 Dec 2013 16:09:32 +0000 (17:09 +0100)
committerArkadiusz Miśkiewicz <arekm@maven.pl>
Sun, 8 Dec 2013 16:09:32 +0000 (17:09 +0100)
uzbl-git.patch [new file with mode: 0644]
uzbl.spec

diff --git a/uzbl-git.patch b/uzbl-git.patch
new file mode 100644 (file)
index 0000000..8d4610a
--- /dev/null
@@ -0,0 +1,11204 @@
+diff --git a/.gitignore b/.gitignore
+index 8c08dc0..092909f 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -1,4 +1,5 @@
+ uzbl-core
++local.mk
+ *.o
+ *.lo
+ *.pyc
+@@ -6,3 +7,4 @@ uzbl-core
+ *~
+ tags
+ sandbox
++/build
+diff --git a/AUTHORS b/AUTHORS
+index b3ce2e2..22d3c9b 100644
+--- a/AUTHORS
++++ b/AUTHORS
+@@ -46,6 +46,7 @@ In alphabetical order:
+     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
++    Håkan Jerning - uzbl-tabbed: autosave_session patch
+     Igor Bogomazov - mouse ptr events
+     Jake Probst <jake.probst@gmail.com> - uzbl_tabbed: multiline tablist, new window opening patches
+     James Campos (aeosynth) <james.r.campos@gmail.com> - Re-orderable gtk notebook tabs in uzbl-tabbed
+diff --git a/Makefile b/Makefile
+index a11fc8d..ba74e57 100644
+--- a/Makefile
++++ b/Makefile
+@@ -1,25 +1,53 @@
++# Create a local.mk file to store default local settings to override the
++# defaults below.
++include $(wildcard local.mk)
++
+ # packagers, set DESTDIR to your "package directory" and PREFIX to the prefix you want to have on the end-user system
+ # end-users who build from source: don't care about DESTDIR, update PREFIX if you want to
+ # RUN_PREFIX : what the prefix is when the software is run. usually the same as PREFIX
+-PREFIX?=/usr/local
+-INSTALLDIR?=$(DESTDIR)$(PREFIX)
+-DOCDIR?=$(INSTALLDIR)/share/uzbl/docs
+-RUN_PREFIX?=$(PREFIX)
++PREFIX     ?= /usr/local
++INSTALLDIR ?= $(DESTDIR)$(PREFIX)
++DOCDIR     ?= $(INSTALLDIR)/share/uzbl/docs
++RUN_PREFIX ?= $(PREFIX)
++
++ENABLE_WEBKIT2 ?= no
++ENABLE_GTK3    ?= auto
++
++PYTHON=python3
++PYTHONV=$(shell $(PYTHON) --version | sed -n /[0-9].[0-9]/p)
++COVERAGE=$(shell which coverage)
++
++# --- configuration ends here ---
++
++ifeq ($(ENABLE_WEBKIT2),auto)
++ENABLE_WEBKIT2 := $(shell pkg-config --exists webkit2gtk-3.0 && echo yes)
++endif
+-# use GTK3-based webkit when it is available
+-USE_GTK3 = $(shell pkg-config --exists gtk+-3.0 webkitgtk-3.0 && echo 1)
++ifeq ($(ENABLE_GTK3),auto)
++ENABLE_GTK3 := $(shell pkg-config --exists gtk+-3.0 && echo yes)
++endif
+-ifeq ($(USE_GTK3),1)
+-      REQ_PKGS += gtk+-3.0 webkitgtk-3.0 javascriptcoregtk-3.0
+-      CPPFLAGS = -DG_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
++ifeq ($(ENABLE_WEBKIT2),yes)
++REQ_PKGS += 'webkit2gtk-3.0 >= 1.2.4' javascriptcoregtk-3.0
++CPPFLAGS += -DUSE_WEBKIT2
++# WebKit2 requires GTK3
++ENABLE_GTK3    := yes
++else
++ifeq ($(ENABLE_GTK3),yes)
++REQ_PKGS += 'webkitgtk-3.0 >= 1.2.4' javascriptcoregtk-3.0
+ else
+-      REQ_PKGS += gtk+-2.0 webkit-1.0 javascriptcoregtk-1.0
+-      CPPFLAGS =
++REQ_PKGS += 'webkit-1.0 >= 1.2.4' javascriptcoregtk-1.0
++endif
+ endif
+-# --- configuration ends here ---
++ifeq ($(ENABLE_GTK3),yes)
++REQ_PKGS += gtk+-3.0
++CPPFLAGS += -DG_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
++else
++REQ_PKGS += gtk+-2.0
++endif
+-REQ_PKGS += libsoup-2.4 gthread-2.0 glib-2.0
++REQ_PKGS += 'libsoup-2.4 >= 2.30' gthread-2.0 glib-2.0
+ ARCH:=$(shell uname -m)
+@@ -33,10 +61,11 @@ LDLIBS:=$(shell pkg-config --libs $(REQ_PKGS) x11)
+ CFLAGS += -std=c99 $(PKG_CFLAGS) -ggdb -W -Wall -Wextra -pedantic -pthread
+-SRC = $(wildcard src/*.c)
++SRC  = $(wildcard src/*.c)
+ HEAD = $(wildcard src/*.h)
+ OBJ  = $(foreach obj, $(SRC:.c=.o),  $(notdir $(obj)))
+ LOBJ = $(foreach obj, $(SRC:.c=.lo), $(notdir $(obj)))
++PY = $(wildcard uzbl/*.py uzbl/plugins/*.py)
+ all: uzbl-browser
+@@ -46,7 +75,13 @@ ${OBJ}: ${HEAD}
+ uzbl-core: ${OBJ}
+-uzbl-browser: uzbl-core
++uzbl-browser: uzbl-core uzbl-event-manager
++
++build: ${PY}
++      $(PYTHON) setup.py build
++
++.PHONY: uzbl-event-manager
++uzbl-event-manager: build
+ # the 'tests' target can never be up to date
+ .PHONY: tests
+@@ -61,38 +96,43 @@ tests: ${LOBJ} force
+       $(CC) -shared -Wl ${LOBJ} -o ./tests/libuzbl-core.so
+       cd ./tests/; $(MAKE)
++test-event-manager: force
++      ${PYTHON} -m unittest discover tests/event-manager -v
++
++coverage-event-manager: force
++      ${PYTHON} ${COVERAGE} erase
++      ${PYTHON} ${COVERAGE} run -m unittest discover tests/event-manager
++      ${PYTHON} ${COVERAGE} html ${PY}
++      # Hmm, I wonder what a good default browser would be
++      uzbl-browser htmlcov/index.html
++
+ test-uzbl-core: uzbl-core
+       ./uzbl-core --uri http://www.uzbl.org --verbose
+ test-uzbl-browser: uzbl-browser
+       ./bin/uzbl-browser --uri http://www.uzbl.org --verbose
+-test-uzbl-core-sandbox: uzbl-core
+-      make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core
+-      make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data
+-      cp -np ./misc/env.sh ./sandbox/env.sh
++test-uzbl-core-sandbox: sandbox uzbl-core sandbox-install-uzbl-core sandbox-install-example-data
+       . ./sandbox/env.sh && uzbl-core --uri http://www.uzbl.org --verbose
+       make DESTDIR=./sandbox uninstall
+       rm -rf ./sandbox/usr
+-test-uzbl-browser-sandbox: uzbl-browser
+-      make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-browser
+-      make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data
+-      cp -np ./misc/env.sh ./sandbox/env.sh
+-      -. ./sandbox/env.sh && uzbl-event-manager restart -avv
++test-uzbl-browser-sandbox: sandbox uzbl-browser sandbox-install-uzbl-browser sandbox-install-example-data
++      . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` restart -navv &
+       . ./sandbox/env.sh && uzbl-browser --uri http://www.uzbl.org --verbose
+-      . ./sandbox/env.sh && uzbl-event-manager stop -ivv
++      . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` stop -vv -o /dev/null
+       make DESTDIR=./sandbox uninstall
+       rm -rf ./sandbox/usr
+-test-uzbl-tabbed-sandbox: uzbl-browser
+-      make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-browser
+-      make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-tabbed
+-      make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data
+-      cp -np ./misc/env.sh ./sandbox/env.sh
+-      -. ./sandbox/env.sh && uzbl-event-manager restart -avv
++test-uzbl-tabbed-sandbox: sandbox uzbl-browser sandbox-install-uzbl-browser sandbox-install-uzbl-tabbed sandbox-install-example-data
++      . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` restart -avv
+       . ./sandbox/env.sh && uzbl-tabbed
+-      . ./sandbox/env.sh && uzbl-event-manager stop -ivv
++      . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` stop -avv
++      make DESTDIR=./sandbox uninstall
++      rm -rf ./sandbox/usr
++
++test-uzbl-event-manager-sandbox: sandbox uzbl-browser sandbox-install-uzbl-browser sandbox-install-example-data
++      . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` restart -navv
+       make DESTDIR=./sandbox uninstall
+       rm -rf ./sandbox/usr
+@@ -102,18 +142,44 @@ clean:
+       find ./examples/ -name "*.pyc" -delete
+       cd ./tests/; $(MAKE) clean
+       rm -rf ./sandbox/
++      $(PYTHON) setup.py clean
+ strip:
+       @echo Stripping binary
+       @strip uzbl-core
+       @echo ... done.
++SANDBOXOPTS=\
++      DESTDIR=./sandbox\
++      RUN_PREFIX=`pwd`/sandbox/usr/local\
++      PYINSTALL_EXTRA='--prefix=./sandbox/usr/local --install-scripts=./sandbox/usr/local/bin'
++
++sandbox: misc/env.sh
++      mkdir -p sandbox/${PREFIX}/lib64
++      cp -p misc/env.sh sandbox/env.sh
++      test -e sandbox/${PREFIX}/lib || ln -s lib64 sandbox/${PREFIX}/lib
++
++sandbox-install-uzbl-browser:
++      make ${SANDBOXOPTS} install-uzbl-browser
++
++sandbox-install-uzbl-tabbed:
++      make ${SANDBOXOPTS} install-uzbl-tabbed
++
++sandbox-install-uzbl-core:
++      make ${SANDBOXOPTS} install-uzbl-core
++
++sandbox-install-event-manager:
++      make ${SANDBOXOPTS} install-event-manager
++
++sandbox-install-example-data:
++      make ${SANDBOXOPTS} install-example-data
++
+ install: install-uzbl-core install-uzbl-browser install-uzbl-tabbed
+ install-dirs:
+       [ -d "$(INSTALLDIR)/bin" ] || install -d -m755 $(INSTALLDIR)/bin
+-install-uzbl-core: all install-dirs
++install-uzbl-core: uzbl-core install-dirs
+       install -d $(INSTALLDIR)/share/uzbl/
+       install -d $(DOCDIR)
+       install -m644 docs/* $(DOCDIR)/
+@@ -123,8 +189,7 @@ install-uzbl-core: all install-dirs
+       install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core
+ install-event-manager: install-dirs
+-      sed "s#^PREFIX = .*#PREFIX = '$(RUN_PREFIX)'#" < bin/uzbl-event-manager > $(INSTALLDIR)/bin/uzbl-event-manager
+-      chmod 755 $(INSTALLDIR)/bin/uzbl-event-manager
++      $(PYTHON) setup.py install --prefix=$(PREFIX) --install-scripts=$(INSTALLDIR)/bin $(PYINSTALL_EXTRA)
+ install-uzbl-browser: install-dirs install-uzbl-core install-event-manager
+       sed 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' < bin/uzbl-browser > $(INSTALLDIR)/bin/uzbl-browser
+diff --git a/README b/README
+index b124fb4..5241aba 100644
+--- a/README
++++ b/README
+@@ -252,18 +252,42 @@ The following commands are recognized:
+   - Open the print dialog.
+ * `include <file>`
+   - Read contents of `<file>` and interpret as a set of `uzbl` commands.
+-* `show_inspector`
+-  - Show the WebInspector
++* `inspector <show | hide | coord <x> <y> | node <node-spec>>`
++  - Control the inspector. The `coord` command coordinates are relative to the
++    viewport, not the page. The `node` subcommand requires webkitgtk >=
++    1.3.17.
++* `spell_checker <ignore <word>... | learn <word>... | autocorrect <word> | guesses <word>>`
++  - Control the spell checker. Requires webkitgtk >= 1.5.1.
+ * `add_cookie <domain> <path> <name> <value> <scheme> <expires>`
+   - Adds a new cookie to the cookie jar
+ * `delete_cookie <domain> <path> <name> <value> [<scheme> <expires>]`
+   - Deletes a matching cookie from the cookie jar. scheme and expire time
+-        is currently not considered when matching.
++    is currently not considered when matching.
+ * `clear_cookies`
+   - Clears all cookies from the cookie jar
+ * `download <uri> [<destination path>]`
+   - Starts a download using the given uri. A destination file path can be given
+     to specify where the download should be written to.
++* `auth <uniqueid> <username> <password>`
++  - Authenticate as `<username>` with `<password>` for the previously issued
++    challenge with the id `<uniqueid>`. authenticating for a invalid id or one
++    expired one has no effect.
++* `snapshot <path>`
++  - Saves an image of the visible page as a PNG to the given path. Only available
++    with webkitgtk >= 1.9.6. This is not in webkit2gtk.
++* `load <string> <uri> [<baseuri>]`
++  - Load a string as text/html with the given uri. If given, all links will be
++    assumed relative to baseuri. Requires webkit2gtk >= 1.9.90.
++* `save [<format> [<path>]]`
++  - Saves the current page to a file in a given format (currently only "mhtml"
++    is supported). Requires webkit2gtk >= 1.9.90.
++* `remove_all_db`
++  - Removes all of the web databases from the current database directory path.
++* `plugin_refresh`
++  - Refreshes the plugin database. Requires webkitgtk >= 1.3.8.
++* `plugin_toggle [<plugin name> [<plugin name>...]]`
++  - Toggles whether the plugins named as arguments are enabled. No arguments is
++    interpreted as "all plugins". Requires webkitgtk >= 1.3.8.
+ ### VARIABLES AND CONSTANTS
+@@ -283,8 +307,10 @@ file).
+ * `uri`: The URI of the current page. (callback: load the uri)
+ * `verbose`: Controls the verbosity printed to `stdout`.
++* `inject_text`: Inject an text string, navigating to the URI "about:blank" and
++  rendering the text string given. Only available in webkit2gtk.
+ * `inject_html`: Inject an HTML string, navigating to the URI "about:blank" and
+-  rendering the HTML sting given.
++  rendering the HTML string given.
+ * `geometry`: Geometry and position of the Uzbl window. Format is
+   "<width>x<height>+<x-offset>+<y-offset>".
+ * `keycmd`: Holds the input buffer (callback: update input buffer).
+@@ -333,11 +359,16 @@ file).
+ * `useragent`: The User-Agent to send to the browser, expands variables in its
+   definition.
+ * `accept_languages`: The Accept-Language header to send with HTTP requests.
++* `transparent`: If set to 1, the background of the view will be transparent
++  (default 0).
++* `view_mode`: The view mode for webkit. One of: "windowed", "floating",
++  "fullscreen", "maximized", or "minimized". Requires webkitgtk >= 1.3.4.
+ * `zoom_level`: The factor by which elements in the page are scaled with respect
+   to their original size. Setting this will resize the currently displayed page.
+ * `zoom_type`: Whether to use "full-content" zoom (defaults to true). With
+   full-content zoom on, all page content, not just text, is zoomed. When
+-  full-content zoom is off, only the text of a page is zoomed.
++  full-content zoom is off, only the text of a page is zoomed. This is
++  unavailable with webkit2gtk. Use `zoom_text_only` instead.
+ * `font_size`: The default font size.
+ * `default_font_family`: The default font family used to display text.
+ * `monospace_font_family`: The default font family used to display monospace
+@@ -349,7 +380,6 @@ file).
+ * `fantasy_font_family`: The default Fantasy font family used to display text.
+ * `monospace_size`: The default size of monospaced font (default 1).
+ * `minimum_font_size`: The minimum font size used to display text (default 1).
+-* `enable_pagecache`: Enable the webkit pagecache (it caches rendered pages for a speedup when you go back or forward in history) (default 0).
+ * `enable_plugins`: Disable embedded plugin objects (default 0).
+ * `enable_scripts`: Disable embedded scripting languages (default 0).
+ * `autoload_images`: Automatically load images (default 1).
+@@ -360,25 +390,109 @@ file).
+   `en_CA` or `pt_BR`) to be used for spell checking, separated by commas.
+   Defaults to the value returned by `gtk_get_default_language`.
+ * `enable_private`: Whether to enable private browsing mode (default 0).
++* `cookie_policy`: If set to 0, all cookies are accepted, if set to 1, all
++  cookies are rejected, and 2 rejects third party cookies (default 0).
+ * `print_backgrounds`: Print background images? (default 0).
+ * `stylesheet_uri`: Use this to override the pagelayout with a custom
+   stylesheet.
+ * `resizable_text_areas`: Whether text areas can be resized (default 0).
+ * `default_encoding`: The default text encoding (default "iso-8859-1").
+-* `current_encoding`: This can be set to force a text encoding.
++* `custom_encoding`: This can be set to force a text encoding. (Used to be
++  `current_encoding` which is now read-only).
+ * `enforce_96_dpi`: Enforce a resolution of 96 DPI (default 1).
++* `editable`: Whether the page can be edited or not (default 0).
+ * `caret_browsing`: Whether the caret is enabled in the text portion of pages
+   (default 0).
+ * `enable_cross_file_access`: Whether a page loaded from a `file://` URI can
+   access the contents of other `file://` URIs. (default 0).
+ * `follow_hint_keys`: keys for keyboard-based navigation and link
+-   highlighting
++  highlighting
+ * `handle_multi_click`: If set to 1, event handlers attached to `2Button*`
+-   and `3Button*` bindings will only be used instead of the default actions in
+-   WebKit (default 0).
++  and `3Button*` bindings will only be used instead of the default actions in
++  WebKit (default 0).
+ * `ssl_ca_file`: File that contains CA certificates.
+ * `ssl_verify`: If set to 1, uzbl won't connect to "https" url unless it can
+    validate certificate presented by remote server against `ssl_ca_file`.
++* `enable_builtin_auth`: Enable WebKits builtin authentication handler
++* `enable_java_applet`: If set to 1, support for Java <applet> tags will be
++  enabled (default 1).
++* `enable_database`: If set to 1, support for HTML5 client-side SQL databases
++  will be enabled (default 1).
++* `enable_local_storage`: If set to 1, websites will be able to store data
++  locally (default 1).
++* `enable_pagecache`: If set to 1, uzbl will store previously visited pages for
++  faster access (the cache is local to each uzbl instance) (default 0).
++* `enable_offline_app_cache`: If set to 1, web applications may be cached locally
++  for offline use (default 1).
++* `enable_universal_file_access`: If set to 1, allow `file://` URIs to access
++  all pages (default 0).
++* `enable_hyperlink_auditing`: If set to 1, the `ping` attribute on anchors will
++  be supported (default 0).
++* `zoom_step`: The change in the zoon level when zooming (default 0.1).
++* `auto_resize_window`: If set to 1, allow web pages to change window dimensions
++  (default 0).
++* `enable_spatial_navigation`: If set to 1, the arrow keys in `Ins` mode will
++  navigate between form elements (default 0).
++* `editing_behavior`: When set to 0, emulate Mac behavior in text fields, 1
++  for Windows behavior, and 2 for *nix behavior (the default).
++* `enable_tab_cycle`: If set to 1, the `Tab` key cycles between elements on
++  the page (default 1).
++* `default_context_menu`: If set to 0, do not cause context menus to appear when
++  right clicking (default 1).
++* `enable_site_workarounds`: If set to 1, enable filters to help unbreak
++  certain websites (default 0).
++* `javascript_clipboard`: If set to 1, JavaScript may access the clipboard
++  (default 0). Requires webkitgtk >= 1.3.0.
++* `javascript_dom_paste`: If set to 1, JavaScript will able to paste from the
++  clipboard (default 0).
++* `enable_frame_flattening`: If set to 1, flatten all frames into a single
++  page to become one scrollable page (default 0). Requires webkitgtk >= 1.3.5.
++* `enable_fullscreen`: If set to 1, Mozilla-style fullscreening will be
++  available (default 0). Requires webkitgtk >= 1.3.8
++* `enable_dns_prefetch`: If set to 1, domain names will be prefetched
++  (default 1). Private browsing does *not* affect this value. Requires
++  webkitgtk >= 1.3.13.
++* `display_insecure_content`: If set to 1, non-HTTPS content will be displayed
++  on HTTPS pages (default 1). Requires webkitgtk >= 1.11.13.
++* `run_insecure_content`: If set to 1, non-HTTPS content will be allowed to run
++  on HTTPS pages (default 1). Requires webkitgtk >= 1.11.13.
++* `maintain_history`: If set to 1, the back/forward list will be kept. (default
++  1).
++* `enable_webgl`: If set to 1, WebGL support will be enabled (default 0).
++  Requires webkitgtk >= 1.3.14.
++* `local_storage_path`: Where to store local databases (default
++  $XDG_DATA_HOME/webkit/databases/). Requires webkit >= 1.5.2.
++* `enable_webaudio`: If set to 1, allows JavaScript to generate audio
++  directly (default 0). Requires webkit >= 1.7.5.
++* `enable_3d_acceleration`: If set to 1, the GPU will be used to render
++  animations and 3D CSS transformations. Requires webkitgtk >= 1.7.90.
++* `zoom_text_only`: If set to 1, only text will be zoomed (default 0). Requires
++  webkit2gtk >= 1.7.91.
++* `enable_smooth_scrolling`: If set to 1, scrolling the page will be smoothed
++  (default 0). Requires webkitgtk >= 1.9.0.
++* `enable_inline_media`: If set to 1, inline playback of media is allowed,
++  otherwise, only full-screen playback is allowed (default 1). Requires
++  webkitgtk >= 1.9.3.
++* `require_click_to_play`: If set to 1, playback of media requires user
++  interaction before playing, otherwise, media will be allowed to autoplay
++  (default 0). Requires webkitgtk >= 1.9.3.
++* `enable_css_shaders`: If set to 1, CSS shaders will be enabled (default 0).
++  Requires webkitgtk >= 1.11.1.
++* `enable_media_stream`: If set to 1, web pages will be able to access the
++  local video and audio input devices (default 0). Requires webkitgtk >= 1.11.1.
++* `cache_model`: The cache model of webkit. Valid values:
++  "document_viewer" (no caching; low memory; usage: single local file),
++  "web_browser" (heavy caching; faster; usage: general browsing),
++  "document_browser" (moderate caching; usage: series of local files)
++  (default "web_browser").
++* `app_cache_size`: The maximum size of the application cache (in bytes)
++  (default UINT_MAX (no quota)). Changing the variable clears the cache.
++  Requires webkitgtk >= 1.3.13.
++* `web_database_directory`: The directory where web databases are stored.
++  (default is under $XDG_DATA_HOME).
++* `web_database_quota`: The default quota for web databases. (default 5MB).
++* `profile_js`: Sets whether to profile JavaScript code.
++* `profile_timeline`: Sets whether to profile the timeline.
+ #### Constants (not dumpable or writeable)
+@@ -396,6 +510,13 @@ file).
+   - overridable with cmdline arg
+   - in GtkSocket mode, this is a random number to prevent name clashes
+ * `PID`: The process ID of this Uzbl instance.
++* `current_encoding`: The current encoding of the web page.
++* `inspected_uri`: The URI that is being inspected. Requires webkitgtk >=
++  1.3.17.
++* `app_cache_directory`: The directory webkit uses to store its cache.
++  Requires webkitgtk >= 1.3.13.
++* `plugin_list`: A JSON list of objects describing the available plugins.
++  Requires webkitgtk >= 1.3.8.
+ ### VARIABLE EXPANSION AND COMMAND / JAVASCRIPT SUBSTITUTION
+@@ -514,10 +635,10 @@ access to the following environment variables:
+ * `$UZBL_SOCKET`: The filename of the Unix socket being used, if any.
+ * `$UZBL_URI`: The URI of the current page.
+ * `$UZBL_TITLE`: The current page title.
++* `$UZBL_PRIVATE`: Set if uzbl is in "private browsing mode", unset otherwise.
+-Handler scripts (`download_handler`, `cookie_handler`, `scheme_handler`,
+-`request_handler`, and `authentication_handler`) are called with special
+-arguments:
++Handler scripts (`download_handler`, `scheme_handler`, and `request_handler`)
++are called with special arguments:
+ * download handler
+@@ -532,16 +653,6 @@ arguments:
+     that the file should be saved to. A download handler using WebKit's internal
+     downloader can just echo this path and exit when this argument is present.
+-* cookie handler
+-
+-  - `$1 GET/PUT`: Whether a cookie should be sent to the server (`GET`) or
+-    stored by the browser (`PUT`).
+-  - `$2 scheme`: Either `http` or `https`.
+-  - `$3 host`: If current page URL is `www.example.com/somepage`, this could be
+-    something else than `example.com`, eg advertising from another host.
+-  - `$4 path`: The request address path.
+-  - `$5 data`: The cookie data. Only included for `PUT` requests.
+-
+ * scheme handler
+   - `$1 URI` of the page to be navigated to
+@@ -550,13 +661,6 @@ arguments:
+   - `$1 URI` of the resource which is being requested
+-* authentication handler:
+-
+-  - `$1`: authentication zone unique identifier
+-  - `$2`: domain part of URL that requests authentication
+-  - `$3`: authentication realm
+-  - `$4`: FALSE if this is the first attempt to authenticate, TRUE otherwise
+-
+ ### Formfiller.sh
+ Example config entries for formfiller script
+@@ -584,47 +688,33 @@ after closing the editor, it will load the data into the formfields. The temp
+ file is removed
+ ### HTTP/BASIC AUTHENTICATION
++HTTP auth can be handled in two different ways. Using the builtin auth dialog
++in WebKit or by dispatching the work to a external script.
+-You can use the authentication_handler variable to denote how http
+-authentication should be handled.
+-If this variable is:
+-
+-* not set or empty: use webkit internal auth dialog
+-* a valid handler (i.e. {sh,sync}_spawn correct_script), use this handler
+-* innvalid handler (spawn, some other command, uses script that does not
+-  print anything): skip authentication.
+-
+-Example:
++To use the builtin auth dialog set `enable_builtin_auth` to 1. With this set
++you'll get a basic GTK+ prompt for username/password when trying to access a
++protected site.
+-  set authentication_handler = sync_spawn /patch/to/your/script
++Whenever authentication is needed the `AUTHENTICATE` event will be sent, this
++is what you would use to hook up a custom authentication system. This event
++will be sent even when using the bultin dialog so remember to disable that if
++adding a dialog of your own.
+-Script will be executed on each authentication request.
+-It will receive four auth-related parameters:
++The `AUTHENTICATE` event has four arguments
++ * a unique identifier to be used in the exchange
++ * domain part of URL that requests authentication
++ * authentication realm
++ * the empty string for the first attempt and "retrying" for further attempts
+-    $1 authentication zone unique identifier (may be used as 'key')
+-    $2 domain part of URL that requests authentication
+-    $3 authentication realm
+-    $4 FALSE if this is the first attempt to authenticate, TRUE otherwise
++After this event has been sent the request is paused until credentials are
++provided. This is done using the `auth` command e.g:
+-Script is expected to print exactly two lines of text on stdout (that means
+-its output must contain exactly two '\n' bytes).
+-The first line contains username, the second one - password.
+-If authentication fails, script will be executed again (with $4 = TRUE).
+-Non-interactive scripts should handle this case and do not try to
+-authenticate again to avoid loops. If number of '\n' characters in scripts
+-output does not equal 2, authentication will fail.
+-That means 401 error will be displayed and uzbl won't try to authenticate anymore.
++    `auth "uniqueid" "alice" "wonderland"`
+-The simplest example of authentication handler script is:
++A super simple setup that will always try to authenticate with the same password
++could look like this. (assuming aliases from the example configuration)
+-#!/bin/sh
+-[ "$4" == "TRUE ] && exit
+-echo alice
+-echo wonderland
+-
+-This script tries to authenticate as user alice with password wonderland once
+-and never retries authentication.
+-See examples for more sofisticated, interactive authentication handler.
++@on_event AUTHENTICATE auth "%1" "alice" "wonderland"
+ ### WINDOW MANAGER INTEGRATION
+@@ -657,11 +747,6 @@ The EM allows:
+ * Many fine-grained events (`hover_over_link`, `key_press`, `key_release`,..)
+ * See example `uzbl-event-manager`.
+-**Note**: Cookie events are sent in addition to (optionally) being handled by
+-  the cookie handler (set by the cookie_handler var). If using a handler it will
+-  take precedence before the internal state configured by (add|delete)_cookie
+-  commands.
+-
+ Events have this format:
+      EVENT [uzbl_instance_name] EVENT_NAME event_details
+@@ -687,14 +772,18 @@ Events have this format:
+   loaded. `uri` is the URI of the page being loaded.
+ * `EVENT [uzbl_instance_name] LOAD_START uri`: A change of the page has been
+   requested. `uri` is the current URI; the one being departed.
+-* `EVENT [uzbl_instance_name] LOAD_FINISHED uri`: Loading has finished for the
++* `EVENT [uzbl_instance_name] LOAD_FINISH uri`: Loading has finished for the
+   page at `uri`.
+ * `EVENT [uzbl_instance_name] LOAD_ERROR uri reason_of_error`: The URI `uri`
+   could not be loaded for the reason described in `reason_of_error`.
+ * `EVENT [uzbl_instance_name] LOAD_PROGRESS percentage` : While the page is
+   loading, gives the `percentage` of the page that has finished loading.
++* `EVENT [uzbl_instance_name] REQUEST_QUEUED uri`: http resource gets
++  enqueued
+ * `EVENT [uzbl_instance_name] REQUEST_STARTING uri`: http resource gets
+   requested
++* `EVENT [uzbl_instance_name] REQUEST_FINISHED uri`: http resource has finished
++  loading
+ * `EVENT [uzbl_instance_name] TITLE_CHANGED title_name`: When the title of the
+   page (and hence maybe, the window title) changed. `title_name` is the new
+   title.
+@@ -743,6 +832,8 @@ Events have this format:
+   be a unix-timestamp or empty
+ * `EVENT [uzbl_instance_name] DELETE_COOKIE domain path name value scheme expire`:
+   When a cookie was deleted. arguments as ADD_COOKIE
++* `EVENT [uzbl_instance_name] AUTHENTICATE uniqueid host realm retry`: When a
++  request requires authentication. authentication is done by calling `auth`
+ Events/requests which the EM and its plugins listens for
+diff --git a/bin/uzbl-browser b/bin/uzbl-browser
+index fb9a368..4381050 100755
+--- a/bin/uzbl-browser
++++ b/bin/uzbl-browser
+@@ -67,9 +67,9 @@ fi
+ # uzbl-event-manager will exit if one is already running.
+ # we could also check if its pid file exists to avoid having to spawn it.
+ DAEMON_SOCKET="$XDG_CACHE_HOME"/uzbl/event_daemon
+-#if [ ! -f "$DAEMON_SOCKET".pid ]
+-#then
++if [ ! -f "$DAEMON_SOCKET".pid ]
++then
+       ${UZBL_EVENT_MANAGER:-uzbl-event-manager -va start}
+-#fi
++fi
+ exec uzbl-core "$@" ${config_file:+--config "$config_file"} --connect-socket $DAEMON_SOCKET
+diff --git a/bin/uzbl-event-manager b/bin/uzbl-event-manager
+index 56253ef..221fa73 100755
+--- a/bin/uzbl-event-manager
++++ b/bin/uzbl-event-manager
+@@ -1,1011 +1,3 @@
+-#!/usr/bin/env python2
+-
+-# Event Manager for Uzbl
+-# 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
+-# it under the terms of the GNU General Public License as published by
+-# the Free Software Foundation, either version 3 of the License, or
+-# (at your option) any later version.
+-#
+-# This program is distributed in the hope that it will be useful,
+-# but WITHOUT ANY WARRANTY; without even the implied warranty of
+-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-# GNU General Public License for more details.
+-#
+-# You should have received a copy of the GNU General Public License
+-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-
+-'''
+-
+-E V E N T _ M A N A G E R . P Y
+-===============================
+-
+-Event manager for uzbl written in python.
+-
+-'''
+-
+-import atexit
+-import imp
+-import logging
+-import os
+-import sys
+-import time
+-import weakref
+-import re
+-import errno
+-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, SIGKILL
+-from socket import socket, AF_UNIX, SOCK_STREAM, error as socket_error
+-from traceback import format_exc
+-
+-
+-def xdghome(key, default):
+-    '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise
+-    use $HOME and the default path.'''
+-
+-    xdgkey = "XDG_%s_HOME" % key
+-    if xdgkey in os.environ.keys() and os.environ[xdgkey]:
+-        return os.environ[xdgkey]
+-
+-    return os.path.join(os.environ['HOME'], default)
+-
+-# `make install` will put the correct value here for your system
+-PREFIX = '/usr/local/'
+-
+-# Setup xdg paths.
+-DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
+-CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
+-
+-# Define some globals.
+-SCRIPTNAME = os.path.basename(sys.argv[0])
+-
+-logger = logging.getLogger(SCRIPTNAME)
+-
+-
+-def get_exc():
+-    '''Format `format_exc` for logging.'''
+-    return "\n%s" % format_exc().rstrip()
+-
+-
+-def expandpath(path):
+-    '''Expand and realpath paths.'''
+-    return os.path.realpath(os.path.expandvars(path))
+-
+-
+-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:
+-        logger.critical('failed to daemonize', exc_info=True)
+-        sys.exit(1)
+-
+-    os.chdir('/')
+-    os.setsid()
+-    os.umask(0)
+-
+-    try:
+-        if os.fork():
+-            os._exit(0)
+-
+-    except OSError:
+-        logger.critical('failed to daemonize', exc_info=True)
+-        sys.exit(1)
+-
+-    if sys.stdout.isatty():
+-        sys.stdout.flush()
+-        sys.stderr.flush()
+-
+-    devnull = '/dev/null'
+-    stdin = file(devnull, 'r')
+-    stdout = file(devnull, 'a+')
+-    stderr = file(devnull, 'a+', 0)
+-
+-    os.dup2(stdin.fileno(), sys.stdin.fileno())
+-    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.'''
+-
+-    try:
+-        dirname = os.path.dirname(path)
+-        if not os.path.isdir(dirname):
+-            logger.debug('creating directories %r', dirname)
+-            os.makedirs(dirname)
+-
+-    except OSError:
+-        logger.error('failed to create directories', exc_info=True)
+-
+-
+-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.'''
+-
+-    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 __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))
+-
+-        if self.kwargs:
+-            elems.append('kwargs=%s' % repr(self.kwargs))
+-
+-        elems.append('plugin=%s' % self.plugin.name)
+-        return u'<handler(%s)>' % ', '.join(elems)
+-
+-    def call(self, uzbl, *args, **kwargs):
+-        '''Execute the handler function and merge argument lists.'''
+-
+-        args = args + self.args
+-        kwargs = dict(self.kwargs.items() + kwargs.items())
+-        self.callback(uzbl, *args, **kwargs)
+-
+-
+-class Plugin(object):
+-    '''Plugin module wrapper object.'''
+-
+-    # Special functions exported from the Plugin instance to the
+-    # plugin namespace.
+-    special_functions = ['require', 'export', 'export_dict', 'connect',
+-            'connect_dict', 'logger', 'unquote', 'splitquoted']
+-
+-    def __init__(self, parent, name, path, plugin):
+-        self.parent = parent
+-        self.name = name
+-        self.path = path
+-        self.plugin = plugin
+-        self.logger = logging.getLogger('plugin.%s' % name)
+-
+-        # Weakrefs to all handlers created by this plugin
+-        self.handlers = set([])
+-
+-        # Plugins init hook
+-        init = getattr(plugin, 'init', None)
+-        self.init = init if callable(init) else None
+-
+-        # Plugins optional after hook
+-        after = getattr(plugin, 'after', None)
+-        self.after = after if callable(after) else None
+-
+-        # Plugins optional cleanup hook
+-        cleanup = getattr(plugin, 'cleanup', None)
+-        self.cleanup = cleanup if callable(cleanup) else None
+-
+-        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 __repr__(self):
+-        return u'<plugin(%r)>' % self.plugin
+-
+-    def export(self, uzbl, attr, obj, prepend=True):
+-        '''Attach `obj` to `uzbl` instance. This is the preferred method
+-        of sharing functionality, functions, data and objects between
+-        plugins.
+-
+-        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.
+-        '''
+-
+-        assert attr not in uzbl.exports, "attr %r already exported by %r" %\
+-            (attr, uzbl.exports[attr][0])
+-
+-        prepend = True if prepend and callable(obj) else False
+-        uzbl.__dict__[attr] = partial(obj, uzbl) if prepend else obj
+-        uzbl.exports[attr] = (self, obj, prepend)
+-        uzbl.logger.info('exported %r to %r by plugin %r, prepended %r',
+-            obj, 'uzbl.%s' % attr, self.name, prepend)
+-
+-    def export_dict(self, uzbl, exports):
+-        for (attr, object) in exports.items():
+-            self.export(uzbl, attr, object)
+-
+-    def find_handler(self, event, callback, args, kwargs):
+-        '''Check if a handler with the identical callback and arguments
+-        exists and return it.'''
+-
+-        # 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 connect(self, uzbl, event, callback, *args, **kwargs):
+-        '''Create an event handler object which handles `event` events.
+-
+-        Arguments passed to the connect function (`args` and `kwargs`) are
+-        stored in the handler object and merged with the event arguments
+-        come handler execution.
+-
+-        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
+-
+-        assert callable(callback), 'callback must be callable'
+-
+-        # 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)
+-
+-        uzbl.handlers[event].append(handler)
+-        uzbl.logger.info('connected %r', handler)
+-        return handler
+-
+-    def connect_dict(self, uzbl, connects):
+-        for (event, callback) in connects.items():
+-            self.connect(uzbl, event, callback)
+-
+-    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)
+-
+-    @classmethod
+-    def unquote(cls, s):
+-        '''Removes quotation marks around strings if any and interprets
+-        \\-escape sequences using `string_escape`'''
+-        if s and s[0] == s[-1] and s[0] in ['"', "'"]:
+-            s = s[1:-1]
+-        return s.encode('utf-8').decode('string_escape').decode('utf-8')
+-
+-    _splitquoted = re.compile("( |\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')")
+-
+-    @classmethod
+-    def splitquoted(cls, text):
+-        '''Splits string on whitespace while respecting quotations'''
+-        parts = cls._splitquoted.split(text)
+-        return [cls.unquote(p) for p in parts if p.strip()]
+-
+-
+-class Uzbl(object):
+-    def __init__(self, parent, child_socket):
+-        self.opts = opts
+-        self.parent = parent
+-        self.child_socket = child_socket
+-        self.child_buffer = []
+-        self.time = time.time()
+-        self.pid = None
+-        self.name = None
+-
+-        # Flag if the instance has raised the INSTANCE_START event.
+-        self.instance_start = False
+-
+-        # Use name "unknown" until name is discovered.
+-        self.logger = logging.getLogger('uzbl-instance[]')
+-
+-        # Track plugin event handlers and exported functions.
+-        self.exports = {}
+-        self.handlers = defaultdict(list)
+-
+-        # Internal vars
+-        self._depth = 0
+-        self._buffer = ''
+-
+-    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)
+-
+-        # 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)
+-
+-    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"
+-
+-        if opts.print_events:
+-            print ascii(u'%s<-- %s' % ('  ' * self._depth, msg))
+-
+-        self.child_buffer.append(ascii("%s\n" % msg))
+-
+-    def do_send(self):
+-        data = ''.join(self.child_buffer)
+-        try:
+-            bsent = self.child_socket.send(data)
+-        except socket_error as e:
+-            if e.errno in (errno.EAGAIN, errno.EINTR):
+-                self.child_buffer = [data]
+-                return
+-            else:
+-                self.logger.error('failed to send', exc_info=True)
+-                return self.close()
+-        else:
+-            if bsent == 0:
+-                self.logger.debug('write end of connection closed')
+-                self.close()
+-            elif bsent < len(data):
+-                self.child_buffer = [data[bsent:]]
+-            else:
+-                del self.child_buffer[:]
+-
+-    def read(self):
+-        '''Read data from the child socket and pass lines to the parse_msg
+-        function.'''
+-
+-        try:
+-            raw = unicode(self.child_socket.recv(8192), 'utf-8', 'ignore')
+-            if not raw:
+-                self.logger.debug('read null byte')
+-                return self.close()
+-
+-        except:
+-            self.logger.error('failed to read', exc_info=True)
+-            return self.close()
+-
+-        lines = (self._buffer + raw).split('\n')
+-        self._buffer = lines.pop()
+-
+-        for line in filter(None, map(unicode.strip, lines)):
+-            try:
+-                self.parse_msg(line.strip())
+-
+-            except:
+-                self.logger.error(get_exc())
+-                self.logger.error('erroneous event: %r' % line)
+-
+-    def parse_msg(self, line):
+-        '''Parse an incoming message from a uzbl instance. Event strings
+-        will be parsed into `self.event(event, args)`.'''
+-
+-        # Split by spaces (and fill missing with nulls)
+-        elems = (line.split(' ', 3) + [''] * 3)[:4]
+-
+-        # Ignore non-event messages.
+-        if elems[0] != 'EVENT':
+-            logger.info('non-event message: %r', line)
+-            if opts.print_events:
+-                print '--- %s' % ascii(line)
+-            return
+-
+-        # 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 = logging.getLogger('uzbl-instance%s' % name)
+-            self.logger.info('found instance name %r', name)
+-
+-        assert self.name == name, 'instance name mismatch'
+-
+-        # Handle the event with the event handlers through the event method
+-        self.event(event, args)
+-
+-    def event(self, event, *args, **kargs):
+-        '''Raise an event.'''
+-
+-        event = event.upper()
+-
+-        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
+-            try:
+-                handler.call(self, *args, **kargs)
+-
+-            except:
+-                self.logger.error('error in handler', exc_info=True)
+-
+-            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 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:
+-            if self.child_socket:
+-                self.logger.debug('closing child socket')
+-                self.child_socket.close()
+-
+-        except:
+-            self.logger.error('failed to close socket', exc_info=True)
+-
+-        finally:
+-            self.child_socket = None
+-
+-        # 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(object):
+-    def __init__(self):
+-        self.opts = opts
+-        self.server_socket = 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(opts.pid_file)
+-
+-        # Register a function to clean up the socket and pid file on exit.
+-        atexit.register(self.quit)
+-
+-        # Add signal handlers.
+-        for sigint in [SIGTERM, SIGINT]:
+-            signal(sigint, self.quit)
+-
+-        # Load plugins into self.plugins
+-        self.load_plugins(opts.plugins)
+-
+-    def load_plugins(self, plugins):
+-        '''Load event manager plugins.'''
+-
+-        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
+-
+-            info = imp.find_module(name, [dir])
+-            module = imp.load_module(name, *info)
+-
+-            # 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 create_server_socket(self):
+-        '''Create the event manager daemon socket for uzbl instance duplex
+-        communication.'''
+-
+-        # Close old socket.
+-        self.close_server_socket()
+-
+-        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.'''
+-
+-        logger.debug('entering main loop')
+-
+-        # Create and listen on the server socket
+-        self.create_server_socket()
+-
+-        if opts.daemon_mode:
+-            # Daemonize the process
+-            daemonize()
+-
+-            # Update the pid file
+-            make_pid_file(opts.pid_file)
+-
+-        try:
+-            # Accept incoming connections and listen for incoming data
+-            self.listen()
+-
+-        except:
+-            if not self._quit:
+-                logger.critical('failed to listen', exc_info=True)
+-
+-        # 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.'''
+-
+-        logger.info('listening on %r', opts.server_socket)
+-
+-        # Count accepted connections
+-        connections = 0
+-
+-        while (self.uzbls or not connections) or (not opts.auto_close):
+-            socks = [self.server_socket] + self.uzbls.keys()
+-            wsocks = [k for k, v in self.uzbls.items() if v.child_buffer]
+-            reads, writes, errors = select(socks, wsocks, socks, 1)
+-
+-            if self.server_socket in reads:
+-                reads.remove(self.server_socket)
+-
+-                # Accept connection and create uzbl instance.
+-                child_socket = self.server_socket.accept()[0]
+-                child_socket.setblocking(False)
+-                self.uzbls[child_socket] = Uzbl(self, child_socket)
+-                connections += 1
+-
+-            for uzbl in [self.uzbls[s] for s in writes if s in self.uzbls ]:
+-                uzbl.do_send()
+-
+-            for uzbl in [self.uzbls[s] for s in reads if s in self.uzbls]:
+-                uzbl.read()
+-
+-            for uzbl in [self.uzbls[s] for s in errors if s in self.uzbls]:
+-                uzbl.logger.error('socket read error')
+-                uzbl.close()
+-
+-        logger.info('auto closing')
+-
+-    def close_server_socket(self):
+-        '''Close and delete the server socket.'''
+-
+-        try:
+-            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:
+-            logger.error('failed to close server socket', exc_info=True)
+-
+-    def quit(self, sigint=None, *args):
+-        '''Close all instance socket objects, server socket and delete the
+-        pid file.'''
+-
+-        if sigint == SIGTERM:
+-            logger.critical('caught SIGTERM, exiting')
+-
+-        elif sigint == SIGINT:
+-            logger.critical('caught SIGINT, exiting')
+-
+-        elif not self._quit:
+-            logger.debug('shutting down event manager')
+-
+-        self.close_server_socket()
+-
+-        for uzbl in self.uzbls.values():
+-            uzbl.close()
+-
+-        del_pid_file(opts.pid_file)
+-
+-        if not self._quit:
+-            logger.info('event manager shut down')
+-            self._quit = True
+-
+-
+-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('failed to create pid file', exc_info=True)
+-
+-
+-def del_pid_file(pid_file):
+-    '''Deletes a pid file at `pid_file`, fails silently.'''
+-
+-    if os.path.isfile(pid_file):
+-        try:
+-            logger.debug('deleting pid file %r', pid_file)
+-            os.remove(pid_file)
+-            logger.info('deleted pid file %r', pid_file)
+-
+-        except:
+-            logger.error('failed to delete pid file', exc_info=True)
+-
+-
+-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
+-
+-    except (IOError, ValueError):
+-        logger.error('failed to read pid', exc_info=True)
+-        return None
+-
+-
+-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('failed to kill %d', pid, exc_info=True)
+-                raise
+-
+-        if (time.time() - start) > 10:
+-            logger.critical('unable to kill process with pid %d', pid)
+-            raise OSError
+-
+-        time.sleep(0.25)
+-
+-
+-def stop_action():
+-    '''Stop the event manager daemon.'''
+-
+-    pid_file = opts.pid_file
+-    if not os.path.isfile(pid_file):
+-        logger.error('could not find running event manager with pid file %r',
+-            pid_file)
+-        return
+-
+-    pid = get_pid(pid_file)
+-    if not pid_running(pid):
+-        logger.debug('no process with pid %r', pid)
+-        del_pid_file(pid_file)
+-        return
+-
+-    logger.debug('terminating process with pid %r', pid)
+-    term_process(pid)
+-    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 = opts.pid_file
+-    if os.path.isfile(pid_file):
+-        pid = get_pid(pid_file)
+-        if pid_running(pid):
+-            logger.error('event manager already started with pid %d', pid)
+-            return
+-
+-        logger.info('no process with pid %d', pid)
+-        del_pid_file(pid_file)
+-
+-    UzblEventDaemon().run()
+-
+-
+-def restart_action():
+-    '''Restart the event manager daemon.'''
+-
+-    stop_action()
+-    start_action()
+-
+-
+-def list_action():
+-    '''List all the plugins that would be loaded in the current search
+-    dirs.'''
+-
+-    names = {}
+-    for plugin in opts.plugins:
+-        (head, tail) = os.path.split(plugin)
+-        if tail not in names:
+-            names[tail] = plugin
+-
+-    for plugin in sorted(names.values()):
+-        print plugin
+-
+-
+-def make_parser():
+-    parser = OptionParser('usage: %prog [options] {start|stop|restart|list}')
+-    add = parser.add_option
+-
+-    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"`')
+-
+-    add('-l', '--load-plugin',
+-        dest='load_plugins', action='append', metavar="PLUGIN", default=[],
+-        help='load plugin, loads before plugins in search dirs')
+-
+-    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')
+-
+-    add('-p', '--pid-file',
+-        metavar="FILE", dest='pid_file',
+-        help='pid file location, defaults to server socket + .pid')
+-
+-    add('-n', '--no-daemon',
+-        dest='daemon_mode', action='store_false', default=True,
+-        help='do not daemonize the process')
+-
+-    add('-a', '--auto-close',
+-        dest='auto_close', action='store_true', default=False,
+-        help='auto close after all instances disconnect')
+-
+-    add('-i', '--no-default-dirs',
+-        dest='default_dirs', action='store_false', default=True,
+-        help='ignore the default plugin search dirs')
+-
+-    add('-o', '--log-file',
+-        dest='log_file', metavar='FILE',
+-        help='write logging output to a file, defaults to server socket +'
+-        ' .log')
+-
+-    add('-q', '--quiet-events',
+-        dest='print_events', action="store_false", default=True,
+-        help="silence the printing of events to stdout")
+-
+-    return parser
+-
+-
+-def init_logger():
+-    log_level = logging.CRITICAL - opts.verbose * 10
+-    logger = logging.getLogger()
+-    logger.setLevel(max(log_level, 10))
+-
+-    # Console
+-    handler = logging.StreamHandler()
+-    handler.setLevel(max(log_level + 10, 10))
+-    handler.setFormatter(logging.Formatter(
+-        '%(name)s: %(levelname)s: %(message)s'))
+-    logger.addHandler(handler)
+-
+-    # Logfile
+-    handler = logging.FileHandler(opts.log_file, 'w', 'utf-8', 1)
+-    handler.setLevel(max(log_level, 10))
+-    handler.setFormatter(logging.Formatter(
+-        '[%(created)f] %(name)s: %(levelname)s: %(message)s'))
+-    logger.addHandler(handler)
+-
+-
+-def main():
+-    global opts
+-
+-    parser = make_parser()
+-
+-    (opts, args) = parser.parse_args()
+-
+-    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
+-
+-    else:
+-        opts.pid_file = expandpath(opts.pid_file)
+-
+-    # Set default log file location
+-    if not opts.log_file:
+-        opts.log_file = "%s.log" % opts.server_socket
+-
+-    else:
+-        opts.log_file = expandpath(opts.log_file)
+-
+-    # Logging setup
+-    init_logger()
+-    logger.info('logging to %r', opts.log_file)
+-
+-    plugins = {}
+-
+-    # 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)
+-
+-        for plugin in matches:
+-            (head, tail) = os.path.split(plugin)
+-            if tail not in plugins:
+-                logger.debug('found plugin: %r', plugin)
+-                plugins[tail] = plugin
+-
+-            else:
+-                logger.debug('ignoring plugin: %r', plugin)
+-
+-    # 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')
+-
+-    # 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
+-
+-            else:
+-                logger.debug('ignoring plugin: %r', plugin)
+-
+-    plugins = plugins.values()
+-
+-    # 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 opts.auto_close:
+-        logger.debug('will auto close')
+-    else:
+-        logger.debug('will not auto close')
+-
+-    if opts.daemon_mode:
+-        logger.debug('will daemonize')
+-    else:
+-        logger.debug('will not daemonize')
+-
+-    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:
+-        action = 'start'
+-        logger.warning('no daemon action given, assuming %r', action)
+-
+-    else:
+-        parser.error('invalid action argument: %r' % args)
+-
+-    logger.info('daemon action %r', action)
+-    # Do action
+-    daemon_actions[action]()
+-
+-    logger.debug('process CPU time: %f', time.clock())
+-
+-
+-if __name__ == "__main__":
+-    main()
+-
+-
+-# vi: set et ts=4:
++#!/usr/bin/python3
++from uzbl import event_manager
++event_manager.main()
+diff --git a/bin/uzbl-tabbed b/bin/uzbl-tabbed
+index b78a54a..12fa249 100755
+--- a/bin/uzbl-tabbed
++++ b/bin/uzbl-tabbed
+@@ -47,6 +47,10 @@
+ #
+ #   Simon Lipp (sloonz)
+ #       Various
++#
++#   Hakan Jerning
++#       Wrote autosave_session patch to have a saved session even if
++#       uzbl-tabbed is closed unexpectedly.
+ # Dependencies:
+@@ -85,6 +89,7 @@
+ #   save_session            = 1
+ #   json_session            = 0
+ #   session_file            = $HOME/.local/share/uzbl/session
++#   autosave_session        = 0
+ #
+ # Inherited uzbl options:
+ #   icon_path               = $HOME/.local/share/uzbl/uzbl.png
+@@ -209,6 +214,7 @@ config = {
+   'save_session':           True,   # Save session in file when quit
+   'saved_sessions_dir':     os.path.join(DATA_DIR, 'sessions/'),
+   'session_file':           os.path.join(DATA_DIR, 'session'),
++  'autosave_session':       False,  # Save session for every tab change
+   # Inherited uzbl options
+   'icon_path':              os.path.join(DATA_DIR, 'uzbl.png'),
+@@ -232,6 +238,11 @@ config = {
+   'selected_https':         'foreground = "#fff"',
+   'selected_https_text':    'foreground = "gold"',
++  #Explicit config file. Unlike the other configs, this one cannot be inherited
++  #from the uzbl config file, as stated above. I've only put it here because
++  #load_session() is already called in UzblTabbed.__init__.
++  'explicit_config_file':   None,
++
+ } # End of config dict.
+ UZBL_TABBED_VARS = config.keys()
+@@ -410,7 +421,7 @@ class GlobalEventDispatcher(EventDispatcher):
+     def new_tab(self, uri = ''):
+         self.uzbl_tabbed.new_tab(uri)
+-    def new_tab_bg(self, uri = ''):
++    def new_bg_tab(self, uri = ''):
+         self.uzbl_tabbed.new_tab(uri, switch = False)
+     def new_tab_next(self, uri = ''):
+@@ -434,6 +445,15 @@ class GlobalEventDispatcher(EventDispatcher):
+     def last_tab(self):
+         self.uzbl_tabbed.goto_tab(-1)
++    def move_current_tab(self, index=0):
++        self.uzbl_tabbed.move_current_tab(absolute=int(index))
++
++    def move_current_tab_left(self):
++        self.uzbl_tabbed.move_current_tab(relative=-1)
++
++    def move_current_tab_right(self):
++        self.uzbl_tabbed.move_current_tab(relative=1)
++
+     def preset_tabs(self, *args):
+         self.uzbl_tabbed.run_preset_command(*args)
+@@ -889,6 +909,9 @@ class UzblTabbed:
+         if(uri):
+           cmd = cmd + ['--uri', str(uri)]
++        if config['explicit_config_file'] is not None:
++            cmd = cmd + ['-c', config['explicit_config_file']]
++
+         gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
+         uzbl = UzblInstance(self, name, uri, title, switch)
+@@ -968,6 +991,18 @@ class UzblTabbed:
+         while tabn < 0: tabn += ntabs
+         self.goto_tab(tabn)
++    def move_tab(self, tab, index):
++        '''Move tab to position.'''
++        self.notebook.reorder_child(tab, index)
++        self.update_tablist()
++
++    def move_current_tab(self, absolute=None, relative=None):
++        '''Move current tab to position.'''
++        current = self.notebook.get_current_page()
++        index = absolute if absolute is not None else current + relative \
++                if current + relative < len(self.notebook) else 0
++        tab = self.notebook.get_nth_page(current)
++        self.move_tab(tab, index)
+     def close_tab(self, tabn=None):
+         '''Closes current tab. Supports negative indexing.'''
+@@ -1030,6 +1065,11 @@ class UzblTabbed:
+         tab = self.notebook.get_nth_page(index)
+         self.notebook.set_focus_child(tab)
+         self.update_tablist(index)
++
++        if config['save_session'] and config['autosave_session']:
++            if len(list(self.notebook)) > 1:
++                self.save_session()
++
+         return True
+@@ -1038,6 +1078,7 @@ class UzblTabbed:
+         for tab in self.notebook:
+             self.tabs[tab].title_changed(True)
++        self.update_tablist()
+         return True
+@@ -1261,6 +1302,8 @@ if __name__ == "__main__":
+       help="directory to create socket")
+     parser.add_option('-f', '--fifodir', dest='fifodir',
+       help="directory to create fifo")
++    parser.add_option('--config-file', dest='config_file',
++      help="configuration file for all uzbl-browser instances")
+     # Parse command line options
+     (options, uris) = parser.parse_args()
+@@ -1275,6 +1318,15 @@ if __name__ == "__main__":
+         import pprint
+         sys.stderr.write("%s\n" % pprint.pformat(config))
++    if options.config_file is not None:
++        if not os.path.exists(options.config_file):
++            errorstr = "Explicit config file {} does not exist" % (
++                options.config_file)
++            error(errorstr)
++            sys.exit(-1)
++
++        config['explicit_config_file'] = options.config_file
++
+     uzbl = UzblTabbed()
+     if options.socketdir:
+diff --git a/examples/config/config b/examples/config/config
+index 11f1d82..d607cb4 100644
+--- a/examples/config/config
++++ b/examples/config/config
+@@ -8,6 +8,7 @@ set prefix      = @(echo $PREFIX)@
+ set data_home   = @(echo $XDG_DATA_HOME)@
+ set cache_home  = @(echo $XDG_CACHE_HOME)@
+ set config_home = @(echo $XDG_CONFIG_HOME)@
++set local_storage_path = @data_home/uzbl/databases/
+ # Interface paths.
+ set fifo_dir   = /tmp
+@@ -70,7 +71,6 @@ set download_handler    = sync_spawn @scripts_dir/download.sh
+ @on_event   LOAD_COMMIT    @set_status <span foreground="green">recv</span>
+   # add some javascript to the page for other 'js' and 'script' commands to access later.
+-@on_event   LOAD_COMMIT    js uzbl = {};
+ @on_event   LOAD_COMMIT    script @scripts_dir/formfiller.js
+ @on_event   LOAD_COMMIT    script @scripts_dir/follow.js
+@@ -86,6 +86,8 @@ set download_handler    = sync_spawn @scripts_dir/download.sh
+ # Switch to command mode if anything else is clicked
+ @on_event   ROOT_ACTIVE    @set_mode command
++@on_event   AUTHENTICATE   spawn @scripts_dir/auth.py "%1" "%2" "%3"
++
+ # Example CONFIG_CHANGED event handler
+ #@on_event  CONFIG_CHANGED print Config changed: %1 = %2
+@@ -97,6 +99,10 @@ set download_handler    = sync_spawn @scripts_dir/download.sh
+ # Custom CSS can be defined here, including link follower hint styles
+ set stylesheet_uri = file://@config_home/uzbl/style.css
++# If WebKits builtin authentication dialog should be used, if enabling remember
++# to disable external authentication handlers
++set enable_builtin_auth = 0
++
+ set show_status       = 1
+ set status_top        = 0
+ set status_background = #303030
+@@ -138,6 +144,8 @@ set useragent         = Uzbl (Webkit @{WEBKIT_MAJOR}.@{WEBKIT_MINOR}) (@(+uname
+ # === Configure cookie blacklist ========================================================
++set cookie_policy = 0
++
+ # Accept 'session cookies' from uzbl.org (when you have a whitelist all other cookies are dropped)
+ #request WHITELIST_COOKIE domain 'uzbl.org$' expires '^$'
+@@ -404,6 +412,9 @@ set formfiller = spawn @scripts_dir/formfiller.sh
+ @cbind  gt              = event NEXT_TAB
+ @cbind  gT              = event PREV_TAB
+ @cbind  gi<index:>_     = event GOTO_TAB %s
++@cbind  <Ctrl><Left>    = event MOVE_CURRENT_TAB_LEFT
++@cbind  <Ctrl><Right>   = event MOVE_CURRENT_TAB_RIGHT
++@cbind  gm<index:>_     = event MOVE_CURRENT_TAB %s
+ # Preset loading
+ set preset = event PRESET_TABS
+diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py
+deleted file mode 100644
+index fc8b392..0000000
+--- a/examples/data/plugins/bind.py
++++ /dev/null
+@@ -1,462 +0,0 @@
+-'''Plugin provides support for binds in uzbl.
+-
+-For example:
+-  event BIND ZZ = exit          -> bind('ZZ', 'exit')
+-  event BIND o _ = uri %s       -> bind('o _', 'uri %s')
+-  event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'")
+-
+-And it is also possible to execute a function on activation:
+-  bind('DD', myhandler)
+-'''
+-
+-import sys
+-import re
+-
+-# Commonly used regular expressions.
+-MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match
+-# Matches <x:y>, <'x':y>, <:'y'>, <x!y>, <'x'!y>, ...
+-PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>'
+-FIND_PROMPTS = re.compile(PROMPTS).split
+-VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match
+-
+-# For accessing a bind glob stack.
+-ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5)
+-
+-
+-# Custom errors.
+-class ArgumentError(Exception): pass
+-
+-
+-class Bindlet(object):
+-    '''Per-instance bind status/state tracker.'''
+-
+-    def __init__(self, uzbl):
+-        self.binds = {'global': {}}
+-        self.uzbl = uzbl
+-        self.depth = 0
+-        self.args = []
+-        self.last_mode = None
+-        self.after_cmds = None
+-        self.stack_binds = []
+-
+-        # A subset of the global mode binds containing non-stack and modkey
+-        # activiated binds for use in the stack mode.
+-        self.globals = []
+-
+-
+-    def __getitem__(self, key):
+-        return self.get_binds(key)
+-
+-
+-    def reset(self):
+-        '''Reset the tracker state and return to last mode.'''
+-
+-        self.depth = 0
+-        self.args = []
+-        self.after_cmds = None
+-        self.stack_binds = []
+-
+-        if self.last_mode:
+-            mode, self.last_mode = self.last_mode, None
+-            self.uzbl.config['mode'] = mode
+-
+-        del self.uzbl.config['keycmd_prompt']
+-
+-
+-    def stack(self, bind, args, depth):
+-        '''Enter or add new bind in the next stack level.'''
+-
+-        if self.depth != depth:
+-            if bind not in self.stack_binds:
+-                self.stack_binds.append(bind)
+-
+-            return
+-
+-        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
+-        self.depth += 1
+-        self.after_cmds = bind.prompts[depth]
+-
+-
+-    def after(self):
+-        '''If a stack was triggered then set the prompt and default value.'''
+-
+-        if self.after_cmds is None:
+-            return
+-
+-        (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None
+-
+-        self.uzbl.clear_keycmd()
+-        if prompt:
+-            self.uzbl.config['keycmd_prompt'] = prompt
+-
+-        if set and is_cmd:
+-            self.uzbl.send(set)
+-
+-        elif set and not is_cmd:
+-            self.uzbl.send('event SET_KEYCMD %s' % set)
+-
+-
+-    def get_binds(self, mode=None):
+-        '''Return the mode binds + globals. If we are stacked then return
+-        the filtered stack list and modkey & non-stack globals.'''
+-
+-        if mode is None:
+-            mode = self.uzbl.config.get('mode', None)
+-
+-        if not mode:
+-            mode = 'global'
+-
+-        if self.depth:
+-            return self.stack_binds + self.globals
+-
+-        globals = self.binds['global']
+-        if mode not in self.binds or mode == 'global':
+-            return filter(None, globals.values())
+-
+-        binds = dict(globals.items() + self.binds[mode].items())
+-        return filter(None, binds.values())
+-
+-
+-    def add_bind(self, mode, glob, bind=None):
+-        '''Insert (or override) a bind into the mode bind dict.'''
+-
+-        if mode not in self.binds:
+-            self.binds[mode] = {glob: bind}
+-            return
+-
+-        binds = self.binds[mode]
+-        binds[glob] = bind
+-
+-        if mode == 'global':
+-            # Regen the global-globals list.
+-            self.globals = []
+-            for bind in binds.values():
+-                if bind is not None and bind.is_global:
+-                    self.globals.append(bind)
+-
+-
+-def ismodbind(glob):
+-    '''Return True if the glob specifies a modbind.'''
+-
+-    return bool(MOD_START(glob))
+-
+-
+-def split_glob(glob):
+-    '''Take a string of the form "<Mod1><Mod2>cmd _" and return a list of the
+-    modkeys in the glob and the command.'''
+-
+-    mods = set()
+-    while True:
+-        match = MOD_START(glob)
+-        if not match:
+-            break
+-
+-        end = match.span()[1]
+-        mods.add(glob[:end])
+-        glob = glob[end:]
+-
+-    return (mods, glob)
+-
+-
+-class Bind(object):
+-
+-    # Class attribute to hold the number of Bind classes created.
+-    counter = [0,]
+-
+-    def __init__(self, glob, handler, *args, **kargs):
+-        self.is_callable = callable(handler)
+-        self._repr_cache = None
+-
+-        if not glob:
+-            raise ArgumentError('glob cannot be blank')
+-
+-        if self.is_callable:
+-            self.function = handler
+-            self.args = args
+-            self.kargs = kargs
+-
+-        elif kargs:
+-            raise ArgumentError('cannot supply kargs for uzbl commands')
+-
+-        elif hasattr(handler, '__iter__'):
+-            self.commands = handler
+-
+-        else:
+-            self.commands = [handler,] + list(args)
+-
+-        self.glob = glob
+-
+-        # Assign unique id.
+-        self.counter[0] += 1
+-        self.bid = self.counter[0]
+-
+-        self.split = split = FIND_PROMPTS(glob)
+-        self.prompts = []
+-        for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]):
+-            prompt, set = map(unquote, [prompt, set])
+-            cmd = True if cmd == '!' else False
+-            if prompt and prompt[-1] != ":":
+-                prompt = "%s:" % prompt
+-
+-            self.prompts.append((prompt, cmd, set))
+-
+-        # Check that there is nothing like: fl*<int:>*
+-        for glob in split[:-1:4]:
+-            if glob.endswith('*'):
+-                msg = "token '*' not at the end of a prompt bind: %r" % split
+-                raise SyntaxError(msg)
+-
+-        # Check that there is nothing like: fl<prompt1:><prompt2:>_
+-        for glob in split[4::4]:
+-            if not glob:
+-                msg = 'found null segment after first prompt: %r' % split
+-                raise SyntaxError(msg)
+-
+-        stack = []
+-        for (index, glob) in enumerate(reversed(split[::4])):
+-            # Is the binding a MODCMD or KEYCMD:
+-            mod_cmd = ismodbind(glob)
+-
+-            # Do we execute on UPDATES or EXEC events?
+-            on_exec = True if glob[-1] in ['!', '_'] else False
+-
+-            # Does the command take arguments?
+-            has_args = True if glob[-1] in ['*', '_'] else False
+-
+-            glob = glob[:-1] if has_args or on_exec else glob
+-            mods, glob = split_glob(glob)
+-            stack.append((on_exec, has_args, mods, glob, index))
+-
+-        self.stack = list(reversed(stack))
+-        self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD])
+-
+-
+-    def __getitem__(self, depth):
+-        '''Get bind info at a depth.'''
+-
+-        if self.is_global:
+-            return self.stack[0]
+-
+-        return self.stack[depth]
+-
+-
+-    def __repr__(self):
+-        if self._repr_cache:
+-            return self._repr_cache
+-
+-        args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
+-
+-        if self.is_callable:
+-            args.append('function=%r' % self.function)
+-            if self.args:
+-                args.append('args=%r' % self.args)
+-
+-            if self.kargs:
+-                args.append('kargs=%r' % self.kargs)
+-
+-        else:
+-            cmdlen = len(self.commands)
+-            cmds = self.commands[0] if cmdlen == 1 else self.commands
+-            args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds))
+-
+-        self._repr_cache = '<Bind(%s)>' % ', '.join(args)
+-        return self._repr_cache
+-
+-
+-def exec_bind(uzbl, bind, *args, **kargs):
+-    '''Execute bind objects.'''
+-
+-    uzbl.event("EXEC_BIND", bind, args, kargs)
+-
+-    if bind.is_callable:
+-        args += bind.args
+-        kargs = dict(bind.kargs.items()+kargs.items())
+-        bind.function(uzbl, *args, **kargs)
+-        return
+-
+-    if kargs:
+-        raise ArgumentError('cannot supply kargs for uzbl commands')
+-
+-    commands = []
+-    cmd_expand = uzbl.cmd_expand
+-    for cmd in bind.commands:
+-        cmd = cmd_expand(cmd, args)
+-        uzbl.send(cmd)
+-
+-
+-def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs):
+-    '''Add a mode bind.'''
+-
+-    bindlet = uzbl.bindlet
+-
+-    if not hasattr(modes, '__iter__'):
+-        modes = unicode(modes).split(',')
+-
+-    # Sort and filter binds.
+-    modes = filter(None, map(unicode.strip, modes))
+-
+-    if callable(handler) or (handler is not None and handler.strip()):
+-        bind = Bind(glob, handler, *args, **kargs)
+-
+-    else:
+-        bind = None
+-
+-    for mode in modes:
+-        if not VALID_MODE(mode):
+-            raise NameError('invalid mode name: %r' % mode)
+-
+-    for mode in modes:
+-        if mode[0] == '-':
+-            mode, bind = mode[1:], None
+-
+-        bindlet.add_bind(mode, glob, bind)
+-        uzbl.event('ADDED_MODE_BIND', mode, glob, bind)
+-
+-
+-def bind(uzbl, glob, handler, *args, **kargs):
+-    '''Legacy bind function.'''
+-
+-    mode_bind(uzbl, 'global', glob, handler, *args, **kargs)
+-
+-
+-def parse_mode_bind(uzbl, args):
+-    '''Parser for the MODE_BIND event.
+-
+-    Example events:
+-        MODE_BIND <mode>         <bind>        = <command>
+-        MODE_BIND command        o<location:>_ = uri %s
+-        MODE_BIND insert,command <BackSpace>   = ...
+-        MODE_BIND global         ...           = ...
+-        MODE_BIND global,-insert ...           = ...
+-    '''
+-
+-    if not args:
+-        raise ArgumentError('missing bind arguments')
+-
+-    split = map(unicode.strip, args.split(' ', 1))
+-    if len(split) != 2:
+-        raise ArgumentError('missing mode or bind section: %r' % args)
+-
+-    modes, args = split[0].split(','), split[1]
+-    split = map(unicode.strip, args.split('=', 1))
+-    if len(split) != 2:
+-        raise ArgumentError('missing delimiter in bind section: %r' % args)
+-
+-    glob, command = split
+-    mode_bind(uzbl, modes, glob, command)
+-
+-
+-def parse_bind(uzbl, args):
+-    '''Legacy parsing of the BIND event and conversion to the new format.
+-
+-    Example events:
+-        request BIND <bind>        = <command>
+-        request BIND o<location:>_ = uri %s
+-        request BIND <BackSpace>   = ...
+-        request BIND ...           = ...
+-    '''
+-
+-    parse_mode_bind(uzbl, "global %s" % args)
+-
+-
+-def mode_changed(uzbl, mode):
+-    '''Clear the stack on all non-stack mode changes.'''
+-
+-    if mode != 'stack':
+-        uzbl.bindlet.reset()
+-
+-
+-def match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet):
+-    (on_exec, has_args, mod_cmd, glob, more) = bind[depth]
+-    cmd = keylet.modcmd if mod_cmd else keylet.keycmd
+-
+-    if mod_cmd and modstate != mod_cmd:
+-        return False
+-
+-    if has_args:
+-        if not cmd.startswith(glob):
+-            return False
+-
+-        args = [cmd[len(glob):],]
+-
+-    elif cmd != glob:
+-        return False
+-
+-    else:
+-        args = []
+-
+-    if bind.is_global or (not more and depth == 0):
+-        exec_bind(uzbl, bind, *args)
+-        if not has_args:
+-            uzbl.clear_current()
+-
+-        return True
+-
+-    elif more:
+-        bindlet.stack(bind, args, depth)
+-        (on_exec, has_args, mod_cmd, glob, more) = bind[depth+1]
+-        if not on_exec and has_args and not glob and not more:
+-            exec_bind(uzbl, bind, *(args+['',]))
+-
+-        return False
+-
+-    args = bindlet.args + args
+-    exec_bind(uzbl, bind, *args)
+-    if not has_args or on_exec:
+-        del uzbl.config['mode']
+-        bindlet.reset()
+-
+-    return True
+-
+-
+-def key_event(uzbl, modstate, keylet, mod_cmd=False, on_exec=False):
+-    bindlet = uzbl.bindlet
+-    depth = bindlet.depth
+-    for bind in bindlet.get_binds():
+-        t = bind[depth]
+-        if (bool(t[MOD_CMD]) != mod_cmd) or (t[ON_EXEC] != on_exec):
+-            continue
+-
+-        if match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet):
+-            return
+-
+-    bindlet.after()
+-
+-    # 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:
+-        del uzbl.config['mode']
+-
+-
+-# plugin init hook
+-def init(uzbl):
+-    '''Export functions and connect handlers to events.'''
+-
+-    connect_dict(uzbl, {
+-        'BIND':             parse_bind,
+-        'MODE_BIND':        parse_mode_bind,
+-        'MODE_CHANGED':     mode_changed,
+-    })
+-
+-    # Connect key related events to the key_event function.
+-    events = [['KEYCMD_UPDATE', 'KEYCMD_EXEC'],
+-              ['MODCMD_UPDATE', 'MODCMD_EXEC']]
+-
+-    for mod_cmd in range(2):
+-        for on_exec in range(2):
+-            event = events[mod_cmd][on_exec]
+-            connect(uzbl, event, key_event, bool(mod_cmd), bool(on_exec))
+-
+-    export_dict(uzbl, {
+-        'bind':         bind,
+-        'mode_bind':    mode_bind,
+-        'bindlet':      Bindlet(uzbl),
+-    })
+-
+-# vi: set et ts=4:
+diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py
+deleted file mode 100644
+index b007975..0000000
+--- a/examples/data/plugins/cmd_expand.py
++++ /dev/null
+@@ -1,40 +0,0 @@
+-def escape(str):
+-    for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]:
+-        str = str.replace(char, (level * '\\') + char)
+-
+-    return str
+-
+-
+-def cmd_expand(uzbl, cmd, args):
+-    '''Exports a function that provides the following
+-    expansions in any uzbl command string:
+-
+-        %s = replace('%s', ' '.join(args))
+-        %r = replace('%r', "'%s'" % escaped(' '.join(args)))
+-        %1 = replace('%1', arg[0])
+-        %2 = replace('%2', arg[1])
+-        %n = replace('%n', arg[n-1])
+-    '''
+-
+-    # Ensure (1) all string representable and (2) correct string encoding.
+-    args = map(unicode, args)
+-
+-    # Direct string replace.
+-    if '%s' in cmd:
+-        cmd = cmd.replace('%s', ' '.join(args))
+-
+-    # Escaped and quoted string replace.
+-    if '%r' in cmd:
+-        cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args)))
+-
+-    # Arg index string replace.
+-    for (index, arg) in enumerate(args):
+-        index += 1
+-        if '%%%d' % index in cmd:
+-            cmd = cmd.replace('%%%d' % index, unicode(arg))
+-
+-    return cmd
+-
+-# plugin init hook
+-def init(uzbl):
+-    export(uzbl, 'cmd_expand', cmd_expand)
+diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py
+deleted file mode 100644
+index e8c7f34..0000000
+--- a/examples/data/plugins/completion.py
++++ /dev/null
+@@ -1,179 +0,0 @@
+-'''Keycmd completion.'''
+-
+-import re
+-
+-# Completion level
+-NONE, ONCE, LIST, COMPLETE = range(4)
+-
+-# The reverse keyword finding re.
+-FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall
+-
+-# Formats
+-LIST_FORMAT = "<span> %s </span>"
+-ITEM_FORMAT = "<span @hint_style>%s</span>%s"
+-
+-def escape(str):
+-    return str.replace("@", "\@")
+-
+-
+-def 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.keylet
+-    left_segment = keylet.keycmd[:keylet.cursor]
+-    partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip()
+-    if partial.startswith('set '):
+-        return ('@%s' % partial[4:].lstrip(), True)
+-
+-    return (partial, False)
+-
+-
+-def stop_completion(uzbl, *args):
+-    '''Stop command completion and return the level to NONE.'''
+-
+-    uzbl.completion.level = NONE
+-    del uzbl.config['completion_list']
+-
+-
+-def complete_completion(uzbl, partial, hint, set_completion=False):
+-    '''Inject the remaining porition of the keyword into the keycmd then stop
+-    the completioning.'''
+-
+-    if set_completion:
+-        remainder = "%s = " % hint[len(partial):]
+-
+-    else:
+-        remainder = "%s " % hint[len(partial):]
+-
+-    uzbl.inject_keycmd(remainder)
+-    stop_completion(uzbl)
+-
+-
+-def partial_completion(uzbl, partial, hint):
+-    '''Inject a common portion of the hints into the keycmd.'''
+-
+-    remainder = hint[len(partial):]
+-    uzbl.inject_keycmd(remainder)
+-
+-
+-def update_completion_list(uzbl, *args):
+-    '''Checks if the user still has a partially completed keyword under his
+-    cursor then update the completion hints list.'''
+-
+-    partial = get_incomplete_keyword(uzbl)[0]
+-    if not partial:
+-        return stop_completion(uzbl)
+-
+-    if uzbl.completion.level < LIST:
+-        return
+-
+-    hints = filter(lambda h: h.startswith(partial), uzbl.completion)
+-    if not hints:
+-        del uzbl.config['completion_list']
+-        return
+-
+-    j = len(partial)
+-    l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)]
+-    uzbl.config['completion_list'] = LIST_FORMAT % ' '.join(l)
+-
+-
+-def start_completion(uzbl, *args):
+-
+-    comp = uzbl.completion
+-    if comp.locked:
+-        return
+-
+-    (partial, set_completion) = get_incomplete_keyword(uzbl)
+-    if not partial:
+-        return stop_completion(uzbl)
+-
+-    if comp.level < COMPLETE:
+-        comp.level += 1
+-
+-    hints = filter(lambda h: h.startswith(partial), comp)
+-    if not hints:
+-        return
+-
+-    elif len(hints) == 1:
+-        comp.lock()
+-        complete_completion(uzbl, partial, hints[0], set_completion)
+-        comp.unlock()
+-        return
+-
+-    elif partial in hints and comp.level == COMPLETE:
+-        comp.lock()
+-        complete_completion(uzbl, partial, partial, set_completion)
+-        comp.unlock()
+-        return
+-
+-    smalllen, smallest = sorted([(len(h), h) for h in hints])[0]
+-    common = ''
+-    for i in range(len(partial), smalllen):
+-        char, same = smallest[i], True
+-        for hint in hints:
+-            if hint[i] != char:
+-                same = False
+-                break
+-
+-        if not same:
+-            break
+-
+-        common += char
+-
+-    if common:
+-        comp.lock()
+-        partial_completion(uzbl, partial, partial+common)
+-        comp.unlock()
+-
+-    update_completion_list(uzbl)
+-
+-
+-def add_builtins(uzbl, builtins):
+-    '''Pump the space delimited list of builtin commands into the
+-    builtin list.'''
+-
+-    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.'''
+-
+-    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):
+-    '''Export functions and connect handlers to events.'''
+-
+-    export_dict(uzbl, {
+-        'completion':       Completions(),
+-        '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
+deleted file mode 100644
+index c9bdf67..0000000
+--- a/examples/data/plugins/config.py
++++ /dev/null
+@@ -1,91 +0,0 @@
+-from re import compile
+-from types import BooleanType
+-from UserDict import DictMixin
+-
+-valid_key = compile('^[A-Za-z0-9_\.]+$').match
+-
+-class Config(DictMixin):
+-    def __init__(self, uzbl):
+-        self.uzbl = uzbl
+-
+-        # Create the base dict and map allowed methods to `self`.
+-        self.data = data = {}
+-
+-        methods = ['__contains__', '__getitem__', '__iter__',
+-            '__len__',  'get', 'has_key', 'items', 'iteritems',
+-            'iterkeys', 'itervalues', 'values']
+-
+-        for method in methods:
+-            setattr(self, method, getattr(data, method))
+-
+-
+-    def __setitem__(self, key, value):
+-        self.set(key, value)
+-
+-    def __delitem__(self, key):
+-        self.set(key)
+-
+-    def update(self, other=None, **kwargs):
+-        if other is None:
+-            other = {}
+-
+-        for (key, value) in dict(other).items() + kwargs.items():
+-            self[key] = value
+-
+-
+-    def set(self, key, value='', force=False):
+-        '''Generates a `set <key> = <value>` command string to send to the
+-        current uzbl instance.
+-
+-        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.'''
+-
+-        assert valid_key(key)
+-
+-        if type(value) == BooleanType:
+-            value = int(value)
+-
+-        else:
+-            value = unicode(value)
+-            assert '\n' not in value
+-
+-        if not force and key in self and self[key] == value:
+-            return
+-
+-        self.uzbl.send(u'set %s = %s' % (key, value))
+-
+-
+-def parse_set_event(uzbl, args):
+-    '''Parse `VARIABLE_SET <var> <type> <value>` event and load the
+-    (key, value) pair into the `uzbl.config` dict.'''
+-
+-    (key, type, raw_value) = (args.split(' ', 2) + ['',])[:3]
+-
+-    assert valid_key(key)
+-    assert type in types
+-
+-    new_value = types[type](raw_value)
+-    old_value = uzbl.config.get(key, None)
+-
+-    # Update new value.
+-    uzbl.config.data[key] = new_value
+-
+-    if old_value != new_value:
+-        uzbl.event('CONFIG_CHANGED', key, new_value)
+-
+-    # Cleanup null config values.
+-    if type == 'str' and not new_value:
+-        del uzbl.config.data[key]
+-
+-
+-# plugin init hook
+-def init(uzbl):
+-    global types
+-    types = {'int': int, 'float': float, 'str': unquote}
+-    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/cookies.py b/examples/data/plugins/cookies.py
+deleted file mode 100644
+index bf59e96..0000000
+--- a/examples/data/plugins/cookies.py
++++ /dev/null
+@@ -1,222 +0,0 @@
+-""" Basic cookie manager
+-    forwards cookies to all other instances connected to the event manager"""
+-
+-from collections import defaultdict
+-import os, re, stat
+-
+-# these are symbolic names for the components of the cookie tuple
+-symbolic = {'domain': 0, 'path':1, 'name':2, 'value':3, 'scheme':4, 'expires':5}
+-
+-# allows for partial cookies
+-# ? allow wildcard in key
+-def match(key, cookie):
+-    for k,c in zip(key,cookie):
+-        if k != c:
+-            return False
+-    return True
+-
+-class NullStore(object):
+-    def add_cookie(self, rawcookie, cookie):
+-        pass
+-
+-    def delete_cookie(self, rkey, key):
+-        pass
+-
+-class ListStore(list):
+-    def add_cookie(self, rawcookie, cookie):
+-        self.append(rawcookie)
+-
+-    def delete_cookie(self, rkey, key):
+-        self[:] = [x for x in self if not match(key, splitquoted(x))]
+-
+-class TextStore(object):
+-    def __init__(self, filename):
+-        self.filename = filename
+-        try:
+-          # make sure existing cookie jar is not world-open
+-          perm_mode = os.stat(self.filename).st_mode
+-          if (perm_mode & (stat.S_IRWXO | stat.S_IRWXG)) > 0:
+-              safe_perm = stat.S_IMODE(perm_mode) & ~(stat.S_IRWXO | stat.S_IRWXG)
+-              os.chmod(self.filename, safe_perm)
+-        except OSError:
+-            pass
+-
+-    def as_event(self, cookie):
+-        """Convert cookie.txt row to uzbls cookie event format"""
+-        scheme = {
+-            'TRUE'  : 'https',
+-            'FALSE' : 'http'
+-        }
+-        extra = ''
+-        if cookie[0].startswith("#HttpOnly_"):
+-            extra = 'Only'
+-            domain = cookie[0][len("#HttpOnly_"):]
+-        elif cookie[0].startswith('#'):
+-            return None
+-        else:
+-            domain = cookie[0]
+-        try:
+-            return (domain,
+-                cookie[2],
+-                cookie[5],
+-                cookie[6],
+-                scheme[cookie[3]] + extra,
+-                cookie[4])
+-        except (KeyError,IndexError):
+-            # Let malformed rows pass through like comments
+-            return None
+-
+-    def as_file(self, cookie):
+-        """Convert cookie event to cookie.txt row"""
+-        secure = {
+-            'https' : 'TRUE',
+-            'http'  : 'FALSE',
+-            'httpsOnly' : 'TRUE',
+-            'httpOnly'  : 'FALSE'
+-        }
+-        http_only = {
+-            'https' : '',
+-            'http'  : '',
+-            'httpsOnly' : '#HttpOnly_',
+-            'httpOnly'  : '#HttpOnly_'
+-        }
+-        return (http_only[cookie[4]] + cookie[0],
+-            'TRUE' if cookie[0].startswith('.') else 'FALSE',
+-            cookie[1],
+-            secure[cookie[4]],
+-            cookie[5],
+-            cookie[2],
+-            cookie[3])
+-
+-    def add_cookie(self, rawcookie, cookie):
+-        assert len(cookie) == 6
+-
+-        # delete equal cookies (ignoring expire time, value and secure flag)
+-        self.delete_cookie(None, cookie[:-3])
+-
+-        # restrict umask before creating the cookie jar
+-        curmask=os.umask(0)
+-        os.umask(curmask| stat.S_IRWXO | stat.S_IRWXG)
+-
+-        first = not os.path.exists(self.filename)
+-        with open(self.filename, 'a') as f:
+-            if first:
+-                print >> f, "# HTTP Cookie File"
+-            print >> f, '\t'.join(self.as_file(cookie))
+-        os.umask(curmask)
+-
+-    def delete_cookie(self, rkey, key):
+-        if not os.path.exists(self.filename):
+-            return
+-
+-        # restrict umask before creating the cookie jar
+-        curmask=os.umask(0)
+-        os.umask(curmask | stat.S_IRWXO | stat.S_IRWXG)
+-
+-        # read all cookies
+-        with open(self.filename, 'r') as f:
+-            cookies = f.readlines()
+-
+-        # write those that don't match the cookie to delete
+-        with open(self.filename, 'w') as f:
+-            for l in cookies:
+-                c = self.as_event(l.split('\t'))
+-                if c is None or not match(key, c):
+-                    print >> f, l,
+-        os.umask(curmask)
+-
+-xdg_data_home = os.environ.get('XDG_DATA_HOME', os.path.join(os.environ['HOME'], '.local/share'))
+-DefaultStore = TextStore(os.path.join(xdg_data_home, 'uzbl/cookies.txt'))
+-SessionStore = TextStore(os.path.join(xdg_data_home, 'uzbl/session-cookies.txt'))
+-
+-def match_list(_list, cookie):
+-    for matcher in _list:
+-        for component, match in matcher:
+-            if match(cookie[component]) is None:
+-                break
+-        else:
+-            return True
+-    return False
+-
+-# accept a cookie only when:
+-# a. there is no whitelist and the cookie is in the blacklist
+-# b. the cookie is in the whitelist and not in the blacklist
+-def accept_cookie(uzbl, cookie):
+-    if uzbl.cookie_whitelist:
+-        if match_list(uzbl.cookie_whitelist, cookie):
+-            return not match_list(uzbl.cookie_blacklist, cookie)
+-        return False
+-
+-    return not match_list(uzbl.cookie_blacklist, cookie)
+-
+-def expires_with_session(uzbl, cookie):
+-    return cookie[5] == ''
+-
+-def get_recipents(uzbl):
+-    """ get a list of Uzbl instances to send the cookie too. """
+-    # This could be a lot more interesting
+-    return [u for u in uzbl.parent.uzbls.values() if u is not uzbl]
+-
+-def get_store(uzbl, session=False):
+-    if session:
+-        return SessionStore
+-    return DefaultStore
+-
+-def add_cookie(uzbl, cookie):
+-    splitted = splitquoted(cookie)
+-    if accept_cookie(uzbl, splitted):
+-        for u in get_recipents(uzbl):
+-            u.send('add_cookie %s' % cookie)
+-
+-        get_store(uzbl, expires_with_session(uzbl, splitted)).add_cookie(cookie, splitted)
+-    else:
+-        logger.debug('cookie %r is blacklisted' % splitted)
+-        uzbl.send('delete_cookie %s' % cookie)
+-
+-def delete_cookie(uzbl, cookie):
+-    for u in get_recipents(uzbl):
+-        u.send('delete_cookie %s' % cookie)
+-
+-    splitted = splitquoted(cookie)
+-    if len(splitted) == 6:
+-        get_store(uzbl, expires_with_session(uzbl, splitted)).delete_cookie(cookie, splitted)
+-    else:
+-        for store in set([get_store(uzbl, session) for session in (True, False)]):
+-            store.delete_cookie(cookie, splitted)
+-
+-# add a cookie matcher to a whitelist or a blacklist.
+-# a matcher is a list of (component, re) tuples that matches a cookie when the
+-# "component" part of the cookie matches the regular expression "re".
+-# "component" is one of the keys defined in the variable "symbolic" above,
+-# or the index of a component of a cookie tuple.
+-def add_cookie_matcher(_list, arg):
+-    args = splitquoted(arg)
+-    mlist = []
+-    for (component, regexp) in zip(args[0::2], args[1::2]):
+-        try:
+-            component = symbolic[component]
+-        except KeyError:
+-            component = int(component)
+-        assert component <= 5
+-        mlist.append((component, re.compile(regexp).search))
+-    _list.append(mlist)
+-
+-def blacklist(uzbl, arg):
+-    add_cookie_matcher(uzbl.cookie_blacklist, arg)
+-
+-def whitelist(uzbl, arg):
+-    add_cookie_matcher(uzbl.cookie_whitelist, arg)
+-
+-def init(uzbl):
+-    connect_dict(uzbl, {
+-        'ADD_COOKIE':       add_cookie,
+-        'DELETE_COOKIE':    delete_cookie,
+-        'BLACKLIST_COOKIE': blacklist,
+-        'WHITELIST_COOKIE': whitelist
+-    })
+-    export_dict(uzbl, {
+-        'cookie_blacklist' : [],
+-        'cookie_whitelist' : []
+-    })
+-
+-# vi: set et ts=4:
+diff --git a/examples/data/plugins/downloads.py b/examples/data/plugins/downloads.py
+deleted file mode 100644
+index 8d796ce..0000000
+--- a/examples/data/plugins/downloads.py
++++ /dev/null
+@@ -1,77 +0,0 @@
+-# this plugin does a very simple display of download progress. to use it, add
+-# @downloads to your status_format.
+-
+-import os
+-ACTIVE_DOWNLOADS = {}
+-
+-# after a download's status has changed this is called to update the status bar
+-def update_download_section(uzbl):
+-    global ACTIVE_DOWNLOADS
+-
+-    if len(ACTIVE_DOWNLOADS):
+-        # add a newline before we list downloads
+-        result = '&#10;downloads:'
+-        for path in ACTIVE_DOWNLOADS:
+-            # add each download
+-            fn = os.path.basename(path)
+-            progress, = ACTIVE_DOWNLOADS[path]
+-
+-            dl = " %s (%d%%)" % (fn, progress * 100)
+-
+-            # replace entities to make sure we don't break our markup
+-            # (this could be done with an @[]@ expansion in uzbl, but then we
+-            # can't use the &#10; above to make a new line)
+-            dl = dl.replace("&", "&amp;").replace("<", "&lt;")
+-            result += dl
+-    else:
+-        result = ''
+-
+-    # and the result gets saved to an uzbl variable that can be used in
+-    # status_format
+-    if uzbl.config.get('downloads', '') != result:
+-          uzbl.config['downloads'] = result
+-
+-def download_started(uzbl, args):
+-    # parse the arguments
+-    args = splitquoted(args)
+-    destination_path = args[0]
+-
+-    # add to the list of active downloads
+-    global ACTIVE_DOWNLOADS
+-    ACTIVE_DOWNLOADS[destination_path] = (0.0,)
+-
+-    # update the progress
+-    update_download_section(uzbl)
+-
+-def download_progress(uzbl, args):
+-    # parse the arguments
+-    args = splitquoted(args)
+-    destination_path = args[0]
+-    progress = float(args[1])
+-
+-    # update the progress
+-    global ACTIVE_DOWNLOADS
+-    ACTIVE_DOWNLOADS[destination_path] = (progress,)
+-
+-    # update the status bar variable
+-    update_download_section(uzbl)
+-
+-def download_complete(uzbl, args):
+-    # parse the arguments
+-    args = splitquoted(args)
+-    destination_path = args[0]
+-
+-    # remove from the list of active downloads
+-    global ACTIVE_DOWNLOADS
+-    del ACTIVE_DOWNLOADS[destination_path]
+-
+-    # update the status bar variable
+-    update_download_section(uzbl)
+-
+-# plugin init hook
+-def init(uzbl):
+-    connect_dict(uzbl, {
+-        'DOWNLOAD_STARTED':     download_started,
+-        'DOWNLOAD_PROGRESS':    download_progress,
+-        'DOWNLOAD_COMPLETE':    download_complete,
+-    })
+diff --git a/examples/data/plugins/history.py b/examples/data/plugins/history.py
+deleted file mode 100644
+index f42f86f..0000000
+--- a/examples/data/plugins/history.py
++++ /dev/null
+@@ -1,129 +0,0 @@
+-import random
+-
+-shared_history = {'':[]}
+-
+-class History(object):
+-    def __init__(self, uzbl):
+-        self.uzbl = uzbl
+-        self._temporary = []
+-        self.prompt = ''
+-        self.cursor = None
+-        self.__temp_tail = False
+-        self.search_key = None
+-
+-    def prev(self):
+-        if self.cursor is None:
+-            self.cursor = len(self) - 1
+-        else:
+-            self.cursor -= 1
+-
+-        if self.search_key:
+-            while self.cursor >= 0 and self.search_key not in self[self.cursor]:
+-                self.cursor -= 1
+-
+-        if self.cursor < 0 or len(self) == 0:
+-            self.cursor = -1
+-            return random.choice(end_messages)
+-
+-        return self[self.cursor]
+-
+-    def next(self):
+-        if self.cursor is None:
+-            return ''
+-
+-        self.cursor += 1
+-
+-        if self.search_key:
+-            while self.cursor < len(self) and self.search_key not in self[self.cursor]:
+-                self.cursor += 1
+-
+-        if self.cursor >= len(shared_history[self.prompt]):
+-            self.cursor = None
+-            self.search_key = None
+-
+-            if self._temporary:
+-                return self._temporary.pop()
+-            return ''
+-
+-        return self[self.cursor]
+-
+-    def change_prompt(self, prompt):
+-        self.prompt = prompt
+-        self._temporary = []
+-        self.__temp_tail = False
+-        if prompt not in shared_history:
+-            shared_history[prompt] = []
+-
+-    def search(self, key):
+-        self.search_key = key
+-        self.cursor = None
+-
+-    def add(self, cmd):
+-        if self._temporary:
+-            self._temporary.pop()
+-
+-        shared_history[self.prompt].append(cmd)
+-        self.cursor = None
+-        self.search_key = None
+-
+-    def add_temporary(self, cmd):
+-        assert not self._temporary
+-
+-        self._temporary.append(cmd)
+-        self.cursor = len(self) - 1
+-
+-    def __getitem__(self, i):
+-        if i < len(shared_history[self.prompt]):
+-            return shared_history[self.prompt][i]
+-        return self._temporary[i-len(shared_history)+1]
+-
+-    def __len__(self):
+-        return len(shared_history[self.prompt]) + len(self._temporary)
+-
+-    def __str__(self):
+-        return "(History %s, %s)" % (self.cursor, self.prompt)
+-
+-def keycmd_exec(uzbl, modstate, keylet):
+-    cmd = keylet.get_keycmd()
+-    if cmd:
+-        uzbl.history.add(cmd)
+-
+-def history_prev(uzbl, _x):
+-    cmd = uzbl.keylet.get_keycmd()
+-    if uzbl.history.cursor is None and cmd:
+-        uzbl.history.add_temporary(cmd)
+-
+-    uzbl.set_keycmd(uzbl.history.prev())
+-    uzbl.logger.debug('PREV %s' % uzbl.history)
+-
+-def history_next(uzbl, _x):
+-    cmd = uzbl.keylet.get_keycmd()
+-
+-    uzbl.set_keycmd(uzbl.history.next())
+-    uzbl.logger.debug('NEXT %s' % uzbl.history)
+-
+-def history_search(uzbl, key):
+-    uzbl.history.search(key)
+-    uzbl.send('event HISTORY_PREV')
+-    uzbl.logger.debug('SEARCH %s %s' % (key, uzbl.history))
+-
+-end_messages = ('Look behind you, A three-headed monkey!', 'error #4: static from nylon underwear.', 'error #5: static from plastic slide rules.', 'error #6: global warming.', 'error #9: doppler effect.', 'error #16: somebody was calculating pi on the server.', 'error #19: floating point processor overflow.', 'error #21: POSIX compliance problem.', 'error #25: Decreasing electron flux.', 'error #26: first Saturday after first full moon in Winter.', 'error #64: CPU needs recalibration.', 'error #116: the real ttys became pseudo ttys and vice-versa.', 'error #229: wrong polarity of neutron flow.', 'error #330: quantum decoherence.', 'error #388: Bad user karma.', 'error #407: Route flapping at the NAP.', 'error #435: Internet shut down due to maintenance.')
+-
+-# plugin init hook
+-def init(uzbl):
+-    connect_dict(uzbl, {
+-        'KEYCMD_EXEC': keycmd_exec,
+-        'HISTORY_PREV': history_prev,
+-        'HISTORY_NEXT': history_next,
+-        'HISTORY_SEARCH': history_search
+-    })
+-
+-    export_dict(uzbl, {
+-        'history' : History(uzbl)
+-    })
+-
+-# plugin after hook
+-def after(uzbl):
+-    uzbl.on_set('keycmd_prompt', lambda uzbl, k, v: uzbl.history.change_prompt(v))
+-
+-# vi: set et ts=4:
+diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py
+deleted file mode 100644
+index 1bb70e3..0000000
+--- a/examples/data/plugins/keycmd.py
++++ /dev/null
+@@ -1,423 +0,0 @@
+-import re
+-
+-# Keycmd format which includes the markup for the cursor.
+-KEYCMD_FORMAT = "%s<span @cursor_style>%s</span>%s"
+-MODCMD_FORMAT = "<span> %s </span>"
+-
+-
+-def escape(str):
+-    for char in ['\\', '@']:
+-        str = str.replace(char, '\\'+char)
+-
+-    return str
+-
+-
+-def uzbl_escape(str):
+-    return "@[%s]@" % escape(str) if str else ''
+-
+-
+-class Keylet(object):
+-    '''Small per-instance object that tracks characters typed.'''
+-
+-    def __init__(self):
+-        # Modcmd tracking
+-        self.modcmd = ''
+-        self.is_modcmd = False
+-
+-        # Keycmd tracking
+-        self.keycmd = ''
+-        self.cursor = 0
+-
+-        self.modmaps = {}
+-        self.ignores = {}
+-
+-
+-    def get_keycmd(self):
+-        '''Get the keycmd-part of the keylet.'''
+-
+-        return self.keycmd
+-
+-
+-    def get_modcmd(self):
+-        '''Get the modcmd-part of the keylet.'''
+-
+-        if not self.is_modcmd:
+-            return ''
+-
+-        return self.modcmd
+-
+-
+-    def modmap_key(self, key):
+-        '''Make some obscure names for some keys friendlier.'''
+-
+-        if key in self.modmaps:
+-            return self.modmaps[key]
+-
+-        elif key.endswith('_L') or key.endswith('_R'):
+-            # Remove left-right discrimination and try again.
+-            return self.modmap_key(key[:-2])
+-
+-        else:
+-            return key
+-
+-
+-    def key_ignored(self, key):
+-        '''Check if the given key is ignored by any ignore rules.'''
+-
+-        for (glob, match) in self.ignores.items():
+-            if match(key):
+-                return True
+-
+-        return False
+-
+-
+-    def __repr__(self):
+-        '''Return a string representation of the keylet.'''
+-
+-        l = []
+-        if self.is_modcmd:
+-            l.append('modcmd=%r' % self.get_modcmd())
+-
+-        if self.keycmd:
+-            l.append('keycmd=%r' % self.get_keycmd())
+-
+-        return '<keylet(%s)>' % ', '.join(l)
+-
+-
+-def add_modmap(uzbl, key, map):
+-    '''Add modmaps.
+-
+-    Examples:
+-        set modmap = request MODMAP
+-        @modmap <Control> <Ctrl>
+-        @modmap <ISO_Left_Tab> <Shift-Tab>
+-        ...
+-
+-    Then:
+-        @bind <Shift-Tab> = <command1>
+-        @bind <Ctrl>x = <command2>
+-        ...
+-
+-    '''
+-
+-    assert len(key)
+-    modmaps = uzbl.keylet.modmaps
+-
+-    modmaps[key.strip('<>')] = map.strip('<>')
+-    uzbl.event("NEW_MODMAP", key, map)
+-
+-
+-def modmap_parse(uzbl, map):
+-    '''Parse a modmap definiton.'''
+-
+-    split = [s.strip() for s in map.split(' ') if s.split()]
+-
+-    if not split or len(split) > 2:
+-        raise Exception('Invalid modmap arugments: %r' % map)
+-
+-    add_modmap(uzbl, *split)
+-
+-
+-def add_key_ignore(uzbl, glob):
+-    '''Add an ignore definition.
+-
+-    Examples:
+-        set ignore_key = request IGNORE_KEY
+-        @ignore_key <Shift>
+-        @ignore_key <ISO_*>
+-        ...
+-    '''
+-
+-    assert len(glob) > 1
+-    ignores = uzbl.keylet.ignores
+-
+-    glob = "<%s>" % glob.strip("<> ")
+-    restr = glob.replace('*', '[^\s]*')
+-    match = re.compile(restr).match
+-
+-    ignores[glob] = match
+-    uzbl.event('NEW_KEY_IGNORE', glob)
+-
+-
+-def clear_keycmd(uzbl, *args):
+-    '''Clear the keycmd for this uzbl instance.'''
+-
+-    k = uzbl.keylet
+-    k.keycmd = ''
+-    k.cursor = 0
+-    del uzbl.config['keycmd']
+-    uzbl.event('KEYCMD_CLEARED')
+-
+-
+-def clear_modcmd(uzbl):
+-    '''Clear the modcmd for this uzbl instance.'''
+-
+-    k = uzbl.keylet
+-    k.modcmd = ''
+-    k.is_modcmd = False
+-
+-    del uzbl.config['modcmd']
+-    uzbl.event('MODCMD_CLEARED')
+-
+-
+-def clear_current(uzbl):
+-    '''Clear the modcmd if is_modcmd else clear keycmd.'''
+-
+-    if uzbl.keylet.is_modcmd:
+-        clear_modcmd(uzbl)
+-
+-    else:
+-        clear_keycmd(uzbl)
+-
+-
+-def update_event(uzbl, modstate, k, execute=True):
+-    '''Raise keycmd & modcmd update events.'''
+-
+-    keycmd, modcmd = k.get_keycmd(), ''.join(modstate) + k.get_modcmd()
+-
+-    if k.is_modcmd:
+-        logger.debug('modcmd_update, %s' % modcmd)
+-        uzbl.event('MODCMD_UPDATE', modstate, k)
+-
+-    else:
+-        logger.debug('keycmd_update, %s' % keycmd)
+-        uzbl.event('KEYCMD_UPDATE', modstate, k)
+-
+-    if uzbl.config.get('modcmd_updates', '1') == '1':
+-        new_modcmd = ''.join(modstate) + k.get_modcmd()
+-        if not new_modcmd or not k.is_modcmd:
+-            del uzbl.config['modcmd']
+-
+-        elif new_modcmd == modcmd:
+-            uzbl.config['modcmd'] = MODCMD_FORMAT % uzbl_escape(modcmd)
+-
+-    if uzbl.config.get('keycmd_events', '1') != '1':
+-        return
+-
+-    new_keycmd = k.get_keycmd()
+-    if not new_keycmd:
+-        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.config['keycmd'] = value
+-
+-
+-def inject_str(str, index, inj):
+-    '''Inject a string into string at at given index.'''
+-
+-    return "%s%s%s" % (str[:index], inj, str[index:])
+-
+-
+-def parse_key_event(uzbl, key):
+-    ''' Build a set from the modstate part of the event, and pass all keys through modmap '''
+-    keylet = uzbl.keylet
+-
+-    modstate, key = splitquoted(key)
+-    modstate = set(['<%s>' % keylet.modmap_key(k) for k in modstate.split('|') if k])
+-    
+-    key = keylet.modmap_key(key)
+-    return modstate, key
+-
+-
+-def key_press(uzbl, key):
+-    '''Handle KEY_PRESS events. Things done by this function include:
+-
+-    1. Ignore all shift key presses (shift can be detected by capital chars)
+-    2. In non-modcmd mode:
+-         a. append char to keycmd
+-    3. If not in modcmd mode and a modkey was pressed set modcmd mode.
+-    4. Keycmd is updated and events raised if anything is changed.'''
+-
+-    k = uzbl.keylet
+-    modstate, key = parse_key_event(uzbl, key)
+-    k.is_modcmd = any(not k.key_ignored(m) for m in modstate)
+-
+-    logger.debug('key press modstate=%s' % str(modstate))
+-    if key.lower() == 'space' and not k.is_modcmd and k.keycmd:
+-        k.keycmd = inject_str(k.keycmd, k.cursor, ' ')
+-        k.cursor += 1
+-
+-    elif not k.is_modcmd and len(key) == 1:
+-        if uzbl.config.get('keycmd_events', '1') != '1':
+-            # TODO, make a note on what's going on here
+-            k.keycmd = ''
+-            k.cursor = 0
+-            del uzbl.config['keycmd']
+-            return
+-
+-        k.keycmd = inject_str(k.keycmd, k.cursor, key)
+-        k.cursor += 1
+-
+-    elif len(key) == 1:
+-        k.modcmd += key
+-
+-    else:
+-        if not k.key_ignored('<%s>' % key):
+-            modstate.add('<%s>' % key)
+-            k.is_modcmd = True
+-
+-    update_event(uzbl, modstate, k)
+-
+-
+-def key_release(uzbl, key):
+-    '''Respond to KEY_RELEASE event. Things done by this function include:
+-
+-    1. If in a mod-command then raise a MODCMD_EXEC.
+-    2. Update the keycmd uzbl variable if anything changed.'''
+-    k = uzbl.keylet
+-    modstate, key = parse_key_event(uzbl, key)
+-
+-    if len(key) > 1:
+-        if k.is_modcmd:
+-            uzbl.event('MODCMD_EXEC', modstate, k)
+-
+-        clear_modcmd(uzbl)
+-
+-
+-def set_keycmd(uzbl, keycmd):
+-    '''Allow setting of the keycmd externally.'''
+-
+-    k = uzbl.keylet
+-    k.keycmd = keycmd
+-    k.cursor = len(keycmd)
+-    update_event(uzbl, set(), k, False)
+-
+-
+-def inject_keycmd(uzbl, keycmd):
+-    '''Allow injecting of a string into the keycmd at the cursor position.'''
+-
+-    k = uzbl.keylet
+-    k.keycmd = inject_str(k.keycmd, k.cursor, keycmd)
+-    k.cursor += len(keycmd)
+-    update_event(uzbl, set(), k, False)
+-
+-
+-def append_keycmd(uzbl, keycmd):
+-    '''Allow appening of a string to the keycmd.'''
+-
+-    k = uzbl.keylet
+-    k.keycmd += keycmd
+-    k.cursor = len(k.keycmd)
+-    update_event(uzbl, set(), k, False)
+-
+-
+-def keycmd_strip_word(uzbl, seps):
+-    ''' Removes the last word from the keycmd, similar to readline ^W '''
+-
+-    seps = seps or ' '
+-    k = uzbl.keylet
+-    if not k.keycmd:
+-        return
+-
+-    head, tail = k.keycmd[:k.cursor].rstrip(seps), k.keycmd[k.cursor:]
+-    rfind = -1
+-    for sep in seps:
+-        p = head.rfind(sep)
+-        if p >= 0 and rfind < p + 1:
+-            rfind = p + 1
+-    if rfind == len(head) and head[-1] in seps:
+-        rfind -= 1
+-    head = head[:rfind] if rfind + 1 else ''
+-    k.keycmd = head + tail
+-    k.cursor = len(head)
+-    update_event(uzbl, set(), k, False)
+-
+-
+-def keycmd_backspace(uzbl, *args):
+-    '''Removes the character at the cursor position in the keycmd.'''
+-
+-    k = uzbl.keylet
+-    if not k.keycmd or not k.cursor:
+-        return
+-
+-    k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:]
+-    k.cursor -= 1
+-    update_event(uzbl, set(), k, False)
+-
+-
+-def keycmd_delete(uzbl, *args):
+-    '''Removes the character after the cursor position in the keycmd.'''
+-
+-    k = uzbl.keylet
+-    if not k.keycmd:
+-        return
+-
+-    k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:]
+-    update_event(uzbl, set(), k, False)
+-
+-
+-def keycmd_exec_current(uzbl, *args):
+-    '''Raise a KEYCMD_EXEC with the current keylet and then clear the
+-    keycmd.'''
+-
+-    uzbl.event('KEYCMD_EXEC', set(), uzbl.keylet)
+-    clear_keycmd(uzbl)
+-
+-
+-def set_cursor_pos(uzbl, index):
+-    '''Allow setting of the cursor position externally. Supports negative
+-    indexing and relative stepping with '+' and '-'.'''
+-
+-    k = uzbl.keylet
+-    if index == '-':
+-        cursor = k.cursor - 1
+-
+-    elif index == '+':
+-        cursor = k.cursor + 1
+-
+-    else:
+-        cursor = int(index.strip())
+-        if cursor < 0:
+-            cursor = len(k.keycmd) + cursor + 1
+-
+-    if cursor < 0:
+-        cursor = 0
+-
+-    if cursor > len(k.keycmd):
+-        cursor = len(k.keycmd)
+-
+-    k.cursor = cursor
+-    update_event(uzbl, set(), k, False)
+-
+-
+-# plugin init hook
+-def init(uzbl):
+-    '''Export functions and connect handlers to events.'''
+-
+-    connect_dict(uzbl, {
+-        'APPEND_KEYCMD':        append_keycmd,
+-        'IGNORE_KEY':           add_key_ignore,
+-        'INJECT_KEYCMD':        inject_keycmd,
+-        'KEYCMD_BACKSPACE':     keycmd_backspace,
+-        'KEYCMD_DELETE':        keycmd_delete,
+-        'KEYCMD_EXEC_CURRENT':  keycmd_exec_current,
+-        'KEYCMD_STRIP_WORD':    keycmd_strip_word,
+-        'KEYCMD_CLEAR':         clear_keycmd,
+-        'KEY_PRESS':            key_press,
+-        'KEY_RELEASE':          key_release,
+-        'MOD_PRESS':            key_press,
+-        'MOD_RELEASE':          key_release,
+-        'MODMAP':               modmap_parse,
+-        'SET_CURSOR_POS':       set_cursor_pos,
+-        'SET_KEYCMD':           set_keycmd,
+-    })
+-
+-    export_dict(uzbl, {
+-        'add_key_ignore':       add_key_ignore,
+-        'add_modmap':           add_modmap,
+-        'append_keycmd':        append_keycmd,
+-        'clear_current':        clear_current,
+-        'clear_keycmd':         clear_keycmd,
+-        'clear_modcmd':         clear_modcmd,
+-        'inject_keycmd':        inject_keycmd,
+-        'keylet':               Keylet(),
+-        'set_cursor_pos':       set_cursor_pos,
+-        'set_keycmd':           set_keycmd,
+-    })
+-
+-# vi: set et ts=4:
+diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py
+deleted file mode 100644
+index e0de706..0000000
+--- a/examples/data/plugins/mode.py
++++ /dev/null
+@@ -1,68 +0,0 @@
+-from collections import defaultdict
+-
+-def parse_mode_config(uzbl, args):
+-    '''Parse `MODE_CONFIG <mode> <var> = <value>` event and update config if
+-    the `<mode>` is the current mode.'''
+-
+-    ustrip = unicode.strip
+-    args = unicode(args)
+-
+-    assert args.strip(), "missing mode config args"
+-    (mode, args) = map(ustrip, (args.strip().split(' ', 1) + ['',])[:2])
+-
+-    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 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 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
+-
+-    # Load mode config
+-    mode_config = uzbl.mode_config.get(mode, None)
+-    if mode_config:
+-        uzbl.config.update(mode_config)
+-
+-    uzbl.send('event MODE_CONFIRM %s' % mode)
+-
+-
+-def confirm_change(uzbl, mode):
+-    if mode and uzbl.config.get('mode', None) == mode:
+-        uzbl.event('MODE_CHANGED', mode)
+-
+-
+-# plugin init hook
+-def init(uzbl):
+-    require('config')
+-    require('on_set')
+-
+-    # Usage `uzbl.mode_config[mode][key] = value`
+-    export(uzbl, 'mode_config', defaultdict(dict))
+-
+-    connect_dict(uzbl, {
+-        'MODE_CONFIG':  parse_mode_config,
+-        'MODE_CONFIRM': confirm_change,
+-    })
+-
+-# 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
+deleted file mode 100644
+index 32f09e2..0000000
+--- a/examples/data/plugins/on_event.py
++++ /dev/null
+@@ -1,88 +0,0 @@
+-'''Plugin provides arbitrary binding of uzbl events to uzbl commands.
+-
+-Formatting options:
+-  %s = space separated string of the arguments
+-  %r = escaped and quoted version of %s
+-  %1 = argument 1
+-  %2 = argument 2
+-  %n = argument n
+-
+-Usage:
+-  request ON_EVENT LINK_HOVER set selected_uri = $1
+-    --> LINK_HOVER http://uzbl.org/
+-    <-- set selected_uri = http://uzbl.org/
+-
+-  request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2
+-    --> CONFIG_CHANGED selected_uri http://uzbl.org/
+-    <-- print Config changed: selected_uri = http://uzbl.org/
+-'''
+-
+-import sys
+-import re
+-
+-def event_handler(uzbl, *args, **kargs):
+-    '''This function handles all the events being watched by various
+-    on_event definitions and responds accordingly.'''
+-
+-    # Could be connected to a EM internal event that can use anything as args
+-    if len(args) == 1 and isinstance(args[0], basestring):
+-        args = splitquoted(args[0])
+-
+-    events = uzbl.on_events
+-    event = kargs['on_event']
+-    if event not in events:
+-        return
+-
+-    commands = events[event]
+-    cmd_expand = uzbl.cmd_expand
+-    for cmd in commands:
+-        cmd = cmd_expand(cmd, args)
+-        uzbl.send(cmd)
+-
+-
+-def on_event(uzbl, event, cmd):
+-    '''Add a new event to watch and respond to.'''
+-
+-    event = event.upper()
+-    events = uzbl.on_events
+-    if event not in events:
+-        connect(uzbl, event, event_handler, on_event=event)
+-        events[event] = []
+-
+-    cmds = events[event]
+-    if cmd not in cmds:
+-        cmds.append(cmd)
+-
+-
+-def parse_on_event(uzbl, args):
+-    '''Parse ON_EVENT events and pass them to the on_event function.
+-
+-    Syntax: "event ON_EVENT <EVENT_NAME> commands".'''
+-
+-    args = args.strip()
+-    assert args, 'missing on event arguments'
+-
+-    (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):
+-    '''Export functions and connect handlers to events.'''
+-
+-    connect(uzbl, 'ON_EVENT', parse_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()
+-
+-# vi: set et ts=4:
+diff --git a/examples/data/plugins/on_set.py b/examples/data/plugins/on_set.py
+deleted file mode 100644
+index 130b816..0000000
+--- a/examples/data/plugins/on_set.py
++++ /dev/null
+@@ -1,92 +0,0 @@
+-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/progress_bar.py b/examples/data/plugins/progress_bar.py
+deleted file mode 100644
+index b2edffc..0000000
+--- a/examples/data/plugins/progress_bar.py
++++ /dev/null
+@@ -1,93 +0,0 @@
+-UPDATES = 0
+-
+-def update_progress(uzbl, progress=None):
+-    '''Updates the progress.output variable on LOAD_PROGRESS update.
+-
+-    The current substitution options are:
+-        %d = done char * done
+-        %p = pending char * remaining
+-        %c = percent done
+-        %i = int done
+-        %s = -\|/ spinner
+-        %t = percent pending
+-        %o = int pending
+-        %r = sprites
+-
+-    Default configuration options:
+-        progress.format  = [%d>%p]%c
+-        progress.width   = 8
+-        progress.done    = =
+-        progress.pending =
+-        progress.spinner = -\|/
+-        progress.sprites = loading
+-    '''
+-
+-    global UPDATES
+-
+-    if progress is None:
+-        UPDATES = 0
+-        progress = 100
+-
+-    else:
+-        UPDATES += 1
+-        progress = int(progress)
+-
+-    # 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(progress))) * count
+-
+-    if '%t' in format or '%o' in format:
+-        count = format.count('%t') + format.count('%o')
+-        width += (3-len(str(100-progress))) * count
+-
+-    done = int(((progress/100.0)*width)+0.5)
+-    pending = width - done
+-
+-    if '%d' in format:
+-        format = format.replace('%d', done_symbol * done)
+-
+-    if '%p' in format:
+-        format = format.replace('%p', pending_symbol * pending)
+-
+-    if '%c' in format:
+-        format = format.replace('%c', '%d%%' % progress)
+-
+-    if '%i' in format:
+-        format = format.replace('%i', '%d' % progress)
+-
+-    if '%t' in format:
+-        format = format.replace('%t', '%d%%' % (100-progress))
+-
+-    if '%o' in format:
+-        format = format.replace('%o', '%d' % (100-progress))
+-
+-    if '%s' in format:
+-        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 = 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 uzbl.config.get('progress.output', None) != format:
+-        uzbl.config['progress.output'] = format
+-
+-# plugin init hook
+-def init(uzbl):
+-    connect_dict(uzbl, {
+-        'LOAD_COMMIT':      lambda uzbl, uri: update_progress(uzbl),
+-        'LOAD_PROGRESS':    update_progress,
+-    })
+diff --git a/examples/data/scripts/auth.py b/examples/data/scripts/auth.py
+index 49fa41e..dbf58dc 100755
+--- a/examples/data/scripts/auth.py
++++ b/examples/data/scripts/auth.py
+@@ -1,5 +1,6 @@
+ #!/usr/bin/env python
++import os
+ import gtk
+ import sys
+@@ -41,13 +42,20 @@ def getText(authInfo, authHost, authRealm):
+     dialog.show_all()
+     rv = dialog.run()
+-    output = login.get_text() + "\n" + password.get_text()
++    output = {
++        'username': login.get_text(),
++        'password': password.get_text()
++    }
+     dialog.destroy()
+     return rv, output
+ if __name__ == '__main__':
+-    rv, output = getText(sys.argv[1], sys.argv[2], sys.argv[3])
++    fifo = open(os.environ.get('UZBL_FIFO'), 'w')
++    me, info, host, realm = sys.argv
++    rv, output = getText(info, host, realm)
+     if (rv == gtk.RESPONSE_OK):
+-        print output;
++        print >> fifo, 'auth "%s" "%s" "%s"' % (
++            info, output['username'], output['password']
++        )
+     else:
+         exit(1)
+diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js
+index 83bbf55..0afeec3 100644
+--- a/examples/data/scripts/follow.js
++++ b/examples/data/scripts/follow.js
+@@ -12,6 +12,7 @@
+ // Globals
+ uzbldivid = 'uzbl_link_hints';
++var uzbl = uzbl || {};
+ uzbl.follow = function() {
+     // Export
+diff --git a/examples/data/scripts/formfiller.js b/examples/data/scripts/formfiller.js
+index 06db648..9c56f7b 100644
+--- a/examples/data/scripts/formfiller.js
++++ b/examples/data/scripts/formfiller.js
+@@ -1,3 +1,5 @@
++var uzbl = uzbl || {};
++
+ uzbl.formfiller = {
+     // this is pointlessly duplicated in uzbl.follow
+diff --git a/examples/data/scripts/history.sh b/examples/data/scripts/history.sh
+index 0709b5e..2d96a07 100755
+--- a/examples/data/scripts/history.sh
++++ b/examples/data/scripts/history.sh
+@@ -1,5 +1,7 @@
+ #!/bin/sh
++[ -n "$UZBL_PRIVATE" ] && exit 0
++
+ . "$UZBL_UTIL_DIR/uzbl-dir.sh"
+ >> "$UZBL_HISTORY_FILE" || exit 1
+diff --git a/examples/data/scripts/scheme.py b/examples/data/scripts/scheme.py
+index 4b0b7ca..c652478 100755
+--- a/examples/data/scripts/scheme.py
++++ b/examples/data/scripts/scheme.py
+@@ -1,6 +1,11 @@
+ #!/usr/bin/env python
+-import os, subprocess, sys, urlparse
++import os, subprocess, sys
++
++try:
++  import urllib.parse as urlparse
++except ImportError:
++  import urlparse
+ def detach_open(cmd):
+     # Thanks to the vast knowledge of Laurence Withers (lwithers) and this message:
+@@ -10,7 +15,7 @@ def detach_open(cmd):
+         for i in range(3): os.dup2(null,i)
+         os.close(null)
+         subprocess.Popen(cmd)
+-    print 'USED'
++    print('USED')
+ if __name__ == '__main__':
+     uri = sys.argv[1]
+diff --git a/misc/env.sh b/misc/env.sh
+index f815c44..d5f0db8 100755
+--- a/misc/env.sh
++++ b/misc/env.sh
+@@ -28,3 +28,11 @@ export XDG_CONFIG_HOME
+ # Needed to run uzbl-browser etc from here.
+ PATH="$(pwd)/sandbox/usr/local/bin:$PATH"
+ export PATH
++
++PYTHONLIB=$(python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib(prefix="/usr/local"))')
++
++UZBL_PLUGIN_PATH="$(pwd)/sandbox/$PYTHONLIB/uzbl/plugins"
++export UZBL_PLUGIN_PATH
++
++PYTHONPATH="$(pwd)/sandbox/$PYTHONLIB/"
++export PYTHONPATH
+diff --git a/setup.py b/setup.py
+new file mode 100644
+index 0000000..5dd6b59
+--- /dev/null
++++ b/setup.py
+@@ -0,0 +1,11 @@
++from distutils.core import setup
++
++setup(name='uzbl',
++      version='201100808',
++      description='Uzbl event daemon',
++      url='http://uzbl.org',
++      packages=['uzbl', 'uzbl.plugins'],
++      scripts=[
++        'bin/uzbl-event-manager',
++        ],
++    )
+diff --git a/src/callbacks.c b/src/callbacks.c
+index eee9f69..370f679 100644
+--- a/src/callbacks.c
++++ b/src/callbacks.c
+@@ -10,6 +10,8 @@
+ #include "type.h"
+ #include "variables.h"
++#include <gdk/gdk.h>
++
+ void
+ link_hover_cb (WebKitWebView *page, const gchar *title, const gchar *link, gpointer data) {
+     (void) page; (void) title; (void) data;
+@@ -147,6 +149,21 @@ key_release_cb (GtkWidget* window, GdkEventKey* event) {
+     return uzbl.behave.forward_keys ? FALSE : TRUE;
+ }
++gint
++get_click_context() {
++    WebKitHitTestResult *ht;
++    guint context;
++
++    if(!uzbl.state.last_button)
++        return -1;
++
++    ht = webkit_web_view_get_hit_test_result (uzbl.gui.web_view, uzbl.state.last_button);
++    g_object_get (ht, "context", &context, NULL);
++    g_object_unref (ht);
++
++    return (gint)context;
++}
++
+ gboolean
+ button_press_cb (GtkWidget* window, GdkEventButton* event) {
+     (void) window;
+@@ -233,22 +250,9 @@ button_release_cb (GtkWidget* window, GdkEventButton* event) {
+ }
+ gboolean
+-motion_notify_cb(GtkWidget* window, GdkEventMotion* event, gpointer user_data) {
+-    (void) window;
+-    (void) event;
+-    (void) user_data;
+-
+-    send_event (PTR_MOVE, NULL,
+-        TYPE_FLOAT, event->x,
+-        TYPE_FLOAT, event->y,
+-        TYPE_INT, event->state,
+-        NULL);
+-
+-    return FALSE;
+-}
+-
+-gboolean
+-navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
++navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
++        WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
++        WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
+     (void) web_view;
+     (void) frame;
+     (void) navigation_action;
+@@ -288,39 +292,16 @@ navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNe
+     return TRUE;
+ }
+-gboolean
+-new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
+-        WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
+-        WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
+-    (void) web_view;
+-    (void) frame;
+-    (void) navigation_action;
+-    (void) policy_decision;
+-    (void) user_data;
+-
+-    if (uzbl.state.verbose)
+-        printf ("New window requested -> %s \n", webkit_network_request_get_uri (request));
+-
+-    /* This event function causes troubles with `target="_blank"` anchors.
+-     * Either we:
+-     *  1. Comment it out and target blank links are ignored.
+-     *  2. Uncomment it and two windows are opened when you click on target
+-     *     blank links.
+-     *
+-     * This problem is caused by create_web_view_cb also being called whenever
+-     * this callback is triggered thus resulting in the doubled events.
+-     *
+-     * We are leaving this uncommented as we would rather links open twice
+-     * than not at all.
+-     */
+-    send_event (NEW_WINDOW, NULL, TYPE_STR, webkit_network_request_get_uri (request), NULL);
+-
+-    webkit_web_policy_decision_ignore (policy_decision);
+-    return TRUE;
++void
++close_web_view_cb(WebKitWebView *webview, gpointer user_data) {
++    (void) webview; (void) user_data;
++    send_event (CLOSE_WINDOW, NULL, NULL);
+ }
+ gboolean
+-mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, gchar *mime_type,  WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
++mime_policy_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
++        WebKitNetworkRequest *request, gchar *mime_type,
++        WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
+     (void) frame;
+     (void) request;
+     (void) user_data;
+@@ -345,11 +326,17 @@ request_starting_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitWebRes
+     (void) response;
+     (void) user_data;
+-    const gchar* uri = webkit_network_request_get_uri (request);
++    const gchar *uri = webkit_network_request_get_uri (request);
++    SoupMessage *message = webkit_network_request_get_message (request);
++
++    if (message) {
++        SoupURI *soup_uri = soup_uri_new (uri);
++        soup_message_set_first_party (message, soup_uri);
++    }
+     if (uzbl.state.verbose)
+         printf("Request starting -> %s\n", uri);
+-    send_event (REQUEST_STARTING, NULL, TYPE_STR, webkit_network_request_get_uri(request), NULL);
++    send_event (REQUEST_STARTING, NULL, TYPE_STR, uri, NULL);
+     if (uzbl.behave.request_handler) {
+         GString *result = g_string_new ("");
+@@ -373,17 +360,16 @@ request_starting_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitWebRes
+ }
+ void
+-create_web_view_js_cb (WebKitWebView* web_view, GParamSpec param_spec) {
++create_web_view_got_uri_cb (WebKitWebView* web_view, GParamSpec param_spec) {
+     (void) web_view;
+     (void) param_spec;
+     webkit_web_view_stop_loading(web_view);
++
+     const gchar* uri = webkit_web_view_get_uri(web_view);
+-    if (strncmp(uri, "javascript:", strlen("javascript:")) == 0) {
++    if (strncmp(uri, "javascript:", strlen("javascript:")) == 0)
+         eval_js(uzbl.gui.web_view, (gchar*) uri + strlen("javascript:"), NULL, "javascript:");
+-        gtk_widget_destroy(GTK_WIDGET(web_view));
+-    }
+     else
+         send_event(NEW_WINDOW, NULL, TYPE_STR, uri, NULL);
+ }
+@@ -394,14 +380,29 @@ create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer us
+     (void) frame;
+     (void) user_data;
+-    if (uzbl.state.verbose)
+-        printf("New web view -> javascript link...\n");
+-
+-    WebKitWebView* new_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
++    // unfortunately we don't know the URL at this point; all webkit-gtk will
++    // tell us is that we're opening a new window.
++    //
++    // (we can't use the new-window-policy-decision-requested event; it doesn't
++    // fire when Javascript requests a new window with window.open().)
++    //
++    // so, we have to create a temporary web view and allow it to load. webkit
++    // segfaults if we try to destroy it or mark it for garbage collection in
++    // create_web_view_got_uri_cb, so we might as well keep it around and reuse
++    // it.
++
++    if(!uzbl.state._tmp_web_view) {
++      uzbl.state._tmp_web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
++
++      g_object_connect (uzbl.state._tmp_web_view, "signal::notify::uri",
++                             G_CALLBACK(create_web_view_got_uri_cb), NULL, NULL);
++
++      // we're taking ownership of this WebView (sinking its floating reference
++      // since it will never be added to a display widget).
++      g_object_ref_sink(uzbl.state._tmp_web_view);
++    }
+-    g_object_connect (new_view, "signal::notify::uri",
+-                           G_CALLBACK(create_web_view_js_cb), NULL, NULL);
+-    return new_view;
++    return uzbl.state._tmp_web_view;
+ }
+ void
+@@ -464,7 +465,21 @@ download_cb(WebKitWebView *web_view, WebKitDownload *download, gpointer user_dat
+     /* get a reasonable suggestion for a filename */
+     const gchar *suggested_filename;
++#ifdef USE_WEBKIT2
++    WebKitURIResponse *response;
++    g_object_get(download, "network-response", &response, NULL);
++#if WEBKIT_CHECK_VERSION (1, 9, 90)
++    g_object_get(response, "suggested-filename", &suggested_filename, NULL);
++#else
++    suggested_filename = webkit_uri_response_get_suggested_filename(respose);
++#endif
++#elif WEBKIT_CHECK_VERSION (1, 9, 6)
++    WebKitNetworkResponse *response;
++    g_object_get(download, "network-response", &response, NULL);
++    g_object_get(response, "suggested-filename", &suggested_filename, NULL);
++#else
+     g_object_get(download, "suggested-filename", &suggested_filename, NULL);
++#endif
+     /* get the mimetype of the download */
+     const gchar *content_type = NULL;
+@@ -582,90 +597,107 @@ run_menu_command(GtkWidget *menu, MenuItem *mi) {
+     (void) menu;
+     if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
+-        gchar* uri;
+-        g_object_get(mi->hittest, "image-uri", &uri, NULL);
+-        gchar* cmd = g_strdup_printf("%s %s", mi->cmd, uri);
++        gchar* cmd = g_strdup_printf("%s %s", mi->cmd, mi->argument);
+         parse_cmd_line(cmd, NULL);
+         g_free(cmd);
+-        g_free(uri);
+-        g_object_unref(mi->hittest);
++        g_free(mi->argument);
+     }
+     else {
+         parse_cmd_line(mi->cmd, NULL);
+     }
+ }
++gboolean
++populate_context_menu (GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gint context) {
++    guint i;
++
++    /* find the user-defined menu items that are approprate for whatever was
++     * clicked and append them to the default context menu. */
++    for(i = 0; i < uzbl.gui.menu_items->len; i++) {
++        MenuItem *mi = g_ptr_array_index(uzbl.gui.menu_items, i);
++        GtkWidget *item;
++
++        gboolean contexts_match = (context & mi->context);
++
++        if(!contexts_match) {
++          continue;
++        }
++
++        if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
++            g_object_get(hit_test_result, "image-uri", &(mi->argument), NULL);
++        }
++
++        if(mi->issep) {
++            item = gtk_separator_menu_item_new();
++        } else {
++            item = gtk_menu_item_new_with_label(mi->name);
++            g_signal_connect(item, "activate",
++                    G_CALLBACK(run_menu_command), mi);
++        }
++
++        gtk_menu_shell_append(GTK_MENU_SHELL(default_menu), item);
++        gtk_widget_show(item);
++    }
++
++    return FALSE;
++}
++
++#if WEBKIT_CHECK_VERSION (1, 9, 0)
++gboolean
++context_menu_cb (WebKitWebView *v, GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gboolean triggered_with_keyboard, gpointer user_data) {
++    (void) v; (void) triggered_with_keyboard; (void) user_data;
++    gint context;
++
++    if(!uzbl.gui.menu_items)
++        return FALSE;
++
++    /* check context */
++    if((context = get_click_context()) == -1)
++        return FALSE;
++    /* display the default menu with our modifications. */
++    return populate_context_menu(default_menu, hit_test_result, context);
++}
++#else
+ void
+ populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) {
+     (void) c;
+-    GUI *g = &uzbl.gui;
+-    GtkWidget *item;
+-    MenuItem *mi;
+-    guint i=0;
+-    gint context, hit=0;
++    gint context;
+-    if(!g->menu_items)
++    if(!uzbl.gui.menu_items)
+         return;
+     /* check context */
+     if((context = get_click_context()) == -1)
+         return;
+-    for(i=0; i < uzbl.gui.menu_items->len; i++) {
+-        hit = 0;
+-        mi = g_ptr_array_index(uzbl.gui.menu_items, i);
++    WebKitHitTestResult *hit_test_result;
++    GdkEventButton ev;
++    gint x, y;
++#if GTK_CHECK_VERSION (3, 0, 0)
++    gdk_window_get_device_position (gtk_widget_get_window(GTK_WIDGET(v)),
++        gdk_device_manager_get_client_pointer (
++            gdk_display_get_device_manager (
++                gtk_widget_get_display (GTK_WIDGET (v)))),
++        &x, &y, NULL);
++#else
++    gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(v)), &x, &y, NULL);
++#endif
++    ev.x = x;
++    ev.y = y;
++    hit_test_result = webkit_web_view_get_hit_test_result(v, &ev);
+-        if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
+-            GdkEventButton ev;
+-            gint x, y;
+-            gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(v)), &x, &y, NULL);
+-            ev.x = x;
+-            ev.y = y;
+-            mi->hittest = webkit_web_view_get_hit_test_result(v, &ev);
+-        }
++    populate_context_menu(m, hit_test_result, context);
+-        if((mi->context > WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) &&
+-                (context & mi->context)) {
+-            if(mi->issep) {
+-                item = gtk_separator_menu_item_new();
+-                gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
+-                gtk_widget_show(item);
+-            }
+-            else {
+-                item = gtk_menu_item_new_with_label(mi->name);
+-                g_signal_connect(item, "activate",
+-                        G_CALLBACK(run_menu_command), mi);
+-                gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
+-                gtk_widget_show(item);
+-            }
+-            hit++;
+-        }
+-
+-        if((mi->context == WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)  &&
+-                (context <= WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) &&
+-                !hit) {
+-            if(mi->issep) {
+-                item = gtk_separator_menu_item_new();
+-                gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
+-                gtk_widget_show(item);
+-            }
+-            else {
+-                item = gtk_menu_item_new_with_label(mi->name);
+-                g_signal_connect(item, "activate",
+-                        G_CALLBACK(run_menu_command), mi);
+-                gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
+-                gtk_widget_show(item);
+-            }
+-        }
+-    }
++    g_object_unref(hit_test_result);
+ }
++#endif
+ void
+ window_object_cleared_cb(WebKitWebView *webview, WebKitWebFrame *frame,
+-    JSGlobalContextRef *context, JSObjectRef *object) {
++        JSGlobalContextRef *context, JSObjectRef *object) {
+     (void) frame; (void) context; (void) object;
+ #if WEBKIT_CHECK_VERSION (1, 3, 13)
+     // Take this opportunity to set some callbacks on the DOM
+diff --git a/src/callbacks.h b/src/callbacks.h
+index 6a10205..56bf612 100644
+--- a/src/callbacks.h
++++ b/src/callbacks.h
+@@ -31,19 +31,11 @@ gboolean
+ key_release_cb (GtkWidget* window, GdkEventKey* event);
+ gboolean
+-motion_notify_cb(GtkWidget* window, GdkEventMotion* event, gpointer user_data);
+-
+-gboolean
+ navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
+         WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
+         WebKitWebPolicyDecision *policy_decision, gpointer user_data);
+ gboolean
+-new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
+-        WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
+-        WebKitWebPolicyDecision *policy_decision, gpointer user_data);
+-
+-gboolean
+ mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request,
+         gchar *mime_type,  WebKitWebPolicyDecision *policy_decision, gpointer user_data);
+@@ -57,8 +49,13 @@ create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer us
+ gboolean
+ download_cb (WebKitWebView *web_view, WebKitDownload *download, gpointer user_data);
++#if WEBKIT_CHECK_VERSION (1, 9, 0)
++gboolean
++context_menu_cb (WebKitWebView *web_view, GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gboolean triggered_with_keyboard, gpointer user_data);
++#else
+ void
+ populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c);
++#endif
+ gboolean
+ button_press_cb (GtkWidget* window, GdkEventButton* event);
+@@ -76,8 +73,11 @@ gboolean
+ scroll_horiz_cb(GtkAdjustment *adjust, void *w);
+ void
++close_web_view_cb(WebKitWebView *webview, gpointer user_data);
++
++void
+ window_object_cleared_cb(WebKitWebView *webview, WebKitWebFrame *frame,
+-      JSGlobalContextRef *context, JSObjectRef *object);
++        JSGlobalContextRef *context, JSObjectRef *object);
+ #if WEBKIT_CHECK_VERSION (1, 3, 13)
+ void
+diff --git a/src/commands.c b/src/commands.c
+index b3546a9..6769189 100644
+--- a/src/commands.c
++++ b/src/commands.c
+@@ -6,6 +6,7 @@
+ #include "callbacks.h"
+ #include "variables.h"
+ #include "type.h"
++#include "soup.h"
+ /* -- command to callback/function map for things we cannot attach to any signals */
+ CommandInfo cmdlist[] =
+@@ -54,12 +55,34 @@ CommandInfo cmdlist[] =
+     { "menu_image_remove",              menu_remove_image, TRUE        },
+     { "menu_editable_remove",           menu_remove_edit, TRUE         },
+     { "hardcopy",                       hardcopy, TRUE                 },
++#ifndef USE_WEBKIT2
++#if WEBKIT_CHECK_VERSION (1, 9, 6)
++    { "snapshot",                       snapshot, TRUE                 },
++#endif
++#endif
++#ifdef USE_WEBKIT2
++#if WEBKIT_CHECK_VERSION (1, 9, 90)
++    { "load",                           load, TRUE                     },
++    { "save",                           save, TRUE                     },
++#endif
++#endif
++    { "remove_all_db",                  remove_all_db, 0               },
++#if WEBKIT_CHECK_VERSION (1, 3, 8)
++    { "plugin_refresh",                 plugin_refresh, TRUE           },
++    { "plugin_toggle",                  plugin_toggle, TRUE            },
++#endif
+     { "include",                        include, TRUE                  },
++    /* Deprecated */
+     { "show_inspector",                 show_inspector, 0              },
++    { "inspector",                      inspector, TRUE                },
++#if WEBKIT_CHECK_VERSION (1, 5, 1)
++    { "spell_checker",                  spell_checker, TRUE            },
++#endif
+     { "add_cookie",                     add_cookie, 0                  },
+     { "delete_cookie",                  delete_cookie, 0               },
+     { "clear_cookies",                  clear_cookies, 0               },
+-    { "download",                       download, 0                    }
++    { "download",                       download, 0                    },
++    { "auth",                           auth, 0                        }
+ };
+ void
+@@ -190,7 +213,11 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
+     uzbl_cmdprop *c = get_var_c(var_name);
+     if(!c) {
+-        set_var_value(var_name, argv_idx(argv, 1));
++        if (argv->len > 1) {
++            set_var_value(var_name, argv_idx(argv, 1));
++        } else {
++            set_var_value(var_name, "1");
++        }
+         return;
+     }
+@@ -230,18 +257,18 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
+         if(argv->len >= 3) {
+             guint i = 2;
+-            int first = strtoul(argv_idx(argv, 1), NULL, 10);
++            int first = strtol(argv_idx(argv, 1), NULL, 10);
+             int  this = first;
+             const gchar *next_s = argv_idx(argv, 2);
+             while(next_s && this != current) {
+-                this   = strtoul(next_s, NULL, 10);
++                this   = strtol(next_s, NULL, 10);
+                 next_s = argv_idx(argv, ++i);
+             }
+             if(next_s)
+-                next = strtoul(next_s, NULL, 10);
++                next = strtol(next_s, NULL, 10);
+             else
+                 next = first;
+         } else
+@@ -250,6 +277,34 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
+         set_var_value_int_c(c, next);
+         break;
+     }
++    case TYPE_ULL:
++    {
++        unsigned long long current = get_var_value_int_c(c);
++        unsigned long long next;
++
++        if(argv->len >= 3) {
++            guint i = 2;
++
++            unsigned long long first = strtoull(argv_idx(argv, 1), NULL, 10);
++            unsigned long long  this = first;
++
++            const gchar *next_s = argv_idx(argv, 2);
++
++            while(next_s && this != current) {
++                this   = strtoull(next_s, NULL, 10);
++                next_s = argv_idx(argv, ++i);
++            }
++
++            if(next_s)
++                next = strtoull(next_s, NULL, 10);
++            else
++                next = first;
++        } else
++            next = !current;
++
++        set_var_value_ull_c(c, next);
++        break;
++    }
+     case TYPE_FLOAT:
+     {
+         float current = get_var_value_float_c(c);
+@@ -325,6 +380,117 @@ hardcopy(WebKitWebView *page, GArray *argv, GString *result) {
+     webkit_web_frame_print(webkit_web_view_get_main_frame(page));
+ }
++#ifndef USE_WEBKIT2
++#if WEBKIT_CHECK_VERSION (1, 9, 6)
++void
++snapshot(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) result;
++    cairo_surface_t* surface;
++
++    surface = webkit_web_view_get_snapshot(page);
++
++    cairo_surface_write_to_png(surface, argv_idx(argv, 0));
++
++    cairo_surface_destroy(surface);
++}
++#endif
++#endif
++
++#ifdef USE_WEBKIT2
++#if WEBKIT_CHECK_VERSION (1, 9, 90)
++void
++load(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) result;
++
++    guint sz = argv->len;
++
++    const gchar *content = sz > 0 ? argv_idx(argv, 0) : NULL;
++    const gchar *content_uri = sz > 2 ? argv_idx(argv, 1) : NULL;
++    const gchar *base_uri = sz > 2 ? argv_idx(argv, 2) : NULL;
++
++    webkit_web_view_load_alternate_html(page, content, content_uri, base_uri);
++}
++
++void
++save(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) result;
++    guint sz = argv->len;
++
++    const gchar *mode_str = sz > 0 ? argv_idx(argv, 0) : NULL;
++
++    WebKitSaveMode mode = WEBKIT_SAVE_MODE_MHTML;
++
++    if (!mode) {
++        mode = WEBKIT_SAVE_MODE_MHTML;
++    } else if (!strcmp("mhtml", mode_str)) {
++        mode = WEBKIT_SAVE_MODE_MHTML;
++    }
++
++    if (sz > 1) {
++        const gchar *path = argv_idx(argv, 1);
++        GFile *gfile = g_file_new_for_path(path);
++
++        webkit_web_view_save_to_file(page, gfile, mode, NULL, NULL, NULL);
++        /* TODO: Don't ignore the error */
++        webkit_web_view_save_to_file_finish(page, NULL, NULL);
++    } else {
++        webkit_web_view_save(page, mode, NULL, NULL, NULL);
++        /* TODO: Don't ignore the error */
++        webkit_web_view_save_finish(page, NULL, NULL);
++    }
++}
++#endif
++#endif
++
++void
++remove_all_db(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) page; (void) argv; (void) result;
++
++    webkit_remove_all_web_databases ();
++}
++
++#if WEBKIT_CHECK_VERSION (1, 3, 8)
++void
++plugin_refresh(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) page; (void) argv; (void) result;
++
++    WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
++    webkit_web_plugin_database_refresh (db);
++}
++
++static void
++plugin_toggle_one(WebKitWebPlugin *plugin, const gchar *name) {
++    const gchar *plugin_name = webkit_web_plugin_get_name (plugin);
++
++    if (!name || !g_strcmp0 (name, plugin_name)) {
++        gboolean enabled = webkit_web_plugin_get_enabled (plugin);
++
++        webkit_web_plugin_set_enabled (plugin, !enabled);
++    }
++}
++
++void
++plugin_toggle(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) page; (void) result;
++
++    WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
++    GSList *plugins = webkit_web_plugin_database_get_plugins (db);
++
++    if (argv->len == 0) {
++        g_slist_foreach (plugins, (GFunc)plugin_toggle_one, NULL);
++    } else {
++        guint i;
++        for (i = 0; i < argv->len; ++i) {
++            const gchar *plugin_name = argv_idx (argv, i);
++
++            g_slist_foreach (plugins, (GFunc)plugin_toggle_one, &plugin_name);
++        }
++    }
++
++    webkit_web_plugin_database_plugins_list_free (plugins);
++}
++#endif
++
+ void
+ include(WebKitWebView *page, GArray *argv, GString *result) {
+     (void) page; (void) result;
+@@ -348,6 +514,103 @@ show_inspector(WebKitWebView *page, GArray *argv, GString *result) {
+ }
+ void
++inspector(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) page; (void) result;
++
++    if (argv->len < 1) {
++        return;
++    }
++
++    const gchar* command = argv_idx (argv, 0);
++
++    if (!g_strcmp0 (command, "show")) {
++        webkit_web_inspector_show (uzbl.gui.inspector);
++    } else if (!g_strcmp0 (command, "close")) {
++        webkit_web_inspector_close (uzbl.gui.inspector);
++    } else if (!g_strcmp0 (command, "coord")) {
++        if (argv->len < 3) {
++            return;
++        }
++
++        gdouble x = strtod (argv_idx (argv, 1), NULL);
++        gdouble y = strtod (argv_idx (argv, 2), NULL);
++
++        /* Let's not tempt the dragons. */
++        if (errno == ERANGE) {
++            return;
++        }
++
++        webkit_web_inspector_inspect_coordinates (uzbl.gui.inspector, x, y);
++#if WEBKIT_CHECK_VERSION (1, 3, 17)
++    } else if (!g_strcmp0 (command, "node")) {
++        /* TODO: Implement */
++#endif
++    }
++}
++
++#if WEBKIT_CHECK_VERSION (1, 5, 1)
++void
++spell_checker(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) page;
++
++    if (argv->len < 1) {
++        return;
++    }
++
++    GObject *obj = webkit_get_text_checker ();
++
++    if (!obj) {
++        return;
++    }
++    if (!WEBKIT_IS_SPELL_CHECKER (obj)) {
++        return;
++    }
++
++    WebKitSpellChecker *checker = WEBKIT_SPELL_CHECKER (obj);
++
++    const gchar* command = argv_idx (argv, 0);
++
++    if (!g_strcmp0 (command, "ignore")) {
++        if (argv->len < 2) {
++            return;
++        }
++
++        guint i;
++        for (i = 1; i < argv->len; ++i) {
++            const gchar *word = argv_idx (argv, i);
++
++            webkit_spell_checker_ignore_word (checker, word);
++        }
++    } else if (!g_strcmp0 (command, "learn")) {
++        if (argv->len < 2) {
++            return;
++        }
++
++        guint i;
++        for (i = 1; i < argv->len; ++i) {
++            const gchar *word = argv_idx (argv, i);
++
++            webkit_spell_checker_learn_word (checker, word);
++        }
++    } else if (!g_strcmp0 (command, "autocorrect")) {
++        if (argv->len != 2) {
++            return;
++        }
++
++        gchar *word = argv_idx (argv, 1);
++
++        char *new_word = webkit_spell_checker_get_autocorrect_suggestions_for_misspelled_word (checker, word);
++
++        g_string_assign (result, new_word);
++
++        free (new_word);
++    } else if (!g_strcmp0 (command, "guesses")) {
++        /* TODO Implement */
++    }
++}
++#endif
++
++void
+ add_cookie(WebKitWebView *page, GArray *argv, GString *result) {
+     (void) page; (void) result;
+     gchar *host, *path, *name, *value, *scheme;
+@@ -583,3 +846,18 @@ act_dump_config_as_events(WebKitWebView *web_view, GArray *argv, GString *result
+     (void)web_view; (void) argv; (void)result;
+     dump_config_as_events();
+ }
++
++void
++auth(WebKitWebView *page, GArray *argv, GString *result) {
++    (void) page; (void) result;
++    gchar *info, *username, *password;
++
++    if(argv->len != 3)
++        return;
++
++    info = argv_idx (argv, 0);
++    username = argv_idx (argv, 1);
++    password = argv_idx (argv, 2);
++
++    authenticate (info, username, password);
++}
+diff --git a/src/commands.h b/src/commands.h
+index 38bd5f2..6a14b9b 100644
+--- a/src/commands.h
++++ b/src/commands.h
+@@ -4,7 +4,11 @@
+ #ifndef __COMMANDS__
+ #define __COMMANDS__
++#ifdef USE_WEBKIT2
++#include <webkit2/webkit2.h>
++#else
+ #include <webkit/webkit.h>
++#endif
+ typedef void (*Command)(WebKitWebView*, GArray *argv, GString *result);
+@@ -51,8 +55,29 @@ void        search_reverse_text (WebKitWebView *page, GArray *argv, GString *res
+ void        search_clear(WebKitWebView *page, GArray *argv, GString *result);
+ void        dehilight (WebKitWebView *page, GArray *argv, GString *result);
+ void        hardcopy(WebKitWebView *page, GArray *argv, GString *result);
++#ifndef USE_WEBKIT2
++#if WEBKIT_CHECK_VERSION (1, 9, 6)
++void        snapshot(WebKitWebView *page, GArray *argv, GString *result);
++#endif
++#endif
++#ifdef USE_WEBKIT2
++#if WEBKIT_CHECK_VERSION (1, 9, 90)
++void        load(WebKitWebView *page, GArray *argv, GString *result);
++void        save(WebKitWebView *page, GArray *argv, GString *result);
++#endif
++#endif
++void        remove_all_db(WebKitWebView *page, GArray *argv, GString *result);
++#if WEBKIT_CHECK_VERSION (1, 3, 8)
++void        plugin_refresh(WebKitWebView *page, GArray *argv, GString *result);
++void        plugin_toggle(WebKitWebView *page, GArray *argv, GString *result);
++#endif
+ void        include(WebKitWebView *page, GArray *argv, GString *result);
++/* Deprecated (use inspector instead) */
+ void        show_inspector(WebKitWebView *page, GArray *argv, GString *result);
++void        inspector(WebKitWebView *page, GArray *argv, GString *result);
++#if WEBKIT_CHECK_VERSION (1, 5, 1)
++void        spell_checker(WebKitWebView *page, GArray *argv, GString *result);
++#endif
+ void        add_cookie(WebKitWebView *page, GArray *argv, GString *result);
+ void        delete_cookie(WebKitWebView *page, GArray *argv, GString *result);
+ void        clear_cookies(WebKitWebView *pag, GArray *argv, GString *result);
+@@ -65,5 +90,6 @@ void        toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result
+ void        toggle_status (WebKitWebView* page, GArray *argv, GString *result);
+ void        act_dump_config(WebKitWebView* page, GArray *argv, GString *result);
+ void        act_dump_config_as_events(WebKitWebView* page, GArray *argv, GString *result);
++void        auth(WebKitWebView* page, GArray *argv, GString *result);
+ #endif
+diff --git a/src/cookie-jar.c b/src/cookie-jar.c
+index 2f6be83..83facd5 100644
+--- a/src/cookie-jar.c
++++ b/src/cookie-jar.c
+@@ -41,21 +41,21 @@ changed(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie) {
+      * propagated to other uzbl instances using add/delete_cookie. */
+     if(!uzbl_jar->in_manual_add) {
+         gchar *scheme = cookie->secure
+-              ? cookie->http_only ? "httpsOnly" : "https"
+-              : cookie->http_only ? "httpOnly"  : "http";
++                ? cookie->http_only ? "httpsOnly" : "https"
++                : cookie->http_only ? "httpOnly"  : "http";
+         gchar *expires = NULL;
+         if(cookie->expires)
+             expires = g_strdup_printf ("%ld", (long)soup_date_to_time_t (cookie->expires));
+-              send_event (new_cookie ? ADD_COOKIE : DELETE_COOKIE, NULL,
+-                      TYPE_STR, cookie->domain,
+-                      TYPE_STR, cookie->path,
+-                      TYPE_STR, cookie->name,
+-                      TYPE_STR, cookie->value,
+-                      TYPE_STR, scheme,
+-                      TYPE_STR, expires ? expires : "",
+-                      NULL);
++            send_event (new_cookie ? ADD_COOKIE : DELETE_COOKIE, NULL,
++                    TYPE_STR, cookie->domain,
++                    TYPE_STR, cookie->path,
++                    TYPE_STR, cookie->name,
++                    TYPE_STR, cookie->value,
++                    TYPE_STR, scheme,
++                    TYPE_STR, expires ? expires : "",
++                    NULL);
+         if(expires)
+             g_free(expires);
+diff --git a/src/events.c b/src/events.c
+index 081a942..8fc8573 100644
+--- a/src/events.c
++++ b/src/events.c
+@@ -21,7 +21,9 @@ const char *event_table[LAST_EVENT] = {
+      "LOAD_COMMIT"      ,
+      "LOAD_FINISH"      ,
+      "LOAD_ERROR"       ,
++     "REQUEST_QUEUED"   ,
+      "REQUEST_STARTING" ,
++     "REQUEST_FINISHED" ,
+      "KEY_PRESS"        ,
+      "KEY_RELEASE"      ,
+      "MOD_PRESS"        ,
+@@ -32,6 +34,7 @@ const char *event_table[LAST_EVENT] = {
+      "GEOMETRY_CHANGED" ,
+      "WEBINSPECTOR"     ,
+      "NEW_WINDOW"       ,
++     "CLOSE_WINDOW"     ,
+      "SELECTION_CHANGED",
+      "VARIABLE_SET"     ,
+      "FIFO_SET"         ,
+@@ -48,7 +51,6 @@ const char *event_table[LAST_EVENT] = {
+      "PLUG_CREATED"     ,
+      "COMMAND_ERROR"    ,
+      "BUILTINS"         ,
+-     "PTR_MOVE"         ,
+      "SCROLL_VERT"      ,
+      "SCROLL_HORIZ"     ,
+      "DOWNLOAD_STARTED" ,
+@@ -57,7 +59,8 @@ const char *event_table[LAST_EVENT] = {
+      "ADD_COOKIE"       ,
+      "DELETE_COOKIE"    ,
+      "FOCUS_ELEMENT"    ,
+-     "BLUR_ELEMENT"
++     "BLUR_ELEMENT"     ,
++     "AUTHENTICATE"
+ };
+ /* for now this is just a alias for GString */
+@@ -168,6 +171,10 @@ vformat_event(int type, const gchar *custom_event, va_list vargs) {
+             g_string_append_printf (event_message, "%d", va_arg (vargs, int));
+             break;
++        case TYPE_ULL:
++            g_string_append_printf (event_message, "%llu", va_arg (vargs, unsigned long long));
++            break;
++
+         case TYPE_STR:
+             /* a string that needs to be escaped */
+             g_string_append_c (event_message, '\'');
+@@ -284,10 +291,10 @@ get_modifier_mask(guint state) {
+             g_string_append(modifiers, "Ctrl|");
+         if(state & GDK_MOD1_MASK)
+             g_string_append(modifiers,"Mod1|");
+-              /* Mod2 is usually Num_Luck. Ignore it as it messes up keybindings.
++        /* Mod2 is usually Num_Luck. Ignore it as it messes up keybindings.
+         if(state & GDK_MOD2_MASK)
+             g_string_append(modifiers,"Mod2|");
+-              */
++        */
+         if(state & GDK_MOD3_MASK)
+             g_string_append(modifiers,"Mod3|");
+         if(state & GDK_MOD4_MASK)
+@@ -349,10 +356,10 @@ guint key_to_modifier(guint keyval) {
+     }
+ }
+-guint button_to_modifier(guint buttonval) {
+-      if(buttonval <= 5)
+-              return 1 << (7 + buttonval);
+-      return 0;
++guint button_to_modifier (guint buttonval) {
++    if(buttonval <= 5)
++        return 1 << (7 + buttonval);
++    return 0;
+ }
+ /* Transform gdk key events to our own events */
+diff --git a/src/events.h b/src/events.h
+index 73d0712..bd9df33 100644
+--- a/src/events.h
++++ b/src/events.h
+@@ -12,20 +12,21 @@
+ /* Event system */
+ enum event_type {
+     LOAD_START, LOAD_COMMIT, LOAD_FINISH, LOAD_ERROR,
+-    REQUEST_STARTING,
++    REQUEST_QUEUED, REQUEST_STARTING, REQUEST_FINISHED,
+     KEY_PRESS, KEY_RELEASE, MOD_PRESS, MOD_RELEASE,
+     COMMAND_EXECUTED,
+     LINK_HOVER, TITLE_CHANGED, GEOMETRY_CHANGED,
+-    WEBINSPECTOR, NEW_WINDOW, SELECTION_CHANGED,
++    WEBINSPECTOR, NEW_WINDOW, CLOSE_WINDOW, SELECTION_CHANGED,
+     VARIABLE_SET, FIFO_SET, SOCKET_SET,
+     INSTANCE_START, INSTANCE_EXIT, LOAD_PROGRESS,
+     LINK_UNHOVER, FORM_ACTIVE, ROOT_ACTIVE,
+     FOCUS_LOST, FOCUS_GAINED, FILE_INCLUDED,
+     PLUG_CREATED, COMMAND_ERROR, BUILTINS,
+-    PTR_MOVE, SCROLL_VERT, SCROLL_HORIZ,
++    SCROLL_VERT, SCROLL_HORIZ,
+     DOWNLOAD_STARTED, DOWNLOAD_PROGRESS, DOWNLOAD_COMPLETE,
+     ADD_COOKIE, DELETE_COOKIE,
+     FOCUS_ELEMENT, BLUR_ELEMENT,
++    AUTHENTICATE,
+     /* must be last entry */
+     LAST_EVENT
+diff --git a/src/inspector.c b/src/inspector.c
+index d0d86b9..d584c77 100644
+--- a/src/inspector.c
++++ b/src/inspector.c
+@@ -8,14 +8,14 @@
+ #include "callbacks.h"
+ #include "type.h"
+-void
++static void
+ hide_window_cb(GtkWidget *widget, gpointer data) {
+     (void) data;
+     gtk_widget_hide(widget);
+ }
+-WebKitWebView*
++static WebKitWebView *
+ create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){
+     (void) data;
+     (void) page;
+@@ -44,7 +44,7 @@ create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpo
+     return WEBKIT_WEB_VIEW(new_web_view);
+ }
+-gboolean
++static gboolean
+ inspector_show_window_cb (WebKitWebInspector* inspector){
+     (void) inspector;
+     gtk_widget_show(uzbl.gui.inspector_window);
+@@ -54,32 +54,32 @@ inspector_show_window_cb (WebKitWebInspector* inspector){
+ }
+ /* TODO: Add variables and code to make use of these functions */
+-gboolean
++static gboolean
+ inspector_close_window_cb (WebKitWebInspector* inspector){
+     (void) inspector;
+     send_event(WEBINSPECTOR, NULL, TYPE_NAME, "close", NULL);
+     return TRUE;
+ }
+-gboolean
++static gboolean
+ inspector_attach_window_cb (WebKitWebInspector* inspector){
+     (void) inspector;
+     return FALSE;
+ }
+-gboolean
++static gboolean
+ inspector_detach_window_cb (WebKitWebInspector* inspector){
+     (void) inspector;
+     return FALSE;
+ }
+-gboolean
++static gboolean
+ inspector_uri_changed_cb (WebKitWebInspector* inspector){
+     (void) inspector;
+     return FALSE;
+ }
+-gboolean
++static gboolean
+ inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){
+     (void) inspector;
+     return FALSE;
+diff --git a/src/io.c b/src/io.c
+index ff418ef..ca3fcf6 100644
+--- a/src/io.c
++++ b/src/io.c
+@@ -47,7 +47,7 @@ control_fifo(GIOChannel *gio, GIOCondition condition) {
+ }
+-gboolean
++static gboolean
+ attach_fifo(gchar *path) {
+     GError *error = NULL;
+     /* we don't really need to write to the file, but if we open the
+@@ -259,7 +259,7 @@ control_client_socket(GIOChannel *clientchan) {
+ }
+-gboolean
++static gboolean
+ attach_socket(gchar *path, struct sockaddr_un *local) {
+     GIOChannel *chan = NULL;
+     int sock = socket (AF_UNIX, SOCK_STREAM, 0);
+diff --git a/src/menu.c b/src/menu.c
+index 451b35a..1d9c5b4 100644
+--- a/src/menu.c
++++ b/src/menu.c
+@@ -2,7 +2,7 @@
+ #include "util.h"
+ #include "uzbl-core.h"
+-void
++static void
+ add_to_menu(GArray *argv, guint context) {
+     GUI *g = &uzbl.gui;
+     MenuItem *m;
+@@ -68,7 +68,7 @@ menu_add_edit(WebKitWebView *page, GArray *argv, GString *result) {
+ }
+-void
++static void
+ add_separator_to_menu(GArray *argv, guint context) {
+     GUI *g = &uzbl.gui;
+     MenuItem *m;
+@@ -127,7 +127,7 @@ menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result) {
+ }
+-void
++static void
+ remove_from_menu(GArray *argv, guint context) {
+     GUI *g = &uzbl.gui;
+     MenuItem *mi;
+diff --git a/src/menu.h b/src/menu.h
+index 03055e5..c4ca047 100644
+--- a/src/menu.h
++++ b/src/menu.h
+@@ -1,14 +1,18 @@
+ #ifndef __MENU__
+ #define __MENU__
++#ifdef USE_WEBKIT2
++#include <webkit2/webkit2.h>
++#else
+ #include <webkit/webkit.h>
++#endif
+ typedef struct {
+     gchar*   name;
+     gchar*   cmd;
+     gboolean issep;
+     guint    context;
+-    WebKitHitTestResult* hittest;
++    gchar*   argument;
+ } MenuItem;
+ void    menu_add(WebKitWebView *page, GArray *argv, GString *result);
+diff --git a/src/soup.c b/src/soup.c
+new file mode 100644
+index 0000000..8430018
+--- /dev/null
++++ b/src/soup.c
+@@ -0,0 +1,183 @@
++#include "uzbl-core.h"
++#include "util.h"
++#include "events.h"
++#include "type.h"
++
++static void handle_authentication    (SoupSession *session,
++                                      SoupMessage *msg,
++                                      SoupAuth    *auth,
++                                      gboolean     retrying,
++                                      gpointer     user_data);
++
++static void handle_request_queued    (SoupSession *session,
++                                      SoupMessage *msg,
++                                      gpointer user_data);
++
++static void handle_request_started   (SoupSession *session,
++                                      SoupMessage *msg,
++                                      gpointer user_data);
++
++static void handle_request_finished (SoupMessage *msg,
++                                     gpointer user_data);
++
++struct _PendingAuth
++{
++    SoupAuth *auth;
++    GList    *messages;
++};
++typedef struct _PendingAuth PendingAuth;
++
++static PendingAuth *pending_auth_new         (SoupAuth *auth);
++static void         pending_auth_free        (PendingAuth *self);
++static void         pending_auth_add_message (PendingAuth *self,
++                                              SoupMessage *message);
++
++void
++uzbl_soup_init (SoupSession *session)
++{
++    uzbl.net.soup_cookie_jar = uzbl_cookie_jar_new ();
++    uzbl.net.pending_auths = g_hash_table_new_full (
++        g_str_hash, g_str_equal,
++        g_free, pending_auth_free
++    );
++
++    soup_session_add_feature (
++        session,
++        SOUP_SESSION_FEATURE (uzbl.net.soup_cookie_jar)
++    );
++
++    g_signal_connect (
++        session, "request-queued",
++        G_CALLBACK (handle_request_queued), NULL
++    );
++
++    g_signal_connect (
++        session, "request-started",
++        G_CALLBACK (handle_request_started), NULL
++    );
++
++    g_signal_connect (
++       session, "authenticate",
++        G_CALLBACK (handle_authentication), NULL
++    );
++}
++
++void authenticate (const char *authinfo,
++                   const char *username,
++                   const char *password)
++{
++    PendingAuth *pending = g_hash_table_lookup (
++        uzbl.net.pending_auths,
++        authinfo
++    );
++
++    if (pending == NULL) {
++        return;
++    }
++
++    soup_auth_authenticate (pending->auth, username, password);
++    for(GList *l = pending->messages; l != NULL; l = l->next) {
++        soup_session_unpause_message (
++            uzbl.net.soup_session,
++            SOUP_MESSAGE (l->data)
++        );
++    }
++
++    g_hash_table_remove (uzbl.net.pending_auths, authinfo);
++}
++
++static void
++handle_request_queued (SoupSession *session,
++                       SoupMessage *msg,
++                       gpointer     user_data)
++{
++    (void) session; (void) user_data;
++
++    send_event (
++        REQUEST_QUEUED, NULL,
++        TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
++        NULL
++    );
++}
++
++static void
++handle_request_started (SoupSession *session,
++                        SoupMessage *msg,
++                        gpointer     user_data)
++{
++    (void) session; (void) user_data;
++
++    send_event (
++        REQUEST_STARTING, NULL,
++        TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
++        NULL
++    );
++
++    g_signal_connect (
++        G_OBJECT (msg), "finished",
++        G_CALLBACK (handle_request_finished), NULL
++    );
++}
++
++static void
++handle_request_finished (SoupMessage *msg, gpointer user_data)
++{
++    (void) user_data;
++
++    send_event (
++        REQUEST_FINISHED, NULL,
++        TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
++        NULL
++    );
++}
++
++static void
++handle_authentication (SoupSession *session,
++                       SoupMessage *msg,
++                       SoupAuth    *auth,
++                       gboolean     retrying,
++                       gpointer     user_data)
++{
++    (void) user_data;
++    PendingAuth *pending;
++    char *authinfo = soup_auth_get_info (auth);
++
++    pending = g_hash_table_lookup (uzbl.net.pending_auths, authinfo);
++    if (pending == NULL) {
++        pending = pending_auth_new (auth);
++        g_hash_table_insert (uzbl.net.pending_auths, authinfo, pending);
++    }
++
++    pending_auth_add_message (pending, msg);
++    soup_session_pause_message (session, msg);
++
++    send_event (
++        AUTHENTICATE, NULL,
++        TYPE_STR, authinfo,
++        TYPE_STR, soup_auth_get_host (auth),
++        TYPE_STR, soup_auth_get_realm (auth),
++        TYPE_STR, (retrying ? "retrying" : ""),
++        NULL
++    );
++}
++
++static PendingAuth *pending_auth_new (SoupAuth *auth)
++{
++    PendingAuth *self = g_new (PendingAuth, 1);
++    self->auth = auth;
++    self->messages = NULL;
++    g_object_ref (auth);
++    return self;
++}
++
++static void pending_auth_free (PendingAuth *self)
++{
++    g_object_unref (self->auth);
++    g_list_free_full (self->messages, g_object_unref);
++    g_free (self);
++}
++
++static void pending_auth_add_message (PendingAuth *self, SoupMessage *message)
++{
++    self->messages = g_list_append (self->messages, g_object_ref (message));
++}
+diff --git a/src/soup.h b/src/soup.h
+new file mode 100644
+index 0000000..ce7d605
+--- /dev/null
++++ b/src/soup.h
+@@ -0,0 +1,18 @@
++/**
++ * Uzbl tweaks and extension for soup
++ */
++
++#ifndef __UZBL_SOUP__
++#define __UZBL_SOUP__
++
++#include <libsoup/soup.h>
++
++/**
++ * Attach uzbl specific behaviour to the given SoupSession
++ */
++void uzbl_soup_init (SoupSession *session);
++
++void authenticate   (const char  *authinfo,
++                     const char  *username,
++                     const char  *password);
++#endif
+diff --git a/src/status-bar.h b/src/status-bar.h
+index cae8905..1fd4ef2 100644
+--- a/src/status-bar.h
++++ b/src/status-bar.h
+@@ -10,7 +10,7 @@
+ #define UZBL_IS_STATUS_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UZBL_TYPE_STATUS_BAR))
+ #define UZBL_STATUS_BAR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), UZBL_TYPE_STATUS_BAR, UzblStatusBarClass))
+-typedef struct _UzblStatusBar       UzblStatusBar;
++typedef struct _UzblStatusBar       UzblStatusBar;
+ typedef struct _UzblStatusBarClass  UzblStatusBarClass;
+ struct _UzblStatusBar {
+diff --git a/src/type.h b/src/type.h
+index eda02c1..826ca3b 100644
+--- a/src/type.h
++++ b/src/type.h
+@@ -9,6 +9,7 @@ enum ptr_type {
+     TYPE_INT = 1,
+     TYPE_STR,
+     TYPE_FLOAT,
++    TYPE_ULL,
+     TYPE_NAME,
+     // used by send_event
+     TYPE_FORMATTEDSTR,
+@@ -24,6 +25,7 @@ enum ptr_type {
+ typedef union uzbl_value_ptr_t {
+     int   *i;
+     float *f;
++    unsigned long long *ull;
+     gchar **s;
+ } uzbl_value_ptr;
+diff --git a/src/util.c b/src/util.c
+index 1468d23..3f7f093 100644
+--- a/src/util.c
++++ b/src/util.c
+@@ -195,3 +195,9 @@ append_escaped (GString *dest, const gchar *src) {
+     }
+     return dest;
+ }
++
++void
++sharg_append (GArray *a, const gchar *str) {
++    const gchar *s = (str ? str : "");
++    g_array_append_val(a, s);
++}
+diff --git a/src/util.h b/src/util.h
+index cc29247..e0a8bbd 100644
+--- a/src/util.h
++++ b/src/util.h
+@@ -12,7 +12,11 @@ char*       str_replace(const char* search, const char* replace, const char* str
+ gboolean    for_each_line_in_file(const gchar *path, void (*callback)(const gchar *l, void *c), void *user_data);
+ gchar*      find_existing_file(const gchar*);
+ gchar*      argv_idx(const GArray*, const guint);
++
+ /**
+  * appends `src' to `dest' with backslash, single-quotes and newlines in
+- * `src' escaped */
++ * `src' escaped
++ */
+ GString *   append_escaped (GString *dest, const gchar *src);
++
++void        sharg_append (GArray *array, const gchar *str);
+diff --git a/src/uzbl-core.c b/src/uzbl-core.c
+index 1288f22..748202f 100644
+--- a/src/uzbl-core.c
++++ b/src/uzbl-core.c
+@@ -39,6 +39,7 @@
+ #include "io.h"
+ #include "variables.h"
+ #include "type.h"
++#include "soup.h"
+ UzblCore uzbl;
+@@ -279,22 +280,6 @@ clean_up(void) {
+     }
+ }
+-gint
+-get_click_context() {
+-    GUI *g = &uzbl.gui;
+-    WebKitHitTestResult *ht;
+-    guint context;
+-
+-    if(!uzbl.state.last_button)
+-        return -1;
+-
+-    ht = webkit_web_view_get_hit_test_result (g->web_view, uzbl.state.last_button);
+-    g_object_get (ht, "context", &context, NULL);
+-      g_object_unref (ht);
+-
+-    return (gint)context;
+-}
+-
+ /* --- SIGNALS --- */
+ sigfunc*
+ setup_signal(int signr, sigfunc *shandler) {
+@@ -310,7 +295,7 @@ setup_signal(int signr, sigfunc *shandler) {
+     return NULL;
+ }
+-void
++static void
+ empty_event_buffer(int s) {
+     (void) s;
+     if(uzbl.state.event_buffer) {
+@@ -356,12 +341,12 @@ parse_cmd_line_cb(const char *line, void *user_data) {
+ }
+ void
+-run_command_file(const gchar *path) {
+-      if(!for_each_line_in_file(path, parse_cmd_line_cb, NULL)) {
+-              gchar *tmp = g_strdup_printf("File %s can not be read.", path);
+-              send_event(COMMAND_ERROR, NULL, TYPE_STR, tmp, NULL);
+-              g_free(tmp);
+-      }
++run_command_file (const gchar *path) {
++    if(!for_each_line_in_file(path, parse_cmd_line_cb, NULL)) {
++        gchar *tmp = g_strdup_printf("File %s can not be read.", path);
++        send_event(COMMAND_ERROR, NULL, TYPE_STR, tmp, NULL);
++        g_free(tmp);
++    }
+ }
+ /* Javascript*/
+@@ -463,12 +448,6 @@ search_text (WebKitWebView *page, const gchar *key, const gboolean forward) {
+     }
+ }
+-void
+-sharg_append(GArray *a, const gchar *str) {
+-    const gchar *s = (str ? str : "");
+-    g_array_append_val(a, s);
+-}
+-
+ /* make sure that the args string you pass can properly be interpreted (eg
+  * properly escaped against whitespace, quotes etc) */
+ gboolean
+@@ -517,7 +496,7 @@ run_command (const gchar *command, const gchar **args, const gboolean sync,
+     return result;
+ }
+-/*@null@*/ gchar**
++/*@null@*/ static gchar**
+ split_quoted(const gchar* src, const gboolean unquote) {
+     /* split on unquoted space or tab, return array of strings;
+        remove a layer of quotes and backslashes if unquote */
+@@ -769,7 +748,6 @@ create_scrolled_win() {
+       "signal::key-release-event",                    (GCallback)key_release_cb,          NULL,
+       "signal::button-press-event",                   (GCallback)button_press_cb,         NULL,
+       "signal::button-release-event",                 (GCallback)button_release_cb,       NULL,
+-      "signal::motion-notify-event",                  (GCallback)motion_notify_cb,        NULL,
+       "signal::notify::title",                        (GCallback)title_change_cb,         NULL,
+       "signal::notify::progress",                     (GCallback)progress_change_cb,      NULL,
+       "signal::notify::load-status",                  (GCallback)load_status_change_cb,   NULL,
+@@ -777,12 +755,16 @@ create_scrolled_win() {
+       "signal::load-error",                           (GCallback)load_error_cb,           NULL,
+       "signal::hovering-over-link",                   (GCallback)link_hover_cb,           NULL,
+       "signal::navigation-policy-decision-requested", (GCallback)navigation_decision_cb,  NULL,
+-      "signal::new-window-policy-decision-requested", (GCallback)new_window_cb,           NULL,
++      "signal::close-web-view",                       (GCallback)close_web_view_cb,       NULL,
+       "signal::download-requested",                   (GCallback)download_cb,             NULL,
+       "signal::create-web-view",                      (GCallback)create_web_view_cb,      NULL,
+       "signal::mime-type-policy-decision-requested",  (GCallback)mime_policy_cb,          NULL,
+       "signal::resource-request-starting",            (GCallback)request_starting_cb,     NULL,
++#if WEBKIT_CHECK_VERSION (1, 9, 0)
++      "signal::context-menu",                         (GCallback)context_menu_cb,         NULL,
++#else
+       "signal::populate-popup",                       (GCallback)populate_popup_cb,       NULL,
++#endif
+       "signal::focus-in-event",                       (GCallback)focus_cb,                NULL,
+       "signal::focus-out-event",                      (GCallback)focus_cb,                NULL,
+       "signal::window-object-cleared",                (GCallback)window_object_cleared_cb,NULL,
+@@ -822,7 +804,6 @@ create_plug() {
+ void
+ settings_init () {
+     State*   s = &uzbl.state;
+-    Network* n = &uzbl.net;
+     int      i;
+     /* Load default config */
+@@ -841,70 +822,13 @@ settings_init () {
+     /* Load config file, if any */
+     if (s->config_file) {
+-              run_command_file(s->config_file);
++        run_command_file(s->config_file);
+         g_setenv("UZBL_CONFIG", s->config_file, TRUE);
+     } else if (uzbl.state.verbose)
+         printf ("No configuration file loaded.\n");
+     if (s->connect_socket_names)
+         init_connect_socket();
+-
+-    g_signal_connect(n->soup_session, "authenticate", G_CALLBACK(handle_authentication), NULL);
+-}
+-
+-
+-void handle_authentication (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data) {
+-    (void) user_data;
+-
+-    if (uzbl.behave.authentication_handler && *uzbl.behave.authentication_handler != 0) {
+-        soup_session_pause_message(session, msg);
+-
+-        GString *result = g_string_new ("");
+-
+-        gchar *info  = g_strdup(soup_auth_get_info(auth));
+-        gchar *host  = g_strdup(soup_auth_get_host(auth));
+-        gchar *realm = g_strdup(soup_auth_get_realm(auth));
+-
+-        GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
+-        const CommandInfo *c = parse_command_parts(uzbl.behave.authentication_handler, a);
+-        if(c) {
+-            sharg_append(a, info);
+-            sharg_append(a, host);
+-            sharg_append(a, realm);
+-            sharg_append(a, retrying ? "TRUE" : "FALSE");
+-
+-            run_parsed_command(c, a, result);
+-        }
+-        g_array_free(a, TRUE);
+-
+-        if (result->len > 0) {
+-            char  *username, *password;
+-            int    number_of_endls=0;
+-
+-            username = result->str;
+-
+-            gchar *p;
+-            for (p = result->str; *p; p++) {
+-                if (*p == '\n') {
+-                    *p = '\0';
+-                    if (++number_of_endls == 1)
+-                        password = p + 1;
+-                }
+-            }
+-
+-            /* If stdout was correct (contains exactly two lines of text) do
+-             * authenticate. */
+-            if (number_of_endls == 2)
+-                soup_auth_authenticate(auth, username, password);
+-        }
+-
+-        soup_session_unpause_message(session, msg);
+-
+-        g_string_free(result, TRUE);
+-        g_free(info);
+-        g_free(host);
+-        g_free(realm);
+-    }
+ }
+ /* Set up gtk, gobject, variable defaults and other things that tests and other
+@@ -922,11 +846,19 @@ initialize(int argc, char** argv) {
+     uzbl.info.webkit_major     = webkit_major_version();
+     uzbl.info.webkit_minor     = webkit_minor_version();
+     uzbl.info.webkit_micro     = webkit_micro_version();
++#ifdef USE_WEBKIT2
++    uzbl.info.webkit2          = 1;
++#else
++    uzbl.info.webkit2          = 0;
++#endif
+     uzbl.info.arch             = ARCH;
+     uzbl.info.commit           = COMMIT;
+     uzbl.state.last_result  = NULL;
++    /* BUG There isn't a getter for this; need to maintain separately. */
++    uzbl.behave.maintain_history = TRUE;
++
+     /* Parse commandline arguments */
+     GOptionContext* context = g_option_context_new ("[ uri ] - load a uri by default");
+     g_option_context_add_main_entries(context, entries, NULL);
+@@ -956,10 +888,8 @@ initialize(int argc, char** argv) {
+     event_buffer_timeout(10);
+     /* HTTP client */
+-    uzbl.net.soup_session      = webkit_get_default_session();
+-    uzbl.net.soup_cookie_jar   = uzbl_cookie_jar_new();
+-
+-    soup_session_add_feature(uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_cookie_jar));
++    uzbl.net.soup_session = webkit_get_default_session();
++    uzbl_soup_init (uzbl.net.soup_session);
+     commands_hash();
+     variables_hash();
+@@ -1081,6 +1011,13 @@ main (int argc, char* argv[]) {
+     gboolean verbose_override = uzbl.state.verbose;
++    /* Finally show the window */
++    if (uzbl.gui.main_window) {
++        gtk_widget_show_all (uzbl.gui.main_window);
++    } else {
++        gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug));
++    }
++
+     /* Read configuration file */
+     settings_init();
+@@ -1099,13 +1036,6 @@ main (int argc, char* argv[]) {
+         g_free(uri_override);
+     }
+-    /* Finally show the window */
+-    if (uzbl.gui.main_window) {
+-        gtk_widget_show_all (uzbl.gui.main_window);
+-    } else {
+-        gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug));
+-    }
+-
+     /* Verbose feedback */
+     if (uzbl.state.verbose) {
+         printf("Uzbl start location: %s\n", argv[0]);
+diff --git a/src/uzbl-core.h b/src/uzbl-core.h
+index 1f9613e..6f27818 100644
+--- a/src/uzbl-core.h
++++ b/src/uzbl-core.h
+@@ -23,7 +23,11 @@
+ #include <sys/un.h>
+ #include <sys/utsname.h>
+ #include <sys/time.h>
++#ifdef USE_WEBKIT2
++#include <webkit2/webkit2.h>
++#else
+ #include <webkit/webkit.h>
++#endif
+ #include <libsoup/soup.h>
+ #include <JavaScriptCore/JavaScript.h>
+@@ -78,6 +82,7 @@ typedef struct {
+     WebKitWebInspector* inspector;
+     /* Custom context menu item */
++    gboolean       custom_context_menu;
+     GPtrArray*     menu_items;
+ } GUI;
+@@ -114,6 +119,9 @@ typedef struct {
+     gboolean        handle_multi_button;
+     GPtrArray*      event_buffer;
+     gchar**         connect_socket_names;
++
++    /* Temporary web view used when a new window is opened */
++    WebKitWebView* _tmp_web_view;
+ } State;
+@@ -121,6 +129,7 @@ typedef struct {
+ typedef struct {
+     SoupSession*    soup_session;
+     UzblCookieJar*  soup_cookie_jar;
++    GHashTable*     pending_auths;
+     SoupLogger*     soup_logger;
+     char*           proxy_url;
+     char*           useragent;
+@@ -129,12 +138,6 @@ typedef struct {
+     gint            max_conns_host;
+ } Network;
+-/* ssl */
+-typedef struct {
+-    gchar *ca_file;
+-    gchar *verify_cert;
+-} Ssl;
+-
+ /* Behaviour */
+ typedef struct {
+     /* Status bar */
+@@ -161,6 +164,7 @@ typedef struct {
+     guint    http_debug;
+     gchar*   shell_cmd;
+     guint    view_source;
++    gboolean maintain_history;
+     gboolean print_version;
+@@ -176,6 +180,7 @@ typedef struct {
+     int     webkit_major;
+     int     webkit_minor;
+     int     webkit_micro;
++    int     webkit2;
+     gchar*  arch;
+     gchar*  commit;
+@@ -189,7 +194,6 @@ typedef struct {
+     GUI           gui;
+     State         state;
+     Network       net;
+-    Ssl           ssl;
+     Behaviour     behave;
+     Communication comm;
+     Info          info;
+@@ -245,19 +249,12 @@ void        search_text (WebKitWebView *page, const gchar *key, const gboolean f
+ void        eval_js(WebKitWebView *web_view, const gchar *script, GString *result, const gchar *script_file);
+ /* Network functions */
+-void        handle_authentication (SoupSession *session,
+-                            SoupMessage *msg,
+-                            SoupAuth    *auth,
+-                            gboolean     retrying,
+-                            gpointer     user_data);
+-
+ void        init_connect_socket();
+ gboolean    remove_socket_from_array(GIOChannel *chan);
+ /* Window */
+ void        retrieve_geometry();
+ void        scroll(GtkAdjustment* bar, gchar *amount_str);
+-gint        get_click_context();
+ #endif
+diff --git a/src/variables.c b/src/variables.c
+index e4763bc..749a176 100644
+--- a/src/variables.c
++++ b/src/variables.c
+@@ -5,6 +5,8 @@
+ #include "io.h"
+ #include "util.h"
++#include <stdlib.h>
++
+ uzbl_cmdprop *
+ get_var_c(const gchar *name) {
+     return g_hash_table_lookup(uzbl.behave.proto_var, name);
+@@ -32,6 +34,13 @@ send_set_var_event(const char *name, const uzbl_cmdprop *c) {
+             TYPE_INT, get_var_value_int_c(c),
+             NULL);
+         break;
++    case TYPE_ULL:
++        send_event (VARIABLE_SET, NULL,
++            TYPE_NAME, name,
++            TYPE_NAME, "ull",
++            TYPE_ULL, get_var_value_ull_c(c),
++            NULL);
++        break;
+     case TYPE_FLOAT:
+         send_event (VARIABLE_SET, NULL,
+             TYPE_NAME, name,
+@@ -78,6 +87,14 @@ set_var_value_int_c(uzbl_cmdprop *c, int i) {
+ }
+ void
++set_var_value_ull_c(uzbl_cmdprop *c, unsigned long long ull) {
++    if(c->setter)
++        ((void (*)(unsigned long long))c->setter)(ull);
++    else
++        *(c->ptr.ull) = ull;
++}
++
++void
+ set_var_value_float_c(uzbl_cmdprop *c, float f) {
+     if(c->setter)
+         ((void (*)(float))c->setter)(f);
+@@ -100,10 +117,16 @@ set_var_value(const gchar *name, gchar *val) {
+             break;
+         case TYPE_INT:
+         {
+-            int i = (int)strtoul(val, NULL, 10);
++            int i = (int)strtol(val, NULL, 10);
+             set_var_value_int_c(c, i);
+             break;
+         }
++        case TYPE_ULL:
++        {
++            unsigned long long ull = strtoull(val, NULL, 10);
++            set_var_value_ull_c(c, ull);
++            break;
++        }
+         case TYPE_FLOAT:
+         {
+             float f = strtod(val, NULL);
+@@ -184,6 +207,24 @@ get_var_value_int(const gchar *name) {
+     return get_var_value_int_c(c);
+ }
++unsigned long long
++get_var_value_ull_c(const uzbl_cmdprop *c) {
++    if(!c) return 0;
++
++    if(c->getter) {
++        return ((unsigned long long (*)())c->getter)();
++    } else if(c->ptr.ull)
++        return *(c->ptr.ull);
++
++    return 0;
++}
++
++unsigned long long
++get_var_value_ull(const gchar *name) {
++    uzbl_cmdprop *c = get_var_c(name);
++    return get_var_value_ull_c(c);
++}
++
+ float
+ get_var_value_float_c(const uzbl_cmdprop *c) {
+     if(!c) return 0;
+@@ -202,7 +243,7 @@ get_var_value_float(const gchar *name) {
+     return get_var_value_float_c(c);
+ }
+-void
++static void
+ dump_var_hash(gpointer k, gpointer v, gpointer ud) {
+     (void) ud;
+     uzbl_cmdprop *c = v;
+@@ -214,10 +255,13 @@ dump_var_hash(gpointer k, gpointer v, gpointer ud) {
+         gchar *v = get_var_value_string_c(c);
+         printf("set %s = %s\n", (char *)k, v);
+         g_free(v);
+-    } else if(c->type == TYPE_INT)
++    } else if(c->type == TYPE_INT) {
+         printf("set %s = %d\n", (char *)k, get_var_value_int_c(c));
+-    else if(c->type == TYPE_FLOAT)
++    } else if(c->type == TYPE_ULL) {
++        printf("set %s = %llu\n", (char *)k, get_var_value_ull_c(c));
++    } else if(c->type == TYPE_FLOAT) {
+         printf("set %s = %f\n", (char *)k, get_var_value_float_c(c));
++    }
+ }
+ void
+@@ -225,7 +269,7 @@ dump_config() {
+     g_hash_table_foreach(uzbl.behave.proto_var, dump_var_hash, NULL);
+ }
+-void
++static void
+ dump_var_hash_as_event(gpointer k, gpointer v, gpointer ud) {
+     (void) ud;
+     uzbl_cmdprop *c = v;
+@@ -240,18 +284,23 @@ dump_config_as_events() {
+ }
+ /* is the given string made up entirely of decimal digits? */
+-gboolean
++static gboolean
+ string_is_integer(const char *s) {
+     return (strspn(s, "0123456789") == strlen(s));
+ }
+-GObject*
++static GObject *
++cookie_jar() {
++    return G_OBJECT(uzbl.net.soup_cookie_jar);
++}
++
++static GObject *
+ view_settings() {
+     return G_OBJECT(webkit_web_view_get_settings(uzbl.gui.web_view));
+ }
+-void
++static void
+ set_window_property(const gchar* prop, const gchar* value) {
+     if(GTK_IS_WIDGET(uzbl.gui.main_window)) {
+         gdk_property_change(
+@@ -276,7 +325,7 @@ uri_change_cb (WebKitWebView *web_view, GParamSpec param_spec) {
+     set_window_property("UZBL_URI", uzbl.state.uri);
+ }
+-gchar *
++static gchar *
+ make_uri_from_user_input(const gchar *uri) {
+     gchar *result = NULL;
+@@ -312,7 +361,7 @@ make_uri_from_user_input(const gchar *uri) {
+     return g_strconcat("http://", uri, NULL);
+ }
+-void
++static void
+ set_uri(const gchar *uri) {
+     /* Strip leading whitespace */
+     while (*uri && isspace(*uri))
+@@ -340,7 +389,7 @@ set_uri(const gchar *uri) {
+     g_free (newuri);
+ }
+-void
++static void
+ set_max_conns(int max_conns) {
+     uzbl.net.max_conns = max_conns;
+@@ -348,7 +397,7 @@ set_max_conns(int max_conns) {
+             SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
+ }
+-void
++static void
+ set_max_conns_host(int max_conns_host) {
+     uzbl.net.max_conns_host = max_conns_host;
+@@ -356,7 +405,7 @@ set_max_conns_host(int max_conns_host) {
+             SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
+ }
+-void
++static void
+ set_http_debug(int debug) {
+     uzbl.behave.http_debug = debug;
+@@ -371,77 +420,258 @@ set_http_debug(int debug) {
+             SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
+ }
+-void
++static void
+ set_ca_file(gchar *path) {
+     g_object_set (uzbl.net.soup_session, "ssl-ca-file", path, NULL);
+ }
+-gchar *
++static gchar *
+ get_ca_file() {
+     gchar *path;
+     g_object_get (uzbl.net.soup_session, "ssl-ca-file", &path, NULL);
+     return path;
+ }
+-void
+-set_verify_cert(int strict) {
+-    g_object_set (uzbl.net.soup_session, "ssl-strict", strict, NULL);
++#define EXPOSE_WEB_INSPECTOR_SETTINGS(SYM, PROPERTY, TYPE) \
++static void set_##SYM(TYPE val) { \
++  g_object_set(uzbl.gui.inspector, (PROPERTY), val, NULL); \
++} \
++static TYPE get_##SYM() { \
++  TYPE val; \
++  g_object_get(uzbl.gui.inspector, (PROPERTY), &val, NULL); \
++  return val; \
+ }
+-int
+-get_verify_cert() {
+-    int strict;
+-    g_object_get (uzbl.net.soup_session, "ssl-strict", &strict, NULL);
+-    return strict;
++EXPOSE_WEB_INSPECTOR_SETTINGS(profile_js,         "javascript-profiling-enabled",       int)
++EXPOSE_WEB_INSPECTOR_SETTINGS(profile_timeline,   "timeline-profiling-enabled",         gchar *)
++
++#undef EXPOSE_WEB_INSPECTOR_SETTINGS
++
++#define EXPOSE_SOUP_SESSION_SETTINGS(SYM, PROPERTY, TYPE) \
++static void set_##SYM(TYPE val) { \
++  g_object_set(uzbl.net.soup_session, (PROPERTY), val, NULL); \
++} \
++static TYPE get_##SYM() { \
++  TYPE val; \
++  g_object_get(uzbl.net.soup_session, (PROPERTY), &val, NULL); \
++  return val; \
+ }
++EXPOSE_SOUP_SESSION_SETTINGS(verify_cert,      "ssl-strict",       int)
++
++#undef EXPOSE_SOUP_SESSION_SETTINGS
++
++#define EXPOSE_SOUP_COOKIE_JAR_SETTINGS(SYM, PROPERTY, TYPE) \
++static void set_##SYM(TYPE val) { \
++  g_object_set(cookie_jar(), (PROPERTY), val, NULL); \
++} \
++static TYPE get_##SYM() { \
++  TYPE val; \
++  g_object_get(cookie_jar(), (PROPERTY), &val, NULL); \
++  return val; \
++}
++
++EXPOSE_SOUP_COOKIE_JAR_SETTINGS(cookie_policy,    "accept-policy",    int)
++
++#undef EXPOSE_SOUP_COOKIE_JAR_SETTINGS
++
++#define EXPOSE_WEBKIT_VIEW_VIEW_SETTINGS(SYM, PROPERTY, TYPE) \
++static void set_##SYM(TYPE val) { \
++  g_object_set(uzbl.gui.web_view, (PROPERTY), val, NULL); \
++} \
++static TYPE get_##SYM() { \
++  TYPE val; \
++  g_object_get(uzbl.gui.web_view, (PROPERTY), &val, NULL); \
++  return val; \
++}
++
++EXPOSE_WEBKIT_VIEW_VIEW_SETTINGS(editable,                "editable",                                  int)
++EXPOSE_WEBKIT_VIEW_VIEW_SETTINGS(transparent,             "transparent",                               int)
++
++#undef EXPOSE_WEBKIT_VIEW_SETTINGS
++
+ #define EXPOSE_WEBKIT_VIEW_SETTINGS(SYM, PROPERTY, TYPE) \
+-void set_##SYM(TYPE val) { \
++static void set_##SYM(TYPE val) { \
+   g_object_set(view_settings(), (PROPERTY), val, NULL); \
+ } \
+-TYPE get_##SYM() { \
++static TYPE get_##SYM() { \
+   TYPE val; \
+   g_object_get(view_settings(), (PROPERTY), &val, NULL); \
+   return val; \
+ }
+-EXPOSE_WEBKIT_VIEW_SETTINGS(default_font_family,    "default-font-family",    gchar *)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(monospace_font_family,  "monospace-font-family",  gchar *)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(sans_serif_font_family, "sans_serif-font-family", gchar *)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(serif_font_family,      "serif-font-family",      gchar *)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(cursive_font_family,    "cursive-font-family",    gchar *)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(fantasy_font_family,    "fantasy-font-family",    gchar *)
++/* Font settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(default_font_family,          "default-font-family",                       gchar *)
++EXPOSE_WEBKIT_VIEW_SETTINGS(monospace_font_family,        "monospace-font-family",                     gchar *)
++EXPOSE_WEBKIT_VIEW_SETTINGS(sans_serif_font_family,       "sans_serif-font-family",                    gchar *)
++EXPOSE_WEBKIT_VIEW_SETTINGS(serif_font_family,            "serif-font-family",                         gchar *)
++EXPOSE_WEBKIT_VIEW_SETTINGS(cursive_font_family,          "cursive-font-family",                       gchar *)
++EXPOSE_WEBKIT_VIEW_SETTINGS(fantasy_font_family,          "fantasy-font-family",                       gchar *)
++
++/* Font size settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(minimum_font_size,            "minimum-font-size",                         int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(font_size,                    "default-font-size",                         int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(monospace_size,               "default-monospace-font-size",               int)
++
++/* Text settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(default_encoding,             "default-encoding",                          gchar *)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enforce_96_dpi,               "enforce-96-dpi",                            int)
++
++/* Feature settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_plugins,               "enable-plugins",                            int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_java_applet,           "enable-java-applet",                        int)
++#if WEBKIT_CHECK_VERSION (1, 3, 14)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_webgl,                 "enable-webgl",                              int)
++#endif
++#if WEBKIT_CHECK_VERSION (1, 7, 5)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_webaudio,              "enable-webaudio",                           int)
++#endif
++#if WEBKIT_CHECK_VERSION (1, 7, 90) // Documentation says 1.7.5, but it's not there.
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_3d_acceleration,       "enable-accelerated-compositing",            int)
++#endif
++#if WEBKIT_CHECK_VERSION (1, 11, 1)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_css_shaders,           "enable-css-shaders",                        int)
++#endif
+-EXPOSE_WEBKIT_VIEW_SETTINGS(minimum_font_size,      "minimum-font-size",            int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(font_size,              "default-font-size",            int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(monospace_size,         "default-monospace-font-size",  int)
++/* HTML5 Database settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_database,              "enable-html5-database",                     int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_local_storage,         "enable-html5-local-storage",                int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_pagecache,             "enable-page-cache",                         int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_offline_app_cache,     "enable-offline-web-application-cache",      int)
++#if WEBKIT_CHECK_VERSION (1, 5, 2)
++EXPOSE_WEBKIT_VIEW_SETTINGS(local_storage_path,           "html5-local-storage-database-path",         gchar *)
++#endif
+-EXPOSE_WEBKIT_VIEW_SETTINGS(enable_plugins, "enable-plugins", int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(enable_scripts, "enable-scripts", int)
++/* Security settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_private_webkit,        "enable-private-browsing",                   int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_universal_file_access, "enable-universal-access-from-file-uris",    int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_cross_file_access,     "enable-file-access-from-file-uris",         int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_hyperlink_auditing,    "enable-hyperlink-auditing",                 int)
++#if WEBKIT_CHECK_VERSION (1, 3, 13)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_dns_prefetch,          "enable-dns-prefetching",                    int)
++#endif
++#if WEBKIT_CHECK_VERSION (1, 11, 2)
++EXPOSE_WEBKIT_VIEW_SETTINGS(display_insecure_content,     "enable-display-of-insecure-content",        int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(run_insecure_content,         "enable-running-of-insecure-content",        int)
++#endif
+-EXPOSE_WEBKIT_VIEW_SETTINGS(javascript_windows, "javascript-can-open-windows-automatically", int)
++/* Display settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(zoom_step,                    "zoom-step",                                 float)
++EXPOSE_WEBKIT_VIEW_SETTINGS(caret_browsing,               "enable-caret-browsing",                     int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(auto_resize_window,           "auto-resize-window",                        int)
++#if WEBKIT_CHECK_VERSION (1, 3, 5)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_frame_flattening,      "enable-frame-flattening",                   int)
++#endif
++#if WEBKIT_CHECK_VERSION (1, 3, 8)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_fullscreen,            "enable-fullscreen",                         int)
++#endif
++#ifdef USE_WEBKIT2
++#if WEBKIT_CHECK_VERSION (1, 7, 91)
++EXPOSE_WEBKIT_VIEW_SETTINGS(zoom_text_only,               "zoom-text-only",                            int)
++#endif
++#endif
++#if WEBKIT_CHECK_VERSION (1, 9, 0)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_smooth_scrolling,      "enable-smooth-scrolling",                   int)
++#endif
+-EXPOSE_WEBKIT_VIEW_SETTINGS(autoload_images,       "auto-load-images",   int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(autoshrink_images,     "auto-shrink-images", int)
++/* Javascript settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_scripts,               "enable-scripts",                            int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(javascript_windows,           "javascript-can-open-windows-automatically", int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(javascript_dom_paste,         "enable-dom-paste",                          int)
++#if WEBKIT_CHECK_VERSION (1, 3, 0)
++EXPOSE_WEBKIT_VIEW_SETTINGS(javascript_clipboard,         "javascript-can-access-clipboard",           int)
++#endif
+-EXPOSE_WEBKIT_VIEW_SETTINGS(enable_pagecache,   "enable-page-cache",       int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(enable_private,     "enable-private-browsing", int)
++/* Media settings */
++#if WEBKIT_CHECK_VERSION (1, 9, 3)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_inline_media,          "media-playback-allows-inline",              int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(require_click_to_play,        "media-playback-requires-user-gesture",      int)
++#endif
++#if WEBKIT_CHECK_VERSION (1, 11, 1)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_media_stream,          "enable-media-stream",                       int)
++#endif
+-EXPOSE_WEBKIT_VIEW_SETTINGS(enable_spellcheck,     "enable-spell-checking",    int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(spellcheck_languages,  "spell-checking-languages", gchar *)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(resizable_text_areas,  "resizable-text-areas",     int)
++/* Image settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(autoload_images,              "auto-load-images",                          int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(autoshrink_images,            "auto-shrink-images",                        int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(stylesheet_uri,   "user-stylesheet-uri", gchar *)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(print_bg,         "print-backgrounds",   int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(enforce_96_dpi,   "enforce-96-dpi",      int)
++/* Spell checking settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_spellcheck,            "enable-spell-checking",                     int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(caret_browsing,   "enable-caret-browsing", int)
++/* Form settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(resizable_text_areas,         "resizable-text-areas",                      int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_spatial_navigation,    "enable-spatial-navigation",                 int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(editing_behavior,             "editing-behavior",                          int)
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_tab_cycle,             "tab-key-cycles-through-elements",           int)
+-EXPOSE_WEBKIT_VIEW_SETTINGS(enable_cross_file_access, "enable-file-access-from-file-uris", int)
++/* Customization */
++EXPOSE_WEBKIT_VIEW_SETTINGS(stylesheet_uri,               "user-stylesheet-uri",                       gchar *)
++#if !WEBKIT_CHECK_VERSION (1, 9, 0)
++EXPOSE_WEBKIT_VIEW_SETTINGS(default_context_menu,         "enable-default-context-menu",               int)
++#endif
+-EXPOSE_WEBKIT_VIEW_SETTINGS(default_encoding, "default-encoding", gchar *)
++/* Hacks */
++EXPOSE_WEBKIT_VIEW_SETTINGS(enable_site_workarounds,      "enable-site-specific-quirks",               int)
+-void
++/* Printing settings */
++EXPOSE_WEBKIT_VIEW_SETTINGS(print_bg,                     "print-backgrounds",                         int)
++
++#undef EXPOSE_WEBKIT_VIEW_SETTINGS
++
++static void
++set_maintain_history (int maintain) {
++    uzbl.behave.maintain_history = maintain;
++
++    webkit_web_view_set_maintains_back_forward_list (uzbl.gui.web_view, maintain);
++}
++
++static int
++get_maintain_history () {
++    return uzbl.behave.maintain_history;
++}
++
++static void
++set_spellcheck_languages(const gchar *languages) {
++  GObject *obj = webkit_get_text_checker ();
++
++  if (!obj) {
++      return;
++  }
++  if (!WEBKIT_IS_SPELL_CHECKER (obj)) {
++      return;
++  }
++
++  WebKitSpellChecker *checker = WEBKIT_SPELL_CHECKER (obj);
++
++  webkit_spell_checker_update_spell_checking_languages (checker, languages);
++  g_object_set(view_settings(), "spell-checking-languages", languages, NULL);
++}
++
++static gchar *
++get_spellcheck_languages() {
++  gchar *val;
++  g_object_get(view_settings(), "spell-checking-languages", &val, NULL);
++  return val;
++}
++
++static void
++set_enable_private (int private) {
++    const char *priv_envvar = "UZBL_PRIVATE";
++
++    if (private)
++        setenv (priv_envvar, "true", 1);
++    else
++        unsetenv (priv_envvar);
++
++    set_enable_private_webkit (private);
++}
++
++static int
++get_enable_private () {
++    return get_enable_private_webkit ();
++}
++
++static void
+ set_proxy_url(const gchar *proxy_url) {
+     g_free(uzbl.net.proxy_url);
+     uzbl.net.proxy_url = g_strdup(proxy_url);
+@@ -459,28 +689,45 @@ set_proxy_url(const gchar *proxy_url) {
+         soup_uri_free(soup_uri);
+ }
+-void
+-set_authentication_handler(const gchar *handler) {
+-    /* Check if WEBKIT_TYPE_SOUP_AUTH_DIALOG feature is set */
+-    GSList *flist = soup_session_get_features (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG);
+-    guint feature_is_set = g_slist_length(flist);
+-    g_slist_free(flist);
+-
+-    g_free(uzbl.behave.authentication_handler);
+-    uzbl.behave.authentication_handler = g_strdup(handler);
+-
+-    if (uzbl.behave.authentication_handler == NULL || *uzbl.behave.authentication_handler == 0) {
+-        if (!feature_is_set)
+-            soup_session_add_feature_by_type
+-                (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG);
++/**
++ * Check if the webkit auth dialog is enabled for the soup session
++ */
++int
++get_enable_builtin_auth () {
++    SoupSessionFeature *auth = soup_session_get_feature (
++        uzbl.net.soup_session,
++        (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG
++    );
++
++    return auth != NULL;
++}
++
++/**
++ * Enable/Disable the webkit auth dialog for the soup session
++ */
++static void
++set_enable_builtin_auth (int enabled) {
++    SoupSessionFeature *auth = soup_session_get_feature (
++        uzbl.net.soup_session,
++        (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG
++    );
++
++    if (enabled > 0) {
++        if (auth == NULL) {
++            soup_session_add_feature_by_type (
++                uzbl.net.soup_session,
++                (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG
++            );
++        }
+     } else {
+-        if (feature_is_set)
+-            soup_session_remove_feature_by_type
+-                (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG);
++        if (auth != NULL) {
++            soup_session_remove_feature (uzbl.net.soup_session, auth);
++        }
+     }
++
+ }
+-void
++static void
+ set_status_background(const gchar *background) {
+     /* labels and hboxes do not draw their own background. applying this
+      * on the vbox/main_window is ok as the statusbar is the only affected
+@@ -501,7 +748,7 @@ set_status_background(const gchar *background) {
+ #endif
+ }
+-void
++static void
+ set_icon(const gchar *icon) {
+     if(file_exists(icon) && uzbl.gui.main_window) {
+         g_free(uzbl.gui.icon);
+@@ -513,7 +760,7 @@ set_icon(const gchar *icon) {
+     }
+ }
+-void
++static void
+ set_window_role(const gchar *role) {
+     if (!uzbl.gui.main_window)
+         return;
+@@ -521,7 +768,7 @@ set_window_role(const gchar *role) {
+     gtk_window_set_role(GTK_WINDOW (uzbl.gui.main_window), role);
+ }
+-gchar *
++static gchar *
+ get_window_role() {
+     if (!uzbl.gui.main_window)
+         return NULL;
+@@ -585,7 +832,7 @@ get_show_status() {
+   return gtk_widget_get_visible(uzbl.gui.status_bar);
+ }
+-void
++static void
+ set_status_top(int status_top) {
+     if (!uzbl.gui.scrolled_win && !uzbl.gui.status_bar)
+         return;
+@@ -612,21 +859,27 @@ set_status_top(int status_top) {
+         gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
+ }
+-void
+-set_current_encoding(const gchar *encoding) {
++static void
++set_custom_encoding(const gchar *encoding) {
+     if(strlen(encoding) == 0)
+         encoding = NULL;
+     webkit_web_view_set_custom_encoding(uzbl.gui.web_view, encoding);
+ }
+-gchar *
+-get_current_encoding() {
++static gchar *
++get_custom_encoding() {
+     const gchar *encoding = webkit_web_view_get_custom_encoding(uzbl.gui.web_view);
+     return g_strdup(encoding);
+ }
+-void
++static gchar *
++get_current_encoding() {
++    const gchar *encoding = webkit_web_view_get_encoding (uzbl.gui.web_view);
++    return g_strdup(encoding);
++}
++
++static void
+ set_fifo_dir(const gchar *fifo_dir) {
+     g_free(uzbl.behave.fifo_dir);
+@@ -636,7 +889,7 @@ set_fifo_dir(const gchar *fifo_dir) {
+       uzbl.behave.fifo_dir = NULL;
+ }
+-void
++static void
+ set_socket_dir(const gchar *socket_dir) {
+     g_free(uzbl.behave.socket_dir);
+@@ -646,26 +899,38 @@ set_socket_dir(const gchar *socket_dir) {
+       uzbl.behave.socket_dir = NULL;
+ }
+-void
++#ifdef USE_WEBKIT2
++static void
++set_inject_text(const gchar *text) {
++    webkit_web_view_load_plain_text (uzbl.gui.web_view, html, NULL);
++}
++#endif
++
++static void
+ set_inject_html(const gchar *html) {
++#ifdef USE_WEBKIT2
++    webkit_web_view_load_html (uzbl.gui.web_view, html, NULL);
++#else
+     webkit_web_view_load_html_string (uzbl.gui.web_view, html, NULL);
++#endif
+ }
+-void
++static void
+ set_useragent(const gchar *useragent) {
+     g_free(uzbl.net.useragent);
+-    if (*useragent == ' ') {
++    if (!useragent || !*useragent) {
+         uzbl.net.useragent = NULL;
+     } else {
+         uzbl.net.useragent = g_strdup(useragent);
+         g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT,
+             uzbl.net.useragent, NULL);
++        g_object_set(view_settings(), "user-agent", uzbl.net.useragent, NULL);
+     }
+ }
+-void
++static void
+ set_accept_languages(const gchar *accept_languages) {
+     g_free(uzbl.net.accept_languages);
+@@ -679,8 +944,7 @@ set_accept_languages(const gchar *accept_languages) {
+     }
+ }
+-/* requires webkit >=1.1.14 */
+-void
++static void
+ set_view_source(int view_source) {
+     uzbl.behave.view_source = view_source;
+@@ -688,6 +952,7 @@ set_view_source(int view_source) {
+             (gboolean) uzbl.behave.view_source);
+ }
++#ifndef USE_WEBKIT2
+ void
+ set_zoom_type (int type) {
+     webkit_web_view_set_full_content_zoom (uzbl.gui.web_view, type);
+@@ -697,17 +962,207 @@ int
+ get_zoom_type () {
+     return webkit_web_view_get_full_content_zoom (uzbl.gui.web_view);
+ }
++#endif
+-void
++static void
+ set_zoom_level(float zoom_level) {
+     webkit_web_view_set_zoom_level (uzbl.gui.web_view, zoom_level);
+ }
+-float
++static float
+ get_zoom_level() {
+     return webkit_web_view_get_zoom_level (uzbl.gui.web_view);
+ }
++static gchar *
++get_cache_model() {
++    WebKitCacheModel model = webkit_get_cache_model ();
++
++    switch (model) {
++    case WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER:
++        return g_strdup("document_viewer");
++    case WEBKIT_CACHE_MODEL_WEB_BROWSER:
++        return g_strdup("web_browser");
++    case WEBKIT_CACHE_MODEL_DOCUMENT_BROWSER:
++        return g_strdup("document_browser");
++    default:
++        return g_strdup("unknown");
++    }
++}
++
++static void
++set_cache_model(const gchar *model) {
++    if (!g_strcmp0 (model, "default")) {
++        webkit_set_cache_model (WEBKIT_CACHE_MODEL_DEFAULT);
++    } else if (!g_strcmp0 (model, "document_viewer")) {
++        webkit_set_cache_model (WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
++    } else if (!g_strcmp0 (model, "web_browser")) {
++        webkit_set_cache_model (WEBKIT_CACHE_MODEL_WEB_BROWSER);
++    } else if (!g_strcmp0 (model, "document_browser")) {
++        webkit_set_cache_model (WEBKIT_CACHE_MODEL_DOCUMENT_BROWSER);
++    }
++}
++
++static gchar *
++get_web_database_directory() {
++    return g_strdup (webkit_get_web_database_directory_path ());
++}
++
++static unsigned long long
++get_web_database_quota () {
++    return webkit_get_default_web_database_quota ();
++}
++
++static void
++set_web_database_quota (unsigned long long quota) {
++    webkit_set_default_web_database_quota (quota);
++}
++
++static void
++set_web_database_directory(const gchar *path) {
++    webkit_set_web_database_directory_path (path);
++}
++
++#if WEBKIT_CHECK_VERSION (1, 3, 4)
++static gchar *
++get_view_mode() {
++    WebKitWebViewViewMode mode = webkit_web_view_get_view_mode (uzbl.gui.web_view);
++
++    switch (mode) {
++    case WEBKIT_WEB_VIEW_VIEW_MODE_WINDOWED:
++        return g_strdup("windowed");
++    case WEBKIT_WEB_VIEW_VIEW_MODE_FLOATING:
++        return g_strdup("floating");
++    case WEBKIT_WEB_VIEW_VIEW_MODE_FULLSCREEN:
++        return g_strdup("fullscreen");
++    case WEBKIT_WEB_VIEW_VIEW_MODE_MAXIMIZED:
++        return g_strdup("maximized");
++    case WEBKIT_WEB_VIEW_VIEW_MODE_MINIMIZED:
++        return g_strdup("minimized");
++    default:
++        return g_strdup("unknown");
++    }
++}
++
++static void
++set_view_mode(const gchar *mode) {
++    if (!g_strcmp0 (mode, "windowed")) {
++        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_WINDOWED);
++    } else if (!g_strcmp0 (mode, "floating")) {
++        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_FLOATING);
++    } else if (!g_strcmp0 (mode, "fullscreen")) {
++        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_FULLSCREEN);
++    } else if (!g_strcmp0 (mode, "maximized")) {
++        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_MAXIMIZED);
++    } else if (!g_strcmp0 (mode, "minimized")) {
++        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_MINIMIZED);
++    }
++}
++#endif
++
++#if WEBKIT_CHECK_VERSION (1, 3, 17)
++static gchar *
++get_inspected_uri() {
++    return g_strdup (webkit_web_inspector_get_inspected_uri (uzbl.gui.inspector));
++}
++#endif
++
++#if WEBKIT_CHECK_VERSION (1, 3, 13)
++static gchar *
++get_app_cache_directory() {
++    return g_strdup (webkit_application_cache_get_database_directory_path ());
++}
++
++static unsigned long long
++get_app_cache_size() {
++    return webkit_application_cache_get_maximum_size ();
++}
++
++static void
++set_app_cache_size(unsigned long long size) {
++    webkit_application_cache_set_maximum_size (size);
++}
++#endif
++
++#if WEBKIT_CHECK_VERSION (1, 3, 8)
++static void
++mimetype_list_append(WebKitWebPluginMIMEType *mimetype, GString *list) {
++    if (*list->str != '[') {
++        g_string_append_c (list, ',');
++    }
++
++    /* Write out a JSON representation of the information */
++    g_string_append_printf (list,
++            "{\"name\": \"%s\","
++            "\"description\": \"%s\","
++            "\"extensions\": [", /* Open array for the extensions */
++            mimetype->name,
++            mimetype->description);
++
++    char **extension = mimetype->extensions;
++    gboolean first = TRUE;
++
++    while (extension) {
++        if (first) {
++            first = FALSE;
++        } else {
++            g_string_append_c (list, ',');
++        }
++        g_string_append (list, *extension);
++
++        ++extension;
++    }
++
++    g_string_append_c (list, '}');
++}
++
++static void
++plugin_list_append(WebKitWebPlugin *plugin, GString *list) {
++    if (*list->str != '[') {
++        g_string_append_c (list, ',');
++    }
++
++    const gchar *desc = webkit_web_plugin_get_description (plugin);
++    gboolean enabled = webkit_web_plugin_get_enabled (plugin);
++    GSList *mimetypes = webkit_web_plugin_get_mimetypes (plugin);
++    const gchar *name = webkit_web_plugin_get_name (plugin);
++    const gchar *path = webkit_web_plugin_get_path (plugin);
++
++    /* Write out a JSON representation of the information */
++    g_string_append_printf (list,
++            "{\"name\": \"%s\","
++            "\"description\": \"%s\","
++            "\"enabled\": %s,"
++            "\"path\": \"%s\","
++            "\"mimetypes\": [", /* Open array for the mimetypes */
++            name,
++            desc,
++            enabled ? "true" : "false",
++            path);
++
++    g_slist_foreach (mimetypes, (GFunc)mimetype_list_append, list);
++
++    /* Close the array and the object */
++    g_string_append (list, "]}");
++}
++
++static gchar *
++get_plugin_list() {
++    WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
++    GSList *plugins = webkit_web_plugin_database_get_plugins (db);
++
++    GString *list = g_string_new ("[");
++
++    g_slist_foreach (plugins, (GFunc)plugin_list_append, list);
++
++    g_string_append_c (list, ']');
++
++    webkit_web_plugin_database_plugins_list_free (plugins);
++
++    return g_string_free (list, FALSE);
++}
++#endif
++
+ /* abbreviations to help keep the table's width humane */
+ /* variables */
+@@ -717,6 +1172,7 @@ get_zoom_level() {
+ #define PTR_V_STR_GETSET(var)    { .type = TYPE_STR,   .dump = 1, .writeable = 1, .getter = (uzbl_fp) get_##var, .setter = (uzbl_fp)set_##var }
+ #define PTR_V_INT_GETSET(var)    { .type = TYPE_INT,   .dump = 1, .writeable = 1, .getter = (uzbl_fp) get_##var, .setter = (uzbl_fp)set_##var }
++#define PTR_V_ULL_GETSET(var)    { .type = TYPE_ULL,   .dump = 1, .writeable = 1, .getter = (uzbl_fp) get_##var, .setter = (uzbl_fp)set_##var }
+ #define PTR_V_FLOAT_GETSET(var)  { .type = TYPE_FLOAT, .dump = 1, .writeable = 1, .getter = (uzbl_fp) get_##var, .setter = (uzbl_fp)set_##var }
+ /* constants */
+@@ -724,6 +1180,11 @@ get_zoom_level() {
+ #define PTR_C_INT(var)           { .ptr = { .i = (int*)&(var) }, .type = TYPE_INT,   .dump = 0, .writeable = 0, .getter = NULL, .setter = NULL }
+ #define PTR_C_FLOAT(var)         { .ptr = { .f = &(var) },       .type = TYPE_FLOAT, .dump = 0, .writeable = 0, .getter = NULL, .setter = NULL }
++/* programmatic constants */
++#define PTR_C_STR_F(get)    { .type = TYPE_STR,   .dump = 0, .writeable = 0, .getter = (uzbl_fp)get, .setter = NULL }
++#define PTR_C_INT_F(get)    { .type = TYPE_INT,   .dump = 0, .writeable = 0, .getter = (uzbl_fp)get, .setter = NULL }
++#define PTR_C_FLOAT_F(get)  { .type = TYPE_FLOAT, .dump = 0, .writeable = 0, .getter = (uzbl_fp)get, .setter = NULL }
++
+ const struct var_name_to_ptr_t {
+     const char *name;
+     uzbl_cmdprop cp;
+@@ -751,7 +1212,6 @@ const struct var_name_to_ptr_t {
+     { "forward_keys",           PTR_V_INT(uzbl.behave.forward_keys,             1,   NULL)},
+-    { "authentication_handler", PTR_V_STR(uzbl.behave.authentication_handler,   1,   set_authentication_handler)},
+     { "scheme_handler",         PTR_V_STR(uzbl.behave.scheme_handler,           1,   NULL)},
+     { "request_handler",        PTR_V_STR(uzbl.behave.request_handler,          1,   NULL)},
+     { "download_handler",       PTR_V_STR(uzbl.behave.download_handler,         1,   NULL)},
+@@ -773,45 +1233,145 @@ const struct var_name_to_ptr_t {
+     { "ssl_ca_file",            PTR_V_STR_GETSET(ca_file)},
+     { "ssl_verify",             PTR_V_INT_GETSET(verify_cert)},
+-    /* exported WebKitWebSettings properties */
+-    { "javascript_windows",     PTR_V_INT_GETSET(javascript_windows)},
+-    { "zoom_level",             PTR_V_FLOAT_GETSET(zoom_level)},
+-    { "zoom_type",              PTR_V_INT_GETSET(zoom_type)},
++    { "enable_builtin_auth",    PTR_V_INT_GETSET(enable_builtin_auth)},
++    { "cache_model",            PTR_V_STR_GETSET(cache_model)},
++#if WEBKIT_CHECK_VERSION (1, 3, 13)
++    { "app_cache_size",         PTR_V_ULL_GETSET(app_cache_size)},
++#endif
++    { "web_database_directory", PTR_V_STR_GETSET(web_database_directory)},
++    { "web_database_quota",     PTR_V_ULL_GETSET(web_database_quota)},
++    /* exported WebKitWebSettings properties */
++    /* Font settings */
+     { "default_font_family",    PTR_V_STR_GETSET(default_font_family)},
+     { "monospace_font_family",  PTR_V_STR_GETSET(monospace_font_family)},
+-    { "cursive_font_family",    PTR_V_STR_GETSET(cursive_font_family)},
+     { "sans_serif_font_family", PTR_V_STR_GETSET(sans_serif_font_family)},
+     { "serif_font_family",      PTR_V_STR_GETSET(serif_font_family)},
++    { "cursive_font_family",    PTR_V_STR_GETSET(cursive_font_family)},
+     { "fantasy_font_family",    PTR_V_STR_GETSET(fantasy_font_family)},
+-
+-    { "monospace_size",         PTR_V_INT_GETSET(monospace_size)},
+-    { "font_size",              PTR_V_INT_GETSET(font_size)},
++    /* Font size settings */
+     { "minimum_font_size",      PTR_V_INT_GETSET(minimum_font_size)},
+-
+-    { "enable_pagecache",       PTR_V_INT_GETSET(enable_pagecache)},
++    { "font_size",              PTR_V_INT_GETSET(font_size)},
++    { "monospace_size",         PTR_V_INT_GETSET(monospace_size)},
++    /* Text settings */
++    { "default_encoding",       PTR_V_STR_GETSET(default_encoding)},
++    { "custom_encoding",        PTR_V_STR_GETSET(custom_encoding)},
++    { "enforce_96_dpi",         PTR_V_INT_GETSET(enforce_96_dpi)},
++    { "editable",               PTR_V_INT_GETSET(editable)},
++    /* Feature settings */
+     { "enable_plugins",         PTR_V_INT_GETSET(enable_plugins)},
++    { "enable_java_applet",     PTR_V_INT_GETSET(enable_java_applet)},
++#if WEBKIT_CHECK_VERSION (1, 3, 14)
++    { "enable_webgl",           PTR_V_INT_GETSET(enable_webgl)},
++#endif
++#if WEBKIT_CHECK_VERSION (1, 7, 5)
++    { "enable_webaudio",        PTR_V_INT_GETSET(enable_webaudio)},
++#endif
++#if WEBKIT_CHECK_VERSION (1, 7, 90) /* Documentation says 1.7.5, but it's not there. */
++    { "enable_3d_acceleration", PTR_V_INT_GETSET(enable_3d_acceleration)},
++#endif
++#if WEBKIT_CHECK_VERSION (1, 11, 1)
++    { "enable_css_shaders",     PTR_V_INT_GETSET(enable_css_shaders)},
++#endif
++    /* HTML5 Database settings */
++    { "enable_database",        PTR_V_INT_GETSET(enable_database)},
++    { "enable_local_storage",   PTR_V_INT_GETSET(enable_local_storage)},
++    { "enable_pagecache",       PTR_V_INT_GETSET(enable_pagecache)},
++    { "enable_offline_app_cache", PTR_V_INT_GETSET(enable_offline_app_cache)},
++#if WEBKIT_CHECK_VERSION (1, 5, 2)
++    { "local_storage_path",     PTR_V_STR_GETSET(local_storage_path)},
++#endif
++    /* Security settings */
++    { "enable_private",         PTR_V_INT_GETSET(enable_private)},
++    { "enable_universal_file_access", PTR_V_INT_GETSET(enable_universal_file_access)},
++    { "enable_cross_file_access", PTR_V_INT_GETSET(enable_cross_file_access)},
++    { "enable_hyperlink_auditing", PTR_V_INT_GETSET(enable_hyperlink_auditing)},
++    { "cookie_policy",          PTR_V_INT_GETSET(cookie_policy)},
++#if WEBKIT_CHECK_VERSION (1, 3, 13)
++    { "enable_dns_prefetch",    PTR_V_INT_GETSET(enable_dns_prefetch)},
++#endif
++#if WEBKIT_CHECK_VERSION (1, 11, 2)
++    { "display_insecure_content",PTR_V_INT_GETSET(display_insecure_content)},
++    { "run_insecure_content",   PTR_V_INT_GETSET(run_insecure_content)},
++#endif
++    { "maintain_history",       PTR_V_INT_GETSET(maintain_history)},
++    /* Display settings */
++    { "transparent",            PTR_V_STR_GETSET(transparent)},
++#if WEBKIT_CHECK_VERSION (1, 3, 4)
++    { "view_mode",              PTR_V_STR_GETSET(view_mode)},
++#endif
++    { "zoom_level",             PTR_V_FLOAT_GETSET(zoom_level)},
++    { "zoom_step",              PTR_V_FLOAT_GETSET(zoom_step)},
++#ifndef USE_WEBKIT2
++    { "zoom_type",              PTR_V_INT_GETSET(zoom_type)},
++#endif
++    { "caret_browsing",         PTR_V_INT_GETSET(caret_browsing)},
++    { "auto_resize_window",     PTR_V_INT_GETSET(auto_resize_window)},
++#if WEBKIT_CHECK_VERSION (1, 3, 5)
++    { "enable_frame_flattening", PTR_V_INT_GETSET(enable_frame_flattening)},
++#endif
++#if WEBKIT_CHECK_VERSION (1, 3, 8)
++    { "enable_fullscreen",      PTR_V_INT_GETSET(enable_fullscreen)},
++#endif
++#ifdef USE_WEBKIT2
++#if WEBKIT_CHECK_VERSION (1, 7, 91)
++    { "zoom_text_only",         PTR_V_INT_GETSET(zoom_text_only)},
++#endif
++#endif
++#if WEBKIT_CHECK_VERSION (1, 9, 0)
++    { "enable_smooth_scrolling",PTR_V_INT_GETSET(enable_smooth_scrolling)},
++#endif
++    /* Javascript settings */
+     { "enable_scripts",         PTR_V_INT_GETSET(enable_scripts)},
++    { "javascript_windows",     PTR_V_INT_GETSET(javascript_windows)},
++    { "javascript_dom_paste",   PTR_V_INT_GETSET(javascript_dom_paste)},
++#if WEBKIT_CHECK_VERSION (1, 3, 0)
++    { "javascript_clipboard",   PTR_V_INT_GETSET(javascript_clipboard)},
++#endif
++    /* Media settings */
++#if WEBKIT_CHECK_VERSION (1, 9, 3)
++    { "enable_inline_media",    PTR_V_INT_GETSET(enable_inline_media)},
++    { "require_click_to_play",  PTR_V_INT_GETSET(require_click_to_play)},
++#endif
++#if WEBKIT_CHECK_VERSION (1, 11, 1)
++    { "enable_media_stream",    PTR_V_INT_GETSET(enable_media_stream)},
++#endif
++    /* Image settings */
+     { "autoload_images",        PTR_V_INT_GETSET(autoload_images)},
+     { "autoshrink_images",      PTR_V_INT_GETSET(autoshrink_images)},
++    /* Spell checking settings */
+     { "enable_spellcheck",      PTR_V_INT_GETSET(enable_spellcheck)},
+     { "spellcheck_languages",   PTR_V_STR_GETSET(spellcheck_languages)},
+-    { "enable_private",         PTR_V_INT_GETSET(enable_private)},
+-    { "print_backgrounds",      PTR_V_INT_GETSET(print_bg)},
+-    { "stylesheet_uri",         PTR_V_STR_GETSET(stylesheet_uri)},
++    /* Form settings */
+     { "resizable_text_areas",   PTR_V_INT_GETSET(resizable_text_areas)},
+-    { "default_encoding",       PTR_V_STR_GETSET(default_encoding)},
+-    { "current_encoding",       PTR_V_STR_GETSET(current_encoding)},
+-    { "enforce_96_dpi",         PTR_V_INT_GETSET(enforce_96_dpi)},
+-    { "caret_browsing",         PTR_V_INT_GETSET(caret_browsing)},
+-    { "enable_cross_file_access", PTR_V_INT_GETSET(enable_cross_file_access)},
++    { "enable_spatial_navigation", PTR_V_INT_GETSET(enable_spatial_navigation)},
++    { "editing_behavior",       PTR_V_INT_GETSET(editing_behavior)},
++    { "enable_tab_cycle",       PTR_V_INT_GETSET(enable_tab_cycle)},
++    /* Customization */
++    { "stylesheet_uri",         PTR_V_STR_GETSET(stylesheet_uri)},
++#if WEBKIT_CHECK_VERSION (1, 9, 0)
++    { "default_context_menu",   PTR_V_INT(uzbl.gui.custom_context_menu,         1,   NULL)},
++#else
++    { "default_context_menu",   PTR_V_INT_GETSET(default_context_menu)},
++#endif
++    /* Hacks */
++    { "enable_site_workarounds", PTR_V_INT_GETSET(enable_site_workarounds)},
++    /* Printing settings */
++    { "print_backgrounds",      PTR_V_INT_GETSET(print_bg)},
++    /* Inspector settings */
++    { "profile_js",             PTR_V_INT_GETSET(profile_js)},
++    { "profile_timeline",       PTR_V_INT_GETSET(profile_timeline)},
++#ifdef USE_WEBKIT2
++    { "inject_text",            { .type = TYPE_STR, .dump = 0, .writeable = 1, .getter = NULL, .setter = (uzbl_fp) set_inject_text }},
++#endif
+     { "inject_html",            { .type = TYPE_STR, .dump = 0, .writeable = 1, .getter = NULL, .setter = (uzbl_fp) set_inject_html }},
+     /* constants (not dumpable or writeable) */
+     { "WEBKIT_MAJOR",           PTR_C_INT(uzbl.info.webkit_major)},
+     { "WEBKIT_MINOR",           PTR_C_INT(uzbl.info.webkit_minor)},
+     { "WEBKIT_MICRO",           PTR_C_INT(uzbl.info.webkit_micro)},
++    { "HAS_WEBKIT2",            PTR_C_INT(uzbl.info.webkit2)},
+     { "ARCH_UZBL",              PTR_C_STR(uzbl.info.arch)},
+     { "COMMIT",                 PTR_C_STR(uzbl.info.commit)},
+     { "TITLE",                  PTR_C_STR(uzbl.gui.main_title)},
+@@ -820,6 +1380,18 @@ const struct var_name_to_ptr_t {
+     { "PID",                    PTR_C_STR(uzbl.info.pid_str)},
+     { "_",                      PTR_C_STR(uzbl.state.last_result)},
++    /* runtime settings */
++    { "current_encoding",       PTR_C_STR_F(get_current_encoding)},
++#if WEBKIT_CHECK_VERSION (1, 3, 17)
++    { "inspected_uri",          PTR_C_STR_F(get_inspected_uri)},
++#endif
++#if WEBKIT_CHECK_VERSION (1, 3, 13)
++    { "app_cache_directory",    PTR_C_STR_F(get_app_cache_directory)},
++#endif
++#if WEBKIT_CHECK_VERSION (1, 3, 8)
++    { "plugin_list",            PTR_C_STR_F(get_plugin_list)},
++#endif
++
+     /* and we terminate the whole thing with the closest thing we have to NULL.
+      * it's important that dump = 0. */
+     { NULL,                     {.ptr = { .i = NULL }, .type = TYPE_INT, .dump = 0, .writeable = 0}}
+diff --git a/src/variables.h b/src/variables.h
+index dade652..0b935eb 100644
+--- a/src/variables.h
++++ b/src/variables.h
+@@ -6,7 +6,11 @@
+ #define __VARIABLES__
+ #include <glib.h>
++#ifdef USE_WEBKIT2
++#include <webkit2/webkit2.h>
++#else
+ #include <webkit/webkit.h>
++#endif
+ #include "type.h"
+@@ -20,11 +24,14 @@ gchar *get_var_value_string_c(const uzbl_cmdprop *c);
+ gchar *get_var_value_string(const char *name);
+ int get_var_value_int_c(const uzbl_cmdprop *c);
+ int get_var_value_int(const char *name);
++unsigned long long get_var_value_ull_c(const uzbl_cmdprop *c);
++unsigned long long get_var_value_ull(const char *name);
+ float get_var_value_float_c(const uzbl_cmdprop *c);
+ float get_var_value_float(const char *name);
+ void set_var_value_string_c(uzbl_cmdprop *c, const gchar *val);
+-void set_var_value_int_c(uzbl_cmdprop *c, int f);
++void set_var_value_int_c(uzbl_cmdprop *c, int i);
++void set_var_value_ull_c(uzbl_cmdprop *c, unsigned long long ull);
+ void set_var_value_float_c(uzbl_cmdprop *c, float f);
+ void send_set_var_event(const char *name, const uzbl_cmdprop *c);
+@@ -34,8 +41,6 @@ void        dump_config_as_events();
+ void        uri_change_cb (WebKitWebView *web_view, GParamSpec param_spec);
+-void        set_show_status(int);
+-
+ void        set_zoom_type(int);
+ int         get_zoom_type();
+diff --git a/tests/event-manager/emtest.py b/tests/event-manager/emtest.py
+new file mode 100644
+index 0000000..27ce21b
+--- /dev/null
++++ b/tests/event-manager/emtest.py
+@@ -0,0 +1,30 @@
++from mock import Mock
++import logging
++from uzbl.event_manager import Uzbl
++
++
++class EventManagerMock(object):
++    def __init__(self,
++        global_plugins=(), instance_plugins=(),
++        global_mock_plugins=(), instance_mock_plugins=()
++    ):
++        self.uzbls = {}
++        self.plugins = {}
++        self.instance_plugins = instance_plugins
++        self.instance_mock_plugins = instance_mock_plugins
++        for plugin in global_plugins:
++            self.plugins[plugin] = plugin(self)
++        for (plugin, mock) in global_mock_plugins:
++            self.plugins[plugin] = mock() if mock else Mock(plugin)
++
++    def add(self):
++        u = Mock(spec=Uzbl)
++        u.parent = self
++        u.logger = logging.getLogger('debug')
++        u.plugins = {}
++        for plugin in self.instance_plugins:
++            u.plugins[plugin] = plugin(u)
++        for (plugin, mock) in self.instance_mock_plugins:
++            u.plugins[plugin] = mock() if mock else Mock(plugin)
++        self.uzbls[Mock()] = u
++        return u
+diff --git a/tests/event-manager/testarguments.py b/tests/event-manager/testarguments.py
+new file mode 100755
+index 0000000..edb102d
+--- /dev/null
++++ b/tests/event-manager/testarguments.py
+@@ -0,0 +1,25 @@
++#!/usr/bin/env python
++
++import unittest
++from uzbl.arguments import Arguments
++
++
++class ArgumentsTest(unittest.TestCase):
++    def test_empty(self):
++        a = Arguments('')
++        self.assertEqual(len(a), 0)
++        self.assertEqual(a.raw(), '')
++
++    def test_space(self):
++        a = Arguments(' foo  bar')
++        self.assertEqual(len(a), 2)
++        self.assertEqual(a.raw(), 'foo  bar')
++        self.assertEqual(a.raw(0, 0), 'foo')
++        self.assertEqual(a.raw(1, 1), 'bar')
++
++    def test_tab(self):
++        a = Arguments('\tfoo\t\tbar')
++        self.assertEqual(len(a), 2)
++        self.assertEqual(a.raw(), 'foo\t\tbar')
++        self.assertEqual(a.raw(0, 0), 'foo')
++        self.assertEqual(a.raw(1, 1), 'bar')
+diff --git a/tests/event-manager/testbind.py b/tests/event-manager/testbind.py
+new file mode 100644
+index 0000000..a2e8d70
+--- /dev/null
++++ b/tests/event-manager/testbind.py
+@@ -0,0 +1,51 @@
++#!/usr/bin/env python
++
++
++import sys
++if '' not in sys.path:
++    sys.path.insert(0, '')
++
++import mock
++import unittest
++from emtest import EventManagerMock
++from uzbl.plugins.bind import Bind, BindPlugin
++from uzbl.plugins.config import Config
++
++
++def justafunction():
++    pass
++
++
++class BindTest(unittest.TestCase):
++    def test_unique_id(self):
++        a = Bind('spam', 'spam')
++        b = Bind('spam', 'spam')
++        self.assertNotEqual(a.bid, b.bid)
++
++
++class BindPluginTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock((), (Config, BindPlugin))
++        self.uzbl = self.event_manager.add()
++
++    def test_add_bind(self):
++        b = BindPlugin[self.uzbl]
++        modes = 'global'
++        glob = 'test'
++        handler = justafunction
++        b.mode_bind(modes, glob, handler)
++
++        binds = b.bindlet.get_binds()
++        self.assertEqual(len(binds), 1)
++        self.assertIs(binds[0].function, justafunction)
++
++    def test_parse_bind(self):
++        b = BindPlugin[self.uzbl]
++        modes = 'global'
++        glob = 'test'
++        handler = 'handler'
++
++        b.parse_mode_bind('%s %s = %s' % (modes, glob, handler))
++        binds = b.bindlet.get_binds()
++        self.assertEqual(len(binds), 1)
++        self.assertEqual(binds[0].commands, [handler])
+diff --git a/tests/event-manager/testcompletion.py b/tests/event-manager/testcompletion.py
+new file mode 100644
+index 0000000..8ae32a2
+--- /dev/null
++++ b/tests/event-manager/testcompletion.py
+@@ -0,0 +1,118 @@
++#!/usr/bin/env python
++# vi: set et ts=4:
++
++
++
++import unittest
++from emtest import EventManagerMock
++from uzbl.plugins.config import Config
++from uzbl.plugins.keycmd import KeyCmd
++from uzbl.plugins.completion import CompletionPlugin
++
++
++class DummyFormatter(object):
++    def format(self, partial, completions):
++        return '[%s] %s' % (partial, ', '.join(completions))
++
++class TestAdd(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock(
++            (), (CompletionPlugin,),
++            (), ((Config, dict), (KeyCmd, None))
++        )
++        self.uzbl = self.event_manager.add()
++
++    def test_builtins(self):
++        c = CompletionPlugin[self.uzbl]
++        c.add_builtins(('spam', 'egg'))
++        self.assertIn('spam', c.completion)
++        self.assertIn('egg', c.completion)
++
++    def test_config(self):
++        c = CompletionPlugin[self.uzbl]
++        c.add_config_key('spam', 'SPAM')
++        self.assertIn('@spam', c.completion)
++
++
++class TestCompletion(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock(
++            (), (KeyCmd, CompletionPlugin),
++            (), ((Config, dict),)
++        )
++        self.uzbl = self.event_manager.add()
++
++        c = CompletionPlugin[self.uzbl]
++        c.listformatter = DummyFormatter()
++        c.add_builtins(('spam', 'egg', 'bar', 'baz'))
++        c.add_config_key('spam', 'SPAM')
++        c.add_config_key('Something', 'Else')
++
++    def test_incomplete_keyword(self):
++        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
++        k.keylet.keycmd = 'sp'
++        k.keylet.cursor = len(k.keylet.keycmd)
++
++        r = c.get_incomplete_keyword()
++        self.assertEqual(r, ('sp', False))
++
++    def test_incomplete_keyword_var(self):
++        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
++        k.keylet.keycmd = 'set @sp'
++        k.keylet.cursor = len(k.keylet.keycmd)
++
++        r = c.get_incomplete_keyword()
++        self.assertEqual(r, ('@sp', False))
++
++    def test_incomplete_keyword_var_noat(self):
++        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
++        k.keylet.keycmd = 'set Some'
++        k.keylet.cursor = len(k.keylet.keycmd)
++
++        r = c.get_incomplete_keyword()
++        self.assertEqual(r, ('@Some', True))
++
++    def test_stop_completion(self):
++        config, c = Config[self.uzbl], CompletionPlugin[self.uzbl]
++        c.completion.level = 99
++        config['completion_list'] = 'test'
++
++        c.stop_completion()
++        self.assertNotIn('completion_list', config)
++        self.assertEqual(c.completion.level, 0)
++
++    def test_completion(self):
++        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
++        config = Config[self.uzbl]
++
++        comp = (
++            ('sp', 'spam '),
++            ('e', 'egg '),
++            ('set @sp', 'set @spam '),
++        )
++
++        for i, o in comp:
++            k.keylet.keycmd = i
++            k.keylet.cursor = len(k.keylet.keycmd)
++
++            c.start_completion()
++            self.assertEqual(k.keylet.keycmd, o)
++            c.start_completion()
++            self.assertNotIn('completion_list', config)
++
++    def test_completion_list(self):
++        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
++        config = Config[self.uzbl]
++
++        comp = (
++            ('b', 'ba', '[ba] bar, baz'),
++        )
++
++        for i, o, l in comp:
++            k.keylet.keycmd = i
++            k.keylet.cursor = len(k.keylet.keycmd)
++
++            c.start_completion()
++            self.assertEqual(k.keylet.keycmd, o)
++            c.start_completion()
++            self.assertEqual(config['completion_list'], l)
+diff --git a/tests/event-manager/testconfig.py b/tests/event-manager/testconfig.py
+new file mode 100644
+index 0000000..d2a891e
+--- /dev/null
++++ b/tests/event-manager/testconfig.py
+@@ -0,0 +1,77 @@
++
++
++import unittest
++from emtest import EventManagerMock
++
++from uzbl.plugins.config import Config
++
++
++class ConfigTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock((), (Config,))
++        self.uzbl = self.event_manager.add()
++
++    def test_set(self):
++        cases = (
++            (True, '1'),
++            (False, '0'),
++            ("test", "test"),
++            (5, '5')
++        )
++        c = Config[self.uzbl]
++        for input, expected in cases:
++            c.set('foo', input)
++            self.uzbl.send.assert_called_once_with(
++                'set foo = ' + expected)
++            self.uzbl.send.reset_mock()
++
++    def test_set_invalid(self):
++        cases = (
++            ("foo\nbar", AssertionError),  # Better Exception type
++            ("bad'key", AssertionError)
++        )
++        c = Config[self.uzbl]
++        for input, exception in cases:
++            self.assertRaises(exception, c.set, input)
++
++    def test_parse(self):
++        cases = (
++            ('foo str value', 'foo', 'value'),
++            ('foo str "ba ba"', 'foo', 'ba ba'),
++            ('foo float 5', 'foo', 5.0)
++        )
++        c = Config[self.uzbl]
++        for input, ekey, evalue in cases:
++            c.parse_set_event(input)
++            self.assertIn(ekey, c)
++            self.assertEqual(c[ekey], evalue)
++            self.uzbl.event.assert_called_once_with(
++                'CONFIG_CHANGED', ekey, evalue)
++            self.uzbl.event.reset_mock()
++
++    def test_parse_null(self):
++        cases = (
++            ('foo str', 'foo'),
++            ('foo str ""', 'foo'),
++            #('foo int', 'foo')  # Not sure if this input is valid
++        )
++        c = Config[self.uzbl]
++        for input, ekey in cases:
++            c.update({'foo': '-'})
++            c.parse_set_event(input)
++            self.assertNotIn(ekey, c)
++            self.uzbl.event.assert_called_once_with(
++                'CONFIG_CHANGED', ekey, '')
++            self.uzbl.event.reset_mock()
++
++    def test_parse_invalid(self):
++        cases = (
++            ('foo bar', AssertionError),  # TypeError?
++            ('foo bad^key', AssertionError),
++            ('', Exception),
++            ('foo int z', ValueError)
++        )
++        c = Config[self.uzbl]
++        for input, exception in cases:
++            self.assertRaises(exception, c.parse_set_event, input)
++            self.assertEqual(len(list(c.keys())), 0)
+diff --git a/tests/event-manager/testcookie.py b/tests/event-manager/testcookie.py
+new file mode 100755
+index 0000000..c33ba3b
+--- /dev/null
++++ b/tests/event-manager/testcookie.py
+@@ -0,0 +1,67 @@
++#!/usr/bin/env python
++
++
++import sys
++if '' not in sys.path:
++    sys.path.insert(0, '')
++
++import unittest
++from emtest import EventManagerMock
++
++from uzbl.plugins.cookies import Cookies
++
++cookies = (
++    r'".nyan.cat" "/" "__utmb" "183192761.1.10.1313990640" "http" "1313992440"',
++    r'".twitter.com" "/" "guest_id" "v1%3A131399064036991891" "http" "1377104460"'
++)
++
++
++class CookieFilterTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock((), (Cookies,))
++        self.uzbl = self.event_manager.add()
++        self.other = self.event_manager.add()
++
++    def test_add_cookie(self):
++        c = Cookies[self.uzbl]
++        c.add_cookie(cookies[0])
++        self.other.send.assert_called_once_with(
++            'add_cookie ' + cookies[0])
++
++    def test_whitelist_block(self):
++        c = Cookies[self.uzbl]
++        c.whitelist_cookie(r'domain "nyan\.cat$"')
++        c.add_cookie(cookies[1])
++        self.uzbl.send.assert_called_once_with(
++            'delete_cookie ' + cookies[1])
++
++    def test_whitelist_accept(self):
++        c = Cookies[self.uzbl]
++        c.whitelist_cookie(r'domain "nyan\.cat$"')
++        c.add_cookie(cookies[0])
++        self.other.send.assert_called_once_with(
++            'add_cookie ' + cookies[0])
++
++    def test_blacklist_block(self):
++        c = Cookies[self.uzbl]
++        c.blacklist_cookie(r'domain "twitter\.com$"')
++        c.add_cookie(cookies[1])
++        self.uzbl.send.assert_called_once_with(
++            'delete_cookie ' + cookies[1])
++
++    def test_blacklist_accept(self):
++        c = Cookies[self.uzbl]
++        c.blacklist_cookie(r'domain "twitter\.com$"')
++        c.add_cookie(cookies[0])
++        self.other.send.assert_called_once_with(
++            'add_cookie ' + cookies[0])
++
++    def test_filter_numeric(self):
++        c = Cookies[self.uzbl]
++        c.blacklist_cookie(r'0 "twitter\.com$"')
++        c.add_cookie(cookies[1])
++        self.uzbl.send.assert_called_once_with(
++            'delete_cookie ' + cookies[1])
++
++if __name__ == '__main__':
++    unittest.main()
+diff --git a/tests/event-manager/testcore.py b/tests/event-manager/testcore.py
+new file mode 100644
+index 0000000..1a8b4fa
+--- /dev/null
++++ b/tests/event-manager/testcore.py
+@@ -0,0 +1,90 @@
++#!/usr/bin/env python
++# vi: set et ts=4:
++
++
++
++import unittest
++from mock import Mock
++from uzbl.core import Uzbl
++
++
++class TestUzbl(unittest.TestCase):
++    def setUp(self):
++        options = Mock()
++        options.print_events = False
++        self.em = Mock()
++        self.proto = Mock()
++        self.uzbl = Uzbl(self.em, self.proto, options)
++
++    def test_repr(self):
++        r = '%r' % self.uzbl
++        self.assertRegex(r, r'<uzbl\(.*\)>')
++
++    def test_event_handler(self):
++        handler = Mock()
++        event, arg = 'FOO', 'test'
++        self.uzbl.connect(event, handler)
++        self.uzbl.event(event, arg)
++        handler.assert_called_once_with(arg)
++
++    def test_parse_sets_name(self):
++        name = 'spam'
++        self.uzbl.parse_msg(' '.join(['EVENT', name, 'FOO', 'BAR']))
++        self.assertEqual(self.uzbl.name, name)
++
++    def test_parse_sends_event(self):
++        handler = Mock()
++        event, arg = 'FOO', 'test'
++        self.uzbl.connect(event, handler)
++        self.uzbl.parse_msg(' '.join(['EVENT', 'instance-name', event, arg]))
++        handler.assert_called_once_with(arg)
++
++    def test_malformed_message(self):
++        # Should not crash
++        self.uzbl.parse_msg('asdaf')
++        self.assertTrue(True)
++
++    def test_send(self):
++        self.uzbl.send('hello  ')
++        self.proto.push.assert_called_once_with('hello\n'.encode('utf-8'))
++
++    def test_close_calls_remove_instance(self):
++        self.uzbl.close()
++        self.em.remove_instance.assert_called_once_with(self.proto.socket)
++
++    def test_close_cleans_plugins(self):
++        p1, p2 = Mock(), Mock()
++        self.uzbl._plugin_instances = (p1, p2)
++        self.uzbl.plugins = {}
++        self.uzbl.close()
++        p1.cleanup.assert_called_once_with()
++        p2.cleanup.assert_called_once_with()
++
++    def test_close_connection_closes_protocol(self):
++        self.uzbl.close_connection(Mock())
++        self.proto.close.assert_called_once_with()
++
++    def test_exit_triggers_close(self):
++        self.uzbl.parse_msg(' '.join(['EVENT', 'spam', 'INSTANCE_EXIT']))
++        self.em.remove_instance.assert_called_once_with(self.proto.socket)
++
++    def test_instance_start(self):
++        pid = 1234
++        self.em.plugind.per_instance_plugins = []
++        self.uzbl.parse_msg(
++            ' '.join(['EVENT', 'spam', 'INSTANCE_START', str(pid)])
++        )
++        self.assertEqual(self.uzbl.pid, pid)
++
++    def test_init_plugins(self):
++        u = self.uzbl
++        class FooPlugin(object):
++            def __init__(self, uzbl): pass
++        class BarPlugin(object):
++            def __init__(self, uzbl): pass
++        self.em.plugind.per_instance_plugins = [FooPlugin, BarPlugin]
++        u.init_plugins()
++        self.assertEqual(len(u.plugins), 2)
++        for t in (FooPlugin, BarPlugin):
++            self.assertIn(t, u.plugins)
++            self.assertTrue(isinstance(u.plugins[t], t))
+diff --git a/tests/event-manager/testdoc.py b/tests/event-manager/testdoc.py
+new file mode 100755
+index 0000000..0f3b8eb
+--- /dev/null
++++ b/tests/event-manager/testdoc.py
+@@ -0,0 +1,22 @@
++#!/usr/bin/env python
++# vi: set et ts=4:
++
++import sys
++if '' not in sys.path:
++    sys.path.insert(0, '')
++
++import unittest
++from doctest import DocTestSuite
++keycmd_tests = DocTestSuite('uzbl.plugins.keycmd')
++arguments_tests = DocTestSuite('uzbl.arguments')
++
++
++def load_tests(loader, standard, pattern):
++    tests = unittest.TestSuite()
++    tests.addTest(keycmd_tests)
++    tests.addTest(arguments_tests)
++    return tests
++
++if __name__ == '__main__':
++    runner = unittest.TextTestRunner()
++    runner.run()
+diff --git a/tests/event-manager/testdownload.py b/tests/event-manager/testdownload.py
+new file mode 100644
+index 0000000..49ed726
+--- /dev/null
++++ b/tests/event-manager/testdownload.py
+@@ -0,0 +1,29 @@
++# vi: set et ts=4:
++
++
++
++import unittest
++from emtest import EventManagerMock
++
++from uzbl.plugins.config import Config
++from uzbl.plugins.downloads import Downloads
++
++
++class DownloadsTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock((), (Downloads, Config))
++        self.uzbl = self.event_manager.add()
++
++    def test_start(self):
++        cases = (
++            ('foo', 'foo', 'foo (0%)'),
++            ('"b@r"', 'b@r', 'b@r (0%'),
++        )
++        d = Downloads[self.uzbl]
++        for input, key, section in cases:
++            d.download_started(input)
++            self.assertIn(key, d.active_downloads)
++            self.assertEqual(d.active_downloads[key], 0)
++            self.uzbl.send.assert_called_once()
++            self.assertIn(section, self.uzbl.send.call_args[0][0])
++            self.uzbl.reset_mock()
+diff --git a/tests/event-manager/testhistory.py b/tests/event-manager/testhistory.py
+new file mode 100644
+index 0000000..0817d9f
+--- /dev/null
++++ b/tests/event-manager/testhistory.py
+@@ -0,0 +1,154 @@
++#!/usr/bin/env python
++# vi: set et ts=4:
++
++
++
++import sys
++if '' not in sys.path:
++    sys.path.insert(0, '')
++
++import unittest
++from emtest import EventManagerMock
++
++from uzbl.plugins.history import History, SharedHistory
++from uzbl.plugins.keycmd import Keylet, KeyCmd
++from uzbl.plugins.on_set import OnSetPlugin
++from uzbl.plugins.config import Config
++
++
++class SharedHistoryTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock((SharedHistory,), ())
++        self.uzbl = self.event_manager.add()
++        self.other = self.event_manager.add()
++
++    def test_instance(self):
++        a = SharedHistory[self.uzbl]
++        b = SharedHistory[self.other]
++        self.assertIs(a, b)
++
++    def test_add_and_get(self):
++        s = SharedHistory[self.uzbl]
++        s.addline('foo', 'bar')
++        s.addline('foo', 'baz')
++        s.addline('foo', 'bap')
++        self.assertEqual(s.get_line_number('foo'), 3)
++        self.assertEqual(s.get_line_number('other'), 0)
++        self.assertEqual(s.getline('foo', 0), 'bar')
++        self.assertEqual(s.getline('foo', 1), 'baz')
++        self.assertEqual(s.getline('foo', 2), 'bap')
++        self.assertEqual(s.getline('foo', -1), 'bap')
++
++    def test_empty_line_number(self):
++        s = SharedHistory[self.uzbl]
++        s.addline('foo', 'bar')
++        self.assertEqual(s.get_line_number(''), 0)
++        self.assertEqual(s.get_line_number('other'), 0)
++
++    def test_get_missing_prompt(self):
++        s = SharedHistory[self.uzbl]
++        s.addline('foo', 'bar')
++        self.assertRaises(IndexError, s.getline, 'bar', 0)
++
++
++class HistoryTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock(
++            (SharedHistory,),
++            (OnSetPlugin, KeyCmd, Config, History)
++        )
++        self.uzbl = self.event_manager.add()
++        self.other = self.event_manager.add()
++        s = SharedHistory[self.uzbl]
++        data = (
++            ('', 'woop'),
++            ('', 'doop'),
++            ('', 'bar'),
++            ('', 'foo'),
++            ('git', 'spam'),
++            ('git', 'egg'),
++            ('foo', 'foo')
++        )
++        for prompt, input in data:
++            s.addline(prompt, input)
++
++    def test_step(self):
++        h = History[self.uzbl]
++        self.assertEqual('', next(h))
++        self.assertEqual('', next(h))
++        self.assertEqual('foo', h.prev())
++        self.assertEqual('bar', h.prev())
++        self.assertEqual('foo', next(h))
++        self.assertEqual('bar', h.prev())
++        self.assertEqual('doop', h.prev())
++        self.assertEqual('woop', h.prev())
++        self.assertTrue(len(h.prev()) > 0)
++        self.assertTrue(len(h.prev()) > 0)
++        self.assertEqual('woop', next(h))
++
++    def test_step_prompt(self):
++        h = History[self.uzbl]
++        h.change_prompt('git')
++        self.assertEqual('', next(h))
++        self.assertEqual('', next(h))
++        self.assertEqual('egg', h.prev())
++        self.assertEqual('spam', h.prev())
++        self.assertTrue(len(h.prev()) > 0)
++        self.assertTrue(len(h.prev()) > 0)
++        self.assertEqual('spam', next(h))
++
++    def test_change_prompt(self):
++        h = History[self.uzbl]
++        self.assertEqual('foo', h.prev())
++        self.assertEqual('bar', h.prev())
++        h.change_prompt('git')
++        self.assertEqual('egg', h.prev())
++        self.assertEqual('spam', h.prev())
++
++    def test_exec(self):
++        modstate = set()
++        keylet = Keylet()
++        keylet.set_keycmd('foo')
++        History[self.uzbl].keycmd_exec(modstate, keylet)
++        s = SharedHistory[self.uzbl]
++        self.assertEqual(s.getline('', -1), 'foo')
++
++    def test_exec_from_history(self):
++        h = History[self.uzbl]
++        self.assertEqual('foo', h.prev())
++        self.assertEqual('bar', h.prev())
++        self.assertEqual('doop', h.prev())
++        modstate = set()
++        keylet = Keylet()
++        keylet.set_keycmd('doop')
++        h.keycmd_exec(modstate, keylet)
++        self.assertEqual('doop', h.prev())
++        self.assertEqual('foo', h.prev())
++        self.assertEqual('bar', h.prev())
++        # do we really want this one here ?
++        self.assertEqual('doop', h.prev())
++        self.assertEqual('woop', h.prev())
++
++    def test_search(self):
++        h = History[self.uzbl]
++        h.search('oop')
++        self.assertEqual('doop', h.prev())
++        self.assertEqual('woop', h.prev())
++        self.assertTrue(len(h.prev()) > 0)
++        self.assertEqual('woop', next(h))
++        self.assertEqual('doop', next(h))
++        # this reset the search
++        self.assertEqual('', next(h))
++        self.assertEqual('foo', h.prev())
++
++    def test_temp(self):
++        kl = KeyCmd[self.uzbl].keylet
++        kl.set_keycmd('uzbl')
++        h = History[self.uzbl]
++        h.change_prompt('foo')
++        # Why is the preserve current logic in this method?
++        h.history_prev(None)
++        self.assertTrue(len(h.prev()) > 0)
++        self.assertEqual('foo', next(h))
++        self.assertEqual('uzbl', next(h))
++        self.assertEqual('', next(h))  # this clears the keycmd
+diff --git a/tests/event-manager/testkeycmd.py b/tests/event-manager/testkeycmd.py
+new file mode 100644
+index 0000000..bc82c6d
+--- /dev/null
++++ b/tests/event-manager/testkeycmd.py
+@@ -0,0 +1,45 @@
++#!/usr/bin/env python
++
++import re
++import mock
++import unittest
++from emtest import EventManagerMock
++from uzbl.plugins.keycmd import KeyCmd
++from uzbl.plugins.config import Config
++
++
++def getkeycmd(s):
++    return re.match(r'@\[([^\]]*)\]@', s).group(1)
++
++class KeyCmdTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock(
++            (), (KeyCmd,),
++            (), ((Config, dict),)
++        )
++        self.uzbl = self.event_manager.add()
++
++    def test_press_key(self):
++        c, k = Config[self.uzbl], KeyCmd[self.uzbl]
++        k.key_press(('', 'a'))
++        self.assertEqual(c.get('modcmd', ''), '')
++        keycmd = getkeycmd(c['keycmd'])
++        self.assertEqual(keycmd, 'a')
++
++    def test_press_keys(self):
++        c, k = Config[self.uzbl], KeyCmd[self.uzbl]
++        string = 'uzbl'
++        for char in string:
++            k.key_press(('', char))
++        self.assertEqual(c.get('modcmd', ''), '')
++        keycmd = getkeycmd(c['keycmd'])
++        self.assertEqual(keycmd, string)
++
++    def test_press_unicode_keys(self):
++        c, k = Config[self.uzbl], KeyCmd[self.uzbl]
++        string = '\u5927\u962a\u5e02'
++        for char in string:
++            k.key_press(('', char))
++        self.assertEqual(c.get('modcmd', ''), '')
++        keycmd = getkeycmd(c['keycmd'])
++        self.assertEqual(keycmd, string)
+diff --git a/tests/event-manager/testmode.py b/tests/event-manager/testmode.py
+new file mode 100644
+index 0000000..d198c32
+--- /dev/null
++++ b/tests/event-manager/testmode.py
+@@ -0,0 +1,91 @@
++#!/usr/bin/env python
++# vi: set et ts=4:
++
++
++
++import unittest
++from emtest import EventManagerMock
++from uzbl.plugins.config import Config
++from uzbl.plugins.mode import ModePlugin
++from uzbl.plugins.on_set import OnSetPlugin
++
++class ModeParseTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock(
++            (), (OnSetPlugin, ModePlugin),
++            (), ((Config, dict),)
++        )
++        self.uzbl = self.event_manager.add()
++
++    def test_parse_config(self):
++        uzbl = self.uzbl
++        m = ModePlugin[uzbl]
++
++        mode, key, value = 'foo', 'x', 'y'
++        m.parse_mode_config((mode, key, '=', value))
++        self.assertIn(mode, m.mode_config)
++        self.assertIn(key, m.mode_config[mode])
++        self.assertEqual(m.mode_config[mode][key], value)
++
++
++class ModeTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock(
++            (), (OnSetPlugin, ModePlugin),
++            (), ((Config, dict),)
++        )
++        self.uzbl = self.event_manager.add()
++
++        mode = ModePlugin[self.uzbl]
++        config = Config[self.uzbl]
++
++        mode.parse_mode_config(('mode0', 'foo', '=', 'default'))
++
++        mode.parse_mode_config(('mode1', 'foo', '=', 'xxx'))
++        mode.parse_mode_config('mode1 bar = "spam spam"')
++        mode.parse_mode_config('mode1 baz = foo="baz"')
++
++        mode.parse_mode_config(('mode2', 'foo', '=', 'XXX'))
++        mode.parse_mode_config(('mode2', 'spam', '=', 'spam'))
++
++        config['default_mode'] = 'mode0'
++        mode.default_mode_updated(None, 'mode0')
++
++    def test_mode_sets_vars(self):
++        mode, config = ModePlugin[self.uzbl], Config[self.uzbl]
++        mode.mode_updated(None, 'mode1')
++
++        self.assertIn('foo', config)
++        self.assertIn('bar', config)
++        self.assertIn('baz', config)
++        self.assertEqual(config['foo'], 'xxx')
++        self.assertEqual(config['bar'], 'spam spam')
++        self.assertEqual(config['baz'], 'foo="baz"')
++
++    def test_mode_overwrite_vars(self):
++        mode, config = ModePlugin[self.uzbl], Config[self.uzbl]
++        config['mode'] = 'mode1'
++        mode.mode_updated(None, 'mode1')
++        config['mode'] = 'mode2'
++        mode.mode_updated(None, 'mode2')
++
++        self.assertIn('foo', config)
++        self.assertIn('bar', config)
++        self.assertIn('baz', config)
++        self.assertIn('spam', config)
++        self.assertEqual(config['foo'], 'XXX')
++        self.assertEqual(config['bar'], 'spam spam')
++        self.assertEqual(config['baz'], 'foo="baz"')
++        self.assertEqual(config['spam'], 'spam')
++
++    def test_default_mode(self):
++        ''' Setting to mode to nothing should enter the default mode'''
++        mode, config = ModePlugin[self.uzbl], Config[self.uzbl]
++
++        config['foo'] = 'nthth'
++        config['mode'] = ''
++        mode.mode_updated(None, '')
++        self.assertEqual(config['mode'], 'mode0')
++        mode.mode_updated(None, config['mode'])
++        self.assertEqual(config['mode'], 'mode0')
++        self.assertEqual(config['foo'], 'default')
+diff --git a/tests/event-manager/testonevent.py b/tests/event-manager/testonevent.py
+new file mode 100644
+index 0000000..7d80dbd
+--- /dev/null
++++ b/tests/event-manager/testonevent.py
+@@ -0,0 +1,59 @@
++#!/usr/bin/env python
++# vi: set et ts=4:
++
++
++
++import unittest
++from emtest import EventManagerMock
++from uzbl.plugins.config import Config
++from uzbl.plugins.mode import ModePlugin
++from uzbl.plugins.on_event import OnEventPlugin
++
++
++class OnEventTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock(
++            (), (OnEventPlugin,),
++        )
++        self.uzbl = self.event_manager.add()
++
++    def test_command(self):
++        oe = OnEventPlugin[self.uzbl]
++        event, command = 'FOO', 'test test'
++
++        oe.on_event(event, [], command)
++        oe.event_handler('', on_event=event)
++        self.uzbl.send.assert_called_once_with(command)
++
++    def test_matching_pattern(self):
++        oe = OnEventPlugin[self.uzbl]
++        event, pattern, command = 'FOO', ['BAR'], 'test test'
++
++        oe.on_event(event, pattern, command)
++        oe.event_handler('BAR else', on_event=event)
++        self.uzbl.send.assert_called_once_with(command)
++
++    def test_non_matching_pattern(self):
++        oe = OnEventPlugin[self.uzbl]
++        event, pattern, command = 'FOO', ['BAR'], 'test test'
++
++        oe.on_event(event, pattern, command)
++        oe.event_handler('FOO else', on_event=event)
++        self.assertFalse(self.uzbl.send.called)
++
++    def test_parse(self):
++        oe = OnEventPlugin[self.uzbl]
++        event, command = 'FOO', 'test test'
++
++        oe.parse_on_event((event, command))
++        self.assertIn(event, oe.events)
++
++    def test_parse_pattern(self):
++        oe = OnEventPlugin[self.uzbl]
++        event, pattern, command = 'FOO', 'BAR', 'test test'
++
++        oe.parse_on_event((event, '[', pattern, ']', command))
++        self.assertIn(event, oe.events)
++        commands = oe.events[event]
++        self.assertIn(command, commands)
++        self.assertEqual(commands[command], [pattern])
+diff --git a/tests/event-manager/testprogressbar.py b/tests/event-manager/testprogressbar.py
+new file mode 100644
+index 0000000..93ebaa8
+--- /dev/null
++++ b/tests/event-manager/testprogressbar.py
+@@ -0,0 +1,94 @@
++#!/usr/bin/env python
++# vi: set et ts=4:
++
++
++
++import sys
++if '' not in sys.path:
++    sys.path.insert(0, '')
++
++import unittest
++from emtest import EventManagerMock
++from uzbl.plugins.config import Config
++from uzbl.plugins.progress_bar import ProgressBar
++
++
++class ProgressBarTest(unittest.TestCase):
++    def setUp(self):
++        self.event_manager = EventManagerMock(
++            (), (ProgressBar,),
++            (), ((Config, dict),)
++        )
++        self.uzbl = self.event_manager.add()
++
++    def test_percent_done(self):
++        uzbl = self.uzbl
++        p, c = ProgressBar[uzbl], Config[uzbl]
++        c['progress.format'] = '%c'
++
++        p.update_progress()
++        inout = (
++            (9,   '9%'),
++            (99,  '99%'),
++            (100, '100%'),
++            #(101, '100%') # TODO
++        )
++
++        for i, o in inout:
++            p.update_progress(i)
++            self.assertEqual(c['progress.output'], o)
++
++    def test_done_char(self):
++        uzbl = self.uzbl
++        p, c = ProgressBar[uzbl], Config[uzbl]
++        c['progress.format'] = '%d'
++
++        p.update_progress()
++        inout = (
++            (9,   '='),
++            (50,  '===='),
++            (99,  '========'),
++            (100, '========'),
++            (101, '========')
++        )
++
++        for i, o in inout:
++            p.update_progress(i)
++            self.assertEqual(c['progress.output'], o)
++
++    def test_pending_char(self):
++        uzbl = self.uzbl
++        p, c = ProgressBar[uzbl], Config[uzbl]
++        c['progress.format'] = '%p'
++        c['progress.pending'] = '-'
++
++        p.update_progress()
++        inout = (
++            (9,   '-------'),
++            (50,  '----'),
++            (99,  ''),
++            (100, ''),
++            (101, '')
++        )
++
++        for i, o in inout:
++            p.update_progress(i)
++            self.assertEqual(c['progress.output'], o)
++
++    def test_percent_pending(self):
++        uzbl = self.uzbl
++        p, c = ProgressBar[uzbl], Config[uzbl]
++        c['progress.format'] = '%t'
++
++        p.update_progress()
++        inout = (
++            (9,   '91%'),
++            (50,  '50%'),
++            (99,  '1%'),
++            (100, '0%'),
++            #(101, '0%')  # TODO
++        )
++
++        for i, o in inout:
++            p.update_progress(i)
++            self.assertEqual(c['progress.output'], o)
+diff --git a/uzbl/__init__.py b/uzbl/__init__.py
+new file mode 100644
+index 0000000..58f0d85
+--- /dev/null
++++ b/uzbl/__init__.py
+@@ -0,0 +1,6 @@
++'''
++Event manager for uzbl
++
++A event manager for uzbl that supports plugins and multiple simultaneus
++connections from uzbl-core(s)
++'''
+diff --git a/uzbl/arguments.py b/uzbl/arguments.py
+new file mode 100644
+index 0000000..7f71b74
+--- /dev/null
++++ b/uzbl/arguments.py
+@@ -0,0 +1,103 @@
++'''
++Arguments parser
++
++provides argument parsing for event handlers
++'''
++
++import re
++import ast
++
++
++class Arguments(tuple):
++    '''
++    Given a argument line gives access to the split parts
++    honoring common quotation and escaping rules
++
++    >>> Arguments(r"simple 'quoted string'")
++    ('simple', 'quoted string')
++    '''
++
++    _splitquoted = re.compile("(\s+|\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')")
++
++    def __new__(cls, s):
++        '''
++        >>> Arguments(r"one two three")
++        ('one', 'two', 'three')
++        >>> Arguments(r"spam 'escaping \\'works\\''")
++        ('spam', "escaping 'works'")
++        >>> # For testing purposes we can pass a preparsed tuple
++        >>> Arguments(('foo', 'bar', 'baz az'))
++        ('foo', 'bar', 'baz az')
++        '''
++        if isinstance(s, tuple):
++            self = tuple.__new__(cls, s)
++            self._raw, self._ref = s, list(range(len(s)))
++            return self
++        raw = cls._splitquoted.split(s)
++        ref = []
++        self = tuple.__new__(cls, cls.parse(raw, ref))
++        self._raw, self._ref = raw, ref
++        return self
++
++    @classmethod
++    def parse(cls, raw, ref):
++        '''
++        Generator used to initialise the arguments tuple
++
++        Indexes to where in source list the arguments start will be put in 'ref'
++        '''
++        c = None
++        for i, part in enumerate(raw):
++            if re.match('\s+', part):
++                # Whitespace ends the current argument, leading ws is ignored
++                if c is not None:
++                    yield c
++                    c = None
++            else:
++                f = unquote(part)
++                if c is None:
++                    # Mark the start of the argument in the raw input
++                    if part != '':
++                        ref.append(i)
++                        c = f
++                else:
++                    c += f
++        if c is not None:
++            yield c
++
++    def raw(self, frm=0, to=None):
++        '''
++        Returs the portion of the raw input that yielded arguments
++        from 'frm' to 'to'
++
++        >>> args = Arguments(r"'spam, spam' egg sausage   and 'spam'")
++        >>> args
++        ('spam, spam', 'egg', 'sausage', 'and', 'spam')
++        >>> args.raw(1)
++        "egg sausage   and 'spam'"
++        '''
++        if len(self._ref) < 1:
++            return ''
++        rfrm = self._ref[frm]
++        if to is None or len(self._ref) <= to + 1:
++            rto = len(self._raw)
++        else:
++            rto = self._ref[to + 1] - 1
++        return ''.join(self._raw[rfrm:rto])
++
++splitquoted = Arguments  # or define a function?
++
++
++def is_quoted(s):
++    return s and s[0] == s[-1] and s[0] in "'\""
++
++
++def unquote(s):
++    '''
++        Returns the input string without quotations and with
++        escape sequences interpreted
++    '''
++
++    if is_quoted(s):
++        return ast.literal_eval(s)
++    return ast.literal_eval('"' + s + '"')
+diff --git a/uzbl/core.py b/uzbl/core.py
+new file mode 100644
+index 0000000..d0f9010
+--- /dev/null
++++ b/uzbl/core.py
+@@ -0,0 +1,153 @@
++import time
++import logging
++from collections import defaultdict
++
++
++class Uzbl(object):
++
++    def __init__(self, parent, proto, options):
++        proto.target = self
++        self.opts = options
++        self.parent = parent
++        self.proto = proto
++        self.time = time.time()
++        self.pid = None
++        self.name = None
++
++        # Flag if the instance has raised the INSTANCE_START event.
++        self.instance_start = False
++
++        # Use name "unknown" until name is discovered.
++        self.logger = logging.getLogger('uzbl-instance[]')
++
++        # Plugin instances
++        self._plugin_instances = []
++        self.plugins = {}
++
++        # Track plugin event handlers
++        self.handlers = defaultdict(list)
++
++        # Internal vars
++        self._depth = 0
++        self._buffer = ''
++
++    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 handlers' % sum([len(l) for l in list(self.handlers.values())])])
++
++    def init_plugins(self):
++        '''Creates instances of per-instance plugins'''
++
++        for plugin in self.parent.plugind.per_instance_plugins:
++            pinst = plugin(self)
++            self._plugin_instances.append(pinst)
++            self.plugins[plugin] = pinst
++
++    def send(self, msg):
++        '''Send a command to the uzbl instance via the child socket
++        instance.'''
++
++        msg = msg.strip()
++
++        if self.opts.print_events:
++            print(('%s<-- %s' % ('  ' * self._depth, msg)))
++
++        self.proto.push((msg+'\n').encode('utf-8'))
++
++    def parse_msg(self, line):
++        '''Parse an incoming message from a uzbl instance. Event strings
++        will be parsed into `self.event(event, args)`.'''
++
++        # Split by spaces (and fill missing with nulls)
++        elems = (line.split(' ', 3) + [''] * 3)[:4]
++
++        # Ignore non-event messages.
++        if elems[0] != 'EVENT':
++            self.logger.info('non-event message: %r', line)
++            if self.opts.print_events:
++                print(('--- %s' % 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 = logging.getLogger('uzbl-instance%s' % name)
++            self.logger.info('found instance name %r', name)
++
++        assert self.name == name, 'instance name mismatch'
++
++        # Handle the event with the event handlers through the event method
++        self.event(event, args)
++
++    def event(self, event, *args, **kargs):
++        '''Raise an event.'''
++
++        event = event.upper()
++
++        if not self.opts.daemon_mode and self.opts.print_events:
++            elems = [event]
++            if args:
++                elems.append(str(args))
++            if kargs:
++                elems.append(str(kargs))
++            print(('%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
++            try:
++                handler(*args, **kargs)
++
++            except Exception:
++                self.logger.error('error in handler', exc_info=True)
++
++            self._depth -= 1
++
++    def close_connection(self, child_socket):
++        '''Close child socket and delete the uzbl instance created for that
++        child socket connection.'''
++        self.proto.close()
++
++    def close(self):
++        '''Close the client socket and call the plugin cleanup hooks.'''
++
++        self.logger.debug('called close method')
++
++        # Remove self from parent uzbls dict.
++        self.logger.debug('removing self from uzbls list')
++        self.parent.remove_instance(self.proto.socket)
++
++        for plugin in self._plugin_instances:
++            plugin.cleanup()
++        del self.plugins  # to avoid cyclic links
++        del self._plugin_instances
++
++        self.logger.info('removed %r', self)
++
++    def connect(self, name, handler):
++        """Attach event handler
++
++        No extra arguments added. Use bound methods and partials to have
++        extra arguments.
++        """
++        self.handlers[name].append(handler)
++
+diff --git a/uzbl/event_manager.py b/uzbl/event_manager.py
+new file mode 100755
+index 0000000..fcf9a47
+--- /dev/null
++++ b/uzbl/event_manager.py
+@@ -0,0 +1,540 @@
++#!/usr/bin/env python3
++
++
++# Event Manager for Uzbl
++# 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
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.
++
++'''
++
++E V E N T _ M A N A G E R . P Y
++===============================
++
++Event manager for uzbl written in python.
++
++'''
++
++import atexit
++import imp
++import logging
++import os
++import sys
++import time
++import weakref
++import re
++import errno
++import asyncore
++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, SIGKILL
++from traceback import format_exc
++
++from uzbl.net import Listener, Protocol
++from uzbl.core import Uzbl
++
++def xdghome(key, default):
++    '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise
++    use $HOME and the default path.'''
++
++    xdgkey = "XDG_%s_HOME" % key
++    if xdgkey in list(os.environ.keys()) and os.environ[xdgkey]:
++        return os.environ[xdgkey]
++
++    return os.path.join(os.environ['HOME'], default)
++
++# Setup xdg paths.
++DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
++CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
++
++# Define some globals.
++SCRIPTNAME = os.path.basename(sys.argv[0])
++
++logger = logging.getLogger(SCRIPTNAME)
++
++
++def get_exc():
++    '''Format `format_exc` for logging.'''
++    return "\n%s" % format_exc().rstrip()
++
++
++def expandpath(path):
++    '''Expand and realpath paths.'''
++    return os.path.realpath(os.path.expandvars(path))
++
++
++
++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:
++        logger.critical('failed to daemonize', exc_info=True)
++        sys.exit(1)
++
++    os.chdir('/')
++    os.setsid()
++    os.umask(0)
++
++    try:
++        if os.fork():
++            os._exit(0)
++
++    except OSError:
++        logger.critical('failed to daemonize', exc_info=True)
++        sys.exit(1)
++
++    if sys.stdout.isatty():
++        sys.stdout.flush()
++        sys.stderr.flush()
++
++    devnull = '/dev/null'
++    stdin = open(devnull, 'r')
++    stdout = open(devnull, 'a+')
++    stderr = open(devnull, 'a+')
++
++    os.dup2(stdin.fileno(), sys.stdin.fileno())
++    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.'''
++
++    try:
++        dirname = os.path.dirname(path)
++        if not os.path.isdir(dirname):
++            logger.debug('creating directories %r', dirname)
++            os.makedirs(dirname)
++
++    except OSError:
++        logger.error('failed to create directories', exc_info=True)
++
++
++class PluginDirectory(object):
++    def __init__(self):
++        self.global_plugins = []
++        self.per_instance_plugins = []
++
++    def load(self):
++        ''' Import plugin files '''
++
++        import uzbl.plugins
++        import pkgutil
++
++        path = uzbl.plugins.__path__
++        for impr, name, ispkg in pkgutil.iter_modules(path, 'uzbl.plugins.'):
++            __import__(name, globals(), locals())
++
++        from uzbl.ext import global_registry, per_instance_registry
++        self.global_plugins.extend(global_registry)
++        self.per_instance_plugins.extend(per_instance_registry)
++
++
++class UzblEventDaemon(object):
++    def __init__(self, listener, plugind):
++        listener.target = self
++        self.opts = opts
++        self.listener = listener
++        self.plugind = plugind
++        self._quit = False
++
++        # Hold uzbl instances
++        # {child socket: Uzbl instance, ..}
++        self.uzbls = {}
++
++        self.plugins = {}
++
++        # Register that the event daemon server has started by creating the
++        # 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)
++
++        # Add signal handlers.
++        for sigint in [SIGTERM, SIGINT]:
++            signal(sigint, self.quit)
++
++        # Scan plugin directory for plugins
++        self.plugind.load()
++
++        # Initialise global plugins with instances in self.plugins
++        self.init_plugins()
++
++    def init_plugins(self):
++        '''Initialise event manager plugins.'''
++        self._plugin_instances = []
++        self.plugins = {}
++
++        for plugin in self.plugind.global_plugins:
++            pinst = plugin(self)
++            self._plugin_instances.append(pinst)
++            self.plugins[plugin] = pinst
++
++    def run(self):
++        '''Main event daemon loop.'''
++
++        logger.debug('entering main loop')
++
++        if opts.daemon_mode:
++            # Daemonize the process
++            daemonize()
++
++            # Update the pid file
++            make_pid_file(opts.pid_file)
++
++        asyncore.loop()
++
++        # Clean up and exit
++        self.quit()
++
++        logger.debug('exiting main loop')
++
++    def add_instance(self, sock):
++        proto = Protocol(sock)
++        uzbl = Uzbl(self, proto, opts)
++        self.uzbls[sock] = uzbl
++
++    def remove_instance(self, sock):
++        if sock in self.uzbls:
++            del self.uzbls[sock]
++        if not self.uzbls and opts.auto_close:
++            self.quit()
++
++    def close_server_socket(self):
++        '''Close and delete the server socket.'''
++
++        try:
++            self.listener.close()
++
++        except:
++            logger.error('failed to close server socket', exc_info=True)
++
++    def quit(self, sigint=None, *args):
++        '''Close all instance socket objects, server socket and delete the
++        pid file.'''
++
++        if sigint == SIGTERM:
++            logger.critical('caught SIGTERM, exiting')
++
++        elif sigint == SIGINT:
++            logger.critical('caught SIGINT, exiting')
++
++        elif not self._quit:
++            logger.debug('shutting down event manager')
++
++        self.close_server_socket()
++
++        for uzbl in list(self.uzbls.values()):
++            uzbl.close()
++
++        if not self._quit:
++            for plugin in self._plugin_instances:
++                plugin.cleanup()
++            del self.plugins  # to avoid cyclic links
++            del self._plugin_instances
++
++        del_pid_file(opts.pid_file)
++
++        if not self._quit:
++            logger.info('event manager shut down')
++            self._quit = True
++            raise SystemExit()
++
++
++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('failed to create pid file', exc_info=True)
++
++
++def del_pid_file(pid_file):
++    '''Deletes a pid file at `pid_file`, fails silently.'''
++
++    if os.path.isfile(pid_file):
++        try:
++            logger.debug('deleting pid file %r', pid_file)
++            os.remove(pid_file)
++            logger.info('deleted pid file %r', pid_file)
++
++        except:
++            logger.error('failed to delete pid file', exc_info=True)
++
++
++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
++
++    except (IOError, ValueError):
++        logger.error('failed to read pid', exc_info=True)
++        return None
++
++
++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('failed to kill %d', pid, exc_info=True)
++                raise
++
++        if (time.time() - start) > 10:
++            logger.critical('unable to kill process with pid %d', pid)
++            raise OSError
++
++        time.sleep(0.25)
++
++
++def stop_action():
++    '''Stop the event manager daemon.'''
++
++    pid_file = opts.pid_file
++    if not os.path.isfile(pid_file):
++        logger.error('could not find running event manager with pid file %r',
++            pid_file)
++        return
++
++    pid = get_pid(pid_file)
++    if not pid_running(pid):
++        logger.debug('no process with pid %r', pid)
++        del_pid_file(pid_file)
++        return
++
++    logger.debug('terminating process with pid %r', pid)
++    term_process(pid)
++    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 = opts.pid_file
++    if os.path.isfile(pid_file):
++        pid = get_pid(pid_file)
++        if pid_running(pid):
++            logger.error('event manager already started with pid %d', pid)
++            return
++
++        logger.info('no process with pid %d', pid)
++        del_pid_file(pid_file)
++
++    listener = Listener(opts.server_socket)
++    listener.start()
++    plugind = PluginDirectory()
++    daemon = UzblEventDaemon(listener, plugind)
++    daemon.run()
++
++
++def restart_action():
++    '''Restart the event manager daemon.'''
++
++    stop_action()
++    start_action()
++
++
++def list_action():
++    '''List all the plugins that would be loaded in the current search
++    dirs.'''
++
++    from types import ModuleType
++    import uzbl.plugins
++    import pkgutil
++    for line in pkgutil.iter_modules(uzbl.plugins.__path__, 'uzbl.plugins.'):
++        imp, name, ispkg = line
++        print(name)
++
++
++def make_parser():
++    parser = OptionParser('usage: %prog [options] {start|stop|restart|list}')
++    add = parser.add_option
++
++    add('-v', '--verbose',
++        dest='verbose', default=2, action='count',
++        help='increase verbosity')
++
++    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')
++
++    add('-p', '--pid-file',
++        metavar="FILE", dest='pid_file',
++        help='pid file location, defaults to server socket + .pid')
++
++    add('-n', '--no-daemon',
++        dest='daemon_mode', action='store_false', default=True,
++        help='do not daemonize the process')
++
++    add('-a', '--auto-close',
++        dest='auto_close', action='store_true', default=False,
++        help='auto close after all instances disconnect')
++
++    add('-o', '--log-file',
++        dest='log_file', metavar='FILE',
++        help='write logging output to a file, defaults to server socket +'
++        ' .log')
++
++    add('-q', '--quiet-events',
++        dest='print_events', action="store_false", default=True,
++        help="silence the printing of events to stdout")
++
++    return parser
++
++
++def init_logger():
++    log_level = logging.CRITICAL - opts.verbose * 10
++    logger = logging.getLogger()
++    logger.setLevel(max(log_level, 10))
++
++    # Console
++    handler = logging.StreamHandler()
++    handler.setLevel(max(log_level + 10, 10))
++    handler.setFormatter(logging.Formatter(
++        '%(name)s: %(levelname)s: %(message)s'))
++    logger.addHandler(handler)
++
++    # Logfile
++    handler = logging.FileHandler(opts.log_file, 'w', 'utf-8', 1)
++    handler.setLevel(max(log_level, 10))
++    handler.setFormatter(logging.Formatter(
++        '[%(created)f] %(name)s: %(levelname)s: %(message)s'))
++    logger.addHandler(handler)
++
++
++def main():
++    global opts
++
++    parser = make_parser()
++
++    (opts, args) = parser.parse_args()
++
++    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
++
++    else:
++        opts.pid_file = expandpath(opts.pid_file)
++
++    # Set default log file location
++    if not opts.log_file:
++        opts.log_file = "%s.log" % opts.server_socket
++
++    else:
++        opts.log_file = expandpath(opts.log_file)
++
++    # Logging setup
++    init_logger()
++    logger.info('logging to %r', opts.log_file)
++
++    if opts.auto_close:
++        logger.debug('will auto close')
++    else:
++        logger.debug('will not auto close')
++
++    if opts.daemon_mode:
++        logger.debug('will daemonize')
++    else:
++        logger.debug('will not daemonize')
++
++    # 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:
++        action = 'start'
++        logger.warning('no daemon action given, assuming %r', action)
++
++    else:
++        parser.error('invalid action argument: %r' % args)
++
++    logger.info('daemon action %r', action)
++    # Do action
++    daemon_actions[action]()
++
++    logger.debug('process CPU time: %f', time.clock())
++
++
++if __name__ == "__main__":
++    main()
++
++
++# vi: set et ts=4:
+diff --git a/uzbl/ext.py b/uzbl/ext.py
+new file mode 100644
+index 0000000..b2795ed
+--- /dev/null
++++ b/uzbl/ext.py
+@@ -0,0 +1,92 @@
++from .event_manager import Uzbl
++import logging
++
++
++per_instance_registry = []
++global_registry = []
++
++
++class PluginMeta(type):
++    """Registers plugin in registry so that it instantiates when needed"""
++
++    def __init__(self, name, bases, dic):
++        super(PluginMeta, self).__init__(name, bases, dic)
++        # Sorry, a bit of black magick
++        if bases == (object,) or bases == (BasePlugin,):
++            # base classes for the plugins
++            return
++        if issubclass(self, PerInstancePlugin):
++            per_instance_registry.append(self)
++        elif issubclass(self, GlobalPlugin):
++            global_registry.append(self)
++
++    def __getitem__(self, owner):
++        """This method returns instance of plugin corresponding to owner
++
++        :param owner: can be uzbl or event manager
++
++        If you will try to get instance of :class:`GlobalPlugin` on uzbl
++        instance it will find instance on it's parent. If you will try to
++        find instance of a :class:`PerInstancePlugin` it will raise
++        :class:`ValueError`
++        """
++        return self._get_instance(owner)
++
++
++class BasePlugin(object, metaclass=PluginMeta):
++    """Base class for all uzbl plugins"""
++
++
++class PerInstancePlugin(BasePlugin):
++    """Base class for plugins which instantiate once per uzbl instance"""
++
++    def __init__(self, uzbl):
++        self.uzbl = uzbl
++        self.logger = uzbl.logger  # we can also append plugin name to logger
++
++    def cleanup(self):
++        """Cleanup state after instance is gone
++
++        Default function avoids cyclic refrences, so don't forget to call
++        super() if overriding
++        """
++        del self.uzbl
++
++    @classmethod
++    def _get_instance(cls, owner):
++        """Returns instance of the plugin
++
++        This method should be private to not violate TOOWTDI
++        """
++        if not isinstance(owner, Uzbl):
++            raise ValueError("Can only get {0} instance for uzbl, not {1}"
++                .format(cls.__name__, type(owner).__name__))
++        # TODO(tailhook) probably subclasses can be returned as well
++        return owner.plugins[cls]
++
++
++class GlobalPlugin(BasePlugin):
++    """Base class for plugins which instantiate once per daemon"""
++
++    def __init__(self, event_manager):
++        self.event_manager = event_manager
++        self.logger = logging.getLogger(self.__module__)
++
++    @classmethod
++    def _get_instance(cls, owner):
++        """Returns instance of the plugin
++
++        This method should be private to not violate TOOWTDI
++        """
++        if isinstance(owner, Uzbl):
++            owner = owner.parent
++        # TODO(tailhook) probably subclasses can be returned as well
++        return owner.plugins[cls]
++
++    def cleanup(self):
++        """Cleanup state after instance is gone
++
++        Default function avoids cyclic refrences, so don't forget to call
++        super() if overriding
++        """
++        del self.event_manager
+diff --git a/uzbl/net.py b/uzbl/net.py
+new file mode 100644
+index 0000000..2c34e7b
+--- /dev/null
++++ b/uzbl/net.py
+@@ -0,0 +1,109 @@
++# Network communication classes
++# vi: set et ts=4:
++import asyncore
++import asynchat
++import socket
++import os
++import logging
++
++logger = logging.getLogger('uzbl.net')
++
++
++class NoTargetSet(Exception):
++    pass
++
++
++class TargetAlreadySet(Exception):
++    pass
++
++
++class WithTarget(object):
++    '''
++        Mixin that adds a property 'target' than can only be set once and
++        raises an exception if not set when accesed
++    '''
++
++    @property
++    def target(self):
++        try:
++            return self._target
++        except AttributeError as e:
++            raise NoTargetSet("No target for %r" % self, e)
++
++    @target.setter
++    def target(self, value):
++        if hasattr(self, '_target') and self._target is not None:
++            raise TargetAlreadySet(
++                "target of listener already set (%r)" % self._target
++            )
++        self._target = value
++
++
++class Listener(asyncore.dispatcher, WithTarget):
++    ''' Waits for new connections and accept()s them '''
++
++    def __init__(self, addr, target=None):
++        asyncore.dispatcher.__init__(self)
++        self.addr = addr
++        self.target = target
++
++    def start(self):
++        self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
++        self.knock()
++        self.set_reuse_addr()
++        self.bind(self.addr)
++        self.listen(5)
++
++    def knock(self):
++        '''Unlink existing socket if it's stale'''
++
++        if os.path.exists(self.addr):
++            logger.info('socket already exists, checking if active')
++            s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
++            try:
++                s.connect(self.addr)
++            except socket.error as e:
++                logger.info('unlinking %r', self.addr)
++                os.unlink(self.addr)
++
++    def writable(self):
++        return False
++
++    def handle_accept(self):
++        try:
++            sock, addr = self.accept()
++        except socket.error:
++            return
++        else:
++            self.target.add_instance(sock)
++
++    def close(self):
++        super(Listener, self).close()
++        if os.path.exists(self.addr):
++            logger.info('unlinking %r', self.addr)
++            os.unlink(self.addr)
++
++    def handle_error(self):
++        raise
++
++
++class Protocol(asynchat.async_chat):
++    ''' A connection with a single client '''
++
++    def __init__(self, socket, target=None):
++        asynchat.async_chat.__init__(self, socket)
++        self.socket = socket
++        self.target = target
++        self.buffer = bytearray()
++        self.set_terminator(b'\n')
++
++    def collect_incoming_data(self, data):
++        self.buffer += data
++
++    def found_terminator(self):
++        val = self.buffer.decode('utf-8')
++        del self.buffer[:]
++        self.target.parse_msg(val)
++
++    def handle_error(self):
++        raise
+diff --git a/uzbl/plugins/__init__.py b/uzbl/plugins/__init__.py
+new file mode 100644
+index 0000000..84cf2ec
+--- /dev/null
++++ b/uzbl/plugins/__init__.py
+@@ -0,0 +1,14 @@
++'''
++Plugins collection
++
++plugins for use with uzbl-event-manager
++'''
++
++import os.path
++
++plugin_path = os.environ.get("UZBL_PLUGIN_PATH",
++    "~/.local/share/uzbl/plugins:/usr/share/uzbl/site-plugins",
++    ).split(":")
++if plugin_path:
++    __path__ = list(map(os.path.expanduser, plugin_path)) + __path__
++
+diff --git a/uzbl/plugins/bind.py b/uzbl/plugins/bind.py
+new file mode 100644
+index 0000000..f40da36
+--- /dev/null
++++ b/uzbl/plugins/bind.py
+@@ -0,0 +1,463 @@
++'''Plugin provides support for binds in uzbl.
++
++For example:
++  event BIND ZZ = exit          -> bind('ZZ', 'exit')
++  event BIND o _ = uri %s       -> bind('o _', 'uri %s')
++  event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'")
++
++And it is also possible to execute a function on activation:
++  bind('DD', myhandler)
++'''
++
++import sys
++import re
++from functools import partial
++from itertools import count
++
++from uzbl.arguments import unquote, splitquoted
++from uzbl.ext import PerInstancePlugin
++from .cmd_expand import cmd_expand
++from .config import Config
++from .keycmd import KeyCmd
++import collections
++
++# Commonly used regular expressions.
++MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match
++# Matches <x:y>, <'x':y>, <:'y'>, <x!y>, <'x'!y>, ...
++PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>'
++FIND_PROMPTS = re.compile(PROMPTS).split
++VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match
++
++# For accessing a bind glob stack.
++ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = list(range(5))
++
++
++# Custom errors.
++class ArgumentError(Exception): pass
++
++
++class Bindlet(object):
++    '''Per-instance bind status/state tracker.'''
++
++    def __init__(self, uzbl):
++        self.binds = {'global': {}}
++        self.uzbl = uzbl
++        self.uzbl_config = Config[uzbl]
++        self.depth = 0
++        self.args = []
++        self.last_mode = None
++        self.after_cmds = None
++        self.stack_binds = []
++
++        # A subset of the global mode binds containing non-stack and modkey
++        # activiated binds for use in the stack mode.
++        self.globals = []
++
++
++    def __getitem__(self, key):
++        return self.get_binds(key)
++
++
++    def reset(self):
++        '''Reset the tracker state and return to last mode.'''
++
++        self.depth = 0
++        self.args = []
++        self.after_cmds = None
++        self.stack_binds = []
++
++        if self.last_mode:
++            mode, self.last_mode = self.last_mode, None
++            self.uzbl_config['mode'] = mode
++
++        del self.uzbl_config['keycmd_prompt']
++
++
++    def stack(self, bind, args, depth):
++        '''Enter or add new bind in the next stack level.'''
++
++        if self.depth != depth:
++            if bind not in self.stack_binds:
++                self.stack_binds.append(bind)
++
++            return
++
++        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
++        self.depth += 1
++        self.after_cmds = bind.prompts[depth]
++
++
++    def after(self):
++        '''If a stack was triggered then set the prompt and default value.'''
++
++        if self.after_cmds is None:
++            return
++
++        (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None
++
++        KeyCmd[self.uzbl].clear_keycmd()
++        if prompt:
++            self.uzbl_config['keycmd_prompt'] = prompt
++
++        if set and is_cmd:
++            self.uzbl.send(set)
++
++        elif set and not is_cmd:
++            self.uzbl.send('event SET_KEYCMD %s' % set)
++
++
++    def get_binds(self, mode=None):
++        '''Return the mode binds + globals. If we are stacked then return
++        the filtered stack list and modkey & non-stack globals.'''
++
++        if mode is None:
++            mode = self.uzbl_config.get('mode', None)
++
++        if not mode:
++            mode = 'global'
++
++        if self.depth:
++            return self.stack_binds + self.globals
++
++        globals = self.binds['global']
++        if mode not in self.binds or mode == 'global':
++            return [_f for _f in list(globals.values()) if _f]
++
++        binds = dict(list(globals.items()) + list(self.binds[mode].items()))
++        return [_f for _f in list(binds.values()) if _f]
++
++
++    def add_bind(self, mode, glob, bind=None):
++        '''Insert (or override) a bind into the mode bind dict.'''
++
++        if mode not in self.binds:
++            self.binds[mode] = {glob: bind}
++            return
++
++        binds = self.binds[mode]
++        binds[glob] = bind
++
++        if mode == 'global':
++            # Regen the global-globals list.
++            self.globals = []
++            for bind in list(binds.values()):
++                if bind is not None and bind.is_global:
++                    self.globals.append(bind)
++
++
++def ismodbind(glob):
++    '''Return True if the glob specifies a modbind.'''
++
++    return bool(MOD_START(glob))
++
++
++def split_glob(glob):
++    '''Take a string of the form "<Mod1><Mod2>cmd _" and return a list of the
++    modkeys in the glob and the command.'''
++
++    mods = set()
++    while True:
++        match = MOD_START(glob)
++        if not match:
++            break
++
++        end = match.span()[1]
++        mods.add(glob[:end])
++        glob = glob[end:]
++
++    return (mods, glob)
++
++
++class Bind(object):
++
++    # unique id generator
++    nextid = count().__next__
++
++    def __init__(self, glob, handler, *args, **kargs):
++        self.is_callable = isinstance(handler, collections.Callable)
++        self._repr_cache = None
++
++        if not glob:
++            raise ArgumentError('glob cannot be blank')
++
++        if self.is_callable:
++            self.function = handler
++            self.args = args
++            self.kargs = kargs
++
++        elif kargs:
++            raise ArgumentError('cannot supply kargs for uzbl commands')
++
++        elif not isinstance(handler, str):
++            self.commands = handler
++
++        else:
++            self.commands = [handler,] + list(args)
++
++        self.glob = glob
++
++        # Assign unique id.
++        self.bid = self.nextid()
++
++        self.split = split = FIND_PROMPTS(glob)
++        self.prompts = []
++        for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]):
++            prompt, set = list(map(unquote, [prompt, set]))
++            cmd = True if cmd == '!' else False
++            if prompt and prompt[-1] != ":":
++                prompt = "%s:" % prompt
++
++            self.prompts.append((prompt, cmd, set))
++
++        # Check that there is nothing like: fl*<int:>*
++        for glob in split[:-1:4]:
++            if glob.endswith('*'):
++                msg = "token '*' not at the end of a prompt bind: %r" % split
++                raise SyntaxError(msg)
++
++        # Check that there is nothing like: fl<prompt1:><prompt2:>_
++        for glob in split[4::4]:
++            if not glob:
++                msg = 'found null segment after first prompt: %r' % split
++                raise SyntaxError(msg)
++
++        stack = []
++        for (index, glob) in enumerate(reversed(split[::4])):
++            # Is the binding a MODCMD or KEYCMD:
++            mod_cmd = ismodbind(glob)
++
++            # Do we execute on UPDATES or EXEC events?
++            on_exec = True if glob[-1] in ['!', '_'] else False
++
++            # Does the command take arguments?
++            has_args = True if glob[-1] in ['*', '_'] else False
++
++            glob = glob[:-1] if has_args or on_exec else glob
++            mods, glob = split_glob(glob)
++            stack.append((on_exec, has_args, mods, glob, index))
++
++        self.stack = list(reversed(stack))
++        self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD])
++
++
++    def __getitem__(self, depth):
++        '''Get bind info at a depth.'''
++
++        if self.is_global:
++            return self.stack[0]
++
++        return self.stack[depth]
++
++
++    def __repr__(self):
++        if self._repr_cache:
++            return self._repr_cache
++
++        args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
++
++        if self.is_callable:
++            args.append('function=%r' % self.function)
++            if self.args:
++                args.append('args=%r' % self.args)
++
++            if self.kargs:
++                args.append('kargs=%r' % self.kargs)
++
++        else:
++            cmdlen = len(self.commands)
++            cmds = self.commands[0] if cmdlen == 1 else self.commands
++            args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds))
++
++        self._repr_cache = '<Bind(%s)>' % ', '.join(args)
++        return self._repr_cache
++
++
++class BindPlugin(PerInstancePlugin):
++    def __init__(self, uzbl):
++        '''Export functions and connect handlers to events.'''
++        super(BindPlugin, self).__init__(uzbl)
++
++        self.bindlet = Bindlet(uzbl)
++
++        uzbl.connect('BIND', self.parse_bind)
++        uzbl.connect('MODE_BIND', self.parse_mode_bind)
++        uzbl.connect('MODE_CHANGED', self.mode_changed)
++
++        # Connect key related events to the key_event function.
++        events = [['KEYCMD_UPDATE', 'KEYCMD_EXEC'],
++                  ['MODCMD_UPDATE', 'MODCMD_EXEC']]
++
++        for mod_cmd in range(2):
++            for on_exec in range(2):
++                event = events[mod_cmd][on_exec]
++                handler = partial(self.key_event,
++                    mod_cmd=bool(mod_cmd),
++                    on_exec=bool(on_exec))
++                uzbl.connect(event, handler)
++
++    def exec_bind(self, bind, *args, **kargs):
++        '''Execute bind objects.'''
++
++        self.uzbl.event("EXEC_BIND", bind, args, kargs)
++
++        if bind.is_callable:
++            args += bind.args
++            kargs = dict(list(bind.kargs.items())+list(kargs.items()))
++            bind.function(self.uzbl, *args, **kargs)
++            return
++
++        if kargs:
++            raise ArgumentError('cannot supply kargs for uzbl commands')
++
++        commands = []
++        for cmd in bind.commands:
++            cmd = cmd_expand(cmd, args)
++            self.uzbl.send(cmd)
++
++    def mode_bind(self, modes, glob, handler=None, *args, **kargs):
++        '''Add a mode bind.'''
++
++        bindlet = self.bindlet
++
++        if isinstance(modes, str):
++            modes = modes.split(',')
++
++        # Sort and filter binds.
++        modes = [_f for _f in map(str.strip, modes) if _f]
++
++        if isinstance(handler, collections.Callable) or (handler is not None and handler.strip()):
++            bind = Bind(glob, handler, *args, **kargs)
++
++        else:
++            bind = None
++
++        for mode in modes:
++            if not VALID_MODE(mode):
++                raise NameError('invalid mode name: %r' % mode)
++
++        for mode in modes:
++            if mode[0] == '-':
++                mode, bind = mode[1:], None
++
++            bindlet.add_bind(mode, glob, bind)
++            self.uzbl.event('ADDED_MODE_BIND', mode, glob, bind)
++        self.logger.info('added bind %s %s %s', mode, glob, bind)
++
++    def bind(self, glob, handler, *args, **kargs):
++        '''Legacy bind function.'''
++
++        self.mode_bind('global', glob, handler, *args, **kargs)
++
++    def parse_mode_bind(self, args):
++        '''Parser for the MODE_BIND event.
++
++        Example events:
++            MODE_BIND <mode>         <bind>        = <command>
++            MODE_BIND command        o<location:>_ = uri %s
++            MODE_BIND insert,command <BackSpace>   = ...
++            MODE_BIND global         ...           = ...
++            MODE_BIND global,-insert ...           = ...
++        '''
++
++        args = splitquoted(args)
++        if len(args) < 2:
++            raise ArgumentError('missing mode or bind section: %r' % args.raw())
++
++        modes = args[0].split(',')
++        for i, g in enumerate(args[1:]):
++            if g == '=':
++                glob = args.raw(1, i)
++                command = args.raw(i+2)
++                break
++        else:
++            raise ArgumentError('missing delimiter in bind section: %r' % args.raw())
++
++        self.mode_bind(modes, glob, command)
++
++    def parse_bind(self, args):
++        '''Legacy parsing of the BIND event and conversion to the new format.
++
++        Example events:
++            request BIND <bind>        = <command>
++            request BIND o<location:>_ = uri %s
++            request BIND <BackSpace>   = ...
++            request BIND ...           = ...
++        '''
++
++        self.parse_mode_bind("global %s" % args)
++
++    def mode_changed(self, mode):
++        '''Clear the stack on all non-stack mode changes.'''
++
++        if mode != 'stack':
++            self.bindlet.reset()
++
++    def match_and_exec(self, bind, depth, modstate, keylet, bindlet):
++        (on_exec, has_args, mod_cmd, glob, more) = bind[depth]
++        cmd = keylet.modcmd if mod_cmd else keylet.keycmd
++
++        if mod_cmd and modstate != mod_cmd:
++            return False
++
++        if has_args:
++            if not cmd.startswith(glob):
++                return False
++
++            args = [cmd[len(glob):],]
++
++        elif cmd != glob:
++            return False
++
++        else:
++            args = []
++
++        if bind.is_global or (not more and depth == 0):
++            self.exec_bind(bind, *args)
++            if not has_args:
++                KeyCmd[self.uzbl].clear_current()
++
++            return True
++
++        elif more:
++            bindlet.stack(bind, args, depth)
++            (on_exec, has_args, mod_cmd, glob, more) = bind[depth+1]
++            if not on_exec and has_args and not glob and not more:
++                self.exec_bind(uzbl, bind, *(args+['',]))
++
++            return False
++
++        args = bindlet.args + args
++        self.exec_bind(bind, *args)
++        if not has_args or on_exec:
++            config = Config[self.uzbl]
++            del config['mode']
++            bindlet.reset()
++
++        return True
++
++    def key_event(self, modstate, keylet, mod_cmd=False, on_exec=False):
++        bindlet = self.bindlet
++        depth = bindlet.depth
++        for bind in bindlet.get_binds():
++            t = bind[depth]
++            if (bool(t[MOD_CMD]) != mod_cmd) or (t[ON_EXEC] != on_exec):
++                continue
++
++            if self.match_and_exec(bind, depth, modstate, keylet, bindlet):
++                return
++
++        bindlet.after()
++
++        # 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:
++            config = Config[uzbl]
++            del config['mode']
++
++# vi: set et ts=4:
+diff --git a/uzbl/plugins/cmd_expand.py b/uzbl/plugins/cmd_expand.py
+new file mode 100644
+index 0000000..46e1e67
+--- /dev/null
++++ b/uzbl/plugins/cmd_expand.py
+@@ -0,0 +1,36 @@
++def escape(str):
++    for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]:
++        str = str.replace(char, (level * '\\') + char)
++
++    return str
++
++
++def cmd_expand(cmd, args):
++    '''Exports a function that provides the following
++    expansions in any uzbl command string:
++
++        %s = replace('%s', ' '.join(args))
++        %r = replace('%r', "'%s'" % escaped(' '.join(args)))
++        %1 = replace('%1', arg[0])
++        %2 = replace('%2', arg[1])
++        %n = replace('%n', arg[n-1])
++    '''
++
++    # Ensure (1) all string representable and (2) correct string encoding.
++    args = list(map(str, args))
++
++    # Direct string replace.
++    if '%s' in cmd:
++        cmd = cmd.replace('%s', ' '.join(args))
++
++    # Escaped and quoted string replace.
++    if '%r' in cmd:
++        cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args)))
++
++    # Arg index string replace.
++    for (index, arg) in enumerate(args):
++        index += 1
++        if '%%%d' % index in cmd:
++            cmd = cmd.replace('%%%d' % index, str(arg))
++
++    return cmd
+diff --git a/uzbl/plugins/completion.py b/uzbl/plugins/completion.py
+new file mode 100644
+index 0000000..ef2f277
+--- /dev/null
++++ b/uzbl/plugins/completion.py
+@@ -0,0 +1,186 @@
++'''Keycmd completion.'''
++
++import re
++
++from uzbl.arguments import splitquoted
++from uzbl.ext import PerInstancePlugin
++from .config import Config
++from .keycmd import KeyCmd
++
++# Completion level
++NONE, ONCE, LIST, COMPLETE = list(range(4))
++
++# The reverse keyword finding re.
++FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall
++
++
++def escape(str):
++    return str.replace("@", "\@")
++
++
++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 add_var(self, var):
++        self.add('@' + var)
++
++
++class CompletionListFormatter(object):
++    LIST_FORMAT = "<span> %s </span>"
++    ITEM_FORMAT = "<span @hint_style>%s</span>%s"
++
++    def format(self, partial, completions):
++        p = len(partial)
++        completions.sort()
++        return self.LIST_FORMAT % ' '.join(
++            [self.ITEM_FORMAT % (escape(h[:p]), h[p:]) for h in completions]
++        )
++
++
++class CompletionPlugin(PerInstancePlugin):
++    def __init__(self, uzbl):
++        '''Export functions and connect handlers to events.'''
++        super(CompletionPlugin, self).__init__(uzbl)
++
++        self.completion = Completions()
++        self.listformatter = CompletionListFormatter()
++
++        uzbl.connect('BUILTINS', self.add_builtins)
++        uzbl.connect('CONFIG_CHANGED', self.add_config_key)
++        uzbl.connect('KEYCMD_CLEARED', self.stop_completion)
++        uzbl.connect('KEYCMD_EXEC', self.stop_completion)
++        uzbl.connect('KEYCMD_UPDATE', self.update_completion_list)
++        uzbl.connect('START_COMPLETION', self.start_completion)
++        uzbl.connect('STOP_COMPLETION', self.stop_completion)
++
++        uzbl.send('dump_config_as_events')
++
++    def get_incomplete_keyword(self):
++        '''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 = KeyCmd[self.uzbl].keylet
++        left_segment = keylet.keycmd[:keylet.cursor]
++        partial = (FIND_SEGMENT(left_segment) + ['', ])[0].lstrip()
++        if partial.startswith('set '):
++            return ('@' + partial[4:].lstrip(), True)
++
++        return (partial, False)
++
++    def stop_completion(self, *args):
++        '''Stop command completion and return the level to NONE.'''
++
++        self.completion.level = NONE
++        if 'completion_list' in Config[self.uzbl]:
++            del Config[self.uzbl]['completion_list']
++
++    def complete_completion(self, partial, hint, set_completion=False):
++        '''Inject the remaining porition of the keyword into the keycmd then stop
++        the completioning.'''
++
++        if set_completion:
++            remainder = "%s = " % hint[len(partial):]
++
++        else:
++            remainder = "%s " % hint[len(partial):]
++
++        KeyCmd[self.uzbl].inject_keycmd(remainder)
++        self.stop_completion()
++
++    def partial_completion(self, partial, hint):
++        '''Inject a common portion of the hints into the keycmd.'''
++
++        remainder = hint[len(partial):]
++        KeyCmd[self.uzbl].inject_keycmd(remainder)
++
++    def update_completion_list(self, *args):
++        '''Checks if the user still has a partially completed keyword under his
++        cursor then update the completion hints list.'''
++
++        partial = self.get_incomplete_keyword()[0]
++        if not partial:
++            return self.stop_completion()
++
++        if self.completion.level < LIST:
++            return
++
++        config = Config[self.uzbl]
++
++        hints = [h for h in self.completion if h.startswith(partial)]
++        if not hints:
++            del config['completion_list']
++            return
++
++        config['completion_list'] = self.listformatter.format(partial, hints)
++
++    def start_completion(self, *args):
++        if self.completion.locked:
++            return
++
++        (partial, set_completion) = self.get_incomplete_keyword()
++        if not partial:
++            return self.stop_completion()
++
++        if self.completion.level < COMPLETE:
++            self.completion.level += 1
++
++        hints = [h for h in self.completion if h.startswith(partial)]
++        if not hints:
++            return
++
++        elif len(hints) == 1:
++            self.completion.lock()
++            self.complete_completion(partial, hints[0], set_completion)
++            self.completion.unlock()
++            return
++
++        elif partial in hints and completion.level == COMPLETE:
++            self.completion.lock()
++            self.complete_completion(partial, partial, set_completion)
++            self.completion.unlock()
++            return
++
++        smalllen, smallest = sorted([(len(h), h) for h in hints])[0]
++        common = ''
++        for i in range(len(partial), smalllen):
++            char, same = smallest[i], True
++            for hint in hints:
++                if hint[i] != char:
++                    same = False
++                    break
++
++            if not same:
++                break
++
++            common += char
++
++        if common:
++            self.completion.lock()
++            self.partial_completion(partial, partial + common)
++            self.completion.unlock()
++
++        self.update_completion_list()
++
++    def add_builtins(self, builtins):
++        '''Pump the space delimited list of builtin commands into the
++        builtin list.'''
++
++        builtins = splitquoted(builtins)
++        self.completion.update(builtins)
++
++    def add_config_key(self, key, value):
++        '''Listen on the CONFIG_CHANGED event and add config keys to the variable
++        list for @var<Tab> like expansion support.'''
++
++        self.completion.add_var(key)
+diff --git a/uzbl/plugins/config.py b/uzbl/plugins/config.py
+new file mode 100644
+index 0000000..4f00315
+--- /dev/null
++++ b/uzbl/plugins/config.py
+@@ -0,0 +1,109 @@
++from re import compile
++
++from uzbl.arguments import splitquoted
++from uzbl.ext import PerInstancePlugin
++
++types = {'int': int, 'float': float, 'str': str}
++
++valid_key = compile('^[A-Za-z0-9_\.]+$').match
++
++class Config(PerInstancePlugin):
++    """Configuration plugin, has dictionary interface for config access
++
++    This class is currenty not inherited from either UserDict or abc.Mapping
++    because not sure what version of python we want to support. It's not
++    hard to implement all needed methods either.
++    """
++
++    def __init__(self, uzbl):
++        super(Config, self).__init__(uzbl)
++
++        self.data = {}
++        uzbl.connect('VARIABLE_SET', self.parse_set_event)
++        assert not 'a' in self.data
++
++    def __getitem__(self, key):
++        return self.data[key]
++
++    def __setitem__(self, key, value):
++        self.set(key, value)
++
++    def __delitem__(self, key):
++        self.set(key)
++
++    def get(self, key, default=None):
++        return self.data.get(key, default)
++
++    def __contains__(self, key):
++        return key in self.data
++
++    def keys(self):
++        return iter(self.data.keys())
++
++    def items(self):
++        return iter(self.data.items())
++
++    def update(self, other=None, **kwargs):
++        if other is None:
++            other = {}
++
++        for (key, value) in list(dict(other).items()) + list(kwargs.items()):
++            self[key] = value
++
++
++    def set(self, key, value='', force=False):
++        '''Generates a `set <key> = <value>` command string to send to the
++        current uzbl instance.
++
++        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.'''
++
++        assert valid_key(key)
++
++        if isinstance(value, bool):
++            value = int(value)
++
++        else:
++            value = str(value)
++            assert '\n' not in value
++
++        if not force and key in self and self[key] == value:
++            return
++
++        self.uzbl.send('set %s = %s' % (key, value))
++
++
++    def parse_set_event(self, args):
++        '''Parse `VARIABLE_SET <var> <type> <value>` event and load the
++        (key, value) pair into the `uzbl.config` dict.'''
++
++        args = splitquoted(args)
++        if len(args) == 2:
++            key, type, raw_value = args[0], args[1], ''
++        elif len(args) == 3:
++            key, type, raw_value = args
++        else:
++            raise Exception('Invalid number of arguments')
++
++        assert valid_key(key)
++        assert type in types
++
++        new_value = types[type](raw_value)
++        old_value = self.data.get(key, None)
++
++        # Update new value.
++        self.data[key] = new_value
++
++        if old_value != new_value:
++            self.uzbl.event('CONFIG_CHANGED', key, new_value)
++
++        # Cleanup null config values.
++        if type == 'str' and not new_value:
++            del self.data[key]
++
++    def cleanup(self):
++        # not sure it's needed, but safer for cyclic links
++        self.data.clear()
++        super(Config, self).cleanup()
++
+diff --git a/uzbl/plugins/cookies.py b/uzbl/plugins/cookies.py
+new file mode 100644
+index 0000000..8875e99
+--- /dev/null
++++ b/uzbl/plugins/cookies.py
+@@ -0,0 +1,226 @@
++""" Basic cookie manager
++    forwards cookies to all other instances connected to the event manager"""
++
++from collections import defaultdict
++import os, re, stat
++
++from uzbl.arguments import splitquoted
++from uzbl.ext import GlobalPlugin, PerInstancePlugin
++
++# these are symbolic names for the components of the cookie tuple
++symbolic = {'domain': 0, 'path':1, 'name':2, 'value':3, 'scheme':4, 'expires':5}
++
++# allows for partial cookies
++# ? allow wildcard in key
++def match(key, cookie):
++    for k,c in zip(key,cookie):
++        if k != c:
++            return False
++    return True
++
++def match_list(_list, cookie):
++    for matcher in _list:
++        for component, match in matcher:
++            if match(cookie[component]) is None:
++                break
++        else:
++            return True
++    return False
++
++def add_cookie_matcher(_list, arg):
++    ''' add a cookie matcher to a whitelist or a blacklist.
++        a matcher is a list of (component, re) tuples that matches a cookie when the
++        "component" part of the cookie matches the regular expression "re".
++        "component" is one of the keys defined in the variable "symbolic" above,
++        or the index of a component of a cookie tuple.
++    '''
++
++    args = splitquoted(arg)
++    mlist = []
++    for (component, regexp) in zip(args[0::2], args[1::2]):
++        try:
++            component = symbolic[component]
++        except KeyError:
++            component = int(component)
++        assert component <= 5
++        mlist.append((component, re.compile(regexp).search))
++    _list.append(mlist)
++
++class NullStore(object):
++    def add_cookie(self, rawcookie, cookie):
++        pass
++
++    def delete_cookie(self, rkey, key):
++        pass
++
++class ListStore(list):
++    def add_cookie(self, rawcookie, cookie):
++        self.append(rawcookie)
++
++    def delete_cookie(self, rkey, key):
++        self[:] = [x for x in self if not match(key, splitquoted(x))]
++
++class TextStore(object):
++    def __init__(self, filename):
++        self.filename = filename
++        try:
++          # make sure existing cookie jar is not world-open
++          perm_mode = os.stat(self.filename).st_mode
++          if (perm_mode & (stat.S_IRWXO | stat.S_IRWXG)) > 0:
++              safe_perm = stat.S_IMODE(perm_mode) & ~(stat.S_IRWXO | stat.S_IRWXG)
++              os.chmod(self.filename, safe_perm)
++        except OSError:
++            pass
++
++    def as_event(self, cookie):
++        """Convert cookie.txt row to uzbls cookie event format"""
++        scheme = {
++            'TRUE'  : 'https',
++            'FALSE' : 'http'
++        }
++        extra = ''
++        if cookie[0].startswith("#HttpOnly_"):
++            extra = 'Only'
++            domain = cookie[0][len("#HttpOnly_"):]
++        elif cookie[0].startswith('#'):
++            return None
++        else:
++            domain = cookie[0]
++        try:
++            return (domain,
++                cookie[2],
++                cookie[5],
++                cookie[6],
++                scheme[cookie[3]] + extra,
++                cookie[4])
++        except (KeyError,IndexError):
++            # Let malformed rows pass through like comments
++            return None
++
++    def as_file(self, cookie):
++        """Convert cookie event to cookie.txt row"""
++        secure = {
++            'https' : 'TRUE',
++            'http'  : 'FALSE',
++            'httpsOnly' : 'TRUE',
++            'httpOnly'  : 'FALSE'
++        }
++        http_only = {
++            'https' : '',
++            'http'  : '',
++            'httpsOnly' : '#HttpOnly_',
++            'httpOnly'  : '#HttpOnly_'
++        }
++        return (http_only[cookie[4]] + cookie[0],
++            'TRUE' if cookie[0].startswith('.') else 'FALSE',
++            cookie[1],
++            secure[cookie[4]],
++            cookie[5],
++            cookie[2],
++            cookie[3])
++
++    def add_cookie(self, rawcookie, cookie):
++        assert len(cookie) == 6
++
++        # delete equal cookies (ignoring expire time, value and secure flag)
++        self.delete_cookie(None, cookie[:-3])
++
++        # restrict umask before creating the cookie jar
++        curmask=os.umask(0)
++        os.umask(curmask| stat.S_IRWXO | stat.S_IRWXG)
++
++        first = not os.path.exists(self.filename)
++        with open(self.filename, 'a') as f:
++            if first:
++                print("# HTTP Cookie File", file=f)
++            print('\t'.join(self.as_file(cookie)), file=f)
++
++    def delete_cookie(self, rkey, key):
++        if not os.path.exists(self.filename):
++            return
++
++        # restrict umask before creating the cookie jar
++        curmask=os.umask(0)
++        os.umask(curmask | stat.S_IRWXO | stat.S_IRWXG)
++
++        # read all cookies
++        with open(self.filename, 'r') as f:
++            cookies = f.readlines()
++
++        # write those that don't match the cookie to delete
++        with open(self.filename, 'w') as f:
++            for l in cookies:
++                c = self.as_event(l.split('\t'))
++                if c is None or not match(key, c):
++                    print(l, end='', file=f)
++        os.umask(curmask)
++
++xdg_data_home = os.environ.get('XDG_DATA_HOME', os.path.join(os.environ['HOME'], '.local/share'))
++DefaultStore = TextStore(os.path.join(xdg_data_home, 'uzbl/cookies.txt'))
++SessionStore = TextStore(os.path.join(xdg_data_home, 'uzbl/session-cookies.txt'))
++
++class Cookies(PerInstancePlugin):
++    def __init__(self, uzbl):
++        super(Cookies, self).__init__(uzbl)
++
++        self.whitelist = []
++        self.blacklist = []
++
++        uzbl.connect('ADD_COOKIE', self.add_cookie)
++        uzbl.connect('DELETE_COOKIE', self.delete_cookie)
++        uzbl.connect('BLACKLIST_COOKIE', self.blacklist_cookie)
++        uzbl.connect('WHITELIST_COOKIE', self.whitelist_cookie)
++
++    # accept a cookie only when:
++    # a. there is no whitelist and the cookie is in the blacklist
++    # b. the cookie is in the whitelist and not in the blacklist
++    def accept_cookie(self, cookie):
++        if self.whitelist:
++            if match_list(self.whitelist, cookie):
++                return not match_list(self.blacklist, cookie)
++            return False
++
++        return not match_list(self.blacklist, cookie)
++
++    def expires_with_session(self, cookie):
++        return cookie[5] == ''
++
++    def get_recipents(self):
++        """ get a list of Uzbl instances to send the cookie too. """
++        # This could be a lot more interesting
++        return [u for u in list(self.uzbl.parent.uzbls.values()) if u is not self.uzbl]
++
++    def get_store(self, session=False):
++        if session:
++            return SessionStore
++        return DefaultStore
++
++    def add_cookie(self, cookie):
++        cookie = splitquoted(cookie)
++        if self.accept_cookie(cookie):
++            for u in self.get_recipents():
++                u.send('add_cookie %s' % cookie.raw())
++
++            self.get_store(self.expires_with_session(cookie)).add_cookie(cookie.raw(), cookie)
++        else:
++            self.logger.debug('cookie %r is blacklisted', cookie)
++            self.uzbl.send('delete_cookie %s' % cookie.raw())
++
++    def delete_cookie(self, cookie):
++        cookie = splitquoted(cookie)
++        for u in self.get_recipents():
++            u.send('delete_cookie %s' % cookie.raw())
++
++        if len(cookie) == 6:
++            self.get_store(self.expires_with_session(cookie)).delete_cookie(cookie.raw(), cookie)
++        else:
++            for store in set([self.get_store(session) for session in (True, False)]):
++                store.delete_cookie(cookie.raw(), cookie)
++
++    def blacklist_cookie(self, arg):
++        add_cookie_matcher(self.blacklist, arg)
++
++    def whitelist_cookie(self, arg):
++        add_cookie_matcher(self.whitelist, arg)
++
++# vi: set et ts=4:
+diff --git a/uzbl/plugins/downloads.py b/uzbl/plugins/downloads.py
+new file mode 100644
+index 0000000..208dbda
+--- /dev/null
++++ b/uzbl/plugins/downloads.py
+@@ -0,0 +1,83 @@
++# this plugin does a very simple display of download progress. to use it, add
++# @downloads to your status_format.
++
++import os
++import html
++
++from uzbl.arguments import splitquoted
++from .config import Config
++from uzbl.ext import PerInstancePlugin
++
++class Downloads(PerInstancePlugin):
++
++    def __init__(self, uzbl):
++        super(Downloads, self).__init__(uzbl)
++        uzbl.connect('DOWNLOAD_STARTED', self.download_started)
++        uzbl.connect('DOWNLOAD_PROGRESS', self.download_progress)
++        uzbl.connect('DOWNLOAD_COMPLETE', self.download_complete)
++        self.active_downloads = {}
++
++    def update_download_section(self):
++        """after a download's status has changed this
++           is called to update the status bar
++        """
++
++        if self.active_downloads:
++            # add a newline before we list downloads
++            result = '&#10;downloads:'
++            for path, progress in list(self.active_downloads.items()):
++                # add each download
++                fn = os.path.basename(path)
++
++                dl = " %s (%d%%)" % (fn, progress * 100)
++
++                # replace entities to make sure we don't break our markup
++                # (this could be done with an @[]@ expansion in uzbl, but then we
++                # can't use the &#10; above to make a new line)
++                dl = html.escape(dl)
++                result += dl
++        else:
++            result = ''
++
++        # and the result gets saved to an uzbl variable that can be used in
++        # status_format
++        config = Config[self.uzbl]
++        if config.get('downloads', '') != result:
++              config['downloads'] = result
++
++    def download_started(self, args):
++        # parse the arguments
++        args = splitquoted(args)
++        destination_path = args[0]
++
++        # add to the list of active downloads
++        self.active_downloads[destination_path] = 0.0
++
++        # update the progress
++        self.update_download_section()
++
++    def download_progress(self, args):
++        # parse the arguments
++        args = splitquoted(args)
++        destination_path = args[0]
++        progress = float(args[1])
++
++        # update the progress
++        self.active_downloads[destination_path] = progress
++
++        # update the status bar variable
++        self.update_download_section()
++
++    def download_complete(self, args):
++        # TODO(tailhook) be more userfriendly: show download for some time!
++
++        # parse the arguments
++        args = splitquoted(args)
++        destination_path = args[0]
++
++        # remove from the list of active downloads
++        del self.active_downloads[destination_path]
++
++        # update the status bar variable
++        self.update_download_section()
++
+diff --git a/uzbl/plugins/history.py b/uzbl/plugins/history.py
+new file mode 100644
+index 0000000..1a0e0fb
+--- /dev/null
++++ b/uzbl/plugins/history.py
+@@ -0,0 +1,148 @@
++import random
++
++from .on_set import OnSetPlugin
++from .keycmd import KeyCmd
++from uzbl.ext import GlobalPlugin, PerInstancePlugin
++
++class SharedHistory(GlobalPlugin):
++
++    def __init__(self, event_manager):
++        super(SharedHistory, self).__init__(event_manager)
++        self.history = {}  #TODO(tailhook) save and load from file
++
++    def get_line_number(self, prompt):
++        try:
++            return len(self.history[prompt])
++        except KeyError:
++            return 0
++
++    def addline(self, prompt, entry):
++        lst = self.history.get(prompt)
++        if lst is None:
++            self.history[prompt] = [entry]
++        else:
++            lst.append(entry)
++
++    def getline(self, prompt, index):
++        try:
++            return self.history[prompt][index]
++        except KeyError:
++            # not existent list is same as empty one
++            raise IndexError()
++
++
++class History(PerInstancePlugin):
++
++    def __init__(self, uzbl):
++        super(History, self).__init__(uzbl)
++        self._tail = ''
++        self.prompt = ''
++        self.cursor = None
++        self.search_key = None
++        uzbl.connect('KEYCMD_EXEC', self.keycmd_exec)
++        uzbl.connect('HISTORY_PREV', self.history_prev)
++        uzbl.connect('HISTORY_NEXT', self.history_next)
++        uzbl.connect('HISTORY_SEARCH', self.history_search)
++        OnSetPlugin[uzbl].on_set('keycmd_prompt',
++            lambda uzbl, k, v: self.change_prompt(v))
++
++    def prev(self):
++        shared = SharedHistory[self.uzbl]
++        if self.cursor is None:
++            self.cursor = shared.get_line_number(self.prompt) - 1
++        else:
++            self.cursor -= 1
++
++        if self.search_key:
++            while self.cursor >= 0:
++                line = shared.getline(self.prompt, self.cursor)
++                if self.search_key in line:
++                    return line
++                self.cursor -= 1
++
++        if self.cursor >= 0:
++            return shared.getline(self.prompt, self.cursor)
++
++        self.cursor = -1
++        return random.choice(end_messages)
++
++    def __next__(self):
++        if self.cursor is None:
++            return ''
++        shared = SharedHistory[self.uzbl]
++
++        self.cursor += 1
++
++        num = shared.get_line_number(self.prompt)
++        if self.search_key:
++            while self.cursor < num:
++                line = shared.getline(self.prompt, self.cursor)
++                if self.search_key in line:
++                    return line
++                self.cursor += 1
++
++        if self.cursor >= num:
++            self.cursor = None
++            self.search_key = None
++            if self._tail:
++                value = self._tail
++                self._tail = None
++                return value
++            return ''
++        return shared.getline(self.prompt, self.cursor)
++
++    def change_prompt(self, prompt):
++        self.prompt = prompt
++        self._tail = None
++
++    def search(self, key):
++        self.search_key = key
++        self.cursor = None
++
++    def __str__(self):
++        return "(History %s, %s)" % (self.cursor, self.prompt)
++
++    def keycmd_exec(self, modstate, keylet):
++        cmd = keylet.get_keycmd()
++        if cmd:
++            SharedHistory[self.uzbl].addline(self.prompt, cmd)
++        self._tail = None
++        self.cursor = None
++        self.search_key = None
++
++    def history_prev(self, _x):
++        cmd = KeyCmd[self.uzbl].keylet.get_keycmd()
++        if self.cursor is None and cmd:
++            self._tail = cmd
++        val = self.prev()
++        KeyCmd[self.uzbl].set_keycmd(val)
++
++    def history_next(self, _x):
++        KeyCmd[self.uzbl].set_keycmd(next(self))
++
++    def history_search(self, key):
++        self.search(key)
++        self.uzbl.send('event HISTORY_PREV')
++
++end_messages = (
++    'Look behind you, A three-headed monkey!',
++    'error #4: static from nylon underwear.',
++    'error #5: static from plastic slide rules.',
++    'error #6: global warming.',
++    'error #9: doppler effect.',
++    'error #16: somebody was calculating pi on the server.',
++    'error #19: floating point processor overflow.',
++    'error #21: POSIX compliance problem.',
++    'error #25: Decreasing electron flux.',
++    'error #26: first Saturday after first full moon in Winter.',
++    'error #64: CPU needs recalibration.',
++    'error #116: the real ttys became pseudo ttys and vice-versa.',
++    'error #229: wrong polarity of neutron flow.',
++    'error #330: quantum decoherence.',
++    'error #388: Bad user karma.',
++    'error #407: Route flapping at the NAP.',
++    'error #435: Internet shut down due to maintenance.',
++    )
++
++
++# vi: set et ts=4:
+diff --git a/uzbl/plugins/keycmd.py b/uzbl/plugins/keycmd.py
+new file mode 100644
+index 0000000..2020fda
+--- /dev/null
++++ b/uzbl/plugins/keycmd.py
+@@ -0,0 +1,503 @@
++import re
++
++from uzbl.arguments import splitquoted
++from uzbl.ext import PerInstancePlugin
++from .config import Config
++
++# Keycmd format which includes the markup for the cursor.
++KEYCMD_FORMAT = "%s<span @cursor_style>%s</span>%s"
++MODCMD_FORMAT = "<span> %s </span>"
++
++
++# FIXME, add utility functions to shared module
++def escape(str):
++    for char in ['\\', '@']:
++        str = str.replace(char, '\\'+char)
++
++    return str
++
++
++def uzbl_escape(str):
++    return "@[%s]@" % escape(str) if str else ''
++
++
++def inject_str(str, index, inj):
++    '''Inject a string into string at at given index.'''
++
++    return "%s%s%s" % (str[:index], inj, str[index:])
++
++
++class Keylet(object):
++    '''Small per-instance object that tracks characters typed.
++
++        >>> k = Keylet()
++        >>> k.set_keycmd('spam')
++        >>> print(k)
++        <keylet(keycmd='spam')>
++        >>> k.append_keycmd(' and egg')
++        >>> print(k)
++        <keylet(keycmd='spam and egg')>
++        >>> print(k.cursor)
++        12
++    '''
++
++    def __init__(self):
++        # Modcmd tracking
++        self.modcmd = ''
++        self.is_modcmd = False
++
++        # Keycmd tracking
++        self.keycmd = ''
++        self.cursor = 0
++
++    def get_keycmd(self):
++        ''' Get the keycmd-part of the keylet. '''
++
++        return self.keycmd
++
++    def clear_keycmd(self):
++        ''' Clears the keycmd part of the keylet '''
++
++        self.keycmd = ''
++        self.cursor = 0
++
++    def get_modcmd(self):
++        ''' Get the modcmd-part of the keylet. '''
++
++        if not self.is_modcmd:
++            return ''
++
++        return self.modcmd
++
++    def clear_modcmd(self):
++        self.modcmd = ''
++        self.is_modcmd = False
++
++    def set_keycmd(self, keycmd):
++        self.keycmd = keycmd
++        self.cursor = len(keycmd)
++
++    def insert_keycmd(self, s):
++        ''' Inserts string at the current position
++
++            >>> k = Keylet()
++            >>> k.set_keycmd('spam')
++            >>> k.cursor = 1
++            >>> k.insert_keycmd('egg')
++            >>> print(k)
++            <keylet(keycmd='seggpam')>
++            >>> print(k.cursor)
++            4
++        '''
++
++        self.keycmd = inject_str(self.keycmd, self.cursor, s)
++        self.cursor += len(s)
++
++    def append_keycmd(self, s):
++        ''' Appends string to to end of keycmd and moves the cursor
++
++            >>> k = Keylet()
++            >>> k.set_keycmd('spam')
++            >>> k.cursor = 1
++            >>> k.append_keycmd('egg')
++            >>> print(k)
++            <keylet(keycmd='spamegg')>
++            >>> print(k.cursor)
++            7
++        '''
++
++        self.keycmd += s
++        self.cursor = len(self.keycmd)
++
++    def backspace(self):
++        ''' Removes the character at the cursor position. '''
++        if not self.keycmd or not self.cursor:
++            return False
++
++        self.keycmd = self.keycmd[:self.cursor-1] + self.keycmd[self.cursor:]
++        self.cursor -= 1
++        return True
++
++    def delete(self):
++        ''' Removes the character after the cursor position. '''
++        if not self.keycmd:
++            return False
++
++        self.keycmd = self.keycmd[:self.cursor] + self.keycmd[self.cursor+1:]
++        return True
++
++    def strip_word(self, seps=' '):
++        ''' Removes the last word from the keycmd, similar to readline ^W
++            returns the part removed or None
++
++            >>> k = Keylet()
++            >>> k.set_keycmd('spam and egg')
++            >>> k.strip_word()
++            'egg'
++            >>> print(k)
++            <keylet(keycmd='spam and ')>
++            >>> k.strip_word()
++            'and'
++            >>> print(k)
++            <keylet(keycmd='spam ')>
++        '''
++        if not self.keycmd:
++            return None
++
++        head, tail = self.keycmd[:self.cursor].rstrip(seps), self.keycmd[self.cursor:]
++        rfind = -1
++        for sep in seps:
++            p = head.rfind(sep)
++            if p >= 0 and rfind < p + 1:
++                rfind = p + 1
++        if rfind == len(head) and head[-1] in seps:
++            rfind -= 1
++        self.keycmd = head[:rfind] if rfind + 1 else '' + tail
++        self.cursor = len(head)
++        return head[rfind:]
++
++    def set_cursor_pos(self, index):
++        ''' Sets the cursor position, Supports negative indexing and relative
++            stepping with '+' and '-'.
++            Returns the new cursor position
++
++            >>> k = Keylet()
++            >>> k.set_keycmd('spam and egg')
++            >>> k.set_cursor_pos(2)
++            2
++            >>> k.set_cursor_pos(-3)
++            10
++            >>> k.set_cursor_pos('+')
++            11
++        '''
++
++        if index == '-':
++            cursor = self.cursor - 1
++
++        elif index == '+':
++            cursor = self.cursor + 1
++
++        else:
++            cursor = int(index)
++            if cursor < 0:
++                cursor = len(self.keycmd) + cursor + 1
++
++        if cursor < 0:
++            cursor = 0
++
++        if cursor > len(self.keycmd):
++            cursor = len(self.keycmd)
++
++        self.cursor = cursor
++        return self.cursor
++
++    def markup(self):
++        ''' Returns t