From 099006da4d6c9d498728aa7ec4ffe6727ce41ab1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Arkadiusz=20Mi=C5=9Bkiewicz?= Date: Sun, 8 Dec 2013 17:12:25 +0100 Subject: [PATCH] - push proper patch --- uzbl-build.patch | 11 + uzbl-git.patch | 11204 --------------------------------------------- 2 files changed, 11 insertions(+), 11204 deletions(-) create mode 100644 uzbl-build.patch delete mode 100644 uzbl-git.patch diff --git a/uzbl-build.patch b/uzbl-build.patch new file mode 100644 index 0000000..1aaabee --- /dev/null +++ b/uzbl-build.patch @@ -0,0 +1,11 @@ +--- uzbl-2013.12.08/Makefile~ 2013-12-08 16:48:28.000000000 +0100 ++++ uzbl-2013.12.08/Makefile 2013-12-08 17:07:53.206953656 +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 diff --git a/uzbl-git.patch b/uzbl-git.patch deleted file mode 100644 index 8d4610a..0000000 --- a/uzbl-git.patch +++ /dev/null @@ -1,11204 +0,0 @@ -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) - uzbl vim syntax & related files - Helmut Grohne (helmut) - move void **ptr to union, various fixes - Henri Kemppainen (DuClare) - many contributions, mostly old handler code -+ HÃ¥kan Jerning - uzbl-tabbed: autosave_session patch - Igor Bogomazov - mouse ptr events - Jake Probst - uzbl_tabbed: multiline tablist, new window opening patches - James Campos (aeosynth) - 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 ` - - Read contents of `` and interpret as a set of `uzbl` commands. --* `show_inspector` -- - Show the WebInspector -+* `inspector | node >` -+ - 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 ... | learn ... | autocorrect | guesses >` -+ - Control the spell checker. Requires webkitgtk >= 1.5.1. - * `add_cookie ` - - Adds a new cookie to the cookie jar - * `delete_cookie [ ]` - - 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 []` - - Starts a download using the given uri. A destination file path can be given - to specify where the download should be written to. -+* `auth ` -+ - Authenticate as `` with `` for the previously issued -+ challenge with the id ``. authenticating for a invalid id or one -+ expired one has no effect. -+* `snapshot ` -+ - 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 []` -+ - 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 [ []]` -+ - 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 [ [...]]` -+ - 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 - "x++". - * `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 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 --# Copyright (c) 2009, Dieter Plaetinck --# --# 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 . -- --''' -- --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'' % ', '.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'' % 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 '' % ', '.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 recv - - # 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_ = event GOTO_TAB %s -+@cbind = event MOVE_CURRENT_TAB_LEFT -+@cbind = event MOVE_CURRENT_TAB_RIGHT -+@cbind gm_ = 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>, <:'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 "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** -- 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_ -- 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 = '' % ', '.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 o_ = uri %s -- MODE_BIND insert,command = ... -- 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 = -- request BIND o_ = uri %s -- request BIND = ... -- 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 = " %s " --ITEM_FORMAT = "%s%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 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 = ` 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 ` 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%s%s" --MODCMD_FORMAT = " %s " -- -- --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 '' % ', '.join(l) -- -- --def add_modmap(uzbl, key, map): -- '''Add modmaps. -- -- Examples: -- set modmap = request MODMAP -- @modmap -- @modmap -- ... -- -- Then: -- @bind = -- @bind x = -- ... -- -- ''' -- -- 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 -- @ignore_key -- ... -- ''' -- -- 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 = ` event and update config if -- the `` 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 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 ` 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 -+ - 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 -+#else - #include -+#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 -+#else - #include -+#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 -+ -+/** -+ * 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 - #include - #include -+#ifdef USE_WEBKIT2 -+#include -+#else - #include -+#endif - #include - #include - -@@ -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 -+ - 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 -+#ifdef USE_WEBKIT2 -+#include -+#else - #include -+#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'') -+ -+ 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 '' % ', '.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 -+# Copyright (c) 2009, Dieter Plaetinck -+# -+# 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 . -+ -+''' -+ -+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>, <:'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 "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** -+ 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_ -+ 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 = '' % ', '.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 o_ = uri %s -+ MODE_BIND insert,command = ... -+ 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 = -+ request BIND o_ = uri %s -+ request BIND = ... -+ 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 = " %s " -+ ITEM_FORMAT = "%s%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 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 = ` 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 ` 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%s%s" -+MODCMD_FORMAT = " %s " -+ -+ -+# 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) -+ -+ >>> k.append_keycmd(' and egg') -+ >>> print(k) -+ -+ >>> 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) -+ -+ >>> 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) -+ -+ >>> 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) -+ -+ >>> k.strip_word() -+ 'and' -+ >>> print(k) -+ -+ ''' -+ 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]@@[ ]@@[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 '' % ', '.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 -+ @modmap -+ ... -+ -+ Then: -+ @bind = -+ @bind x = -+ ... -+ -+ ''' -+ -+ 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 -+ @ignore_key -+ ... -+ ''' -+ -+ 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 = ` event and update config -+ if the `` 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 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 ` 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 -- 2.43.0