-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