+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 = ' 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 above to make a new line)
+- dl = dl.replace("&", "&").replace("<", "<")
+- 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 = ' 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 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 the keycmd with the cursor in pango markup spliced in
++
++ >>> k = Keylet()
++ >>> k.set_keycmd('spam and egg')
++ >>> k.set_cursor_pos(4)
++ 4
++ >>> k.markup()
++ '@[spam]@<span @cursor_style>@[ ]@</span>@[and egg]@'
++ '''
++
++ if self.cursor < len(self.keycmd):
++ curchar = self.keycmd[self.cursor]
++ else:
++ curchar = ' '
++ chunks = [self.keycmd[:self.cursor], curchar, self.keycmd[self.cursor+1:]]
++ return KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks))
++
++ 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)
++
++
++class KeyCmd(PerInstancePlugin):
++ def __init__(self, uzbl):
++ '''Export functions and connect handlers to events.'''
++ super(KeyCmd, self).__init__(uzbl)
++
++ self.keylet = Keylet()
++ self.modmaps = {}
++ self.ignores = {}
++
++ uzbl.connect('APPEND_KEYCMD', self.append_keycmd)
++ uzbl.connect('IGNORE_KEY', self.add_key_ignore)
++ uzbl.connect('INJECT_KEYCMD', self.inject_keycmd)
++ uzbl.connect('KEYCMD_BACKSPACE', self.keycmd_backspace)
++ uzbl.connect('KEYCMD_DELETE', self.keycmd_delete)
++ uzbl.connect('KEYCMD_EXEC_CURRENT', self.keycmd_exec_current)
++ uzbl.connect('KEYCMD_STRIP_WORD', self.keycmd_strip_word)
++ uzbl.connect('KEYCMD_CLEAR', self.clear_keycmd)
++ uzbl.connect('KEY_PRESS', self.key_press)
++ uzbl.connect('KEY_RELEASE', self.key_release)
++ uzbl.connect('MOD_PRESS', self.key_press)
++ uzbl.connect('MOD_RELEASE', self.key_release)
++ uzbl.connect('MODMAP', self.modmap_parse)
++ uzbl.connect('SET_CURSOR_POS', self.set_cursor_pos)
++ uzbl.connect('SET_KEYCMD', self.set_keycmd)
++
++ 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 list(self.ignores.items()):
++ if match(key):
++ return True
++
++ return False
++
++ def add_modmap(self, 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 = self.modmaps
++
++ modmaps[key.strip('<>')] = map.strip('<>')
++ self.uzbl.event("NEW_MODMAP", key, map)
++
++ def modmap_parse(self, map):
++ '''Parse a modmap definiton.'''
++
++ split = splitquoted(map)
++
++ if not split or len(split) > 2:
++ raise Exception('Invalid modmap arugments: %r' % map)
++
++ self.add_modmap(*split)
++
++ def add_key_ignore(self, glob):
++ '''Add an ignore definition.
++
++ Examples:
++ set ignore_key = request IGNORE_KEY
++ @ignore_key <Shift>
++ @ignore_key <ISO_*>
++ ...
++ '''
++
++ assert len(glob) > 1
++ ignores = self.ignores
++
++ glob = "<%s>" % glob.strip("<> ")
++ restr = glob.replace('*', '[^\s]*')
++ match = re.compile(restr).match
++
++ ignores[glob] = match
++ self.uzbl.event('NEW_KEY_IGNORE', glob)
++
++ def clear_keycmd(self, *args):
++ '''Clear the keycmd for this uzbl instance.'''
++
++ self.keylet.clear_keycmd()
++ config = Config[self.uzbl]
++ del config['keycmd']
++ self.uzbl.event('KEYCMD_CLEARED')
++
++ def clear_modcmd(self):
++ '''Clear the modcmd for this uzbl instance.'''
++
++ self.keylet.clear_modcmd()
++
++ config = Config[self.uzbl]
++ del config['modcmd']
++ self.uzbl.event('MODCMD_CLEARED')
++
++ def clear_current(self):
++ '''Clear the modcmd if is_modcmd else clear keycmd.'''
++
++ if self.keylet.is_modcmd:
++ self.clear_modcmd()
++
++ else:
++ self.clear_keycmd()
++
++ def update_event(self, modstate, k, execute=True):
++ '''Raise keycmd & modcmd update events.'''
++
++ keycmd, modcmd = k.get_keycmd(), ''.join(modstate) + k.get_modcmd()
++
++ if k.is_modcmd:
++ self.logger.debug('modcmd_update, %s', modcmd)
++ self.uzbl.event('MODCMD_UPDATE', modstate, k)
++
++ else:
++ self.logger.debug('keycmd_update, %s', keycmd)
++ self.uzbl.event('KEYCMD_UPDATE', modstate, k)
++
++ config = Config[self.uzbl]
++ if config.get('modcmd_updates', '1') == '1':
++ new_modcmd = ''.join(modstate) + k.get_modcmd()
++ if not new_modcmd or not k.is_modcmd:
++ if 'modcmd' in config:
++ del config['modcmd']
++
++ elif new_modcmd == modcmd:
++ config['modcmd'] = MODCMD_FORMAT % uzbl_escape(modcmd)
++
++ if config.get('keycmd_events', '1') != '1':
++ return
++
++ new_keycmd = k.get_keycmd()
++ if not new_keycmd:
++ del config['keycmd']
++
++ elif new_keycmd == keycmd:
++ # Generate the pango markup for the cursor in the keycmd.
++ config['keycmd'] = str(k.markup())
++
++ def parse_key_event(self, key):
++ ''' Build a set from the modstate part of the event, and pass all keys through modmap '''
++
++ modstate, key = splitquoted(key)
++ modstate = set(['<%s>' % self.modmap_key(k) for k in modstate.split('|') if k])
++
++ key = self.modmap_key(key)
++ return modstate, key
++
++ def key_press(self, 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 = self.keylet
++ config = Config[self.uzbl]
++ modstate, key = self.parse_key_event(key)
++ k.is_modcmd = any(not self.key_ignored(m) for m in modstate)
++
++ self.logger.debug('key press modstate=%s', modstate)
++ if key.lower() == 'space' and not k.is_modcmd and k.keycmd:
++ k.insert_keycmd(' ')
++
++ elif not k.is_modcmd and len(key) == 1:
++ if config.get('keycmd_events', '1') != '1':
++ # TODO, make a note on what's going on here
++ k.keycmd = ''
++ k.cursor = 0
++ del config['keycmd']
++ return
++
++ k.insert_keycmd(key)
++
++ elif len(key) == 1:
++ k.modcmd += key
++
++ else:
++ if not self.key_ignored('<%s>' % key):
++ modstate.add('<%s>' % key)
++ k.is_modcmd = True
++
++ self.update_event(modstate, k)
++
++ def key_release(self, 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 = self.keylet
++ modstate, key = self.parse_key_event(key)
++
++ if len(key) > 1:
++ if k.is_modcmd:
++ self.uzbl.event('MODCMD_EXEC', modstate, k)
++
++ self.clear_modcmd()
++
++ def set_keycmd(self, keycmd):
++ '''Allow setting of the keycmd externally.'''
++
++ self.keylet.set_keycmd(keycmd)
++ self.update_event(set(), self.keylet, False)
++
++ def inject_keycmd(self, keycmd):
++ '''Allow injecting of a string into the keycmd at the cursor position.'''
++
++ self.keylet.insert_keycmd(keycmd)
++ self.update_event(set(), self.keylet, False)
++
++ def append_keycmd(self, keycmd):
++ '''Allow appening of a string to the keycmd.'''
++
++ self.keylet.append_keycmd(keycmd)
++ self.update_event(set(), self.keylet, False)
++
++ def keycmd_strip_word(self, args):
++ ''' Removes the last word from the keycmd, similar to readline ^W '''
++
++ args = splitquoted(args)
++ assert len(args) <= 1
++ self.logger.debug('STRIPWORD %r %r', args, self.keylet)
++ if self.keylet.strip_word(*args):
++ self.update_event(set(), self.keylet, False)
++
++ def keycmd_backspace(self, *args):
++ '''Removes the character at the cursor position in the keycmd.'''
++
++ if self.keylet.backspace():
++ self.update_event(set(), self.keylet, False)
++
++ def keycmd_delete(self, *args):
++ '''Removes the character after the cursor position in the keycmd.'''
++
++ if self.keylet.delete():
++ self.update_event(set(), self.keylet, False)
++
++ def keycmd_exec_current(self, *args):
++ '''Raise a KEYCMD_EXEC with the current keylet and then clear the
++ keycmd.'''
++
++ self.uzbl.event('KEYCMD_EXEC', set(), self.keylet)
++ self.clear_keycmd()
++
++ def set_cursor_pos(self, args):
++ '''Allow setting of the cursor position externally. Supports negative
++ indexing and relative stepping with '+' and '-'.'''
++
++ args = splitquoted(args)
++ assert len(args) == 1
++
++ self.keylet.set_cursor_pos(args[0])
++ self.update_event(set(), self.keylet, False)
++
++# vi: set et ts=4:
++
+diff --git a/uzbl/plugins/mode.py b/uzbl/plugins/mode.py
+new file mode 100644
+index 0000000..6eaf009
+--- /dev/null
++++ b/uzbl/plugins/mode.py
+@@ -0,0 +1,69 @@
++from collections import defaultdict
++
++from .on_set import OnSetPlugin
++from .config import Config
++from uzbl.arguments import splitquoted, is_quoted
++from uzbl.ext import PerInstancePlugin
++
++
++class ModePlugin(PerInstancePlugin):
++ def __init__(self, uzbl):
++ super(ModePlugin, self).__init__(uzbl)
++ self.mode_config = defaultdict(dict)
++ uzbl.connect('MODE_CONFIG', self.parse_mode_config)
++ uzbl.connect('MODE_CONFIRM', self.confirm_change)
++ OnSetPlugin[uzbl].on_set('mode', self.mode_updated, False)
++ OnSetPlugin[uzbl].on_set('default_mode', self.default_mode_updated, False)
++
++ def cleanup(self):
++ self.mode_config.clear()
++
++ def parse_mode_config(self, args):
++ '''Parse `MODE_CONFIG <mode> <var> = <value>` event and update config
++ if the `<mode>` is the current mode.'''
++
++ args = splitquoted(args)
++ assert len(args) >= 3, 'missing mode config args %r' % args
++ mode = args[0]
++ key = args[1]
++ assert args[2] == '=', 'invalid mode config set syntax'
++
++ # Use the rest of the line verbatim as the value unless it's a
++ # single properly quoted string
++ if len(args) == 4 and is_quoted(args.raw(3)):
++ value = args[3]
++ else:
++ value = args.raw(3).strip()
++
++ self.logger.debug('value %r', value)
++
++ self.mode_config[mode][key] = value
++ config = Config[self.uzbl]
++ if config.get('mode', None) == mode:
++ config[key] = value
++
++ def default_mode_updated(self, var, mode):
++ config = Config[self.uzbl]
++ if mode and not config.get('mode', None):
++ self.logger.debug('setting mode to default %r' % mode)
++ config['mode'] = mode
++
++ def mode_updated(self, var, mode):
++ config = Config[self.uzbl]
++ if not mode:
++ mode = config.get('default_mode', 'command')
++ self.logger.debug('setting mode to default %r' % mode)
++ config['mode'] = mode
++ return
++
++ # Load mode config
++ mode_config = self.mode_config.get(mode, None)
++ if mode_config:
++ config.update(mode_config)
++
++ self.uzbl.send('event MODE_CONFIRM %s' % mode)
++
++ def confirm_change(self, mode):
++ config = Config[self.uzbl]
++ if mode and config.get('mode', None) == mode:
++ self.uzbl.event('MODE_CHANGED', mode)
+diff --git a/uzbl/plugins/on_event.py b/uzbl/plugins/on_event.py
+new file mode 100644
+index 0000000..cf33799
+--- /dev/null
++++ b/uzbl/plugins/on_event.py
+@@ -0,0 +1,106 @@
++'''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 re
++import fnmatch
++from functools import partial
++
++from uzbl.arguments import splitquoted
++from .cmd_expand import cmd_expand
++from uzbl.ext import PerInstancePlugin
++
++def match_args(pattern, args):
++ if len(pattern) > len(args):
++ return False
++ for p, a in zip(pattern, args):
++ if not fnmatch.fnmatch(a, p):
++ return False
++ return True
++
++
++class OnEventPlugin(PerInstancePlugin):
++
++ def __init__(self, uzbl):
++ '''Export functions and connect handlers to events.'''
++ super(OnEventPlugin, self).__init__(uzbl)
++
++ self.events = {}
++
++ uzbl.connect('ON_EVENT', self.parse_on_event)
++
++ def event_handler(self, *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], str):
++ args = splitquoted(args[0])
++
++ event = kargs['on_event']
++ if event not in self.events:
++ return
++
++ commands = self.events[event]
++ for cmd, pattern in list(commands.items()):
++ if not pattern or match_args(pattern, args):
++ cmd = cmd_expand(cmd, args)
++ self.uzbl.send(cmd)
++
++ def on_event(self, event, pattern, cmd):
++ '''Add a new event to watch and respond to.'''
++
++ event = event.upper()
++ self.logger.debug('new event handler %r %r %r', event, pattern, cmd)
++ if event not in self.events:
++ self.uzbl.connect(event,
++ partial(self.event_handler, on_event=event))
++ self.events[event] = {}
++
++ cmds = self.events[event]
++ if cmd not in cmds:
++ cmds[cmd] = pattern
++
++ def parse_on_event(self, args):
++ '''Parse ON_EVENT events and pass them to the on_event function.
++
++ Syntax: "event ON_EVENT <EVENT_NAME> commands".'''
++
++ args = splitquoted(args)
++ assert args, 'missing on event arguments'
++
++ # split arguments into event name, optional argument pattern and command
++ event = args[0]
++ pattern = []
++ if args[1] == '[':
++ for i, arg in enumerate(args[2:]):
++ if arg == ']':
++ break
++ pattern.append(arg)
++ command = args.raw(3+i)
++ else:
++ command = args.raw(1)
++
++ assert event and command, 'missing on event command'
++ self.on_event(event, pattern, command)
++
++ def cleanup(self):
++ self.events.clear()
++ super(OnEventPlugin, self).cleanup()
++
++# vi: set et ts=4:
+diff --git a/uzbl/plugins/on_set.py b/uzbl/plugins/on_set.py
+new file mode 100644
+index 0000000..f6b9229
+--- /dev/null
++++ b/uzbl/plugins/on_set.py
+@@ -0,0 +1,85 @@
++from re import compile
++from functools import partial
++
++import uzbl.plugins.config
++from .cmd_expand import cmd_expand
++from uzbl.arguments import splitquoted
++from uzbl.ext import PerInstancePlugin
++import collections
++
++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
++
++
++class OnSetPlugin(PerInstancePlugin):
++
++ def __init__(self, uzbl):
++ super(OnSetPlugin, self).__init__(uzbl)
++ self.on_sets = {}
++ uzbl.connect('ON_SET', self.parse_on_set)
++ uzbl.connect('CONFIG_CHANGED', self.check_for_handlers)
++
++ def _exec_handlers(self, handlers, key, arg):
++ '''Execute the on_set handlers that matched the key.'''
++
++ for handler in handlers:
++ if isinstance(handler, collections.Callable):
++ handler(key, arg)
++ else:
++ self.uzbl.send(cmd_expand(handler, [key, arg]))
++
++ def check_for_handlers(self, key, arg):
++ '''Check for handlers for the current key.'''
++
++ for (matcher, handlers) in list(self.on_sets.values()):
++ if matcher(key):
++ self._exec_handlers(handlers, key, arg)
++
++ def on_set(self, glob, handler, prepend=True):
++ '''Add a new handler for a config key change.
++
++ Structure of the `self.on_sets` dict:
++ { glob : ( glob matcher function, handlers list ), .. }
++ '''
++
++ assert valid_glob(glob)
++
++ while '**' in glob:
++ glob = glob.replace('**', '*')
++
++ if isinstance(handler, collections.Callable):
++ orig_handler = handler
++ if prepend:
++ handler = partial(handler, self.uzbl)
++
++ else:
++ orig_handler = handler = str(handler)
++
++ if glob in self.on_sets:
++ (matcher, handlers) = self.on_sets[glob]
++ handlers.append(handler)
++
++ else:
++ matcher = make_matcher(glob)
++ self.on_sets[glob] = (matcher, [handler,])
++
++ self.logger.info('on set %r call %r' % (glob, orig_handler))
++
++
++ def parse_on_set(self, args):
++ '''Parse `ON_SET <glob> <command>` event then pass arguments to the
++ `on_set(..)` function.'''
++
++ args = splitquoted(args)
++ assert len(args) >= 2
++ glob = args[0]
++ command = args.raw(1)
++
++ assert glob and command and valid_glob(glob)
++ self.on_set(glob, command)
++
+diff --git a/uzbl/plugins/progress_bar.py b/uzbl/plugins/progress_bar.py
+new file mode 100644
+index 0000000..5eb10d3
+--- /dev/null
++++ b/uzbl/plugins/progress_bar.py
+@@ -0,0 +1,92 @@
++import re
++from .config import Config
++
++from uzbl.ext import PerInstancePlugin
++
++class ProgressBar(PerInstancePlugin):
++ splitfrmt = re.compile(r'(%[A-Z][^%]|%[^%])').split
++
++ def __init__(self, uzbl):
++ super(ProgressBar, self).__init__(uzbl)
++ uzbl.connect('LOAD_COMMIT', lambda uri: self.update_progress())
++ uzbl.connect('LOAD_PROGRESS', self.update_progress)
++ self.updates = 0
++
++ def update_progress(self, 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
++ '''
++
++ if progress is None:
++ self.updates = 0
++ progress = 100
++
++ else:
++ self.updates += 1
++ progress = int(progress)
++
++ # Get progress config vars.
++ config = Config[self.uzbl]
++ frmt = config.get('progress.format', '[%d>%p]%c')
++ width = int(config.get('progress.width', 8))
++ done_symbol = config.get('progress.done', '=')
++ pend = config.get('progress.pending', None)
++ pending_symbol = pend if pend else ' '
++
++ # Get spinner character
++ spinner = config.get('progress.spinner', '-\\|/')
++ index = 0 if progress == 100 else self.updates % len(spinner)
++ spinner = '\\\\' if spinner[index] == '\\' else spinner[index]
++
++ # get sprite character
++ sprites = config.get('progress.sprites', 'loading')
++ index = int(((progress/100.0)*len(sprites))+0.5)-1
++ sprite = '%r' % ('\\\\' if sprites[index] == '\\' else sprites[index])
++
++ # Inflate the done and pending bars to stop the progress bar
++ # jumping around.
++ if '%c' in frmt or '%i' in frmt:
++ count = frmt.count('%c') + frmt.count('%i')
++ width += (3-len(str(progress))) * count
++
++ if '%t' in frmt or '%o' in frmt:
++ count = frmt.count('%t') + frmt.count('%o')
++ width += (3-len(str(100-progress))) * count
++
++ done = int(((progress/100.0)*width)+0.5)
++ pending = width - done
++
++ # values to replace with
++ values = {
++ 'd': done_symbol * done,
++ 'p': pending_symbol * pending,
++ 'c': '%d%%' % progress,
++ 'i': '%d' % progress,
++ 't': '%d%%' % (100 - progress),
++ 'o': '%d' % (100 - progress),
++ 's': spinner,
++ 'r': sprite
++ }
++
++ frmt = ''.join([str(values[k[1:]]) if k.startswith('%') else k for
++ k in self.splitfrmt(frmt)])
++
++ if config.get('progress.output', None) != frmt:
++ config['progress.output'] = frmt
+--- uzbl-2012.05.14/Makefile~ 2013-12-08 16:55:13.000000000 +0100
++++ uzbl-2012.05.14/Makefile 2013-12-08 16:56:12.412142265 +0100
+@@ -189,7 +189,7 @@
+ install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core
+
+ install-event-manager: install-dirs
+- $(PYTHON) setup.py install --prefix=$(PREFIX) --install-scripts=$(INSTALLDIR)/bin $(PYINSTALL_EXTRA)
++ $(PYTHON) setup.py install --prefix=$(PREFIX) --root=$(DESTDIR) --install-scripts=$(PREFIX)/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