]> git.pld-linux.org Git - packages/uzbl.git/blob - uzbl-git.patch
- up to 2013.12.08 git snap (which doesn't hang on some analytics js)
[packages/uzbl.git] / uzbl-git.patch
1 diff --git a/.gitignore b/.gitignore
2 index 8c08dc0..092909f 100644
3 --- a/.gitignore
4 +++ b/.gitignore
5 @@ -1,4 +1,5 @@
6  uzbl-core
7 +local.mk
8  *.o
9  *.lo
10  *.pyc
11 @@ -6,3 +7,4 @@ uzbl-core
12  *~
13  tags
14  sandbox
15 +/build
16 diff --git a/AUTHORS b/AUTHORS
17 index b3ce2e2..22d3c9b 100644
18 --- a/AUTHORS
19 +++ b/AUTHORS
20 @@ -46,6 +46,7 @@ In alphabetical order:
21      Gregor Uhlenheuer (kongo2002) <kongo2002@googlemail.com> - uzbl vim syntax & related files
22      Helmut Grohne (helmut) - move void **ptr to union, various fixes
23      Henri Kemppainen (DuClare) <email is akarinotengoku AT THE DOMAIN OF gmail.com> - many contributions, mostly old handler code
24 +    HÃ¥kan Jerning - uzbl-tabbed: autosave_session patch
25      Igor Bogomazov - mouse ptr events
26      Jake Probst <jake.probst@gmail.com> - uzbl_tabbed: multiline tablist, new window opening patches
27      James Campos (aeosynth) <james.r.campos@gmail.com> - Re-orderable gtk notebook tabs in uzbl-tabbed
28 diff --git a/Makefile b/Makefile
29 index a11fc8d..ba74e57 100644
30 --- a/Makefile
31 +++ b/Makefile
32 @@ -1,25 +1,53 @@
33 +# Create a local.mk file to store default local settings to override the
34 +# defaults below.
35 +include $(wildcard local.mk)
36 +
37  # packagers, set DESTDIR to your "package directory" and PREFIX to the prefix you want to have on the end-user system
38  # end-users who build from source: don't care about DESTDIR, update PREFIX if you want to
39  # RUN_PREFIX : what the prefix is when the software is run. usually the same as PREFIX
40 -PREFIX?=/usr/local
41 -INSTALLDIR?=$(DESTDIR)$(PREFIX)
42 -DOCDIR?=$(INSTALLDIR)/share/uzbl/docs
43 -RUN_PREFIX?=$(PREFIX)
44 +PREFIX     ?= /usr/local
45 +INSTALLDIR ?= $(DESTDIR)$(PREFIX)
46 +DOCDIR     ?= $(INSTALLDIR)/share/uzbl/docs
47 +RUN_PREFIX ?= $(PREFIX)
48 +
49 +ENABLE_WEBKIT2 ?= no
50 +ENABLE_GTK3    ?= auto
51 +
52 +PYTHON=python3
53 +PYTHONV=$(shell $(PYTHON) --version | sed -n /[0-9].[0-9]/p)
54 +COVERAGE=$(shell which coverage)
55 +
56 +# --- configuration ends here ---
57 +
58 +ifeq ($(ENABLE_WEBKIT2),auto)
59 +ENABLE_WEBKIT2 := $(shell pkg-config --exists webkit2gtk-3.0 && echo yes)
60 +endif
61  
62 -# use GTK3-based webkit when it is available
63 -USE_GTK3 = $(shell pkg-config --exists gtk+-3.0 webkitgtk-3.0 && echo 1)
64 +ifeq ($(ENABLE_GTK3),auto)
65 +ENABLE_GTK3 := $(shell pkg-config --exists gtk+-3.0 && echo yes)
66 +endif
67  
68 -ifeq ($(USE_GTK3),1)
69 -       REQ_PKGS += gtk+-3.0 webkitgtk-3.0 javascriptcoregtk-3.0
70 -       CPPFLAGS = -DG_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
71 +ifeq ($(ENABLE_WEBKIT2),yes)
72 +REQ_PKGS += 'webkit2gtk-3.0 >= 1.2.4' javascriptcoregtk-3.0
73 +CPPFLAGS += -DUSE_WEBKIT2
74 +# WebKit2 requires GTK3
75 +ENABLE_GTK3    := yes
76 +else
77 +ifeq ($(ENABLE_GTK3),yes)
78 +REQ_PKGS += 'webkitgtk-3.0 >= 1.2.4' javascriptcoregtk-3.0
79  else
80 -       REQ_PKGS += gtk+-2.0 webkit-1.0 javascriptcoregtk-1.0
81 -       CPPFLAGS =
82 +REQ_PKGS += 'webkit-1.0 >= 1.2.4' javascriptcoregtk-1.0
83 +endif
84  endif
85  
86 -# --- configuration ends here ---
87 +ifeq ($(ENABLE_GTK3),yes)
88 +REQ_PKGS += gtk+-3.0
89 +CPPFLAGS += -DG_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
90 +else
91 +REQ_PKGS += gtk+-2.0
92 +endif
93  
94 -REQ_PKGS += libsoup-2.4 gthread-2.0 glib-2.0
95 +REQ_PKGS += 'libsoup-2.4 >= 2.30' gthread-2.0 glib-2.0
96  
97  ARCH:=$(shell uname -m)
98  
99 @@ -33,10 +61,11 @@ LDLIBS:=$(shell pkg-config --libs $(REQ_PKGS) x11)
100  
101  CFLAGS += -std=c99 $(PKG_CFLAGS) -ggdb -W -Wall -Wextra -pedantic -pthread
102  
103 -SRC = $(wildcard src/*.c)
104 +SRC  = $(wildcard src/*.c)
105  HEAD = $(wildcard src/*.h)
106  OBJ  = $(foreach obj, $(SRC:.c=.o),  $(notdir $(obj)))
107  LOBJ = $(foreach obj, $(SRC:.c=.lo), $(notdir $(obj)))
108 +PY = $(wildcard uzbl/*.py uzbl/plugins/*.py)
109  
110  all: uzbl-browser
111  
112 @@ -46,7 +75,13 @@ ${OBJ}: ${HEAD}
113  
114  uzbl-core: ${OBJ}
115  
116 -uzbl-browser: uzbl-core
117 +uzbl-browser: uzbl-core uzbl-event-manager
118 +
119 +build: ${PY}
120 +       $(PYTHON) setup.py build
121 +
122 +.PHONY: uzbl-event-manager
123 +uzbl-event-manager: build
124  
125  # the 'tests' target can never be up to date
126  .PHONY: tests
127 @@ -61,38 +96,43 @@ tests: ${LOBJ} force
128         $(CC) -shared -Wl ${LOBJ} -o ./tests/libuzbl-core.so
129         cd ./tests/; $(MAKE)
130  
131 +test-event-manager: force
132 +       ${PYTHON} -m unittest discover tests/event-manager -v
133 +
134 +coverage-event-manager: force
135 +       ${PYTHON} ${COVERAGE} erase
136 +       ${PYTHON} ${COVERAGE} run -m unittest discover tests/event-manager
137 +       ${PYTHON} ${COVERAGE} html ${PY}
138 +       # Hmm, I wonder what a good default browser would be
139 +       uzbl-browser htmlcov/index.html
140 +
141  test-uzbl-core: uzbl-core
142         ./uzbl-core --uri http://www.uzbl.org --verbose
143  
144  test-uzbl-browser: uzbl-browser
145         ./bin/uzbl-browser --uri http://www.uzbl.org --verbose
146  
147 -test-uzbl-core-sandbox: uzbl-core
148 -       make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core
149 -       make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data
150 -       cp -np ./misc/env.sh ./sandbox/env.sh
151 +test-uzbl-core-sandbox: sandbox uzbl-core sandbox-install-uzbl-core sandbox-install-example-data
152         . ./sandbox/env.sh && uzbl-core --uri http://www.uzbl.org --verbose
153         make DESTDIR=./sandbox uninstall
154         rm -rf ./sandbox/usr
155  
156 -test-uzbl-browser-sandbox: uzbl-browser
157 -       make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-browser
158 -       make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data
159 -       cp -np ./misc/env.sh ./sandbox/env.sh
160 -       -. ./sandbox/env.sh && uzbl-event-manager restart -avv
161 +test-uzbl-browser-sandbox: sandbox uzbl-browser sandbox-install-uzbl-browser sandbox-install-example-data
162 +       . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` restart -navv &
163         . ./sandbox/env.sh && uzbl-browser --uri http://www.uzbl.org --verbose
164 -       . ./sandbox/env.sh && uzbl-event-manager stop -ivv
165 +       . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` stop -vv -o /dev/null
166         make DESTDIR=./sandbox uninstall
167         rm -rf ./sandbox/usr
168  
169 -test-uzbl-tabbed-sandbox: uzbl-browser
170 -       make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-browser
171 -       make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-tabbed
172 -       make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data
173 -       cp -np ./misc/env.sh ./sandbox/env.sh
174 -       -. ./sandbox/env.sh && uzbl-event-manager restart -avv
175 +test-uzbl-tabbed-sandbox: sandbox uzbl-browser sandbox-install-uzbl-browser sandbox-install-uzbl-tabbed sandbox-install-example-data
176 +       . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` restart -avv
177         . ./sandbox/env.sh && uzbl-tabbed
178 -       . ./sandbox/env.sh && uzbl-event-manager stop -ivv
179 +       . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` stop -avv
180 +       make DESTDIR=./sandbox uninstall
181 +       rm -rf ./sandbox/usr
182 +
183 +test-uzbl-event-manager-sandbox: sandbox uzbl-browser sandbox-install-uzbl-browser sandbox-install-example-data
184 +       . ./sandbox/env.sh && ${PYTHON} -S `which uzbl-event-manager` restart -navv
185         make DESTDIR=./sandbox uninstall
186         rm -rf ./sandbox/usr
187  
188 @@ -102,18 +142,44 @@ clean:
189         find ./examples/ -name "*.pyc" -delete
190         cd ./tests/; $(MAKE) clean
191         rm -rf ./sandbox/
192 +       $(PYTHON) setup.py clean
193  
194  strip:
195         @echo Stripping binary
196         @strip uzbl-core
197         @echo ... done.
198  
199 +SANDBOXOPTS=\
200 +       DESTDIR=./sandbox\
201 +       RUN_PREFIX=`pwd`/sandbox/usr/local\
202 +       PYINSTALL_EXTRA='--prefix=./sandbox/usr/local --install-scripts=./sandbox/usr/local/bin'
203 +
204 +sandbox: misc/env.sh
205 +       mkdir -p sandbox/${PREFIX}/lib64
206 +       cp -p misc/env.sh sandbox/env.sh
207 +       test -e sandbox/${PREFIX}/lib || ln -s lib64 sandbox/${PREFIX}/lib
208 +
209 +sandbox-install-uzbl-browser:
210 +       make ${SANDBOXOPTS} install-uzbl-browser
211 +
212 +sandbox-install-uzbl-tabbed:
213 +       make ${SANDBOXOPTS} install-uzbl-tabbed
214 +
215 +sandbox-install-uzbl-core:
216 +       make ${SANDBOXOPTS} install-uzbl-core
217 +
218 +sandbox-install-event-manager:
219 +       make ${SANDBOXOPTS} install-event-manager
220 +
221 +sandbox-install-example-data:
222 +       make ${SANDBOXOPTS} install-example-data
223 +
224  install: install-uzbl-core install-uzbl-browser install-uzbl-tabbed
225  
226  install-dirs:
227         [ -d "$(INSTALLDIR)/bin" ] || install -d -m755 $(INSTALLDIR)/bin
228  
229 -install-uzbl-core: all install-dirs
230 +install-uzbl-core: uzbl-core install-dirs
231         install -d $(INSTALLDIR)/share/uzbl/
232         install -d $(DOCDIR)
233         install -m644 docs/* $(DOCDIR)/
234 @@ -123,8 +189,7 @@ install-uzbl-core: all install-dirs
235         install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core
236  
237  install-event-manager: install-dirs
238 -       sed "s#^PREFIX = .*#PREFIX = '$(RUN_PREFIX)'#" < bin/uzbl-event-manager > $(INSTALLDIR)/bin/uzbl-event-manager
239 -       chmod 755 $(INSTALLDIR)/bin/uzbl-event-manager
240 +       $(PYTHON) setup.py install --prefix=$(PREFIX) --install-scripts=$(INSTALLDIR)/bin $(PYINSTALL_EXTRA)
241  
242  install-uzbl-browser: install-dirs install-uzbl-core install-event-manager
243         sed 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' < bin/uzbl-browser > $(INSTALLDIR)/bin/uzbl-browser
244 diff --git a/README b/README
245 index b124fb4..5241aba 100644
246 --- a/README
247 +++ b/README
248 @@ -252,18 +252,42 @@ The following commands are recognized:
249    - Open the print dialog.
250  * `include <file>`
251    - Read contents of `<file>` and interpret as a set of `uzbl` commands.
252 -* `show_inspector`
253 -  - Show the WebInspector
254 +* `inspector <show | hide | coord <x> <y> | node <node-spec>>`
255 +  - Control the inspector. The `coord` command coordinates are relative to the
256 +    viewport, not the page. The `node` subcommand requires webkitgtk >=
257 +    1.3.17.
258 +* `spell_checker <ignore <word>... | learn <word>... | autocorrect <word> | guesses <word>>`
259 +  - Control the spell checker. Requires webkitgtk >= 1.5.1.
260  * `add_cookie <domain> <path> <name> <value> <scheme> <expires>`
261    - Adds a new cookie to the cookie jar
262  * `delete_cookie <domain> <path> <name> <value> [<scheme> <expires>]`
263    - Deletes a matching cookie from the cookie jar. scheme and expire time
264 -         is currently not considered when matching.
265 +    is currently not considered when matching.
266  * `clear_cookies`
267    - Clears all cookies from the cookie jar
268  * `download <uri> [<destination path>]`
269    - Starts a download using the given uri. A destination file path can be given
270      to specify where the download should be written to.
271 +* `auth <uniqueid> <username> <password>`
272 +  - Authenticate as `<username>` with `<password>` for the previously issued
273 +    challenge with the id `<uniqueid>`. authenticating for a invalid id or one
274 +    expired one has no effect.
275 +* `snapshot <path>`
276 +  - Saves an image of the visible page as a PNG to the given path. Only available
277 +    with webkitgtk >= 1.9.6. This is not in webkit2gtk.
278 +* `load <string> <uri> [<baseuri>]`
279 +  - Load a string as text/html with the given uri. If given, all links will be
280 +    assumed relative to baseuri. Requires webkit2gtk >= 1.9.90.
281 +* `save [<format> [<path>]]`
282 +  - Saves the current page to a file in a given format (currently only "mhtml"
283 +    is supported). Requires webkit2gtk >= 1.9.90.
284 +* `remove_all_db`
285 +  - Removes all of the web databases from the current database directory path.
286 +* `plugin_refresh`
287 +  - Refreshes the plugin database. Requires webkitgtk >= 1.3.8.
288 +* `plugin_toggle [<plugin name> [<plugin name>...]]`
289 +  - Toggles whether the plugins named as arguments are enabled. No arguments is
290 +    interpreted as "all plugins". Requires webkitgtk >= 1.3.8.
291  
292  ### VARIABLES AND CONSTANTS
293  
294 @@ -283,8 +307,10 @@ file).
295  
296  * `uri`: The URI of the current page. (callback: load the uri)
297  * `verbose`: Controls the verbosity printed to `stdout`.
298 +* `inject_text`: Inject an text string, navigating to the URI "about:blank" and
299 +  rendering the text string given. Only available in webkit2gtk.
300  * `inject_html`: Inject an HTML string, navigating to the URI "about:blank" and
301 -  rendering the HTML sting given.
302 +  rendering the HTML string given.
303  * `geometry`: Geometry and position of the Uzbl window. Format is
304    "<width>x<height>+<x-offset>+<y-offset>".
305  * `keycmd`: Holds the input buffer (callback: update input buffer).
306 @@ -333,11 +359,16 @@ file).
307  * `useragent`: The User-Agent to send to the browser, expands variables in its
308    definition.
309  * `accept_languages`: The Accept-Language header to send with HTTP requests.
310 +* `transparent`: If set to 1, the background of the view will be transparent
311 +  (default 0).
312 +* `view_mode`: The view mode for webkit. One of: "windowed", "floating",
313 +  "fullscreen", "maximized", or "minimized". Requires webkitgtk >= 1.3.4.
314  * `zoom_level`: The factor by which elements in the page are scaled with respect
315    to their original size. Setting this will resize the currently displayed page.
316  * `zoom_type`: Whether to use "full-content" zoom (defaults to true). With
317    full-content zoom on, all page content, not just text, is zoomed. When
318 -  full-content zoom is off, only the text of a page is zoomed.
319 +  full-content zoom is off, only the text of a page is zoomed. This is
320 +  unavailable with webkit2gtk. Use `zoom_text_only` instead.
321  * `font_size`: The default font size.
322  * `default_font_family`: The default font family used to display text.
323  * `monospace_font_family`: The default font family used to display monospace
324 @@ -349,7 +380,6 @@ file).
325  * `fantasy_font_family`: The default Fantasy font family used to display text.
326  * `monospace_size`: The default size of monospaced font (default 1).
327  * `minimum_font_size`: The minimum font size used to display text (default 1).
328 -* `enable_pagecache`: Enable the webkit pagecache (it caches rendered pages for a speedup when you go back or forward in history) (default 0).
329  * `enable_plugins`: Disable embedded plugin objects (default 0).
330  * `enable_scripts`: Disable embedded scripting languages (default 0).
331  * `autoload_images`: Automatically load images (default 1).
332 @@ -360,25 +390,109 @@ file).
333    `en_CA` or `pt_BR`) to be used for spell checking, separated by commas.
334    Defaults to the value returned by `gtk_get_default_language`.
335  * `enable_private`: Whether to enable private browsing mode (default 0).
336 +* `cookie_policy`: If set to 0, all cookies are accepted, if set to 1, all
337 +  cookies are rejected, and 2 rejects third party cookies (default 0).
338  * `print_backgrounds`: Print background images? (default 0).
339  * `stylesheet_uri`: Use this to override the pagelayout with a custom
340    stylesheet.
341  * `resizable_text_areas`: Whether text areas can be resized (default 0).
342  * `default_encoding`: The default text encoding (default "iso-8859-1").
343 -* `current_encoding`: This can be set to force a text encoding.
344 +* `custom_encoding`: This can be set to force a text encoding. (Used to be
345 +  `current_encoding` which is now read-only).
346  * `enforce_96_dpi`: Enforce a resolution of 96 DPI (default 1).
347 +* `editable`: Whether the page can be edited or not (default 0).
348  * `caret_browsing`: Whether the caret is enabled in the text portion of pages
349    (default 0).
350  * `enable_cross_file_access`: Whether a page loaded from a `file://` URI can
351    access the contents of other `file://` URIs. (default 0).
352  * `follow_hint_keys`: keys for keyboard-based navigation and link
353 -   highlighting
354 +  highlighting
355  * `handle_multi_click`: If set to 1, event handlers attached to `2Button*`
356 -   and `3Button*` bindings will only be used instead of the default actions in
357 -   WebKit (default 0).
358 +  and `3Button*` bindings will only be used instead of the default actions in
359 +  WebKit (default 0).
360  * `ssl_ca_file`: File that contains CA certificates.
361  * `ssl_verify`: If set to 1, uzbl won't connect to "https" url unless it can
362     validate certificate presented by remote server against `ssl_ca_file`.
363 +* `enable_builtin_auth`: Enable WebKits builtin authentication handler
364 +* `enable_java_applet`: If set to 1, support for Java <applet> tags will be
365 +  enabled (default 1).
366 +* `enable_database`: If set to 1, support for HTML5 client-side SQL databases
367 +  will be enabled (default 1).
368 +* `enable_local_storage`: If set to 1, websites will be able to store data
369 +  locally (default 1).
370 +* `enable_pagecache`: If set to 1, uzbl will store previously visited pages for
371 +  faster access (the cache is local to each uzbl instance) (default 0).
372 +* `enable_offline_app_cache`: If set to 1, web applications may be cached locally
373 +  for offline use (default 1).
374 +* `enable_universal_file_access`: If set to 1, allow `file://` URIs to access
375 +  all pages (default 0).
376 +* `enable_hyperlink_auditing`: If set to 1, the `ping` attribute on anchors will
377 +  be supported (default 0).
378 +* `zoom_step`: The change in the zoon level when zooming (default 0.1).
379 +* `auto_resize_window`: If set to 1, allow web pages to change window dimensions
380 +  (default 0).
381 +* `enable_spatial_navigation`: If set to 1, the arrow keys in `Ins` mode will
382 +  navigate between form elements (default 0).
383 +* `editing_behavior`: When set to 0, emulate Mac behavior in text fields, 1
384 +  for Windows behavior, and 2 for *nix behavior (the default).
385 +* `enable_tab_cycle`: If set to 1, the `Tab` key cycles between elements on
386 +  the page (default 1).
387 +* `default_context_menu`: If set to 0, do not cause context menus to appear when
388 +  right clicking (default 1).
389 +* `enable_site_workarounds`: If set to 1, enable filters to help unbreak
390 +  certain websites (default 0).
391 +* `javascript_clipboard`: If set to 1, JavaScript may access the clipboard
392 +  (default 0). Requires webkitgtk >= 1.3.0.
393 +* `javascript_dom_paste`: If set to 1, JavaScript will able to paste from the
394 +  clipboard (default 0).
395 +* `enable_frame_flattening`: If set to 1, flatten all frames into a single
396 +  page to become one scrollable page (default 0). Requires webkitgtk >= 1.3.5.
397 +* `enable_fullscreen`: If set to 1, Mozilla-style fullscreening will be
398 +  available (default 0). Requires webkitgtk >= 1.3.8
399 +* `enable_dns_prefetch`: If set to 1, domain names will be prefetched
400 +  (default 1). Private browsing does *not* affect this value. Requires
401 +  webkitgtk >= 1.3.13.
402 +* `display_insecure_content`: If set to 1, non-HTTPS content will be displayed
403 +  on HTTPS pages (default 1). Requires webkitgtk >= 1.11.13.
404 +* `run_insecure_content`: If set to 1, non-HTTPS content will be allowed to run
405 +  on HTTPS pages (default 1). Requires webkitgtk >= 1.11.13.
406 +* `maintain_history`: If set to 1, the back/forward list will be kept. (default
407 +  1).
408 +* `enable_webgl`: If set to 1, WebGL support will be enabled (default 0).
409 +  Requires webkitgtk >= 1.3.14.
410 +* `local_storage_path`: Where to store local databases (default
411 +  $XDG_DATA_HOME/webkit/databases/). Requires webkit >= 1.5.2.
412 +* `enable_webaudio`: If set to 1, allows JavaScript to generate audio
413 +  directly (default 0). Requires webkit >= 1.7.5.
414 +* `enable_3d_acceleration`: If set to 1, the GPU will be used to render
415 +  animations and 3D CSS transformations. Requires webkitgtk >= 1.7.90.
416 +* `zoom_text_only`: If set to 1, only text will be zoomed (default 0). Requires
417 +  webkit2gtk >= 1.7.91.
418 +* `enable_smooth_scrolling`: If set to 1, scrolling the page will be smoothed
419 +  (default 0). Requires webkitgtk >= 1.9.0.
420 +* `enable_inline_media`: If set to 1, inline playback of media is allowed,
421 +  otherwise, only full-screen playback is allowed (default 1). Requires
422 +  webkitgtk >= 1.9.3.
423 +* `require_click_to_play`: If set to 1, playback of media requires user
424 +  interaction before playing, otherwise, media will be allowed to autoplay
425 +  (default 0). Requires webkitgtk >= 1.9.3.
426 +* `enable_css_shaders`: If set to 1, CSS shaders will be enabled (default 0).
427 +  Requires webkitgtk >= 1.11.1.
428 +* `enable_media_stream`: If set to 1, web pages will be able to access the
429 +  local video and audio input devices (default 0). Requires webkitgtk >= 1.11.1.
430 +* `cache_model`: The cache model of webkit. Valid values:
431 +  "document_viewer" (no caching; low memory; usage: single local file),
432 +  "web_browser" (heavy caching; faster; usage: general browsing),
433 +  "document_browser" (moderate caching; usage: series of local files)
434 +  (default "web_browser").
435 +* `app_cache_size`: The maximum size of the application cache (in bytes)
436 +  (default UINT_MAX (no quota)). Changing the variable clears the cache.
437 +  Requires webkitgtk >= 1.3.13.
438 +* `web_database_directory`: The directory where web databases are stored.
439 +  (default is under $XDG_DATA_HOME).
440 +* `web_database_quota`: The default quota for web databases. (default 5MB).
441 +* `profile_js`: Sets whether to profile JavaScript code.
442 +* `profile_timeline`: Sets whether to profile the timeline.
443  
444  #### Constants (not dumpable or writeable)
445  
446 @@ -396,6 +510,13 @@ file).
447    - overridable with cmdline arg
448    - in GtkSocket mode, this is a random number to prevent name clashes
449  * `PID`: The process ID of this Uzbl instance.
450 +* `current_encoding`: The current encoding of the web page.
451 +* `inspected_uri`: The URI that is being inspected. Requires webkitgtk >=
452 +  1.3.17.
453 +* `app_cache_directory`: The directory webkit uses to store its cache.
454 +  Requires webkitgtk >= 1.3.13.
455 +* `plugin_list`: A JSON list of objects describing the available plugins.
456 +  Requires webkitgtk >= 1.3.8.
457  
458  ### VARIABLE EXPANSION AND COMMAND / JAVASCRIPT SUBSTITUTION
459  
460 @@ -514,10 +635,10 @@ access to the following environment variables:
461  * `$UZBL_SOCKET`: The filename of the Unix socket being used, if any.
462  * `$UZBL_URI`: The URI of the current page.
463  * `$UZBL_TITLE`: The current page title.
464 +* `$UZBL_PRIVATE`: Set if uzbl is in "private browsing mode", unset otherwise.
465  
466 -Handler scripts (`download_handler`, `cookie_handler`, `scheme_handler`,
467 -`request_handler`, and `authentication_handler`) are called with special
468 -arguments:
469 +Handler scripts (`download_handler`, `scheme_handler`, and `request_handler`)
470 +are called with special arguments:
471  
472  * download handler
473  
474 @@ -532,16 +653,6 @@ arguments:
475      that the file should be saved to. A download handler using WebKit's internal
476      downloader can just echo this path and exit when this argument is present.
477  
478 -* cookie handler
479 -
480 -  - `$1 GET/PUT`: Whether a cookie should be sent to the server (`GET`) or
481 -    stored by the browser (`PUT`).
482 -  - `$2 scheme`: Either `http` or `https`.
483 -  - `$3 host`: If current page URL is `www.example.com/somepage`, this could be
484 -    something else than `example.com`, eg advertising from another host.
485 -  - `$4 path`: The request address path.
486 -  - `$5 data`: The cookie data. Only included for `PUT` requests.
487 -
488  * scheme handler
489  
490    - `$1 URI` of the page to be navigated to
491 @@ -550,13 +661,6 @@ arguments:
492  
493    - `$1 URI` of the resource which is being requested
494  
495 -* authentication handler:
496 -
497 -  - `$1`: authentication zone unique identifier
498 -  - `$2`: domain part of URL that requests authentication
499 -  - `$3`: authentication realm
500 -  - `$4`: FALSE if this is the first attempt to authenticate, TRUE otherwise
501 -
502  ### Formfiller.sh
503  
504  Example config entries for formfiller script
505 @@ -584,47 +688,33 @@ after closing the editor, it will load the data into the formfields. The temp
506  file is removed
507  
508  ### HTTP/BASIC AUTHENTICATION
509 +HTTP auth can be handled in two different ways. Using the builtin auth dialog
510 +in WebKit or by dispatching the work to a external script.
511  
512 -You can use the authentication_handler variable to denote how http
513 -authentication should be handled.
514 -If this variable is:
515 -
516 -* not set or empty: use webkit internal auth dialog
517 -* a valid handler (i.e. {sh,sync}_spawn correct_script), use this handler
518 -* innvalid handler (spawn, some other command, uses script that does not
519 -  print anything): skip authentication.
520 -
521 -Example:
522 +To use the builtin auth dialog set `enable_builtin_auth` to 1. With this set
523 +you'll get a basic GTK+ prompt for username/password when trying to access a
524 +protected site.
525  
526 -  set authentication_handler = sync_spawn /patch/to/your/script
527 +Whenever authentication is needed the `AUTHENTICATE` event will be sent, this
528 +is what you would use to hook up a custom authentication system. This event
529 +will be sent even when using the bultin dialog so remember to disable that if
530 +adding a dialog of your own.
531  
532 -Script will be executed on each authentication request.
533 -It will receive four auth-related parameters:
534 +The `AUTHENTICATE` event has four arguments
535 + * a unique identifier to be used in the exchange
536 + * domain part of URL that requests authentication
537 + * authentication realm
538 + * the empty string for the first attempt and "retrying" for further attempts
539  
540 -    $1 authentication zone unique identifier (may be used as 'key')
541 -    $2 domain part of URL that requests authentication
542 -    $3 authentication realm
543 -    $4 FALSE if this is the first attempt to authenticate, TRUE otherwise
544 +After this event has been sent the request is paused until credentials are
545 +provided. This is done using the `auth` command e.g:
546  
547 -Script is expected to print exactly two lines of text on stdout (that means
548 -its output must contain exactly two '\n' bytes).
549 -The first line contains username, the second one - password.
550 -If authentication fails, script will be executed again (with $4 = TRUE).
551 -Non-interactive scripts should handle this case and do not try to
552 -authenticate again to avoid loops. If number of '\n' characters in scripts
553 -output does not equal 2, authentication will fail.
554 -That means 401 error will be displayed and uzbl won't try to authenticate anymore.
555 +    `auth "uniqueid" "alice" "wonderland"`
556  
557 -The simplest example of authentication handler script is:
558 +A super simple setup that will always try to authenticate with the same password
559 +could look like this. (assuming aliases from the example configuration)
560  
561 -#!/bin/sh
562 -[ "$4" == "TRUE ] && exit
563 -echo alice
564 -echo wonderland
565 -
566 -This script tries to authenticate as user alice with password wonderland once
567 -and never retries authentication.
568 -See examples for more sofisticated, interactive authentication handler.
569 +@on_event AUTHENTICATE auth "%1" "alice" "wonderland"
570  
571  ### WINDOW MANAGER INTEGRATION
572  
573 @@ -657,11 +747,6 @@ The EM allows:
574  * Many fine-grained events (`hover_over_link`, `key_press`, `key_release`,..)
575  * See example `uzbl-event-manager`.
576  
577 -**Note**: Cookie events are sent in addition to (optionally) being handled by
578 -  the cookie handler (set by the cookie_handler var). If using a handler it will
579 -  take precedence before the internal state configured by (add|delete)_cookie
580 -  commands.
581 -
582  Events have this format:
583  
584       EVENT [uzbl_instance_name] EVENT_NAME event_details
585 @@ -687,14 +772,18 @@ Events have this format:
586    loaded. `uri` is the URI of the page being loaded.
587  * `EVENT [uzbl_instance_name] LOAD_START uri`: A change of the page has been
588    requested. `uri` is the current URI; the one being departed.
589 -* `EVENT [uzbl_instance_name] LOAD_FINISHED uri`: Loading has finished for the
590 +* `EVENT [uzbl_instance_name] LOAD_FINISH uri`: Loading has finished for the
591    page at `uri`.
592  * `EVENT [uzbl_instance_name] LOAD_ERROR uri reason_of_error`: The URI `uri`
593    could not be loaded for the reason described in `reason_of_error`.
594  * `EVENT [uzbl_instance_name] LOAD_PROGRESS percentage` : While the page is
595    loading, gives the `percentage` of the page that has finished loading.
596 +* `EVENT [uzbl_instance_name] REQUEST_QUEUED uri`: http resource gets
597 +  enqueued
598  * `EVENT [uzbl_instance_name] REQUEST_STARTING uri`: http resource gets
599    requested
600 +* `EVENT [uzbl_instance_name] REQUEST_FINISHED uri`: http resource has finished
601 +  loading
602  * `EVENT [uzbl_instance_name] TITLE_CHANGED title_name`: When the title of the
603    page (and hence maybe, the window title) changed. `title_name` is the new
604    title.
605 @@ -743,6 +832,8 @@ Events have this format:
606    be a unix-timestamp or empty
607  * `EVENT [uzbl_instance_name] DELETE_COOKIE domain path name value scheme expire`:
608    When a cookie was deleted. arguments as ADD_COOKIE
609 +* `EVENT [uzbl_instance_name] AUTHENTICATE uniqueid host realm retry`: When a
610 +  request requires authentication. authentication is done by calling `auth`
611  
612  Events/requests which the EM and its plugins listens for
613  
614 diff --git a/bin/uzbl-browser b/bin/uzbl-browser
615 index fb9a368..4381050 100755
616 --- a/bin/uzbl-browser
617 +++ b/bin/uzbl-browser
618 @@ -67,9 +67,9 @@ fi
619  # uzbl-event-manager will exit if one is already running.
620  # we could also check if its pid file exists to avoid having to spawn it.
621  DAEMON_SOCKET="$XDG_CACHE_HOME"/uzbl/event_daemon
622 -#if [ ! -f "$DAEMON_SOCKET".pid ]
623 -#then
624 +if [ ! -f "$DAEMON_SOCKET".pid ]
625 +then
626         ${UZBL_EVENT_MANAGER:-uzbl-event-manager -va start}
627 -#fi
628 +fi
629  
630  exec uzbl-core "$@" ${config_file:+--config "$config_file"} --connect-socket $DAEMON_SOCKET
631 diff --git a/bin/uzbl-event-manager b/bin/uzbl-event-manager
632 index 56253ef..221fa73 100755
633 --- a/bin/uzbl-event-manager
634 +++ b/bin/uzbl-event-manager
635 @@ -1,1011 +1,3 @@
636 -#!/usr/bin/env python2
637 -
638 -# Event Manager for Uzbl
639 -# Copyright (c) 2009-2010, Mason Larobina <mason.larobina@gmail.com>
640 -# Copyright (c) 2009, Dieter Plaetinck <dieter@plaetinck.be>
641 -#
642 -# This program is free software: you can redistribute it and/or modify
643 -# it under the terms of the GNU General Public License as published by
644 -# the Free Software Foundation, either version 3 of the License, or
645 -# (at your option) any later version.
646 -#
647 -# This program is distributed in the hope that it will be useful,
648 -# but WITHOUT ANY WARRANTY; without even the implied warranty of
649 -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
650 -# GNU General Public License for more details.
651 -#
652 -# You should have received a copy of the GNU General Public License
653 -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
654 -
655 -'''
656 -
657 -E V E N T _ M A N A G E R . P Y
658 -===============================
659 -
660 -Event manager for uzbl written in python.
661 -
662 -'''
663 -
664 -import atexit
665 -import imp
666 -import logging
667 -import os
668 -import sys
669 -import time
670 -import weakref
671 -import re
672 -import errno
673 -from collections import defaultdict
674 -from functools import partial
675 -from glob import glob
676 -from itertools import count
677 -from optparse import OptionParser
678 -from select import select
679 -from signal import signal, SIGTERM, SIGINT, SIGKILL
680 -from socket import socket, AF_UNIX, SOCK_STREAM, error as socket_error
681 -from traceback import format_exc
682 -
683 -
684 -def xdghome(key, default):
685 -    '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise
686 -    use $HOME and the default path.'''
687 -
688 -    xdgkey = "XDG_%s_HOME" % key
689 -    if xdgkey in os.environ.keys() and os.environ[xdgkey]:
690 -        return os.environ[xdgkey]
691 -
692 -    return os.path.join(os.environ['HOME'], default)
693 -
694 -# `make install` will put the correct value here for your system
695 -PREFIX = '/usr/local/'
696 -
697 -# Setup xdg paths.
698 -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
699 -CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
700 -
701 -# Define some globals.
702 -SCRIPTNAME = os.path.basename(sys.argv[0])
703 -
704 -logger = logging.getLogger(SCRIPTNAME)
705 -
706 -
707 -def get_exc():
708 -    '''Format `format_exc` for logging.'''
709 -    return "\n%s" % format_exc().rstrip()
710 -
711 -
712 -def expandpath(path):
713 -    '''Expand and realpath paths.'''
714 -    return os.path.realpath(os.path.expandvars(path))
715 -
716 -
717 -def ascii(u):
718 -    '''Convert unicode strings into ascii for transmission over
719 -    ascii-only streams/sockets/devices.'''
720 -    return u.encode('utf-8')
721 -
722 -
723 -def daemonize():
724 -    '''Daemonize the process using the Stevens' double-fork magic.'''
725 -
726 -    logger.info('entering daemon mode')
727 -
728 -    try:
729 -        if os.fork():
730 -            os._exit(0)
731 -
732 -    except OSError:
733 -        logger.critical('failed to daemonize', exc_info=True)
734 -        sys.exit(1)
735 -
736 -    os.chdir('/')
737 -    os.setsid()
738 -    os.umask(0)
739 -
740 -    try:
741 -        if os.fork():
742 -            os._exit(0)
743 -
744 -    except OSError:
745 -        logger.critical('failed to daemonize', exc_info=True)
746 -        sys.exit(1)
747 -
748 -    if sys.stdout.isatty():
749 -        sys.stdout.flush()
750 -        sys.stderr.flush()
751 -
752 -    devnull = '/dev/null'
753 -    stdin = file(devnull, 'r')
754 -    stdout = file(devnull, 'a+')
755 -    stderr = file(devnull, 'a+', 0)
756 -
757 -    os.dup2(stdin.fileno(), sys.stdin.fileno())
758 -    os.dup2(stdout.fileno(), sys.stdout.fileno())
759 -    os.dup2(stderr.fileno(), sys.stderr.fileno())
760 -
761 -    logger.info('entered daemon mode')
762 -
763 -
764 -def make_dirs(path):
765 -    '''Make all basedirs recursively as required.'''
766 -
767 -    try:
768 -        dirname = os.path.dirname(path)
769 -        if not os.path.isdir(dirname):
770 -            logger.debug('creating directories %r', dirname)
771 -            os.makedirs(dirname)
772 -
773 -    except OSError:
774 -        logger.error('failed to create directories', exc_info=True)
775 -
776 -
777 -class EventHandler(object):
778 -    '''Event handler class. Used to store args and kwargs which are merged
779 -    come time to call the callback with the event args and kwargs.'''
780 -
781 -    nextid = count().next
782 -
783 -    def __init__(self, plugin, event, callback, args, kwargs):
784 -        self.id = self.nextid()
785 -        self.plugin = plugin
786 -        self.event = event
787 -        self.callback = callback
788 -        self.args = args
789 -        self.kwargs = kwargs
790 -
791 -    def __repr__(self):
792 -        elems = ['id=%d' % self.id, 'event=%s' % self.event,
793 -            'callback=%r' % self.callback]
794 -
795 -        if self.args:
796 -            elems.append('args=%s' % repr(self.args))
797 -
798 -        if self.kwargs:
799 -            elems.append('kwargs=%s' % repr(self.kwargs))
800 -
801 -        elems.append('plugin=%s' % self.plugin.name)
802 -        return u'<handler(%s)>' % ', '.join(elems)
803 -
804 -    def call(self, uzbl, *args, **kwargs):
805 -        '''Execute the handler function and merge argument lists.'''
806 -
807 -        args = args + self.args
808 -        kwargs = dict(self.kwargs.items() + kwargs.items())
809 -        self.callback(uzbl, *args, **kwargs)
810 -
811 -
812 -class Plugin(object):
813 -    '''Plugin module wrapper object.'''
814 -
815 -    # Special functions exported from the Plugin instance to the
816 -    # plugin namespace.
817 -    special_functions = ['require', 'export', 'export_dict', 'connect',
818 -            'connect_dict', 'logger', 'unquote', 'splitquoted']
819 -
820 -    def __init__(self, parent, name, path, plugin):
821 -        self.parent = parent
822 -        self.name = name
823 -        self.path = path
824 -        self.plugin = plugin
825 -        self.logger = logging.getLogger('plugin.%s' % name)
826 -
827 -        # Weakrefs to all handlers created by this plugin
828 -        self.handlers = set([])
829 -
830 -        # Plugins init hook
831 -        init = getattr(plugin, 'init', None)
832 -        self.init = init if callable(init) else None
833 -
834 -        # Plugins optional after hook
835 -        after = getattr(plugin, 'after', None)
836 -        self.after = after if callable(after) else None
837 -
838 -        # Plugins optional cleanup hook
839 -        cleanup = getattr(plugin, 'cleanup', None)
840 -        self.cleanup = cleanup if callable(cleanup) else None
841 -
842 -        assert init or after or cleanup, "missing hooks in plugin"
843 -
844 -        # Export plugin's instance methods to plugin namespace
845 -        for attr in self.special_functions:
846 -            plugin.__dict__[attr] = getattr(self, attr)
847 -
848 -    def __repr__(self):
849 -        return u'<plugin(%r)>' % self.plugin
850 -
851 -    def export(self, uzbl, attr, obj, prepend=True):
852 -        '''Attach `obj` to `uzbl` instance. This is the preferred method
853 -        of sharing functionality, functions, data and objects between
854 -        plugins.
855 -
856 -        If the object is callable you may wish to turn the callable object
857 -        in to a meta-instance-method by prepending `uzbl` to the call stack.
858 -        You can change this behaviour with the `prepend` argument.
859 -        '''
860 -
861 -        assert attr not in uzbl.exports, "attr %r already exported by %r" %\
862 -            (attr, uzbl.exports[attr][0])
863 -
864 -        prepend = True if prepend and callable(obj) else False
865 -        uzbl.__dict__[attr] = partial(obj, uzbl) if prepend else obj
866 -        uzbl.exports[attr] = (self, obj, prepend)
867 -        uzbl.logger.info('exported %r to %r by plugin %r, prepended %r',
868 -            obj, 'uzbl.%s' % attr, self.name, prepend)
869 -
870 -    def export_dict(self, uzbl, exports):
871 -        for (attr, object) in exports.items():
872 -            self.export(uzbl, attr, object)
873 -
874 -    def find_handler(self, event, callback, args, kwargs):
875 -        '''Check if a handler with the identical callback and arguments
876 -        exists and return it.'''
877 -
878 -        # Remove dead refs
879 -        self.handlers -= set(filter(lambda ref: not ref(), self.handlers))
880 -
881 -        # Find existing identical handler
882 -        for handler in [ref() for ref in self.handlers]:
883 -            if handler.event == event and handler.callback == callback \
884 -              and handler.args == args and handler.kwargs == kwargs:
885 -                return handler
886 -
887 -    def connect(self, uzbl, event, callback, *args, **kwargs):
888 -        '''Create an event handler object which handles `event` events.
889 -
890 -        Arguments passed to the connect function (`args` and `kwargs`) are
891 -        stored in the handler object and merged with the event arguments
892 -        come handler execution.
893 -
894 -        All handler functions must behave like a `uzbl` instance-method (that
895 -        means `uzbl` is prepended to the callback call arguments).'''
896 -
897 -        # Sanitise and check event name
898 -        event = event.upper().strip()
899 -        assert event and ' ' not in event
900 -
901 -        assert callable(callback), 'callback must be callable'
902 -
903 -        # Check if an identical handler already exists
904 -        handler = self.find_handler(event, callback, args, kwargs)
905 -        if not handler:
906 -            # Create a new handler
907 -            handler = EventHandler(self, event, callback, args, kwargs)
908 -            self.handlers.add(weakref.ref(handler))
909 -            self.logger.info('new %r', handler)
910 -
911 -        uzbl.handlers[event].append(handler)
912 -        uzbl.logger.info('connected %r', handler)
913 -        return handler
914 -
915 -    def connect_dict(self, uzbl, connects):
916 -        for (event, callback) in connects.items():
917 -            self.connect(uzbl, event, callback)
918 -
919 -    def require(self, plugin):
920 -        '''Check that plugin with name `plugin` has been loaded. Use this to
921 -        ensure that your plugins dependencies have been met.'''
922 -
923 -        assert plugin in self.parent.plugins, self.logger.critical(
924 -            'plugin %r required by plugin %r', plugin, self.name)
925 -
926 -    @classmethod
927 -    def unquote(cls, s):
928 -        '''Removes quotation marks around strings if any and interprets
929 -        \\-escape sequences using `string_escape`'''
930 -        if s and s[0] == s[-1] and s[0] in ['"', "'"]:
931 -            s = s[1:-1]
932 -        return s.encode('utf-8').decode('string_escape').decode('utf-8')
933 -
934 -    _splitquoted = re.compile("( |\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')")
935 -
936 -    @classmethod
937 -    def splitquoted(cls, text):
938 -        '''Splits string on whitespace while respecting quotations'''
939 -        parts = cls._splitquoted.split(text)
940 -        return [cls.unquote(p) for p in parts if p.strip()]
941 -
942 -
943 -class Uzbl(object):
944 -    def __init__(self, parent, child_socket):
945 -        self.opts = opts
946 -        self.parent = parent
947 -        self.child_socket = child_socket
948 -        self.child_buffer = []
949 -        self.time = time.time()
950 -        self.pid = None
951 -        self.name = None
952 -
953 -        # Flag if the instance has raised the INSTANCE_START event.
954 -        self.instance_start = False
955 -
956 -        # Use name "unknown" until name is discovered.
957 -        self.logger = logging.getLogger('uzbl-instance[]')
958 -
959 -        # Track plugin event handlers and exported functions.
960 -        self.exports = {}
961 -        self.handlers = defaultdict(list)
962 -
963 -        # Internal vars
964 -        self._depth = 0
965 -        self._buffer = ''
966 -
967 -    def __repr__(self):
968 -        return '<uzbl(%s)>' % ', '.join([
969 -            'pid=%s' % (self.pid if self.pid else "Unknown"),
970 -            'name=%s' % ('%r' % self.name if self.name else "Unknown"),
971 -            'uptime=%f' % (time.time() - self.time),
972 -            '%d exports' % len(self.exports.keys()),
973 -            '%d handlers' % sum([len(l) for l in self.handlers.values()])])
974 -
975 -    def init_plugins(self):
976 -        '''Call the init and after hooks in all loaded plugins for this
977 -        instance.'''
978 -
979 -        # Initialise each plugin with the current uzbl instance.
980 -        for plugin in self.parent.plugins.values():
981 -            if plugin.init:
982 -                self.logger.debug('calling %r plugin init hook', plugin.name)
983 -                plugin.init(self)
984 -
985 -        # Allow plugins to use exported features of other plugins by calling an
986 -        # optional `after` function in the plugins namespace.
987 -        for plugin in self.parent.plugins.values():
988 -            if plugin.after:
989 -                self.logger.debug('calling %r plugin after hook', plugin.name)
990 -                plugin.after(self)
991 -
992 -    def send(self, msg):
993 -        '''Send a command to the uzbl instance via the child socket
994 -        instance.'''
995 -
996 -        msg = msg.strip()
997 -        assert self.child_socket, "socket inactive"
998 -
999 -        if opts.print_events:
1000 -            print ascii(u'%s<-- %s' % ('  ' * self._depth, msg))
1001 -
1002 -        self.child_buffer.append(ascii("%s\n" % msg))
1003 -
1004 -    def do_send(self):
1005 -        data = ''.join(self.child_buffer)
1006 -        try:
1007 -            bsent = self.child_socket.send(data)
1008 -        except socket_error as e:
1009 -            if e.errno in (errno.EAGAIN, errno.EINTR):
1010 -                self.child_buffer = [data]
1011 -                return
1012 -            else:
1013 -                self.logger.error('failed to send', exc_info=True)
1014 -                return self.close()
1015 -        else:
1016 -            if bsent == 0:
1017 -                self.logger.debug('write end of connection closed')
1018 -                self.close()
1019 -            elif bsent < len(data):
1020 -                self.child_buffer = [data[bsent:]]
1021 -            else:
1022 -                del self.child_buffer[:]
1023 -
1024 -    def read(self):
1025 -        '''Read data from the child socket and pass lines to the parse_msg
1026 -        function.'''
1027 -
1028 -        try:
1029 -            raw = unicode(self.child_socket.recv(8192), 'utf-8', 'ignore')
1030 -            if not raw:
1031 -                self.logger.debug('read null byte')
1032 -                return self.close()
1033 -
1034 -        except:
1035 -            self.logger.error('failed to read', exc_info=True)
1036 -            return self.close()
1037 -
1038 -        lines = (self._buffer + raw).split('\n')
1039 -        self._buffer = lines.pop()
1040 -
1041 -        for line in filter(None, map(unicode.strip, lines)):
1042 -            try:
1043 -                self.parse_msg(line.strip())
1044 -
1045 -            except:
1046 -                self.logger.error(get_exc())
1047 -                self.logger.error('erroneous event: %r' % line)
1048 -
1049 -    def parse_msg(self, line):
1050 -        '''Parse an incoming message from a uzbl instance. Event strings
1051 -        will be parsed into `self.event(event, args)`.'''
1052 -
1053 -        # Split by spaces (and fill missing with nulls)
1054 -        elems = (line.split(' ', 3) + [''] * 3)[:4]
1055 -
1056 -        # Ignore non-event messages.
1057 -        if elems[0] != 'EVENT':
1058 -            logger.info('non-event message: %r', line)
1059 -            if opts.print_events:
1060 -                print '--- %s' % ascii(line)
1061 -            return
1062 -
1063 -        # Check event string elements
1064 -        (name, event, args) = elems[1:]
1065 -        assert name and event, 'event string missing elements'
1066 -        if not self.name:
1067 -            self.name = name
1068 -            self.logger = logging.getLogger('uzbl-instance%s' % name)
1069 -            self.logger.info('found instance name %r', name)
1070 -
1071 -        assert self.name == name, 'instance name mismatch'
1072 -
1073 -        # Handle the event with the event handlers through the event method
1074 -        self.event(event, args)
1075 -
1076 -    def event(self, event, *args, **kargs):
1077 -        '''Raise an event.'''
1078 -
1079 -        event = event.upper()
1080 -
1081 -        if not opts.daemon_mode and opts.print_events:
1082 -            elems = [event]
1083 -            if args:
1084 -                elems.append(unicode(args))
1085 -            if kargs:
1086 -                elems.append(unicode(kargs))
1087 -            print ascii(u'%s--> %s' % ('  ' * self._depth, ' '.join(elems)))
1088 -
1089 -        if event == "INSTANCE_START" and args:
1090 -            assert not self.instance_start, 'instance already started'
1091 -
1092 -            self.pid = int(args[0])
1093 -            self.logger.info('found instance pid %r', self.pid)
1094 -
1095 -            self.init_plugins()
1096 -
1097 -        elif event == "INSTANCE_EXIT":
1098 -            self.logger.info('uzbl instance exit')
1099 -            self.close()
1100 -
1101 -        if event not in self.handlers:
1102 -            return
1103 -
1104 -        for handler in self.handlers[event]:
1105 -            self._depth += 1
1106 -            try:
1107 -                handler.call(self, *args, **kargs)
1108 -
1109 -            except:
1110 -                self.logger.error('error in handler', exc_info=True)
1111 -
1112 -            self._depth -= 1
1113 -
1114 -    def close_connection(self, child_socket):
1115 -        '''Close child socket and delete the uzbl instance created for that
1116 -        child socket connection.'''
1117 -
1118 -    def close(self):
1119 -        '''Close the client socket and call the plugin cleanup hooks.'''
1120 -
1121 -        self.logger.debug('called close method')
1122 -
1123 -        # Remove self from parent uzbls dict.
1124 -        if self.child_socket in self.parent.uzbls:
1125 -            self.logger.debug('removing self from uzbls list')
1126 -            del self.parent.uzbls[self.child_socket]
1127 -
1128 -        try:
1129 -            if self.child_socket:
1130 -                self.logger.debug('closing child socket')
1131 -                self.child_socket.close()
1132 -
1133 -        except:
1134 -            self.logger.error('failed to close socket', exc_info=True)
1135 -
1136 -        finally:
1137 -            self.child_socket = None
1138 -
1139 -        # Call plugins cleanup hooks.
1140 -        for plugin in self.parent.plugins.values():
1141 -            if plugin.cleanup:
1142 -                self.logger.debug('calling %r plugin cleanup hook',
1143 -                    plugin.name)
1144 -                plugin.cleanup(self)
1145 -
1146 -        logger.info('removed %r', self)
1147 -
1148 -
1149 -class UzblEventDaemon(object):
1150 -    def __init__(self):
1151 -        self.opts = opts
1152 -        self.server_socket = None
1153 -        self._quit = False
1154 -
1155 -        # Hold uzbl instances
1156 -        # {child socket: Uzbl instance, ..}
1157 -        self.uzbls = {}
1158 -
1159 -        # Hold plugins
1160 -        # {plugin name: Plugin instance, ..}
1161 -        self.plugins = {}
1162 -
1163 -        # Register that the event daemon server has started by creating the
1164 -        # pid file.
1165 -        make_pid_file(opts.pid_file)
1166 -
1167 -        # Register a function to clean up the socket and pid file on exit.
1168 -        atexit.register(self.quit)
1169 -
1170 -        # Add signal handlers.
1171 -        for sigint in [SIGTERM, SIGINT]:
1172 -            signal(sigint, self.quit)
1173 -
1174 -        # Load plugins into self.plugins
1175 -        self.load_plugins(opts.plugins)
1176 -
1177 -    def load_plugins(self, plugins):
1178 -        '''Load event manager plugins.'''
1179 -
1180 -        for path in plugins:
1181 -            logger.debug('loading plugin %r', path)
1182 -            (dir, file) = os.path.split(path)
1183 -            name = file[:-3] if file.lower().endswith('.py') else file
1184 -
1185 -            info = imp.find_module(name, [dir])
1186 -            module = imp.load_module(name, *info)
1187 -
1188 -            # Check if the plugin has a callable hook.
1189 -            hooks = filter(callable, [getattr(module, attr, None) \
1190 -                for attr in ['init', 'after', 'cleanup']])
1191 -            assert hooks, "no hooks in plugin %r" % module
1192 -
1193 -            logger.debug('creating plugin instance for %r plugin', name)
1194 -            plugin = Plugin(self, name, path, module)
1195 -            self.plugins[name] = plugin
1196 -            logger.info('new %r', plugin)
1197 -
1198 -    def create_server_socket(self):
1199 -        '''Create the event manager daemon socket for uzbl instance duplex
1200 -        communication.'''
1201 -
1202 -        # Close old socket.
1203 -        self.close_server_socket()
1204 -
1205 -        sock = socket(AF_UNIX, SOCK_STREAM)
1206 -        sock.bind(opts.server_socket)
1207 -        sock.listen(5)
1208 -
1209 -        self.server_socket = sock
1210 -        logger.debug('bound server socket to %r', opts.server_socket)
1211 -
1212 -    def run(self):
1213 -        '''Main event daemon loop.'''
1214 -
1215 -        logger.debug('entering main loop')
1216 -
1217 -        # Create and listen on the server socket
1218 -        self.create_server_socket()
1219 -
1220 -        if opts.daemon_mode:
1221 -            # Daemonize the process
1222 -            daemonize()
1223 -
1224 -            # Update the pid file
1225 -            make_pid_file(opts.pid_file)
1226 -
1227 -        try:
1228 -            # Accept incoming connections and listen for incoming data
1229 -            self.listen()
1230 -
1231 -        except:
1232 -            if not self._quit:
1233 -                logger.critical('failed to listen', exc_info=True)
1234 -
1235 -        # Clean up and exit
1236 -        self.quit()
1237 -
1238 -        logger.debug('exiting main loop')
1239 -
1240 -    def listen(self):
1241 -        '''Accept incoming connections and constantly poll instance sockets
1242 -        for incoming data.'''
1243 -
1244 -        logger.info('listening on %r', opts.server_socket)
1245 -
1246 -        # Count accepted connections
1247 -        connections = 0
1248 -
1249 -        while (self.uzbls or not connections) or (not opts.auto_close):
1250 -            socks = [self.server_socket] + self.uzbls.keys()
1251 -            wsocks = [k for k, v in self.uzbls.items() if v.child_buffer]
1252 -            reads, writes, errors = select(socks, wsocks, socks, 1)
1253 -
1254 -            if self.server_socket in reads:
1255 -                reads.remove(self.server_socket)
1256 -
1257 -                # Accept connection and create uzbl instance.
1258 -                child_socket = self.server_socket.accept()[0]
1259 -                child_socket.setblocking(False)
1260 -                self.uzbls[child_socket] = Uzbl(self, child_socket)
1261 -                connections += 1
1262 -
1263 -            for uzbl in [self.uzbls[s] for s in writes if s in self.uzbls ]:
1264 -                uzbl.do_send()
1265 -
1266 -            for uzbl in [self.uzbls[s] for s in reads if s in self.uzbls]:
1267 -                uzbl.read()
1268 -
1269 -            for uzbl in [self.uzbls[s] for s in errors if s in self.uzbls]:
1270 -                uzbl.logger.error('socket read error')
1271 -                uzbl.close()
1272 -
1273 -        logger.info('auto closing')
1274 -
1275 -    def close_server_socket(self):
1276 -        '''Close and delete the server socket.'''
1277 -
1278 -        try:
1279 -            if self.server_socket:
1280 -                logger.debug('closing server socket')
1281 -                self.server_socket.close()
1282 -                self.server_socket = None
1283 -
1284 -            if os.path.exists(opts.server_socket):
1285 -                logger.info('unlinking %r', opts.server_socket)
1286 -                os.unlink(opts.server_socket)
1287 -
1288 -        except:
1289 -            logger.error('failed to close server socket', exc_info=True)
1290 -
1291 -    def quit(self, sigint=None, *args):
1292 -        '''Close all instance socket objects, server socket and delete the
1293 -        pid file.'''
1294 -
1295 -        if sigint == SIGTERM:
1296 -            logger.critical('caught SIGTERM, exiting')
1297 -
1298 -        elif sigint == SIGINT:
1299 -            logger.critical('caught SIGINT, exiting')
1300 -
1301 -        elif not self._quit:
1302 -            logger.debug('shutting down event manager')
1303 -
1304 -        self.close_server_socket()
1305 -
1306 -        for uzbl in self.uzbls.values():
1307 -            uzbl.close()
1308 -
1309 -        del_pid_file(opts.pid_file)
1310 -
1311 -        if not self._quit:
1312 -            logger.info('event manager shut down')
1313 -            self._quit = True
1314 -
1315 -
1316 -def make_pid_file(pid_file):
1317 -    '''Creates a pid file at `pid_file`, fails silently.'''
1318 -
1319 -    try:
1320 -        logger.debug('creating pid file %r', pid_file)
1321 -        make_dirs(pid_file)
1322 -        pid = os.getpid()
1323 -        fileobj = open(pid_file, 'w')
1324 -        fileobj.write('%d' % pid)
1325 -        fileobj.close()
1326 -        logger.info('created pid file %r with pid %d', pid_file, pid)
1327 -
1328 -    except:
1329 -        logger.error('failed to create pid file', exc_info=True)
1330 -
1331 -
1332 -def del_pid_file(pid_file):
1333 -    '''Deletes a pid file at `pid_file`, fails silently.'''
1334 -
1335 -    if os.path.isfile(pid_file):
1336 -        try:
1337 -            logger.debug('deleting pid file %r', pid_file)
1338 -            os.remove(pid_file)
1339 -            logger.info('deleted pid file %r', pid_file)
1340 -
1341 -        except:
1342 -            logger.error('failed to delete pid file', exc_info=True)
1343 -
1344 -
1345 -def get_pid(pid_file):
1346 -    '''Reads a pid from pid file `pid_file`, fails None.'''
1347 -
1348 -    try:
1349 -        logger.debug('reading pid file %r', pid_file)
1350 -        fileobj = open(pid_file, 'r')
1351 -        pid = int(fileobj.read())
1352 -        fileobj.close()
1353 -        logger.info('read pid %d from pid file %r', pid, pid_file)
1354 -        return pid
1355 -
1356 -    except (IOError, ValueError):
1357 -        logger.error('failed to read pid', exc_info=True)
1358 -        return None
1359 -
1360 -
1361 -def pid_running(pid):
1362 -    '''Checks if a process with a pid `pid` is running.'''
1363 -
1364 -    try:
1365 -        os.kill(pid, 0)
1366 -    except OSError:
1367 -        return False
1368 -    else:
1369 -        return True
1370 -
1371 -
1372 -def term_process(pid):
1373 -    '''Asks nicely then forces process with pid `pid` to exit.'''
1374 -
1375 -    try:
1376 -        logger.info('sending SIGTERM to process with pid %r', pid)
1377 -        os.kill(pid, SIGTERM)
1378 -
1379 -    except OSError:
1380 -        logger.error(get_exc())
1381 -
1382 -    logger.debug('waiting for process with pid %r to exit', pid)
1383 -    start = time.time()
1384 -    while True:
1385 -        if not pid_running(pid):
1386 -            logger.debug('process with pid %d exit', pid)
1387 -            return True
1388 -
1389 -        if (time.time() - start) > 5:
1390 -            logger.warning('process with pid %d failed to exit', pid)
1391 -            logger.info('sending SIGKILL to process with pid %d', pid)
1392 -            try:
1393 -                os.kill(pid, SIGKILL)
1394 -            except:
1395 -                logger.critical('failed to kill %d', pid, exc_info=True)
1396 -                raise
1397 -
1398 -        if (time.time() - start) > 10:
1399 -            logger.critical('unable to kill process with pid %d', pid)
1400 -            raise OSError
1401 -
1402 -        time.sleep(0.25)
1403 -
1404 -
1405 -def stop_action():
1406 -    '''Stop the event manager daemon.'''
1407 -
1408 -    pid_file = opts.pid_file
1409 -    if not os.path.isfile(pid_file):
1410 -        logger.error('could not find running event manager with pid file %r',
1411 -            pid_file)
1412 -        return
1413 -
1414 -    pid = get_pid(pid_file)
1415 -    if not pid_running(pid):
1416 -        logger.debug('no process with pid %r', pid)
1417 -        del_pid_file(pid_file)
1418 -        return
1419 -
1420 -    logger.debug('terminating process with pid %r', pid)
1421 -    term_process(pid)
1422 -    del_pid_file(pid_file)
1423 -    logger.info('stopped event manager process with pid %d', pid)
1424 -
1425 -
1426 -def start_action():
1427 -    '''Start the event manager daemon.'''
1428 -
1429 -    pid_file = opts.pid_file
1430 -    if os.path.isfile(pid_file):
1431 -        pid = get_pid(pid_file)
1432 -        if pid_running(pid):
1433 -            logger.error('event manager already started with pid %d', pid)
1434 -            return
1435 -
1436 -        logger.info('no process with pid %d', pid)
1437 -        del_pid_file(pid_file)
1438 -
1439 -    UzblEventDaemon().run()
1440 -
1441 -
1442 -def restart_action():
1443 -    '''Restart the event manager daemon.'''
1444 -
1445 -    stop_action()
1446 -    start_action()
1447 -
1448 -
1449 -def list_action():
1450 -    '''List all the plugins that would be loaded in the current search
1451 -    dirs.'''
1452 -
1453 -    names = {}
1454 -    for plugin in opts.plugins:
1455 -        (head, tail) = os.path.split(plugin)
1456 -        if tail not in names:
1457 -            names[tail] = plugin
1458 -
1459 -    for plugin in sorted(names.values()):
1460 -        print plugin
1461 -
1462 -
1463 -def make_parser():
1464 -    parser = OptionParser('usage: %prog [options] {start|stop|restart|list}')
1465 -    add = parser.add_option
1466 -
1467 -    add('-v', '--verbose',
1468 -        dest='verbose', default=2, action='count',
1469 -        help='increase verbosity')
1470 -
1471 -    add('-d', '--plugin-dir',
1472 -        dest='plugin_dirs', action='append', metavar="DIR", default=[],
1473 -        help='add extra plugin search dir, same as `-l "DIR/*.py"`')
1474 -
1475 -    add('-l', '--load-plugin',
1476 -        dest='load_plugins', action='append', metavar="PLUGIN", default=[],
1477 -        help='load plugin, loads before plugins in search dirs')
1478 -
1479 -    socket_location = os.path.join(CACHE_DIR, 'event_daemon')
1480 -
1481 -    add('-s', '--server-socket',
1482 -        dest='server_socket', metavar="SOCKET", default=socket_location,
1483 -        help='server AF_UNIX socket location')
1484 -
1485 -    add('-p', '--pid-file',
1486 -        metavar="FILE", dest='pid_file',
1487 -        help='pid file location, defaults to server socket + .pid')
1488 -
1489 -    add('-n', '--no-daemon',
1490 -        dest='daemon_mode', action='store_false', default=True,
1491 -        help='do not daemonize the process')
1492 -
1493 -    add('-a', '--auto-close',
1494 -        dest='auto_close', action='store_true', default=False,
1495 -        help='auto close after all instances disconnect')
1496 -
1497 -    add('-i', '--no-default-dirs',
1498 -        dest='default_dirs', action='store_false', default=True,
1499 -        help='ignore the default plugin search dirs')
1500 -
1501 -    add('-o', '--log-file',
1502 -        dest='log_file', metavar='FILE',
1503 -        help='write logging output to a file, defaults to server socket +'
1504 -        ' .log')
1505 -
1506 -    add('-q', '--quiet-events',
1507 -        dest='print_events', action="store_false", default=True,
1508 -        help="silence the printing of events to stdout")
1509 -
1510 -    return parser
1511 -
1512 -
1513 -def init_logger():
1514 -    log_level = logging.CRITICAL - opts.verbose * 10
1515 -    logger = logging.getLogger()
1516 -    logger.setLevel(max(log_level, 10))
1517 -
1518 -    # Console
1519 -    handler = logging.StreamHandler()
1520 -    handler.setLevel(max(log_level + 10, 10))
1521 -    handler.setFormatter(logging.Formatter(
1522 -        '%(name)s: %(levelname)s: %(message)s'))
1523 -    logger.addHandler(handler)
1524 -
1525 -    # Logfile
1526 -    handler = logging.FileHandler(opts.log_file, 'w', 'utf-8', 1)
1527 -    handler.setLevel(max(log_level, 10))
1528 -    handler.setFormatter(logging.Formatter(
1529 -        '[%(created)f] %(name)s: %(levelname)s: %(message)s'))
1530 -    logger.addHandler(handler)
1531 -
1532 -
1533 -def main():
1534 -    global opts
1535 -
1536 -    parser = make_parser()
1537 -
1538 -    (opts, args) = parser.parse_args()
1539 -
1540 -    opts.server_socket = expandpath(opts.server_socket)
1541 -
1542 -    # Set default pid file location
1543 -    if not opts.pid_file:
1544 -        opts.pid_file = "%s.pid" % opts.server_socket
1545 -
1546 -    else:
1547 -        opts.pid_file = expandpath(opts.pid_file)
1548 -
1549 -    # Set default log file location
1550 -    if not opts.log_file:
1551 -        opts.log_file = "%s.log" % opts.server_socket
1552 -
1553 -    else:
1554 -        opts.log_file = expandpath(opts.log_file)
1555 -
1556 -    # Logging setup
1557 -    init_logger()
1558 -    logger.info('logging to %r', opts.log_file)
1559 -
1560 -    plugins = {}
1561 -
1562 -    # Load all `opts.load_plugins` into the plugins list
1563 -    for path in opts.load_plugins:
1564 -        path = expandpath(path)
1565 -        matches = glob(path)
1566 -        if not matches:
1567 -            parser.error('cannot find plugin(s): %r' % path)
1568 -
1569 -        for plugin in matches:
1570 -            (head, tail) = os.path.split(plugin)
1571 -            if tail not in plugins:
1572 -                logger.debug('found plugin: %r', plugin)
1573 -                plugins[tail] = plugin
1574 -
1575 -            else:
1576 -                logger.debug('ignoring plugin: %r', plugin)
1577 -
1578 -    # Add default plugin locations
1579 -    if opts.default_dirs:
1580 -        logger.debug('adding default plugin dirs to plugin dirs list')
1581 -        opts.plugin_dirs += [os.path.join(DATA_DIR, 'plugins/'),
1582 -            os.path.join(PREFIX, 'share/uzbl/examples/data/plugins/')]
1583 -
1584 -    else:
1585 -        logger.debug('ignoring default plugin dirs')
1586 -
1587 -    # Load all plugins in `opts.plugin_dirs` into the plugins list
1588 -    for dir in opts.plugin_dirs:
1589 -        dir = expandpath(dir)
1590 -        logger.debug('searching plugin dir: %r', dir)
1591 -        for plugin in glob(os.path.join(dir, '*.py')):
1592 -            (head, tail) = os.path.split(plugin)
1593 -            if tail not in plugins:
1594 -                logger.debug('found plugin: %r', plugin)
1595 -                plugins[tail] = plugin
1596 -
1597 -            else:
1598 -                logger.debug('ignoring plugin: %r', plugin)
1599 -
1600 -    plugins = plugins.values()
1601 -
1602 -    # Check all the paths in the plugins list are files
1603 -    for plugin in plugins:
1604 -        if not os.path.isfile(plugin):
1605 -            parser.error('plugin not a file: %r' % plugin)
1606 -
1607 -    if opts.auto_close:
1608 -        logger.debug('will auto close')
1609 -    else:
1610 -        logger.debug('will not auto close')
1611 -
1612 -    if opts.daemon_mode:
1613 -        logger.debug('will daemonize')
1614 -    else:
1615 -        logger.debug('will not daemonize')
1616 -
1617 -    opts.plugins = plugins
1618 -
1619 -    # init like {start|stop|..} daemon actions
1620 -    daemon_actions = {'start': start_action, 'stop': stop_action,
1621 -        'restart': restart_action, 'list': list_action}
1622 -
1623 -    if len(args) == 1:
1624 -        action = args[0]
1625 -        if action not in daemon_actions:
1626 -            parser.error('invalid action: %r' % action)
1627 -
1628 -    elif not args:
1629 -        action = 'start'
1630 -        logger.warning('no daemon action given, assuming %r', action)
1631 -
1632 -    else:
1633 -        parser.error('invalid action argument: %r' % args)
1634 -
1635 -    logger.info('daemon action %r', action)
1636 -    # Do action
1637 -    daemon_actions[action]()
1638 -
1639 -    logger.debug('process CPU time: %f', time.clock())
1640 -
1641 -
1642 -if __name__ == "__main__":
1643 -    main()
1644 -
1645 -
1646 -# vi: set et ts=4:
1647 +#!/usr/bin/python3
1648 +from uzbl import event_manager
1649 +event_manager.main()
1650 diff --git a/bin/uzbl-tabbed b/bin/uzbl-tabbed
1651 index b78a54a..12fa249 100755
1652 --- a/bin/uzbl-tabbed
1653 +++ b/bin/uzbl-tabbed
1654 @@ -47,6 +47,10 @@
1655  #
1656  #   Simon Lipp (sloonz)
1657  #       Various
1658 +#
1659 +#   Hakan Jerning
1660 +#       Wrote autosave_session patch to have a saved session even if
1661 +#       uzbl-tabbed is closed unexpectedly.
1662  
1663  
1664  # Dependencies:
1665 @@ -85,6 +89,7 @@
1666  #   save_session            = 1
1667  #   json_session            = 0
1668  #   session_file            = $HOME/.local/share/uzbl/session
1669 +#   autosave_session        = 0
1670  #
1671  # Inherited uzbl options:
1672  #   icon_path               = $HOME/.local/share/uzbl/uzbl.png
1673 @@ -209,6 +214,7 @@ config = {
1674    'save_session':           True,   # Save session in file when quit
1675    'saved_sessions_dir':     os.path.join(DATA_DIR, 'sessions/'),
1676    'session_file':           os.path.join(DATA_DIR, 'session'),
1677 +  'autosave_session':       False,  # Save session for every tab change
1678  
1679    # Inherited uzbl options
1680    'icon_path':              os.path.join(DATA_DIR, 'uzbl.png'),
1681 @@ -232,6 +238,11 @@ config = {
1682    'selected_https':         'foreground = "#fff"',
1683    'selected_https_text':    'foreground = "gold"',
1684  
1685 +  #Explicit config file. Unlike the other configs, this one cannot be inherited
1686 +  #from the uzbl config file, as stated above. I've only put it here because
1687 +  #load_session() is already called in UzblTabbed.__init__.
1688 +  'explicit_config_file':   None,
1689 +
1690  } # End of config dict.
1691  
1692  UZBL_TABBED_VARS = config.keys()
1693 @@ -410,7 +421,7 @@ class GlobalEventDispatcher(EventDispatcher):
1694      def new_tab(self, uri = ''):
1695          self.uzbl_tabbed.new_tab(uri)
1696  
1697 -    def new_tab_bg(self, uri = ''):
1698 +    def new_bg_tab(self, uri = ''):
1699          self.uzbl_tabbed.new_tab(uri, switch = False)
1700  
1701      def new_tab_next(self, uri = ''):
1702 @@ -434,6 +445,15 @@ class GlobalEventDispatcher(EventDispatcher):
1703      def last_tab(self):
1704          self.uzbl_tabbed.goto_tab(-1)
1705  
1706 +    def move_current_tab(self, index=0):
1707 +        self.uzbl_tabbed.move_current_tab(absolute=int(index))
1708 +
1709 +    def move_current_tab_left(self):
1710 +        self.uzbl_tabbed.move_current_tab(relative=-1)
1711 +
1712 +    def move_current_tab_right(self):
1713 +        self.uzbl_tabbed.move_current_tab(relative=1)
1714 +
1715      def preset_tabs(self, *args):
1716          self.uzbl_tabbed.run_preset_command(*args)
1717  
1718 @@ -889,6 +909,9 @@ class UzblTabbed:
1719          if(uri):
1720            cmd = cmd + ['--uri', str(uri)]
1721  
1722 +        if config['explicit_config_file'] is not None:
1723 +            cmd = cmd + ['-c', config['explicit_config_file']]
1724 +
1725          gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
1726  
1727          uzbl = UzblInstance(self, name, uri, title, switch)
1728 @@ -968,6 +991,18 @@ class UzblTabbed:
1729          while tabn < 0: tabn += ntabs
1730          self.goto_tab(tabn)
1731  
1732 +    def move_tab(self, tab, index):
1733 +        '''Move tab to position.'''
1734 +        self.notebook.reorder_child(tab, index)
1735 +        self.update_tablist()
1736 +
1737 +    def move_current_tab(self, absolute=None, relative=None):
1738 +        '''Move current tab to position.'''
1739 +        current = self.notebook.get_current_page()
1740 +        index = absolute if absolute is not None else current + relative \
1741 +                if current + relative < len(self.notebook) else 0
1742 +        tab = self.notebook.get_nth_page(current)
1743 +        self.move_tab(tab, index)
1744  
1745      def close_tab(self, tabn=None):
1746          '''Closes current tab. Supports negative indexing.'''
1747 @@ -1030,6 +1065,11 @@ class UzblTabbed:
1748          tab = self.notebook.get_nth_page(index)
1749          self.notebook.set_focus_child(tab)
1750          self.update_tablist(index)
1751 +
1752 +        if config['save_session'] and config['autosave_session']:
1753 +            if len(list(self.notebook)) > 1:
1754 +                self.save_session()
1755 +
1756          return True
1757  
1758  
1759 @@ -1038,6 +1078,7 @@ class UzblTabbed:
1760  
1761          for tab in self.notebook:
1762              self.tabs[tab].title_changed(True)
1763 +        self.update_tablist()
1764          return True
1765  
1766  
1767 @@ -1261,6 +1302,8 @@ if __name__ == "__main__":
1768        help="directory to create socket")
1769      parser.add_option('-f', '--fifodir', dest='fifodir',
1770        help="directory to create fifo")
1771 +    parser.add_option('--config-file', dest='config_file',
1772 +      help="configuration file for all uzbl-browser instances")
1773  
1774      # Parse command line options
1775      (options, uris) = parser.parse_args()
1776 @@ -1275,6 +1318,15 @@ if __name__ == "__main__":
1777          import pprint
1778          sys.stderr.write("%s\n" % pprint.pformat(config))
1779  
1780 +    if options.config_file is not None:
1781 +        if not os.path.exists(options.config_file):
1782 +            errorstr = "Explicit config file {} does not exist" % (
1783 +                options.config_file)
1784 +            error(errorstr)
1785 +            sys.exit(-1)
1786 +
1787 +        config['explicit_config_file'] = options.config_file
1788 +
1789      uzbl = UzblTabbed()
1790  
1791      if options.socketdir:
1792 diff --git a/examples/config/config b/examples/config/config
1793 index 11f1d82..d607cb4 100644
1794 --- a/examples/config/config
1795 +++ b/examples/config/config
1796 @@ -8,6 +8,7 @@ set prefix      = @(echo $PREFIX)@
1797  set data_home   = @(echo $XDG_DATA_HOME)@
1798  set cache_home  = @(echo $XDG_CACHE_HOME)@
1799  set config_home = @(echo $XDG_CONFIG_HOME)@
1800 +set local_storage_path = @data_home/uzbl/databases/
1801  
1802  # Interface paths.
1803  set fifo_dir   = /tmp
1804 @@ -70,7 +71,6 @@ set download_handler    = sync_spawn @scripts_dir/download.sh
1805  @on_event   LOAD_COMMIT    @set_status <span foreground="green">recv</span>
1806  
1807    # add some javascript to the page for other 'js' and 'script' commands to access later.
1808 -@on_event   LOAD_COMMIT    js uzbl = {};
1809  @on_event   LOAD_COMMIT    script @scripts_dir/formfiller.js
1810  @on_event   LOAD_COMMIT    script @scripts_dir/follow.js
1811  
1812 @@ -86,6 +86,8 @@ set download_handler    = sync_spawn @scripts_dir/download.sh
1813  # Switch to command mode if anything else is clicked
1814  @on_event   ROOT_ACTIVE    @set_mode command
1815  
1816 +@on_event   AUTHENTICATE   spawn @scripts_dir/auth.py "%1" "%2" "%3"
1817 +
1818  # Example CONFIG_CHANGED event handler
1819  #@on_event  CONFIG_CHANGED print Config changed: %1 = %2
1820  
1821 @@ -97,6 +99,10 @@ set download_handler    = sync_spawn @scripts_dir/download.sh
1822  # Custom CSS can be defined here, including link follower hint styles
1823  set stylesheet_uri = file://@config_home/uzbl/style.css
1824  
1825 +# If WebKits builtin authentication dialog should be used, if enabling remember
1826 +# to disable external authentication handlers
1827 +set enable_builtin_auth = 0
1828 +
1829  set show_status       = 1
1830  set status_top        = 0
1831  set status_background = #303030
1832 @@ -138,6 +144,8 @@ set useragent         = Uzbl (Webkit @{WEBKIT_MAJOR}.@{WEBKIT_MINOR}) (@(+uname
1833  
1834  # === Configure cookie blacklist ========================================================
1835  
1836 +set cookie_policy = 0
1837 +
1838  # Accept 'session cookies' from uzbl.org (when you have a whitelist all other cookies are dropped)
1839  #request WHITELIST_COOKIE domain 'uzbl.org$' expires '^$'
1840  
1841 @@ -404,6 +412,9 @@ set formfiller = spawn @scripts_dir/formfiller.sh
1842  @cbind  gt              = event NEXT_TAB
1843  @cbind  gT              = event PREV_TAB
1844  @cbind  gi<index:>_     = event GOTO_TAB %s
1845 +@cbind  <Ctrl><Left>    = event MOVE_CURRENT_TAB_LEFT
1846 +@cbind  <Ctrl><Right>   = event MOVE_CURRENT_TAB_RIGHT
1847 +@cbind  gm<index:>_     = event MOVE_CURRENT_TAB %s
1848  
1849  # Preset loading
1850  set preset = event PRESET_TABS
1851 diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py
1852 deleted file mode 100644
1853 index fc8b392..0000000
1854 --- a/examples/data/plugins/bind.py
1855 +++ /dev/null
1856 @@ -1,462 +0,0 @@
1857 -'''Plugin provides support for binds in uzbl.
1858 -
1859 -For example:
1860 -  event BIND ZZ = exit          -> bind('ZZ', 'exit')
1861 -  event BIND o _ = uri %s       -> bind('o _', 'uri %s')
1862 -  event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'")
1863 -
1864 -And it is also possible to execute a function on activation:
1865 -  bind('DD', myhandler)
1866 -'''
1867 -
1868 -import sys
1869 -import re
1870 -
1871 -# Commonly used regular expressions.
1872 -MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match
1873 -# Matches <x:y>, <'x':y>, <:'y'>, <x!y>, <'x'!y>, ...
1874 -PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>'
1875 -FIND_PROMPTS = re.compile(PROMPTS).split
1876 -VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match
1877 -
1878 -# For accessing a bind glob stack.
1879 -ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5)
1880 -
1881 -
1882 -# Custom errors.
1883 -class ArgumentError(Exception): pass
1884 -
1885 -
1886 -class Bindlet(object):
1887 -    '''Per-instance bind status/state tracker.'''
1888 -
1889 -    def __init__(self, uzbl):
1890 -        self.binds = {'global': {}}
1891 -        self.uzbl = uzbl
1892 -        self.depth = 0
1893 -        self.args = []
1894 -        self.last_mode = None
1895 -        self.after_cmds = None
1896 -        self.stack_binds = []
1897 -
1898 -        # A subset of the global mode binds containing non-stack and modkey
1899 -        # activiated binds for use in the stack mode.
1900 -        self.globals = []
1901 -
1902 -
1903 -    def __getitem__(self, key):
1904 -        return self.get_binds(key)
1905 -
1906 -
1907 -    def reset(self):
1908 -        '''Reset the tracker state and return to last mode.'''
1909 -
1910 -        self.depth = 0
1911 -        self.args = []
1912 -        self.after_cmds = None
1913 -        self.stack_binds = []
1914 -
1915 -        if self.last_mode:
1916 -            mode, self.last_mode = self.last_mode, None
1917 -            self.uzbl.config['mode'] = mode
1918 -
1919 -        del self.uzbl.config['keycmd_prompt']
1920 -
1921 -
1922 -    def stack(self, bind, args, depth):
1923 -        '''Enter or add new bind in the next stack level.'''
1924 -
1925 -        if self.depth != depth:
1926 -            if bind not in self.stack_binds:
1927 -                self.stack_binds.append(bind)
1928 -
1929 -            return
1930 -
1931 -        mode = self.uzbl.config.get('mode', None)
1932 -        if mode != 'stack':
1933 -            self.last_mode = mode
1934 -            self.uzbl.config['mode'] = 'stack'
1935 -
1936 -        self.stack_binds = [bind,]
1937 -        self.args += args
1938 -        self.depth += 1
1939 -        self.after_cmds = bind.prompts[depth]
1940 -
1941 -
1942 -    def after(self):
1943 -        '''If a stack was triggered then set the prompt and default value.'''
1944 -
1945 -        if self.after_cmds is None:
1946 -            return
1947 -
1948 -        (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None
1949 -
1950 -        self.uzbl.clear_keycmd()
1951 -        if prompt:
1952 -            self.uzbl.config['keycmd_prompt'] = prompt
1953 -
1954 -        if set and is_cmd:
1955 -            self.uzbl.send(set)
1956 -
1957 -        elif set and not is_cmd:
1958 -            self.uzbl.send('event SET_KEYCMD %s' % set)
1959 -
1960 -
1961 -    def get_binds(self, mode=None):
1962 -        '''Return the mode binds + globals. If we are stacked then return
1963 -        the filtered stack list and modkey & non-stack globals.'''
1964 -
1965 -        if mode is None:
1966 -            mode = self.uzbl.config.get('mode', None)
1967 -
1968 -        if not mode:
1969 -            mode = 'global'
1970 -
1971 -        if self.depth:
1972 -            return self.stack_binds + self.globals
1973 -
1974 -        globals = self.binds['global']
1975 -        if mode not in self.binds or mode == 'global':
1976 -            return filter(None, globals.values())
1977 -
1978 -        binds = dict(globals.items() + self.binds[mode].items())
1979 -        return filter(None, binds.values())
1980 -
1981 -
1982 -    def add_bind(self, mode, glob, bind=None):
1983 -        '''Insert (or override) a bind into the mode bind dict.'''
1984 -
1985 -        if mode not in self.binds:
1986 -            self.binds[mode] = {glob: bind}
1987 -            return
1988 -
1989 -        binds = self.binds[mode]
1990 -        binds[glob] = bind
1991 -
1992 -        if mode == 'global':
1993 -            # Regen the global-globals list.
1994 -            self.globals = []
1995 -            for bind in binds.values():
1996 -                if bind is not None and bind.is_global:
1997 -                    self.globals.append(bind)
1998 -
1999 -
2000 -def ismodbind(glob):
2001 -    '''Return True if the glob specifies a modbind.'''
2002 -
2003 -    return bool(MOD_START(glob))
2004 -
2005 -
2006 -def split_glob(glob):
2007 -    '''Take a string of the form "<Mod1><Mod2>cmd _" and return a list of the
2008 -    modkeys in the glob and the command.'''
2009 -
2010 -    mods = set()
2011 -    while True:
2012 -        match = MOD_START(glob)
2013 -        if not match:
2014 -            break
2015 -
2016 -        end = match.span()[1]
2017 -        mods.add(glob[:end])
2018 -        glob = glob[end:]
2019 -
2020 -    return (mods, glob)
2021 -
2022 -
2023 -class Bind(object):
2024 -
2025 -    # Class attribute to hold the number of Bind classes created.
2026 -    counter = [0,]
2027 -
2028 -    def __init__(self, glob, handler, *args, **kargs):
2029 -        self.is_callable = callable(handler)
2030 -        self._repr_cache = None
2031 -
2032 -        if not glob:
2033 -            raise ArgumentError('glob cannot be blank')
2034 -
2035 -        if self.is_callable:
2036 -            self.function = handler
2037 -            self.args = args
2038 -            self.kargs = kargs
2039 -
2040 -        elif kargs:
2041 -            raise ArgumentError('cannot supply kargs for uzbl commands')
2042 -
2043 -        elif hasattr(handler, '__iter__'):
2044 -            self.commands = handler
2045 -
2046 -        else:
2047 -            self.commands = [handler,] + list(args)
2048 -
2049 -        self.glob = glob
2050 -
2051 -        # Assign unique id.
2052 -        self.counter[0] += 1
2053 -        self.bid = self.counter[0]
2054 -
2055 -        self.split = split = FIND_PROMPTS(glob)
2056 -        self.prompts = []
2057 -        for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]):
2058 -            prompt, set = map(unquote, [prompt, set])
2059 -            cmd = True if cmd == '!' else False
2060 -            if prompt and prompt[-1] != ":":
2061 -                prompt = "%s:" % prompt
2062 -
2063 -            self.prompts.append((prompt, cmd, set))
2064 -
2065 -        # Check that there is nothing like: fl*<int:>*
2066 -        for glob in split[:-1:4]:
2067 -            if glob.endswith('*'):
2068 -                msg = "token '*' not at the end of a prompt bind: %r" % split
2069 -                raise SyntaxError(msg)
2070 -
2071 -        # Check that there is nothing like: fl<prompt1:><prompt2:>_
2072 -        for glob in split[4::4]:
2073 -            if not glob:
2074 -                msg = 'found null segment after first prompt: %r' % split
2075 -                raise SyntaxError(msg)
2076 -
2077 -        stack = []
2078 -        for (index, glob) in enumerate(reversed(split[::4])):
2079 -            # Is the binding a MODCMD or KEYCMD:
2080 -            mod_cmd = ismodbind(glob)
2081 -
2082 -            # Do we execute on UPDATES or EXEC events?
2083 -            on_exec = True if glob[-1] in ['!', '_'] else False
2084 -
2085 -            # Does the command take arguments?
2086 -            has_args = True if glob[-1] in ['*', '_'] else False
2087 -
2088 -            glob = glob[:-1] if has_args or on_exec else glob
2089 -            mods, glob = split_glob(glob)
2090 -            stack.append((on_exec, has_args, mods, glob, index))
2091 -
2092 -        self.stack = list(reversed(stack))
2093 -        self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD])
2094 -
2095 -
2096 -    def __getitem__(self, depth):
2097 -        '''Get bind info at a depth.'''
2098 -
2099 -        if self.is_global:
2100 -            return self.stack[0]
2101 -
2102 -        return self.stack[depth]
2103 -
2104 -
2105 -    def __repr__(self):
2106 -        if self._repr_cache:
2107 -            return self._repr_cache
2108 -
2109 -        args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
2110 -
2111 -        if self.is_callable:
2112 -            args.append('function=%r' % self.function)
2113 -            if self.args:
2114 -                args.append('args=%r' % self.args)
2115 -
2116 -            if self.kargs:
2117 -                args.append('kargs=%r' % self.kargs)
2118 -
2119 -        else:
2120 -            cmdlen = len(self.commands)
2121 -            cmds = self.commands[0] if cmdlen == 1 else self.commands
2122 -            args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds))
2123 -
2124 -        self._repr_cache = '<Bind(%s)>' % ', '.join(args)
2125 -        return self._repr_cache
2126 -
2127 -
2128 -def exec_bind(uzbl, bind, *args, **kargs):
2129 -    '''Execute bind objects.'''
2130 -
2131 -    uzbl.event("EXEC_BIND", bind, args, kargs)
2132 -
2133 -    if bind.is_callable:
2134 -        args += bind.args
2135 -        kargs = dict(bind.kargs.items()+kargs.items())
2136 -        bind.function(uzbl, *args, **kargs)
2137 -        return
2138 -
2139 -    if kargs:
2140 -        raise ArgumentError('cannot supply kargs for uzbl commands')
2141 -
2142 -    commands = []
2143 -    cmd_expand = uzbl.cmd_expand
2144 -    for cmd in bind.commands:
2145 -        cmd = cmd_expand(cmd, args)
2146 -        uzbl.send(cmd)
2147 -
2148 -
2149 -def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs):
2150 -    '''Add a mode bind.'''
2151 -
2152 -    bindlet = uzbl.bindlet
2153 -
2154 -    if not hasattr(modes, '__iter__'):
2155 -        modes = unicode(modes).split(',')
2156 -
2157 -    # Sort and filter binds.
2158 -    modes = filter(None, map(unicode.strip, modes))
2159 -
2160 -    if callable(handler) or (handler is not None and handler.strip()):
2161 -        bind = Bind(glob, handler, *args, **kargs)
2162 -
2163 -    else:
2164 -        bind = None
2165 -
2166 -    for mode in modes:
2167 -        if not VALID_MODE(mode):
2168 -            raise NameError('invalid mode name: %r' % mode)
2169 -
2170 -    for mode in modes:
2171 -        if mode[0] == '-':
2172 -            mode, bind = mode[1:], None
2173 -
2174 -        bindlet.add_bind(mode, glob, bind)
2175 -        uzbl.event('ADDED_MODE_BIND', mode, glob, bind)
2176 -
2177 -
2178 -def bind(uzbl, glob, handler, *args, **kargs):
2179 -    '''Legacy bind function.'''
2180 -
2181 -    mode_bind(uzbl, 'global', glob, handler, *args, **kargs)
2182 -
2183 -
2184 -def parse_mode_bind(uzbl, args):
2185 -    '''Parser for the MODE_BIND event.
2186 -
2187 -    Example events:
2188 -        MODE_BIND <mode>         <bind>        = <command>
2189 -        MODE_BIND command        o<location:>_ = uri %s
2190 -        MODE_BIND insert,command <BackSpace>   = ...
2191 -        MODE_BIND global         ...           = ...
2192 -        MODE_BIND global,-insert ...           = ...
2193 -    '''
2194 -
2195 -    if not args:
2196 -        raise ArgumentError('missing bind arguments')
2197 -
2198 -    split = map(unicode.strip, args.split(' ', 1))
2199 -    if len(split) != 2:
2200 -        raise ArgumentError('missing mode or bind section: %r' % args)
2201 -
2202 -    modes, args = split[0].split(','), split[1]
2203 -    split = map(unicode.strip, args.split('=', 1))
2204 -    if len(split) != 2:
2205 -        raise ArgumentError('missing delimiter in bind section: %r' % args)
2206 -
2207 -    glob, command = split
2208 -    mode_bind(uzbl, modes, glob, command)
2209 -
2210 -
2211 -def parse_bind(uzbl, args):
2212 -    '''Legacy parsing of the BIND event and conversion to the new format.
2213 -
2214 -    Example events:
2215 -        request BIND <bind>        = <command>
2216 -        request BIND o<location:>_ = uri %s
2217 -        request BIND <BackSpace>   = ...
2218 -        request BIND ...           = ...
2219 -    '''
2220 -
2221 -    parse_mode_bind(uzbl, "global %s" % args)
2222 -
2223 -
2224 -def mode_changed(uzbl, mode):
2225 -    '''Clear the stack on all non-stack mode changes.'''
2226 -
2227 -    if mode != 'stack':
2228 -        uzbl.bindlet.reset()
2229 -
2230 -
2231 -def match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet):
2232 -    (on_exec, has_args, mod_cmd, glob, more) = bind[depth]
2233 -    cmd = keylet.modcmd if mod_cmd else keylet.keycmd
2234 -
2235 -    if mod_cmd and modstate != mod_cmd:
2236 -        return False
2237 -
2238 -    if has_args:
2239 -        if not cmd.startswith(glob):
2240 -            return False
2241 -
2242 -        args = [cmd[len(glob):],]
2243 -
2244 -    elif cmd != glob:
2245 -        return False
2246 -
2247 -    else:
2248 -        args = []
2249 -
2250 -    if bind.is_global or (not more and depth == 0):
2251 -        exec_bind(uzbl, bind, *args)
2252 -        if not has_args:
2253 -            uzbl.clear_current()
2254 -
2255 -        return True
2256 -
2257 -    elif more:
2258 -        bindlet.stack(bind, args, depth)
2259 -        (on_exec, has_args, mod_cmd, glob, more) = bind[depth+1]
2260 -        if not on_exec and has_args and not glob and not more:
2261 -            exec_bind(uzbl, bind, *(args+['',]))
2262 -
2263 -        return False
2264 -
2265 -    args = bindlet.args + args
2266 -    exec_bind(uzbl, bind, *args)
2267 -    if not has_args or on_exec:
2268 -        del uzbl.config['mode']
2269 -        bindlet.reset()
2270 -
2271 -    return True
2272 -
2273 -
2274 -def key_event(uzbl, modstate, keylet, mod_cmd=False, on_exec=False):
2275 -    bindlet = uzbl.bindlet
2276 -    depth = bindlet.depth
2277 -    for bind in bindlet.get_binds():
2278 -        t = bind[depth]
2279 -        if (bool(t[MOD_CMD]) != mod_cmd) or (t[ON_EXEC] != on_exec):
2280 -            continue
2281 -
2282 -        if match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet):
2283 -            return
2284 -
2285 -    bindlet.after()
2286 -
2287 -    # Return to the previous mode if the KEYCMD_EXEC keycmd doesn't match any
2288 -    # binds in the stack mode.
2289 -    if on_exec and not mod_cmd and depth and depth == bindlet.depth:
2290 -        del uzbl.config['mode']
2291 -
2292 -
2293 -# plugin init hook
2294 -def init(uzbl):
2295 -    '''Export functions and connect handlers to events.'''
2296 -
2297 -    connect_dict(uzbl, {
2298 -        'BIND':             parse_bind,
2299 -        'MODE_BIND':        parse_mode_bind,
2300 -        'MODE_CHANGED':     mode_changed,
2301 -    })
2302 -
2303 -    # Connect key related events to the key_event function.
2304 -    events = [['KEYCMD_UPDATE', 'KEYCMD_EXEC'],
2305 -              ['MODCMD_UPDATE', 'MODCMD_EXEC']]
2306 -
2307 -    for mod_cmd in range(2):
2308 -        for on_exec in range(2):
2309 -            event = events[mod_cmd][on_exec]
2310 -            connect(uzbl, event, key_event, bool(mod_cmd), bool(on_exec))
2311 -
2312 -    export_dict(uzbl, {
2313 -        'bind':         bind,
2314 -        'mode_bind':    mode_bind,
2315 -        'bindlet':      Bindlet(uzbl),
2316 -    })
2317 -
2318 -# vi: set et ts=4:
2319 diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py
2320 deleted file mode 100644
2321 index b007975..0000000
2322 --- a/examples/data/plugins/cmd_expand.py
2323 +++ /dev/null
2324 @@ -1,40 +0,0 @@
2325 -def escape(str):
2326 -    for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]:
2327 -        str = str.replace(char, (level * '\\') + char)
2328 -
2329 -    return str
2330 -
2331 -
2332 -def cmd_expand(uzbl, cmd, args):
2333 -    '''Exports a function that provides the following
2334 -    expansions in any uzbl command string:
2335 -
2336 -        %s = replace('%s', ' '.join(args))
2337 -        %r = replace('%r', "'%s'" % escaped(' '.join(args)))
2338 -        %1 = replace('%1', arg[0])
2339 -        %2 = replace('%2', arg[1])
2340 -        %n = replace('%n', arg[n-1])
2341 -    '''
2342 -
2343 -    # Ensure (1) all string representable and (2) correct string encoding.
2344 -    args = map(unicode, args)
2345 -
2346 -    # Direct string replace.
2347 -    if '%s' in cmd:
2348 -        cmd = cmd.replace('%s', ' '.join(args))
2349 -
2350 -    # Escaped and quoted string replace.
2351 -    if '%r' in cmd:
2352 -        cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args)))
2353 -
2354 -    # Arg index string replace.
2355 -    for (index, arg) in enumerate(args):
2356 -        index += 1
2357 -        if '%%%d' % index in cmd:
2358 -            cmd = cmd.replace('%%%d' % index, unicode(arg))
2359 -
2360 -    return cmd
2361 -
2362 -# plugin init hook
2363 -def init(uzbl):
2364 -    export(uzbl, 'cmd_expand', cmd_expand)
2365 diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py
2366 deleted file mode 100644
2367 index e8c7f34..0000000
2368 --- a/examples/data/plugins/completion.py
2369 +++ /dev/null
2370 @@ -1,179 +0,0 @@
2371 -'''Keycmd completion.'''
2372 -
2373 -import re
2374 -
2375 -# Completion level
2376 -NONE, ONCE, LIST, COMPLETE = range(4)
2377 -
2378 -# The reverse keyword finding re.
2379 -FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall
2380 -
2381 -# Formats
2382 -LIST_FORMAT = "<span> %s </span>"
2383 -ITEM_FORMAT = "<span @hint_style>%s</span>%s"
2384 -
2385 -def escape(str):
2386 -    return str.replace("@", "\@")
2387 -
2388 -
2389 -def get_incomplete_keyword(uzbl):
2390 -    '''Gets the segment of the keycmd leading up to the cursor position and
2391 -    uses a regular expression to search backwards finding parially completed
2392 -    keywords or @variables. Returns a null string if the correct completion
2393 -    conditions aren't met.'''
2394 -
2395 -    keylet = uzbl.keylet
2396 -    left_segment = keylet.keycmd[:keylet.cursor]
2397 -    partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip()
2398 -    if partial.startswith('set '):
2399 -        return ('@%s' % partial[4:].lstrip(), True)
2400 -
2401 -    return (partial, False)
2402 -
2403 -
2404 -def stop_completion(uzbl, *args):
2405 -    '''Stop command completion and return the level to NONE.'''
2406 -
2407 -    uzbl.completion.level = NONE
2408 -    del uzbl.config['completion_list']
2409 -
2410 -
2411 -def complete_completion(uzbl, partial, hint, set_completion=False):
2412 -    '''Inject the remaining porition of the keyword into the keycmd then stop
2413 -    the completioning.'''
2414 -
2415 -    if set_completion:
2416 -        remainder = "%s = " % hint[len(partial):]
2417 -
2418 -    else:
2419 -        remainder = "%s " % hint[len(partial):]
2420 -
2421 -    uzbl.inject_keycmd(remainder)
2422 -    stop_completion(uzbl)
2423 -
2424 -
2425 -def partial_completion(uzbl, partial, hint):
2426 -    '''Inject a common portion of the hints into the keycmd.'''
2427 -
2428 -    remainder = hint[len(partial):]
2429 -    uzbl.inject_keycmd(remainder)
2430 -
2431 -
2432 -def update_completion_list(uzbl, *args):
2433 -    '''Checks if the user still has a partially completed keyword under his
2434 -    cursor then update the completion hints list.'''
2435 -
2436 -    partial = get_incomplete_keyword(uzbl)[0]
2437 -    if not partial:
2438 -        return stop_completion(uzbl)
2439 -
2440 -    if uzbl.completion.level < LIST:
2441 -        return
2442 -
2443 -    hints = filter(lambda h: h.startswith(partial), uzbl.completion)
2444 -    if not hints:
2445 -        del uzbl.config['completion_list']
2446 -        return
2447 -
2448 -    j = len(partial)
2449 -    l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)]
2450 -    uzbl.config['completion_list'] = LIST_FORMAT % ' '.join(l)
2451 -
2452 -
2453 -def start_completion(uzbl, *args):
2454 -
2455 -    comp = uzbl.completion
2456 -    if comp.locked:
2457 -        return
2458 -
2459 -    (partial, set_completion) = get_incomplete_keyword(uzbl)
2460 -    if not partial:
2461 -        return stop_completion(uzbl)
2462 -
2463 -    if comp.level < COMPLETE:
2464 -        comp.level += 1
2465 -
2466 -    hints = filter(lambda h: h.startswith(partial), comp)
2467 -    if not hints:
2468 -        return
2469 -
2470 -    elif len(hints) == 1:
2471 -        comp.lock()
2472 -        complete_completion(uzbl, partial, hints[0], set_completion)
2473 -        comp.unlock()
2474 -        return
2475 -
2476 -    elif partial in hints and comp.level == COMPLETE:
2477 -        comp.lock()
2478 -        complete_completion(uzbl, partial, partial, set_completion)
2479 -        comp.unlock()
2480 -        return
2481 -
2482 -    smalllen, smallest = sorted([(len(h), h) for h in hints])[0]
2483 -    common = ''
2484 -    for i in range(len(partial), smalllen):
2485 -        char, same = smallest[i], True
2486 -        for hint in hints:
2487 -            if hint[i] != char:
2488 -                same = False
2489 -                break
2490 -
2491 -        if not same:
2492 -            break
2493 -
2494 -        common += char
2495 -
2496 -    if common:
2497 -        comp.lock()
2498 -        partial_completion(uzbl, partial, partial+common)
2499 -        comp.unlock()
2500 -
2501 -    update_completion_list(uzbl)
2502 -
2503 -
2504 -def add_builtins(uzbl, builtins):
2505 -    '''Pump the space delimited list of builtin commands into the
2506 -    builtin list.'''
2507 -
2508 -    uzbl.completion.update(builtins.split())
2509 -
2510 -
2511 -def add_config_key(uzbl, key, value):
2512 -    '''Listen on the CONFIG_CHANGED event and add config keys to the variable
2513 -    list for @var<Tab> like expansion support.'''
2514 -
2515 -    uzbl.completion.add("@%s" % key)
2516 -
2517 -
2518 -class Completions(set):
2519 -    def __init__(self):
2520 -        set.__init__(self)
2521 -        self.locked = False
2522 -        self.level = NONE
2523 -
2524 -    def lock(self):
2525 -        self.locked = True
2526 -
2527 -    def unlock(self):
2528 -        self.locked = False
2529 -
2530 -
2531 -def init(uzbl):
2532 -    '''Export functions and connect handlers to events.'''
2533 -
2534 -    export_dict(uzbl, {
2535 -        'completion':       Completions(),
2536 -        'start_completion': start_completion,
2537 -    })
2538 -
2539 -    connect_dict(uzbl, {
2540 -        'BUILTINS':         add_builtins,
2541 -        'CONFIG_CHANGED':   add_config_key,
2542 -        'KEYCMD_CLEARED':   stop_completion,
2543 -        'KEYCMD_EXEC':      stop_completion,
2544 -        'KEYCMD_UPDATE':    update_completion_list,
2545 -        'START_COMPLETION': start_completion,
2546 -        'STOP_COMPLETION':  stop_completion,
2547 -    })
2548 -
2549 -    uzbl.send('dump_config_as_events')
2550 diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py
2551 deleted file mode 100644
2552 index c9bdf67..0000000
2553 --- a/examples/data/plugins/config.py
2554 +++ /dev/null
2555 @@ -1,91 +0,0 @@
2556 -from re import compile
2557 -from types import BooleanType
2558 -from UserDict import DictMixin
2559 -
2560 -valid_key = compile('^[A-Za-z0-9_\.]+$').match
2561 -
2562 -class Config(DictMixin):
2563 -    def __init__(self, uzbl):
2564 -        self.uzbl = uzbl
2565 -
2566 -        # Create the base dict and map allowed methods to `self`.
2567 -        self.data = data = {}
2568 -
2569 -        methods = ['__contains__', '__getitem__', '__iter__',
2570 -            '__len__',  'get', 'has_key', 'items', 'iteritems',
2571 -            'iterkeys', 'itervalues', 'values']
2572 -
2573 -        for method in methods:
2574 -            setattr(self, method, getattr(data, method))
2575 -
2576 -
2577 -    def __setitem__(self, key, value):
2578 -        self.set(key, value)
2579 -
2580 -    def __delitem__(self, key):
2581 -        self.set(key)
2582 -
2583 -    def update(self, other=None, **kwargs):
2584 -        if other is None:
2585 -            other = {}
2586 -
2587 -        for (key, value) in dict(other).items() + kwargs.items():
2588 -            self[key] = value
2589 -
2590 -
2591 -    def set(self, key, value='', force=False):
2592 -        '''Generates a `set <key> = <value>` command string to send to the
2593 -        current uzbl instance.
2594 -
2595 -        Note that the config dict isn't updated by this function. The config
2596 -        dict is only updated after a successful `VARIABLE_SET ..` event
2597 -        returns from the uzbl instance.'''
2598 -
2599 -        assert valid_key(key)
2600 -
2601 -        if type(value) == BooleanType:
2602 -            value = int(value)
2603 -
2604 -        else:
2605 -            value = unicode(value)
2606 -            assert '\n' not in value
2607 -
2608 -        if not force and key in self and self[key] == value:
2609 -            return
2610 -
2611 -        self.uzbl.send(u'set %s = %s' % (key, value))
2612 -
2613 -
2614 -def parse_set_event(uzbl, args):
2615 -    '''Parse `VARIABLE_SET <var> <type> <value>` event and load the
2616 -    (key, value) pair into the `uzbl.config` dict.'''
2617 -
2618 -    (key, type, raw_value) = (args.split(' ', 2) + ['',])[:3]
2619 -
2620 -    assert valid_key(key)
2621 -    assert type in types
2622 -
2623 -    new_value = types[type](raw_value)
2624 -    old_value = uzbl.config.get(key, None)
2625 -
2626 -    # Update new value.
2627 -    uzbl.config.data[key] = new_value
2628 -
2629 -    if old_value != new_value:
2630 -        uzbl.event('CONFIG_CHANGED', key, new_value)
2631 -
2632 -    # Cleanup null config values.
2633 -    if type == 'str' and not new_value:
2634 -        del uzbl.config.data[key]
2635 -
2636 -
2637 -# plugin init hook
2638 -def init(uzbl):
2639 -    global types
2640 -    types = {'int': int, 'float': float, 'str': unquote}
2641 -    export(uzbl, 'config', Config(uzbl))
2642 -    connect(uzbl, 'VARIABLE_SET', parse_set_event)
2643 -
2644 -# plugin cleanup hook
2645 -def cleanup(uzbl):
2646 -    uzbl.config.data.clear()
2647 diff --git a/examples/data/plugins/cookies.py b/examples/data/plugins/cookies.py
2648 deleted file mode 100644
2649 index bf59e96..0000000
2650 --- a/examples/data/plugins/cookies.py
2651 +++ /dev/null
2652 @@ -1,222 +0,0 @@
2653 -""" Basic cookie manager
2654 -    forwards cookies to all other instances connected to the event manager"""
2655 -
2656 -from collections import defaultdict
2657 -import os, re, stat
2658 -
2659 -# these are symbolic names for the components of the cookie tuple
2660 -symbolic = {'domain': 0, 'path':1, 'name':2, 'value':3, 'scheme':4, 'expires':5}
2661 -
2662 -# allows for partial cookies
2663 -# ? allow wildcard in key
2664 -def match(key, cookie):
2665 -    for k,c in zip(key,cookie):
2666 -        if k != c:
2667 -            return False
2668 -    return True
2669 -
2670 -class NullStore(object):
2671 -    def add_cookie(self, rawcookie, cookie):
2672 -        pass
2673 -
2674 -    def delete_cookie(self, rkey, key):
2675 -        pass
2676 -
2677 -class ListStore(list):
2678 -    def add_cookie(self, rawcookie, cookie):
2679 -        self.append(rawcookie)
2680 -
2681 -    def delete_cookie(self, rkey, key):
2682 -        self[:] = [x for x in self if not match(key, splitquoted(x))]
2683 -
2684 -class TextStore(object):
2685 -    def __init__(self, filename):
2686 -        self.filename = filename
2687 -        try:
2688 -          # make sure existing cookie jar is not world-open
2689 -          perm_mode = os.stat(self.filename).st_mode
2690 -          if (perm_mode & (stat.S_IRWXO | stat.S_IRWXG)) > 0:
2691 -              safe_perm = stat.S_IMODE(perm_mode) & ~(stat.S_IRWXO | stat.S_IRWXG)
2692 -              os.chmod(self.filename, safe_perm)
2693 -        except OSError:
2694 -            pass
2695 -
2696 -    def as_event(self, cookie):
2697 -        """Convert cookie.txt row to uzbls cookie event format"""
2698 -        scheme = {
2699 -            'TRUE'  : 'https',
2700 -            'FALSE' : 'http'
2701 -        }
2702 -        extra = ''
2703 -        if cookie[0].startswith("#HttpOnly_"):
2704 -            extra = 'Only'
2705 -            domain = cookie[0][len("#HttpOnly_"):]
2706 -        elif cookie[0].startswith('#'):
2707 -            return None
2708 -        else:
2709 -            domain = cookie[0]
2710 -        try:
2711 -            return (domain,
2712 -                cookie[2],
2713 -                cookie[5],
2714 -                cookie[6],
2715 -                scheme[cookie[3]] + extra,
2716 -                cookie[4])
2717 -        except (KeyError,IndexError):
2718 -            # Let malformed rows pass through like comments
2719 -            return None
2720 -
2721 -    def as_file(self, cookie):
2722 -        """Convert cookie event to cookie.txt row"""
2723 -        secure = {
2724 -            'https' : 'TRUE',
2725 -            'http'  : 'FALSE',
2726 -            'httpsOnly' : 'TRUE',
2727 -            'httpOnly'  : 'FALSE'
2728 -        }
2729 -        http_only = {
2730 -            'https' : '',
2731 -            'http'  : '',
2732 -            'httpsOnly' : '#HttpOnly_',
2733 -            'httpOnly'  : '#HttpOnly_'
2734 -        }
2735 -        return (http_only[cookie[4]] + cookie[0],
2736 -            'TRUE' if cookie[0].startswith('.') else 'FALSE',
2737 -            cookie[1],
2738 -            secure[cookie[4]],
2739 -            cookie[5],
2740 -            cookie[2],
2741 -            cookie[3])
2742 -
2743 -    def add_cookie(self, rawcookie, cookie):
2744 -        assert len(cookie) == 6
2745 -
2746 -        # delete equal cookies (ignoring expire time, value and secure flag)
2747 -        self.delete_cookie(None, cookie[:-3])
2748 -
2749 -        # restrict umask before creating the cookie jar
2750 -        curmask=os.umask(0)
2751 -        os.umask(curmask| stat.S_IRWXO | stat.S_IRWXG)
2752 -
2753 -        first = not os.path.exists(self.filename)
2754 -        with open(self.filename, 'a') as f:
2755 -            if first:
2756 -                print >> f, "# HTTP Cookie File"
2757 -            print >> f, '\t'.join(self.as_file(cookie))
2758 -        os.umask(curmask)
2759 -
2760 -    def delete_cookie(self, rkey, key):
2761 -        if not os.path.exists(self.filename):
2762 -            return
2763 -
2764 -        # restrict umask before creating the cookie jar
2765 -        curmask=os.umask(0)
2766 -        os.umask(curmask | stat.S_IRWXO | stat.S_IRWXG)
2767 -
2768 -        # read all cookies
2769 -        with open(self.filename, 'r') as f:
2770 -            cookies = f.readlines()
2771 -
2772 -        # write those that don't match the cookie to delete
2773 -        with open(self.filename, 'w') as f:
2774 -            for l in cookies:
2775 -                c = self.as_event(l.split('\t'))
2776 -                if c is None or not match(key, c):
2777 -                    print >> f, l,
2778 -        os.umask(curmask)
2779 -
2780 -xdg_data_home = os.environ.get('XDG_DATA_HOME', os.path.join(os.environ['HOME'], '.local/share'))
2781 -DefaultStore = TextStore(os.path.join(xdg_data_home, 'uzbl/cookies.txt'))
2782 -SessionStore = TextStore(os.path.join(xdg_data_home, 'uzbl/session-cookies.txt'))
2783 -
2784 -def match_list(_list, cookie):
2785 -    for matcher in _list:
2786 -        for component, match in matcher:
2787 -            if match(cookie[component]) is None:
2788 -                break
2789 -        else:
2790 -            return True
2791 -    return False
2792 -
2793 -# accept a cookie only when:
2794 -# a. there is no whitelist and the cookie is in the blacklist
2795 -# b. the cookie is in the whitelist and not in the blacklist
2796 -def accept_cookie(uzbl, cookie):
2797 -    if uzbl.cookie_whitelist:
2798 -        if match_list(uzbl.cookie_whitelist, cookie):
2799 -            return not match_list(uzbl.cookie_blacklist, cookie)
2800 -        return False
2801 -
2802 -    return not match_list(uzbl.cookie_blacklist, cookie)
2803 -
2804 -def expires_with_session(uzbl, cookie):
2805 -    return cookie[5] == ''
2806 -
2807 -def get_recipents(uzbl):
2808 -    """ get a list of Uzbl instances to send the cookie too. """
2809 -    # This could be a lot more interesting
2810 -    return [u for u in uzbl.parent.uzbls.values() if u is not uzbl]
2811 -
2812 -def get_store(uzbl, session=False):
2813 -    if session:
2814 -        return SessionStore
2815 -    return DefaultStore
2816 -
2817 -def add_cookie(uzbl, cookie):
2818 -    splitted = splitquoted(cookie)
2819 -    if accept_cookie(uzbl, splitted):
2820 -        for u in get_recipents(uzbl):
2821 -            u.send('add_cookie %s' % cookie)
2822 -
2823 -        get_store(uzbl, expires_with_session(uzbl, splitted)).add_cookie(cookie, splitted)
2824 -    else:
2825 -        logger.debug('cookie %r is blacklisted' % splitted)
2826 -        uzbl.send('delete_cookie %s' % cookie)
2827 -
2828 -def delete_cookie(uzbl, cookie):
2829 -    for u in get_recipents(uzbl):
2830 -        u.send('delete_cookie %s' % cookie)
2831 -
2832 -    splitted = splitquoted(cookie)
2833 -    if len(splitted) == 6:
2834 -        get_store(uzbl, expires_with_session(uzbl, splitted)).delete_cookie(cookie, splitted)
2835 -    else:
2836 -        for store in set([get_store(uzbl, session) for session in (True, False)]):
2837 -            store.delete_cookie(cookie, splitted)
2838 -
2839 -# add a cookie matcher to a whitelist or a blacklist.
2840 -# a matcher is a list of (component, re) tuples that matches a cookie when the
2841 -# "component" part of the cookie matches the regular expression "re".
2842 -# "component" is one of the keys defined in the variable "symbolic" above,
2843 -# or the index of a component of a cookie tuple.
2844 -def add_cookie_matcher(_list, arg):
2845 -    args = splitquoted(arg)
2846 -    mlist = []
2847 -    for (component, regexp) in zip(args[0::2], args[1::2]):
2848 -        try:
2849 -            component = symbolic[component]
2850 -        except KeyError:
2851 -            component = int(component)
2852 -        assert component <= 5
2853 -        mlist.append((component, re.compile(regexp).search))
2854 -    _list.append(mlist)
2855 -
2856 -def blacklist(uzbl, arg):
2857 -    add_cookie_matcher(uzbl.cookie_blacklist, arg)
2858 -
2859 -def whitelist(uzbl, arg):
2860 -    add_cookie_matcher(uzbl.cookie_whitelist, arg)
2861 -
2862 -def init(uzbl):
2863 -    connect_dict(uzbl, {
2864 -        'ADD_COOKIE':       add_cookie,
2865 -        'DELETE_COOKIE':    delete_cookie,
2866 -        'BLACKLIST_COOKIE': blacklist,
2867 -        'WHITELIST_COOKIE': whitelist
2868 -    })
2869 -    export_dict(uzbl, {
2870 -        'cookie_blacklist' : [],
2871 -        'cookie_whitelist' : []
2872 -    })
2873 -
2874 -# vi: set et ts=4:
2875 diff --git a/examples/data/plugins/downloads.py b/examples/data/plugins/downloads.py
2876 deleted file mode 100644
2877 index 8d796ce..0000000
2878 --- a/examples/data/plugins/downloads.py
2879 +++ /dev/null
2880 @@ -1,77 +0,0 @@
2881 -# this plugin does a very simple display of download progress. to use it, add
2882 -# @downloads to your status_format.
2883 -
2884 -import os
2885 -ACTIVE_DOWNLOADS = {}
2886 -
2887 -# after a download's status has changed this is called to update the status bar
2888 -def update_download_section(uzbl):
2889 -    global ACTIVE_DOWNLOADS
2890 -
2891 -    if len(ACTIVE_DOWNLOADS):
2892 -        # add a newline before we list downloads
2893 -        result = '&#10;downloads:'
2894 -        for path in ACTIVE_DOWNLOADS:
2895 -            # add each download
2896 -            fn = os.path.basename(path)
2897 -            progress, = ACTIVE_DOWNLOADS[path]
2898 -
2899 -            dl = " %s (%d%%)" % (fn, progress * 100)
2900 -
2901 -            # replace entities to make sure we don't break our markup
2902 -            # (this could be done with an @[]@ expansion in uzbl, but then we
2903 -            # can't use the &#10; above to make a new line)
2904 -            dl = dl.replace("&", "&amp;").replace("<", "&lt;")
2905 -            result += dl
2906 -    else:
2907 -        result = ''
2908 -
2909 -    # and the result gets saved to an uzbl variable that can be used in
2910 -    # status_format
2911 -    if uzbl.config.get('downloads', '') != result:
2912 -          uzbl.config['downloads'] = result
2913 -
2914 -def download_started(uzbl, args):
2915 -    # parse the arguments
2916 -    args = splitquoted(args)
2917 -    destination_path = args[0]
2918 -
2919 -    # add to the list of active downloads
2920 -    global ACTIVE_DOWNLOADS
2921 -    ACTIVE_DOWNLOADS[destination_path] = (0.0,)
2922 -
2923 -    # update the progress
2924 -    update_download_section(uzbl)
2925 -
2926 -def download_progress(uzbl, args):
2927 -    # parse the arguments
2928 -    args = splitquoted(args)
2929 -    destination_path = args[0]
2930 -    progress = float(args[1])
2931 -
2932 -    # update the progress
2933 -    global ACTIVE_DOWNLOADS
2934 -    ACTIVE_DOWNLOADS[destination_path] = (progress,)
2935 -
2936 -    # update the status bar variable
2937 -    update_download_section(uzbl)
2938 -
2939 -def download_complete(uzbl, args):
2940 -    # parse the arguments
2941 -    args = splitquoted(args)
2942 -    destination_path = args[0]
2943 -
2944 -    # remove from the list of active downloads
2945 -    global ACTIVE_DOWNLOADS
2946 -    del ACTIVE_DOWNLOADS[destination_path]
2947 -
2948 -    # update the status bar variable
2949 -    update_download_section(uzbl)
2950 -
2951 -# plugin init hook
2952 -def init(uzbl):
2953 -    connect_dict(uzbl, {
2954 -        'DOWNLOAD_STARTED':     download_started,
2955 -        'DOWNLOAD_PROGRESS':    download_progress,
2956 -        'DOWNLOAD_COMPLETE':    download_complete,
2957 -    })
2958 diff --git a/examples/data/plugins/history.py b/examples/data/plugins/history.py
2959 deleted file mode 100644
2960 index f42f86f..0000000
2961 --- a/examples/data/plugins/history.py
2962 +++ /dev/null
2963 @@ -1,129 +0,0 @@
2964 -import random
2965 -
2966 -shared_history = {'':[]}
2967 -
2968 -class History(object):
2969 -    def __init__(self, uzbl):
2970 -        self.uzbl = uzbl
2971 -        self._temporary = []
2972 -        self.prompt = ''
2973 -        self.cursor = None
2974 -        self.__temp_tail = False
2975 -        self.search_key = None
2976 -
2977 -    def prev(self):
2978 -        if self.cursor is None:
2979 -            self.cursor = len(self) - 1
2980 -        else:
2981 -            self.cursor -= 1
2982 -
2983 -        if self.search_key:
2984 -            while self.cursor >= 0 and self.search_key not in self[self.cursor]:
2985 -                self.cursor -= 1
2986 -
2987 -        if self.cursor < 0 or len(self) == 0:
2988 -            self.cursor = -1
2989 -            return random.choice(end_messages)
2990 -
2991 -        return self[self.cursor]
2992 -
2993 -    def next(self):
2994 -        if self.cursor is None:
2995 -            return ''
2996 -
2997 -        self.cursor += 1
2998 -
2999 -        if self.search_key:
3000 -            while self.cursor < len(self) and self.search_key not in self[self.cursor]:
3001 -                self.cursor += 1
3002 -
3003 -        if self.cursor >= len(shared_history[self.prompt]):
3004 -            self.cursor = None
3005 -            self.search_key = None
3006 -
3007 -            if self._temporary:
3008 -                return self._temporary.pop()
3009 -            return ''
3010 -
3011 -        return self[self.cursor]
3012 -
3013 -    def change_prompt(self, prompt):
3014 -        self.prompt = prompt
3015 -        self._temporary = []
3016 -        self.__temp_tail = False
3017 -        if prompt not in shared_history:
3018 -            shared_history[prompt] = []
3019 -
3020 -    def search(self, key):
3021 -        self.search_key = key
3022 -        self.cursor = None
3023 -
3024 -    def add(self, cmd):
3025 -        if self._temporary:
3026 -            self._temporary.pop()
3027 -
3028 -        shared_history[self.prompt].append(cmd)
3029 -        self.cursor = None
3030 -        self.search_key = None
3031 -
3032 -    def add_temporary(self, cmd):
3033 -        assert not self._temporary
3034 -
3035 -        self._temporary.append(cmd)
3036 -        self.cursor = len(self) - 1
3037 -
3038 -    def __getitem__(self, i):
3039 -        if i < len(shared_history[self.prompt]):
3040 -            return shared_history[self.prompt][i]
3041 -        return self._temporary[i-len(shared_history)+1]
3042 -
3043 -    def __len__(self):
3044 -        return len(shared_history[self.prompt]) + len(self._temporary)
3045 -
3046 -    def __str__(self):
3047 -        return "(History %s, %s)" % (self.cursor, self.prompt)
3048 -
3049 -def keycmd_exec(uzbl, modstate, keylet):
3050 -    cmd = keylet.get_keycmd()
3051 -    if cmd:
3052 -        uzbl.history.add(cmd)
3053 -
3054 -def history_prev(uzbl, _x):
3055 -    cmd = uzbl.keylet.get_keycmd()
3056 -    if uzbl.history.cursor is None and cmd:
3057 -        uzbl.history.add_temporary(cmd)
3058 -
3059 -    uzbl.set_keycmd(uzbl.history.prev())
3060 -    uzbl.logger.debug('PREV %s' % uzbl.history)
3061 -
3062 -def history_next(uzbl, _x):
3063 -    cmd = uzbl.keylet.get_keycmd()
3064 -
3065 -    uzbl.set_keycmd(uzbl.history.next())
3066 -    uzbl.logger.debug('NEXT %s' % uzbl.history)
3067 -
3068 -def history_search(uzbl, key):
3069 -    uzbl.history.search(key)
3070 -    uzbl.send('event HISTORY_PREV')
3071 -    uzbl.logger.debug('SEARCH %s %s' % (key, uzbl.history))
3072 -
3073 -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.')
3074 -
3075 -# plugin init hook
3076 -def init(uzbl):
3077 -    connect_dict(uzbl, {
3078 -        'KEYCMD_EXEC': keycmd_exec,
3079 -        'HISTORY_PREV': history_prev,
3080 -        'HISTORY_NEXT': history_next,
3081 -        'HISTORY_SEARCH': history_search
3082 -    })
3083 -
3084 -    export_dict(uzbl, {
3085 -        'history' : History(uzbl)
3086 -    })
3087 -
3088 -# plugin after hook
3089 -def after(uzbl):
3090 -    uzbl.on_set('keycmd_prompt', lambda uzbl, k, v: uzbl.history.change_prompt(v))
3091 -
3092 -# vi: set et ts=4:
3093 diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py
3094 deleted file mode 100644
3095 index 1bb70e3..0000000
3096 --- a/examples/data/plugins/keycmd.py
3097 +++ /dev/null
3098 @@ -1,423 +0,0 @@
3099 -import re
3100 -
3101 -# Keycmd format which includes the markup for the cursor.
3102 -KEYCMD_FORMAT = "%s<span @cursor_style>%s</span>%s"
3103 -MODCMD_FORMAT = "<span> %s </span>"
3104 -
3105 -
3106 -def escape(str):
3107 -    for char in ['\\', '@']:
3108 -        str = str.replace(char, '\\'+char)
3109 -
3110 -    return str
3111 -
3112 -
3113 -def uzbl_escape(str):
3114 -    return "@[%s]@" % escape(str) if str else ''
3115 -
3116 -
3117 -class Keylet(object):
3118 -    '''Small per-instance object that tracks characters typed.'''
3119 -
3120 -    def __init__(self):
3121 -        # Modcmd tracking
3122 -        self.modcmd = ''
3123 -        self.is_modcmd = False
3124 -
3125 -        # Keycmd tracking
3126 -        self.keycmd = ''
3127 -        self.cursor = 0
3128 -
3129 -        self.modmaps = {}
3130 -        self.ignores = {}
3131 -
3132 -
3133 -    def get_keycmd(self):
3134 -        '''Get the keycmd-part of the keylet.'''
3135 -
3136 -        return self.keycmd
3137 -
3138 -
3139 -    def get_modcmd(self):
3140 -        '''Get the modcmd-part of the keylet.'''
3141 -
3142 -        if not self.is_modcmd:
3143 -            return ''
3144 -
3145 -        return self.modcmd
3146 -
3147 -
3148 -    def modmap_key(self, key):
3149 -        '''Make some obscure names for some keys friendlier.'''
3150 -
3151 -        if key in self.modmaps:
3152 -            return self.modmaps[key]
3153 -
3154 -        elif key.endswith('_L') or key.endswith('_R'):
3155 -            # Remove left-right discrimination and try again.
3156 -            return self.modmap_key(key[:-2])
3157 -
3158 -        else:
3159 -            return key
3160 -
3161 -
3162 -    def key_ignored(self, key):
3163 -        '''Check if the given key is ignored by any ignore rules.'''
3164 -
3165 -        for (glob, match) in self.ignores.items():
3166 -            if match(key):
3167 -                return True
3168 -
3169 -        return False
3170 -
3171 -
3172 -    def __repr__(self):
3173 -        '''Return a string representation of the keylet.'''
3174 -
3175 -        l = []
3176 -        if self.is_modcmd:
3177 -            l.append('modcmd=%r' % self.get_modcmd())
3178 -
3179 -        if self.keycmd:
3180 -            l.append('keycmd=%r' % self.get_keycmd())
3181 -
3182 -        return '<keylet(%s)>' % ', '.join(l)
3183 -
3184 -
3185 -def add_modmap(uzbl, key, map):
3186 -    '''Add modmaps.
3187 -
3188 -    Examples:
3189 -        set modmap = request MODMAP
3190 -        @modmap <Control> <Ctrl>
3191 -        @modmap <ISO_Left_Tab> <Shift-Tab>
3192 -        ...
3193 -
3194 -    Then:
3195 -        @bind <Shift-Tab> = <command1>
3196 -        @bind <Ctrl>x = <command2>
3197 -        ...
3198 -
3199 -    '''
3200 -
3201 -    assert len(key)
3202 -    modmaps = uzbl.keylet.modmaps
3203 -
3204 -    modmaps[key.strip('<>')] = map.strip('<>')
3205 -    uzbl.event("NEW_MODMAP", key, map)
3206 -
3207 -
3208 -def modmap_parse(uzbl, map):
3209 -    '''Parse a modmap definiton.'''
3210 -
3211 -    split = [s.strip() for s in map.split(' ') if s.split()]
3212 -
3213 -    if not split or len(split) > 2:
3214 -        raise Exception('Invalid modmap arugments: %r' % map)
3215 -
3216 -    add_modmap(uzbl, *split)
3217 -
3218 -
3219 -def add_key_ignore(uzbl, glob):
3220 -    '''Add an ignore definition.
3221 -
3222 -    Examples:
3223 -        set ignore_key = request IGNORE_KEY
3224 -        @ignore_key <Shift>
3225 -        @ignore_key <ISO_*>
3226 -        ...
3227 -    '''
3228 -
3229 -    assert len(glob) > 1
3230 -    ignores = uzbl.keylet.ignores
3231 -
3232 -    glob = "<%s>" % glob.strip("<> ")
3233 -    restr = glob.replace('*', '[^\s]*')
3234 -    match = re.compile(restr).match
3235 -
3236 -    ignores[glob] = match
3237 -    uzbl.event('NEW_KEY_IGNORE', glob)
3238 -
3239 -
3240 -def clear_keycmd(uzbl, *args):
3241 -    '''Clear the keycmd for this uzbl instance.'''
3242 -
3243 -    k = uzbl.keylet
3244 -    k.keycmd = ''
3245 -    k.cursor = 0
3246 -    del uzbl.config['keycmd']
3247 -    uzbl.event('KEYCMD_CLEARED')
3248 -
3249 -
3250 -def clear_modcmd(uzbl):
3251 -    '''Clear the modcmd for this uzbl instance.'''
3252 -
3253 -    k = uzbl.keylet
3254 -    k.modcmd = ''
3255 -    k.is_modcmd = False
3256 -
3257 -    del uzbl.config['modcmd']
3258 -    uzbl.event('MODCMD_CLEARED')
3259 -
3260 -
3261 -def clear_current(uzbl):
3262 -    '''Clear the modcmd if is_modcmd else clear keycmd.'''
3263 -
3264 -    if uzbl.keylet.is_modcmd:
3265 -        clear_modcmd(uzbl)
3266 -
3267 -    else:
3268 -        clear_keycmd(uzbl)
3269 -
3270 -
3271 -def update_event(uzbl, modstate, k, execute=True):
3272 -    '''Raise keycmd & modcmd update events.'''
3273 -
3274 -    keycmd, modcmd = k.get_keycmd(), ''.join(modstate) + k.get_modcmd()
3275 -
3276 -    if k.is_modcmd:
3277 -        logger.debug('modcmd_update, %s' % modcmd)
3278 -        uzbl.event('MODCMD_UPDATE', modstate, k)
3279 -
3280 -    else:
3281 -        logger.debug('keycmd_update, %s' % keycmd)
3282 -        uzbl.event('KEYCMD_UPDATE', modstate, k)
3283 -
3284 -    if uzbl.config.get('modcmd_updates', '1') == '1':
3285 -        new_modcmd = ''.join(modstate) + k.get_modcmd()
3286 -        if not new_modcmd or not k.is_modcmd:
3287 -            del uzbl.config['modcmd']
3288 -
3289 -        elif new_modcmd == modcmd:
3290 -            uzbl.config['modcmd'] = MODCMD_FORMAT % uzbl_escape(modcmd)
3291 -
3292 -    if uzbl.config.get('keycmd_events', '1') != '1':
3293 -        return
3294 -
3295 -    new_keycmd = k.get_keycmd()
3296 -    if not new_keycmd:
3297 -        del uzbl.config['keycmd']
3298 -
3299 -    elif new_keycmd == keycmd:
3300 -        # Generate the pango markup for the cursor in the keycmd.
3301 -        curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' '
3302 -        chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]]
3303 -        value = KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks))
3304 -
3305 -        uzbl.config['keycmd'] = value
3306 -
3307 -
3308 -def inject_str(str, index, inj):
3309 -    '''Inject a string into string at at given index.'''
3310 -
3311 -    return "%s%s%s" % (str[:index], inj, str[index:])
3312 -
3313 -
3314 -def parse_key_event(uzbl, key):
3315 -    ''' Build a set from the modstate part of the event, and pass all keys through modmap '''
3316 -    keylet = uzbl.keylet
3317 -
3318 -    modstate, key = splitquoted(key)
3319 -    modstate = set(['<%s>' % keylet.modmap_key(k) for k in modstate.split('|') if k])
3320 -    
3321 -    key = keylet.modmap_key(key)
3322 -    return modstate, key
3323 -
3324 -
3325 -def key_press(uzbl, key):
3326 -    '''Handle KEY_PRESS events. Things done by this function include:
3327 -
3328 -    1. Ignore all shift key presses (shift can be detected by capital chars)
3329 -    2. In non-modcmd mode:
3330 -         a. append char to keycmd
3331 -    3. If not in modcmd mode and a modkey was pressed set modcmd mode.
3332 -    4. Keycmd is updated and events raised if anything is changed.'''
3333 -
3334 -    k = uzbl.keylet
3335 -    modstate, key = parse_key_event(uzbl, key)
3336 -    k.is_modcmd = any(not k.key_ignored(m) for m in modstate)
3337 -
3338 -    logger.debug('key press modstate=%s' % str(modstate))
3339 -    if key.lower() == 'space' and not k.is_modcmd and k.keycmd:
3340 -        k.keycmd = inject_str(k.keycmd, k.cursor, ' ')
3341 -        k.cursor += 1
3342 -
3343 -    elif not k.is_modcmd and len(key) == 1:
3344 -        if uzbl.config.get('keycmd_events', '1') != '1':
3345 -            # TODO, make a note on what's going on here
3346 -            k.keycmd = ''
3347 -            k.cursor = 0
3348 -            del uzbl.config['keycmd']
3349 -            return
3350 -
3351 -        k.keycmd = inject_str(k.keycmd, k.cursor, key)
3352 -        k.cursor += 1
3353 -
3354 -    elif len(key) == 1:
3355 -        k.modcmd += key
3356 -
3357 -    else:
3358 -        if not k.key_ignored('<%s>' % key):
3359 -            modstate.add('<%s>' % key)
3360 -            k.is_modcmd = True
3361 -
3362 -    update_event(uzbl, modstate, k)
3363 -
3364 -
3365 -def key_release(uzbl, key):
3366 -    '''Respond to KEY_RELEASE event. Things done by this function include:
3367 -
3368 -    1. If in a mod-command then raise a MODCMD_EXEC.
3369 -    2. Update the keycmd uzbl variable if anything changed.'''
3370 -    k = uzbl.keylet
3371 -    modstate, key = parse_key_event(uzbl, key)
3372 -
3373 -    if len(key) > 1:
3374 -        if k.is_modcmd:
3375 -            uzbl.event('MODCMD_EXEC', modstate, k)
3376 -
3377 -        clear_modcmd(uzbl)
3378 -
3379 -
3380 -def set_keycmd(uzbl, keycmd):
3381 -    '''Allow setting of the keycmd externally.'''
3382 -
3383 -    k = uzbl.keylet
3384 -    k.keycmd = keycmd
3385 -    k.cursor = len(keycmd)
3386 -    update_event(uzbl, set(), k, False)
3387 -
3388 -
3389 -def inject_keycmd(uzbl, keycmd):
3390 -    '''Allow injecting of a string into the keycmd at the cursor position.'''
3391 -
3392 -    k = uzbl.keylet
3393 -    k.keycmd = inject_str(k.keycmd, k.cursor, keycmd)
3394 -    k.cursor += len(keycmd)
3395 -    update_event(uzbl, set(), k, False)
3396 -
3397 -
3398 -def append_keycmd(uzbl, keycmd):
3399 -    '''Allow appening of a string to the keycmd.'''
3400 -
3401 -    k = uzbl.keylet
3402 -    k.keycmd += keycmd
3403 -    k.cursor = len(k.keycmd)
3404 -    update_event(uzbl, set(), k, False)
3405 -
3406 -
3407 -def keycmd_strip_word(uzbl, seps):
3408 -    ''' Removes the last word from the keycmd, similar to readline ^W '''
3409 -
3410 -    seps = seps or ' '
3411 -    k = uzbl.keylet
3412 -    if not k.keycmd:
3413 -        return
3414 -
3415 -    head, tail = k.keycmd[:k.cursor].rstrip(seps), k.keycmd[k.cursor:]
3416 -    rfind = -1
3417 -    for sep in seps:
3418 -        p = head.rfind(sep)
3419 -        if p >= 0 and rfind < p + 1:
3420 -            rfind = p + 1
3421 -    if rfind == len(head) and head[-1] in seps:
3422 -        rfind -= 1
3423 -    head = head[:rfind] if rfind + 1 else ''
3424 -    k.keycmd = head + tail
3425 -    k.cursor = len(head)
3426 -    update_event(uzbl, set(), k, False)
3427 -
3428 -
3429 -def keycmd_backspace(uzbl, *args):
3430 -    '''Removes the character at the cursor position in the keycmd.'''
3431 -
3432 -    k = uzbl.keylet
3433 -    if not k.keycmd or not k.cursor:
3434 -        return
3435 -
3436 -    k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:]
3437 -    k.cursor -= 1
3438 -    update_event(uzbl, set(), k, False)
3439 -
3440 -
3441 -def keycmd_delete(uzbl, *args):
3442 -    '''Removes the character after the cursor position in the keycmd.'''
3443 -
3444 -    k = uzbl.keylet
3445 -    if not k.keycmd:
3446 -        return
3447 -
3448 -    k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:]
3449 -    update_event(uzbl, set(), k, False)
3450 -
3451 -
3452 -def keycmd_exec_current(uzbl, *args):
3453 -    '''Raise a KEYCMD_EXEC with the current keylet and then clear the
3454 -    keycmd.'''
3455 -
3456 -    uzbl.event('KEYCMD_EXEC', set(), uzbl.keylet)
3457 -    clear_keycmd(uzbl)
3458 -
3459 -
3460 -def set_cursor_pos(uzbl, index):
3461 -    '''Allow setting of the cursor position externally. Supports negative
3462 -    indexing and relative stepping with '+' and '-'.'''
3463 -
3464 -    k = uzbl.keylet
3465 -    if index == '-':
3466 -        cursor = k.cursor - 1
3467 -
3468 -    elif index == '+':
3469 -        cursor = k.cursor + 1
3470 -
3471 -    else:
3472 -        cursor = int(index.strip())
3473 -        if cursor < 0:
3474 -            cursor = len(k.keycmd) + cursor + 1
3475 -
3476 -    if cursor < 0:
3477 -        cursor = 0
3478 -
3479 -    if cursor > len(k.keycmd):
3480 -        cursor = len(k.keycmd)
3481 -
3482 -    k.cursor = cursor
3483 -    update_event(uzbl, set(), k, False)
3484 -
3485 -
3486 -# plugin init hook
3487 -def init(uzbl):
3488 -    '''Export functions and connect handlers to events.'''
3489 -
3490 -    connect_dict(uzbl, {
3491 -        'APPEND_KEYCMD':        append_keycmd,
3492 -        'IGNORE_KEY':           add_key_ignore,
3493 -        'INJECT_KEYCMD':        inject_keycmd,
3494 -        'KEYCMD_BACKSPACE':     keycmd_backspace,
3495 -        'KEYCMD_DELETE':        keycmd_delete,
3496 -        'KEYCMD_EXEC_CURRENT':  keycmd_exec_current,
3497 -        'KEYCMD_STRIP_WORD':    keycmd_strip_word,
3498 -        'KEYCMD_CLEAR':         clear_keycmd,
3499 -        'KEY_PRESS':            key_press,
3500 -        'KEY_RELEASE':          key_release,
3501 -        'MOD_PRESS':            key_press,
3502 -        'MOD_RELEASE':          key_release,
3503 -        'MODMAP':               modmap_parse,
3504 -        'SET_CURSOR_POS':       set_cursor_pos,
3505 -        'SET_KEYCMD':           set_keycmd,
3506 -    })
3507 -
3508 -    export_dict(uzbl, {
3509 -        'add_key_ignore':       add_key_ignore,
3510 -        'add_modmap':           add_modmap,
3511 -        'append_keycmd':        append_keycmd,
3512 -        'clear_current':        clear_current,
3513 -        'clear_keycmd':         clear_keycmd,
3514 -        'clear_modcmd':         clear_modcmd,
3515 -        'inject_keycmd':        inject_keycmd,
3516 -        'keylet':               Keylet(),
3517 -        'set_cursor_pos':       set_cursor_pos,
3518 -        'set_keycmd':           set_keycmd,
3519 -    })
3520 -
3521 -# vi: set et ts=4:
3522 diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py
3523 deleted file mode 100644
3524 index e0de706..0000000
3525 --- a/examples/data/plugins/mode.py
3526 +++ /dev/null
3527 @@ -1,68 +0,0 @@
3528 -from collections import defaultdict
3529 -
3530 -def parse_mode_config(uzbl, args):
3531 -    '''Parse `MODE_CONFIG <mode> <var> = <value>` event and update config if
3532 -    the `<mode>` is the current mode.'''
3533 -
3534 -    ustrip = unicode.strip
3535 -    args = unicode(args)
3536 -
3537 -    assert args.strip(), "missing mode config args"
3538 -    (mode, args) = map(ustrip, (args.strip().split(' ', 1) + ['',])[:2])
3539 -
3540 -    assert args.strip(), "missing mode config set arg"
3541 -    (key, value) = map(ustrip, (args.strip().split('=', 1) + [None,])[:2])
3542 -    assert key and value is not None, "invalid mode config set syntax"
3543 -
3544 -    uzbl.mode_config[mode][key] = value
3545 -    if uzbl.config.get('mode', None) == mode:
3546 -        uzbl.config[key] = value
3547 -
3548 -
3549 -def default_mode_updated(uzbl, var, mode):
3550 -    if mode and not uzbl.config.get('mode', None):
3551 -        logger.debug('setting mode to default %r' % mode)
3552 -        uzbl.config['mode'] = mode
3553 -
3554 -
3555 -def mode_updated(uzbl, var, mode):
3556 -    if not mode:
3557 -        mode = uzbl.config.get('default_mode', 'command')
3558 -        logger.debug('setting mode to default %r' % mode)
3559 -        uzbl.config['mode'] = mode
3560 -        return
3561 -
3562 -    # Load mode config
3563 -    mode_config = uzbl.mode_config.get(mode, None)
3564 -    if mode_config:
3565 -        uzbl.config.update(mode_config)
3566 -
3567 -    uzbl.send('event MODE_CONFIRM %s' % mode)
3568 -
3569 -
3570 -def confirm_change(uzbl, mode):
3571 -    if mode and uzbl.config.get('mode', None) == mode:
3572 -        uzbl.event('MODE_CHANGED', mode)
3573 -
3574 -
3575 -# plugin init hook
3576 -def init(uzbl):
3577 -    require('config')
3578 -    require('on_set')
3579 -
3580 -    # Usage `uzbl.mode_config[mode][key] = value`
3581 -    export(uzbl, 'mode_config', defaultdict(dict))
3582 -
3583 -    connect_dict(uzbl, {
3584 -        'MODE_CONFIG':  parse_mode_config,
3585 -        'MODE_CONFIRM': confirm_change,
3586 -    })
3587 -
3588 -# plugin after hook
3589 -def after(uzbl):
3590 -    uzbl.on_set('mode', mode_updated)
3591 -    uzbl.on_set('default_mode', default_mode_updated)
3592 -
3593 -# plugin cleanup hook
3594 -def cleanup(uzbl):
3595 -    uzbl.mode_config.clear()
3596 diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py
3597 deleted file mode 100644
3598 index 32f09e2..0000000
3599 --- a/examples/data/plugins/on_event.py
3600 +++ /dev/null
3601 @@ -1,88 +0,0 @@
3602 -'''Plugin provides arbitrary binding of uzbl events to uzbl commands.
3603 -
3604 -Formatting options:
3605 -  %s = space separated string of the arguments
3606 -  %r = escaped and quoted version of %s
3607 -  %1 = argument 1
3608 -  %2 = argument 2
3609 -  %n = argument n
3610 -
3611 -Usage:
3612 -  request ON_EVENT LINK_HOVER set selected_uri = $1
3613 -    --> LINK_HOVER http://uzbl.org/
3614 -    <-- set selected_uri = http://uzbl.org/
3615 -
3616 -  request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2
3617 -    --> CONFIG_CHANGED selected_uri http://uzbl.org/
3618 -    <-- print Config changed: selected_uri = http://uzbl.org/
3619 -'''
3620 -
3621 -import sys
3622 -import re
3623 -
3624 -def event_handler(uzbl, *args, **kargs):
3625 -    '''This function handles all the events being watched by various
3626 -    on_event definitions and responds accordingly.'''
3627 -
3628 -    # Could be connected to a EM internal event that can use anything as args
3629 -    if len(args) == 1 and isinstance(args[0], basestring):
3630 -        args = splitquoted(args[0])
3631 -
3632 -    events = uzbl.on_events
3633 -    event = kargs['on_event']
3634 -    if event not in events:
3635 -        return
3636 -
3637 -    commands = events[event]
3638 -    cmd_expand = uzbl.cmd_expand
3639 -    for cmd in commands:
3640 -        cmd = cmd_expand(cmd, args)
3641 -        uzbl.send(cmd)
3642 -
3643 -
3644 -def on_event(uzbl, event, cmd):
3645 -    '''Add a new event to watch and respond to.'''
3646 -
3647 -    event = event.upper()
3648 -    events = uzbl.on_events
3649 -    if event not in events:
3650 -        connect(uzbl, event, event_handler, on_event=event)
3651 -        events[event] = []
3652 -
3653 -    cmds = events[event]
3654 -    if cmd not in cmds:
3655 -        cmds.append(cmd)
3656 -
3657 -
3658 -def parse_on_event(uzbl, args):
3659 -    '''Parse ON_EVENT events and pass them to the on_event function.
3660 -
3661 -    Syntax: "event ON_EVENT <EVENT_NAME> commands".'''
3662 -
3663 -    args = args.strip()
3664 -    assert args, 'missing on event arguments'
3665 -
3666 -    (event, command) = (args.split(' ', 1) + ['',])[:2]
3667 -    assert event and command, 'missing on event command'
3668 -    on_event(uzbl, event, command)
3669 -
3670 -
3671 -# plugin init hook
3672 -def init(uzbl):
3673 -    '''Export functions and connect handlers to events.'''
3674 -
3675 -    connect(uzbl, 'ON_EVENT', parse_on_event)
3676 -
3677 -    export_dict(uzbl, {
3678 -        'on_event':     on_event,
3679 -        'on_events':    {},
3680 -    })
3681 -
3682 -# plugin cleanup hook
3683 -def cleanup(uzbl):
3684 -    for handlers in uzbl.on_events.values():
3685 -        del handlers[:]
3686 -
3687 -    uzbl.on_events.clear()
3688 -
3689 -# vi: set et ts=4:
3690 diff --git a/examples/data/plugins/on_set.py b/examples/data/plugins/on_set.py
3691 deleted file mode 100644
3692 index 130b816..0000000
3693 --- a/examples/data/plugins/on_set.py
3694 +++ /dev/null
3695 @@ -1,92 +0,0 @@
3696 -from re import compile
3697 -from functools import partial
3698 -
3699 -valid_glob = compile('^[A-Za-z0-9_\*\.]+$').match
3700 -
3701 -def make_matcher(glob):
3702 -    '''Make matcher function from simple glob.'''
3703 -
3704 -    pattern = "^%s$" % glob.replace('*', '[^\s]*')
3705 -    return compile(pattern).match
3706 -
3707 -
3708 -def exec_handlers(uzbl, handlers, key, arg):
3709 -    '''Execute the on_set handlers that matched the key.'''
3710 -
3711 -    for handler in handlers:
3712 -        if callable(handler):
3713 -            handler(key, arg)
3714 -
3715 -        else:
3716 -            uzbl.send(uzbl.cmd_expand(handler, [key, arg]))
3717 -
3718 -
3719 -def check_for_handlers(uzbl, key, arg):
3720 -    '''Check for handlers for the current key.'''
3721 -
3722 -    for (matcher, handlers) in uzbl.on_sets.values():
3723 -        if matcher(key):
3724 -            exec_handlers(uzbl, handlers, key, arg)
3725 -
3726 -
3727 -def on_set(uzbl, glob, handler, prepend=True):
3728 -    '''Add a new handler for a config key change.
3729 -
3730 -    Structure of the `uzbl.on_sets` dict:
3731 -      { glob : ( glob matcher function, handlers list ), .. }
3732 -    '''
3733 -
3734 -    assert valid_glob(glob)
3735 -
3736 -    while '**' in glob:
3737 -        glob = glob.replace('**', '*')
3738 -
3739 -    if callable(handler):
3740 -        orig_handler = handler
3741 -        if prepend:
3742 -            handler = partial(handler, uzbl)
3743 -
3744 -    else:
3745 -        orig_handler = handler = unicode(handler)
3746 -
3747 -    if glob in uzbl.on_sets:
3748 -        (matcher, handlers) = uzbl.on_sets[glob]
3749 -        handlers.append(handler)
3750 -
3751 -    else:
3752 -        matcher = make_matcher(glob)
3753 -        uzbl.on_sets[glob] = (matcher, [handler,])
3754 -
3755 -    uzbl.logger.info('on set %r call %r' % (glob, orig_handler))
3756 -
3757 -
3758 -def parse_on_set(uzbl, args):
3759 -    '''Parse `ON_SET <glob> <command>` event then pass arguments to the
3760 -    `on_set(..)` function.'''
3761 -
3762 -    (glob, command) = (args.split(' ', 1) + [None,])[:2]
3763 -    assert glob and command and valid_glob(glob)
3764 -    on_set(uzbl, glob, command)
3765 -
3766 -
3767 -# plugins init hook
3768 -def init(uzbl):
3769 -    require('config')
3770 -    require('cmd_expand')
3771 -
3772 -    export_dict(uzbl, {
3773 -        'on_sets':  {},
3774 -        'on_set':   on_set,
3775 -    })
3776 -
3777 -    connect_dict(uzbl, {
3778 -        'ON_SET':           parse_on_set,
3779 -        'CONFIG_CHANGED':   check_for_handlers,
3780 -    })
3781 -
3782 -# plugins cleanup hook
3783 -def cleanup(uzbl):
3784 -    for (matcher, handlers) in uzbl.on_sets.values():
3785 -        del handlers[:]
3786 -
3787 -    uzbl.on_sets.clear()
3788 diff --git a/examples/data/plugins/progress_bar.py b/examples/data/plugins/progress_bar.py
3789 deleted file mode 100644
3790 index b2edffc..0000000
3791 --- a/examples/data/plugins/progress_bar.py
3792 +++ /dev/null
3793 @@ -1,93 +0,0 @@
3794 -UPDATES = 0
3795 -
3796 -def update_progress(uzbl, progress=None):
3797 -    '''Updates the progress.output variable on LOAD_PROGRESS update.
3798 -
3799 -    The current substitution options are:
3800 -        %d = done char * done
3801 -        %p = pending char * remaining
3802 -        %c = percent done
3803 -        %i = int done
3804 -        %s = -\|/ spinner
3805 -        %t = percent pending
3806 -        %o = int pending
3807 -        %r = sprites
3808 -
3809 -    Default configuration options:
3810 -        progress.format  = [%d>%p]%c
3811 -        progress.width   = 8
3812 -        progress.done    = =
3813 -        progress.pending =
3814 -        progress.spinner = -\|/
3815 -        progress.sprites = loading
3816 -    '''
3817 -
3818 -    global UPDATES
3819 -
3820 -    if progress is None:
3821 -        UPDATES = 0
3822 -        progress = 100
3823 -
3824 -    else:
3825 -        UPDATES += 1
3826 -        progress = int(progress)
3827 -
3828 -    # Get progress config vars.
3829 -    format = uzbl.config.get('progress.format', '[%d>%p]%c')
3830 -    width = int(uzbl.config.get('progress.width', 8))
3831 -    done_symbol = uzbl.config.get('progress.done', '=')
3832 -    pend = uzbl.config.get('progress.pending', None)
3833 -    pending_symbol = pend if pend else ' '
3834 -
3835 -    # Inflate the done and pending bars to stop the progress bar
3836 -    # jumping around.
3837 -    if '%c' in format or '%i' in format:
3838 -        count = format.count('%c') + format.count('%i')
3839 -        width += (3-len(str(progress))) * count
3840 -
3841 -    if '%t' in format or '%o' in format:
3842 -        count = format.count('%t') + format.count('%o')
3843 -        width += (3-len(str(100-progress))) * count
3844 -
3845 -    done = int(((progress/100.0)*width)+0.5)
3846 -    pending = width - done
3847 -
3848 -    if '%d' in format:
3849 -        format = format.replace('%d', done_symbol * done)
3850 -
3851 -    if '%p' in format:
3852 -        format = format.replace('%p', pending_symbol * pending)
3853 -
3854 -    if '%c' in format:
3855 -        format = format.replace('%c', '%d%%' % progress)
3856 -
3857 -    if '%i' in format:
3858 -        format = format.replace('%i', '%d' % progress)
3859 -
3860 -    if '%t' in format:
3861 -        format = format.replace('%t', '%d%%' % (100-progress))
3862 -
3863 -    if '%o' in format:
3864 -        format = format.replace('%o', '%d' % (100-progress))
3865 -
3866 -    if '%s' in format:
3867 -        spinner = uzbl.config.get('progress.spinner', '-\\|/')
3868 -        index = 0 if progress == 100 else UPDATES % len(spinner)
3869 -        spin = '\\\\' if spinner[index] == '\\' else spinner[index]
3870 -        format = format.replace('%s', spin)
3871 -
3872 -    if '%r' in format:
3873 -        sprites = uzbl.config.get('progress.sprites', 'loading')
3874 -        index = int(((progress/100.0)*len(sprites))+0.5)-1
3875 -        sprite = '\\\\' if sprites[index] == '\\' else sprites[index]
3876 -        format = format.replace('%r', sprite)
3877 -
3878 -    if uzbl.config.get('progress.output', None) != format:
3879 -        uzbl.config['progress.output'] = format
3880 -
3881 -# plugin init hook
3882 -def init(uzbl):
3883 -    connect_dict(uzbl, {
3884 -        'LOAD_COMMIT':      lambda uzbl, uri: update_progress(uzbl),
3885 -        'LOAD_PROGRESS':    update_progress,
3886 -    })
3887 diff --git a/examples/data/scripts/auth.py b/examples/data/scripts/auth.py
3888 index 49fa41e..dbf58dc 100755
3889 --- a/examples/data/scripts/auth.py
3890 +++ b/examples/data/scripts/auth.py
3891 @@ -1,5 +1,6 @@
3892  #!/usr/bin/env python
3893  
3894 +import os
3895  import gtk
3896  import sys
3897  
3898 @@ -41,13 +42,20 @@ def getText(authInfo, authHost, authRealm):
3899      dialog.show_all()
3900      rv = dialog.run()
3901  
3902 -    output = login.get_text() + "\n" + password.get_text()
3903 +    output = {
3904 +        'username': login.get_text(),
3905 +        'password': password.get_text()
3906 +    }
3907      dialog.destroy()
3908      return rv, output
3909  
3910  if __name__ == '__main__':
3911 -    rv, output = getText(sys.argv[1], sys.argv[2], sys.argv[3])
3912 +    fifo = open(os.environ.get('UZBL_FIFO'), 'w')
3913 +    me, info, host, realm = sys.argv
3914 +    rv, output = getText(info, host, realm)
3915      if (rv == gtk.RESPONSE_OK):
3916 -        print output;
3917 +        print >> fifo, 'auth "%s" "%s" "%s"' % (
3918 +            info, output['username'], output['password']
3919 +        )
3920      else:
3921          exit(1)
3922 diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js
3923 index 83bbf55..0afeec3 100644
3924 --- a/examples/data/scripts/follow.js
3925 +++ b/examples/data/scripts/follow.js
3926 @@ -12,6 +12,7 @@
3927  
3928  // Globals
3929  uzbldivid = 'uzbl_link_hints';
3930 +var uzbl = uzbl || {};
3931  
3932  uzbl.follow = function() {
3933      // Export
3934 diff --git a/examples/data/scripts/formfiller.js b/examples/data/scripts/formfiller.js
3935 index 06db648..9c56f7b 100644
3936 --- a/examples/data/scripts/formfiller.js
3937 +++ b/examples/data/scripts/formfiller.js
3938 @@ -1,3 +1,5 @@
3939 +var uzbl = uzbl || {};
3940 +
3941  uzbl.formfiller = {
3942  
3943      // this is pointlessly duplicated in uzbl.follow
3944 diff --git a/examples/data/scripts/history.sh b/examples/data/scripts/history.sh
3945 index 0709b5e..2d96a07 100755
3946 --- a/examples/data/scripts/history.sh
3947 +++ b/examples/data/scripts/history.sh
3948 @@ -1,5 +1,7 @@
3949  #!/bin/sh
3950  
3951 +[ -n "$UZBL_PRIVATE" ] && exit 0
3952 +
3953  . "$UZBL_UTIL_DIR/uzbl-dir.sh"
3954  
3955  >> "$UZBL_HISTORY_FILE" || exit 1
3956 diff --git a/examples/data/scripts/scheme.py b/examples/data/scripts/scheme.py
3957 index 4b0b7ca..c652478 100755
3958 --- a/examples/data/scripts/scheme.py
3959 +++ b/examples/data/scripts/scheme.py
3960 @@ -1,6 +1,11 @@
3961  #!/usr/bin/env python
3962  
3963 -import os, subprocess, sys, urlparse
3964 +import os, subprocess, sys
3965 +
3966 +try:
3967 +  import urllib.parse as urlparse
3968 +except ImportError:
3969 +  import urlparse
3970  
3971  def detach_open(cmd):
3972      # Thanks to the vast knowledge of Laurence Withers (lwithers) and this message:
3973 @@ -10,7 +15,7 @@ def detach_open(cmd):
3974          for i in range(3): os.dup2(null,i)
3975          os.close(null)
3976          subprocess.Popen(cmd)
3977 -    print 'USED'
3978 +    print('USED')
3979  
3980  if __name__ == '__main__':
3981      uri = sys.argv[1]
3982 diff --git a/misc/env.sh b/misc/env.sh
3983 index f815c44..d5f0db8 100755
3984 --- a/misc/env.sh
3985 +++ b/misc/env.sh
3986 @@ -28,3 +28,11 @@ export XDG_CONFIG_HOME
3987  # Needed to run uzbl-browser etc from here.
3988  PATH="$(pwd)/sandbox/usr/local/bin:$PATH"
3989  export PATH
3990 +
3991 +PYTHONLIB=$(python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib(prefix="/usr/local"))')
3992 +
3993 +UZBL_PLUGIN_PATH="$(pwd)/sandbox/$PYTHONLIB/uzbl/plugins"
3994 +export UZBL_PLUGIN_PATH
3995 +
3996 +PYTHONPATH="$(pwd)/sandbox/$PYTHONLIB/"
3997 +export PYTHONPATH
3998 diff --git a/setup.py b/setup.py
3999 new file mode 100644
4000 index 0000000..5dd6b59
4001 --- /dev/null
4002 +++ b/setup.py
4003 @@ -0,0 +1,11 @@
4004 +from distutils.core import setup
4005 +
4006 +setup(name='uzbl',
4007 +      version='201100808',
4008 +      description='Uzbl event daemon',
4009 +      url='http://uzbl.org',
4010 +      packages=['uzbl', 'uzbl.plugins'],
4011 +      scripts=[
4012 +        'bin/uzbl-event-manager',
4013 +        ],
4014 +    )
4015 diff --git a/src/callbacks.c b/src/callbacks.c
4016 index eee9f69..370f679 100644
4017 --- a/src/callbacks.c
4018 +++ b/src/callbacks.c
4019 @@ -10,6 +10,8 @@
4020  #include "type.h"
4021  #include "variables.h"
4022  
4023 +#include <gdk/gdk.h>
4024 +
4025  void
4026  link_hover_cb (WebKitWebView *page, const gchar *title, const gchar *link, gpointer data) {
4027      (void) page; (void) title; (void) data;
4028 @@ -147,6 +149,21 @@ key_release_cb (GtkWidget* window, GdkEventKey* event) {
4029      return uzbl.behave.forward_keys ? FALSE : TRUE;
4030  }
4031  
4032 +gint
4033 +get_click_context() {
4034 +    WebKitHitTestResult *ht;
4035 +    guint context;
4036 +
4037 +    if(!uzbl.state.last_button)
4038 +        return -1;
4039 +
4040 +    ht = webkit_web_view_get_hit_test_result (uzbl.gui.web_view, uzbl.state.last_button);
4041 +    g_object_get (ht, "context", &context, NULL);
4042 +    g_object_unref (ht);
4043 +
4044 +    return (gint)context;
4045 +}
4046 +
4047  gboolean
4048  button_press_cb (GtkWidget* window, GdkEventButton* event) {
4049      (void) window;
4050 @@ -233,22 +250,9 @@ button_release_cb (GtkWidget* window, GdkEventButton* event) {
4051  }
4052  
4053  gboolean
4054 -motion_notify_cb(GtkWidget* window, GdkEventMotion* event, gpointer user_data) {
4055 -    (void) window;
4056 -    (void) event;
4057 -    (void) user_data;
4058 -
4059 -    send_event (PTR_MOVE, NULL,
4060 -        TYPE_FLOAT, event->x,
4061 -        TYPE_FLOAT, event->y,
4062 -        TYPE_INT, event->state,
4063 -        NULL);
4064 -
4065 -    return FALSE;
4066 -}
4067 -
4068 -gboolean
4069 -navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
4070 +navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
4071 +        WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
4072 +        WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
4073      (void) web_view;
4074      (void) frame;
4075      (void) navigation_action;
4076 @@ -288,39 +292,16 @@ navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNe
4077      return TRUE;
4078  }
4079  
4080 -gboolean
4081 -new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
4082 -        WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
4083 -        WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
4084 -    (void) web_view;
4085 -    (void) frame;
4086 -    (void) navigation_action;
4087 -    (void) policy_decision;
4088 -    (void) user_data;
4089 -
4090 -    if (uzbl.state.verbose)
4091 -        printf ("New window requested -> %s \n", webkit_network_request_get_uri (request));
4092 -
4093 -    /* This event function causes troubles with `target="_blank"` anchors.
4094 -     * Either we:
4095 -     *  1. Comment it out and target blank links are ignored.
4096 -     *  2. Uncomment it and two windows are opened when you click on target
4097 -     *     blank links.
4098 -     *
4099 -     * This problem is caused by create_web_view_cb also being called whenever
4100 -     * this callback is triggered thus resulting in the doubled events.
4101 -     *
4102 -     * We are leaving this uncommented as we would rather links open twice
4103 -     * than not at all.
4104 -     */
4105 -    send_event (NEW_WINDOW, NULL, TYPE_STR, webkit_network_request_get_uri (request), NULL);
4106 -
4107 -    webkit_web_policy_decision_ignore (policy_decision);
4108 -    return TRUE;
4109 +void
4110 +close_web_view_cb(WebKitWebView *webview, gpointer user_data) {
4111 +    (void) webview; (void) user_data;
4112 +    send_event (CLOSE_WINDOW, NULL, NULL);
4113  }
4114  
4115  gboolean
4116 -mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, gchar *mime_type,  WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
4117 +mime_policy_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
4118 +        WebKitNetworkRequest *request, gchar *mime_type,
4119 +        WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
4120      (void) frame;
4121      (void) request;
4122      (void) user_data;
4123 @@ -345,11 +326,17 @@ request_starting_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitWebRes
4124      (void) response;
4125      (void) user_data;
4126  
4127 -    const gchar* uri = webkit_network_request_get_uri (request);
4128 +    const gchar *uri = webkit_network_request_get_uri (request);
4129 +    SoupMessage *message = webkit_network_request_get_message (request);
4130 +
4131 +    if (message) {
4132 +        SoupURI *soup_uri = soup_uri_new (uri);
4133 +        soup_message_set_first_party (message, soup_uri);
4134 +    }
4135  
4136      if (uzbl.state.verbose)
4137          printf("Request starting -> %s\n", uri);
4138 -    send_event (REQUEST_STARTING, NULL, TYPE_STR, webkit_network_request_get_uri(request), NULL);
4139 +    send_event (REQUEST_STARTING, NULL, TYPE_STR, uri, NULL);
4140  
4141      if (uzbl.behave.request_handler) {
4142          GString *result = g_string_new ("");
4143 @@ -373,17 +360,16 @@ request_starting_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitWebRes
4144  }
4145  
4146  void
4147 -create_web_view_js_cb (WebKitWebView* web_view, GParamSpec param_spec) {
4148 +create_web_view_got_uri_cb (WebKitWebView* web_view, GParamSpec param_spec) {
4149      (void) web_view;
4150      (void) param_spec;
4151  
4152      webkit_web_view_stop_loading(web_view);
4153 +
4154      const gchar* uri = webkit_web_view_get_uri(web_view);
4155  
4156 -    if (strncmp(uri, "javascript:", strlen("javascript:")) == 0) {
4157 +    if (strncmp(uri, "javascript:", strlen("javascript:")) == 0)
4158          eval_js(uzbl.gui.web_view, (gchar*) uri + strlen("javascript:"), NULL, "javascript:");
4159 -        gtk_widget_destroy(GTK_WIDGET(web_view));
4160 -    }
4161      else
4162          send_event(NEW_WINDOW, NULL, TYPE_STR, uri, NULL);
4163  }
4164 @@ -394,14 +380,29 @@ create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer us
4165      (void) frame;
4166      (void) user_data;
4167  
4168 -    if (uzbl.state.verbose)
4169 -        printf("New web view -> javascript link...\n");
4170 -
4171 -    WebKitWebView* new_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
4172 +    // unfortunately we don't know the URL at this point; all webkit-gtk will
4173 +    // tell us is that we're opening a new window.
4174 +    //
4175 +    // (we can't use the new-window-policy-decision-requested event; it doesn't
4176 +    // fire when Javascript requests a new window with window.open().)
4177 +    //
4178 +    // so, we have to create a temporary web view and allow it to load. webkit
4179 +    // segfaults if we try to destroy it or mark it for garbage collection in
4180 +    // create_web_view_got_uri_cb, so we might as well keep it around and reuse
4181 +    // it.
4182 +
4183 +    if(!uzbl.state._tmp_web_view) {
4184 +      uzbl.state._tmp_web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
4185 +
4186 +      g_object_connect (uzbl.state._tmp_web_view, "signal::notify::uri",
4187 +                             G_CALLBACK(create_web_view_got_uri_cb), NULL, NULL);
4188 +
4189 +      // we're taking ownership of this WebView (sinking its floating reference
4190 +      // since it will never be added to a display widget).
4191 +      g_object_ref_sink(uzbl.state._tmp_web_view);
4192 +    }
4193  
4194 -    g_object_connect (new_view, "signal::notify::uri",
4195 -                           G_CALLBACK(create_web_view_js_cb), NULL, NULL);
4196 -    return new_view;
4197 +    return uzbl.state._tmp_web_view;
4198  }
4199  
4200  void
4201 @@ -464,7 +465,21 @@ download_cb(WebKitWebView *web_view, WebKitDownload *download, gpointer user_dat
4202  
4203      /* get a reasonable suggestion for a filename */
4204      const gchar *suggested_filename;
4205 +#ifdef USE_WEBKIT2
4206 +    WebKitURIResponse *response;
4207 +    g_object_get(download, "network-response", &response, NULL);
4208 +#if WEBKIT_CHECK_VERSION (1, 9, 90)
4209 +    g_object_get(response, "suggested-filename", &suggested_filename, NULL);
4210 +#else
4211 +    suggested_filename = webkit_uri_response_get_suggested_filename(respose);
4212 +#endif
4213 +#elif WEBKIT_CHECK_VERSION (1, 9, 6)
4214 +    WebKitNetworkResponse *response;
4215 +    g_object_get(download, "network-response", &response, NULL);
4216 +    g_object_get(response, "suggested-filename", &suggested_filename, NULL);
4217 +#else
4218      g_object_get(download, "suggested-filename", &suggested_filename, NULL);
4219 +#endif
4220  
4221      /* get the mimetype of the download */
4222      const gchar *content_type = NULL;
4223 @@ -582,90 +597,107 @@ run_menu_command(GtkWidget *menu, MenuItem *mi) {
4224      (void) menu;
4225  
4226      if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
4227 -        gchar* uri;
4228 -        g_object_get(mi->hittest, "image-uri", &uri, NULL);
4229 -        gchar* cmd = g_strdup_printf("%s %s", mi->cmd, uri);
4230 +        gchar* cmd = g_strdup_printf("%s %s", mi->cmd, mi->argument);
4231  
4232          parse_cmd_line(cmd, NULL);
4233  
4234          g_free(cmd);
4235 -        g_free(uri);
4236 -        g_object_unref(mi->hittest);
4237 +        g_free(mi->argument);
4238      }
4239      else {
4240          parse_cmd_line(mi->cmd, NULL);
4241      }
4242  }
4243  
4244 +gboolean
4245 +populate_context_menu (GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gint context) {
4246 +    guint i;
4247 +
4248 +    /* find the user-defined menu items that are approprate for whatever was
4249 +     * clicked and append them to the default context menu. */
4250 +    for(i = 0; i < uzbl.gui.menu_items->len; i++) {
4251 +        MenuItem *mi = g_ptr_array_index(uzbl.gui.menu_items, i);
4252 +        GtkWidget *item;
4253 +
4254 +        gboolean contexts_match = (context & mi->context);
4255 +
4256 +        if(!contexts_match) {
4257 +          continue;
4258 +        }
4259 +
4260 +        if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
4261 +            g_object_get(hit_test_result, "image-uri", &(mi->argument), NULL);
4262 +        }
4263 +
4264 +        if(mi->issep) {
4265 +            item = gtk_separator_menu_item_new();
4266 +        } else {
4267 +            item = gtk_menu_item_new_with_label(mi->name);
4268 +            g_signal_connect(item, "activate",
4269 +                    G_CALLBACK(run_menu_command), mi);
4270 +        }
4271 +
4272 +        gtk_menu_shell_append(GTK_MENU_SHELL(default_menu), item);
4273 +        gtk_widget_show(item);
4274 +    }
4275 +
4276 +    return FALSE;
4277 +}
4278 +
4279 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
4280 +gboolean
4281 +context_menu_cb (WebKitWebView *v, GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gboolean triggered_with_keyboard, gpointer user_data) {
4282 +    (void) v; (void) triggered_with_keyboard; (void) user_data;
4283 +    gint context;
4284 +
4285 +    if(!uzbl.gui.menu_items)
4286 +        return FALSE;
4287 +
4288 +    /* check context */
4289 +    if((context = get_click_context()) == -1)
4290 +        return FALSE;
4291  
4292 +    /* display the default menu with our modifications. */
4293 +    return populate_context_menu(default_menu, hit_test_result, context);
4294 +}
4295 +#else
4296  void
4297  populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) {
4298      (void) c;
4299 -    GUI *g = &uzbl.gui;
4300 -    GtkWidget *item;
4301 -    MenuItem *mi;
4302 -    guint i=0;
4303 -    gint context, hit=0;
4304 +    gint context;
4305  
4306 -    if(!g->menu_items)
4307 +    if(!uzbl.gui.menu_items)
4308          return;
4309  
4310      /* check context */
4311      if((context = get_click_context()) == -1)
4312          return;
4313  
4314 -    for(i=0; i < uzbl.gui.menu_items->len; i++) {
4315 -        hit = 0;
4316 -        mi = g_ptr_array_index(uzbl.gui.menu_items, i);
4317 +    WebKitHitTestResult *hit_test_result;
4318 +    GdkEventButton ev;
4319 +    gint x, y;
4320 +#if GTK_CHECK_VERSION (3, 0, 0)
4321 +    gdk_window_get_device_position (gtk_widget_get_window(GTK_WIDGET(v)),
4322 +        gdk_device_manager_get_client_pointer (
4323 +            gdk_display_get_device_manager (
4324 +                gtk_widget_get_display (GTK_WIDGET (v)))),
4325 +        &x, &y, NULL);
4326 +#else
4327 +    gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(v)), &x, &y, NULL);
4328 +#endif
4329 +    ev.x = x;
4330 +    ev.y = y;
4331 +    hit_test_result = webkit_web_view_get_hit_test_result(v, &ev);
4332  
4333 -        if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
4334 -            GdkEventButton ev;
4335 -            gint x, y;
4336 -            gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(v)), &x, &y, NULL);
4337 -            ev.x = x;
4338 -            ev.y = y;
4339 -            mi->hittest = webkit_web_view_get_hit_test_result(v, &ev);
4340 -        }
4341 +    populate_context_menu(m, hit_test_result, context);
4342  
4343 -        if((mi->context > WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) &&
4344 -                (context & mi->context)) {
4345 -            if(mi->issep) {
4346 -                item = gtk_separator_menu_item_new();
4347 -                gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
4348 -                gtk_widget_show(item);
4349 -            }
4350 -            else {
4351 -                item = gtk_menu_item_new_with_label(mi->name);
4352 -                g_signal_connect(item, "activate",
4353 -                        G_CALLBACK(run_menu_command), mi);
4354 -                gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
4355 -                gtk_widget_show(item);
4356 -            }
4357 -            hit++;
4358 -        }
4359 -
4360 -        if((mi->context == WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)  &&
4361 -                (context <= WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) &&
4362 -                !hit) {
4363 -            if(mi->issep) {
4364 -                item = gtk_separator_menu_item_new();
4365 -                gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
4366 -                gtk_widget_show(item);
4367 -            }
4368 -            else {
4369 -                item = gtk_menu_item_new_with_label(mi->name);
4370 -                g_signal_connect(item, "activate",
4371 -                        G_CALLBACK(run_menu_command), mi);
4372 -                gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
4373 -                gtk_widget_show(item);
4374 -            }
4375 -        }
4376 -    }
4377 +    g_object_unref(hit_test_result);
4378  }
4379 +#endif
4380  
4381  void
4382  window_object_cleared_cb(WebKitWebView *webview, WebKitWebFrame *frame,
4383 -    JSGlobalContextRef *context, JSObjectRef *object) {
4384 +        JSGlobalContextRef *context, JSObjectRef *object) {
4385      (void) frame; (void) context; (void) object;
4386  #if WEBKIT_CHECK_VERSION (1, 3, 13)
4387      // Take this opportunity to set some callbacks on the DOM
4388 diff --git a/src/callbacks.h b/src/callbacks.h
4389 index 6a10205..56bf612 100644
4390 --- a/src/callbacks.h
4391 +++ b/src/callbacks.h
4392 @@ -31,19 +31,11 @@ gboolean
4393  key_release_cb (GtkWidget* window, GdkEventKey* event);
4394  
4395  gboolean
4396 -motion_notify_cb(GtkWidget* window, GdkEventMotion* event, gpointer user_data);
4397 -
4398 -gboolean
4399  navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
4400          WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
4401          WebKitWebPolicyDecision *policy_decision, gpointer user_data);
4402  
4403  gboolean
4404 -new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
4405 -        WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
4406 -        WebKitWebPolicyDecision *policy_decision, gpointer user_data);
4407 -
4408 -gboolean
4409  mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request,
4410          gchar *mime_type,  WebKitWebPolicyDecision *policy_decision, gpointer user_data);
4411  
4412 @@ -57,8 +49,13 @@ create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer us
4413  gboolean
4414  download_cb (WebKitWebView *web_view, WebKitDownload *download, gpointer user_data);
4415  
4416 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
4417 +gboolean
4418 +context_menu_cb (WebKitWebView *web_view, GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gboolean triggered_with_keyboard, gpointer user_data);
4419 +#else
4420  void
4421  populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c);
4422 +#endif
4423  
4424  gboolean
4425  button_press_cb (GtkWidget* window, GdkEventButton* event);
4426 @@ -76,8 +73,11 @@ gboolean
4427  scroll_horiz_cb(GtkAdjustment *adjust, void *w);
4428  
4429  void
4430 +close_web_view_cb(WebKitWebView *webview, gpointer user_data);
4431 +
4432 +void
4433  window_object_cleared_cb(WebKitWebView *webview, WebKitWebFrame *frame,
4434 -       JSGlobalContextRef *context, JSObjectRef *object);
4435 +        JSGlobalContextRef *context, JSObjectRef *object);
4436  
4437  #if WEBKIT_CHECK_VERSION (1, 3, 13)
4438  void
4439 diff --git a/src/commands.c b/src/commands.c
4440 index b3546a9..6769189 100644
4441 --- a/src/commands.c
4442 +++ b/src/commands.c
4443 @@ -6,6 +6,7 @@
4444  #include "callbacks.h"
4445  #include "variables.h"
4446  #include "type.h"
4447 +#include "soup.h"
4448  
4449  /* -- command to callback/function map for things we cannot attach to any signals */
4450  CommandInfo cmdlist[] =
4451 @@ -54,12 +55,34 @@ CommandInfo cmdlist[] =
4452      { "menu_image_remove",              menu_remove_image, TRUE        },
4453      { "menu_editable_remove",           menu_remove_edit, TRUE         },
4454      { "hardcopy",                       hardcopy, TRUE                 },
4455 +#ifndef USE_WEBKIT2
4456 +#if WEBKIT_CHECK_VERSION (1, 9, 6)
4457 +    { "snapshot",                       snapshot, TRUE                 },
4458 +#endif
4459 +#endif
4460 +#ifdef USE_WEBKIT2
4461 +#if WEBKIT_CHECK_VERSION (1, 9, 90)
4462 +    { "load",                           load, TRUE                     },
4463 +    { "save",                           save, TRUE                     },
4464 +#endif
4465 +#endif
4466 +    { "remove_all_db",                  remove_all_db, 0               },
4467 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
4468 +    { "plugin_refresh",                 plugin_refresh, TRUE           },
4469 +    { "plugin_toggle",                  plugin_toggle, TRUE            },
4470 +#endif
4471      { "include",                        include, TRUE                  },
4472 +    /* Deprecated */
4473      { "show_inspector",                 show_inspector, 0              },
4474 +    { "inspector",                      inspector, TRUE                },
4475 +#if WEBKIT_CHECK_VERSION (1, 5, 1)
4476 +    { "spell_checker",                  spell_checker, TRUE            },
4477 +#endif
4478      { "add_cookie",                     add_cookie, 0                  },
4479      { "delete_cookie",                  delete_cookie, 0               },
4480      { "clear_cookies",                  clear_cookies, 0               },
4481 -    { "download",                       download, 0                    }
4482 +    { "download",                       download, 0                    },
4483 +    { "auth",                           auth, 0                        }
4484  };
4485  
4486  void
4487 @@ -190,7 +213,11 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
4488      uzbl_cmdprop *c = get_var_c(var_name);
4489  
4490      if(!c) {
4491 -        set_var_value(var_name, argv_idx(argv, 1));
4492 +        if (argv->len > 1) {
4493 +            set_var_value(var_name, argv_idx(argv, 1));
4494 +        } else {
4495 +            set_var_value(var_name, "1");
4496 +        }
4497          return;
4498      }
4499  
4500 @@ -230,18 +257,18 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
4501          if(argv->len >= 3) {
4502              guint i = 2;
4503  
4504 -            int first = strtoul(argv_idx(argv, 1), NULL, 10);
4505 +            int first = strtol(argv_idx(argv, 1), NULL, 10);
4506              int  this = first;
4507  
4508              const gchar *next_s = argv_idx(argv, 2);
4509  
4510              while(next_s && this != current) {
4511 -                this   = strtoul(next_s, NULL, 10);
4512 +                this   = strtol(next_s, NULL, 10);
4513                  next_s = argv_idx(argv, ++i);
4514              }
4515  
4516              if(next_s)
4517 -                next = strtoul(next_s, NULL, 10);
4518 +                next = strtol(next_s, NULL, 10);
4519              else
4520                  next = first;
4521          } else
4522 @@ -250,6 +277,34 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
4523          set_var_value_int_c(c, next);
4524          break;
4525      }
4526 +    case TYPE_ULL:
4527 +    {
4528 +        unsigned long long current = get_var_value_int_c(c);
4529 +        unsigned long long next;
4530 +
4531 +        if(argv->len >= 3) {
4532 +            guint i = 2;
4533 +
4534 +            unsigned long long first = strtoull(argv_idx(argv, 1), NULL, 10);
4535 +            unsigned long long  this = first;
4536 +
4537 +            const gchar *next_s = argv_idx(argv, 2);
4538 +
4539 +            while(next_s && this != current) {
4540 +                this   = strtoull(next_s, NULL, 10);
4541 +                next_s = argv_idx(argv, ++i);
4542 +            }
4543 +
4544 +            if(next_s)
4545 +                next = strtoull(next_s, NULL, 10);
4546 +            else
4547 +                next = first;
4548 +        } else
4549 +            next = !current;
4550 +
4551 +        set_var_value_ull_c(c, next);
4552 +        break;
4553 +    }
4554      case TYPE_FLOAT:
4555      {
4556          float current = get_var_value_float_c(c);
4557 @@ -325,6 +380,117 @@ hardcopy(WebKitWebView *page, GArray *argv, GString *result) {
4558      webkit_web_frame_print(webkit_web_view_get_main_frame(page));
4559  }
4560  
4561 +#ifndef USE_WEBKIT2
4562 +#if WEBKIT_CHECK_VERSION (1, 9, 6)
4563 +void
4564 +snapshot(WebKitWebView *page, GArray *argv, GString *result) {
4565 +    (void) result;
4566 +    cairo_surface_t* surface;
4567 +
4568 +    surface = webkit_web_view_get_snapshot(page);
4569 +
4570 +    cairo_surface_write_to_png(surface, argv_idx(argv, 0));
4571 +
4572 +    cairo_surface_destroy(surface);
4573 +}
4574 +#endif
4575 +#endif
4576 +
4577 +#ifdef USE_WEBKIT2
4578 +#if WEBKIT_CHECK_VERSION (1, 9, 90)
4579 +void
4580 +load(WebKitWebView *page, GArray *argv, GString *result) {
4581 +    (void) result;
4582 +
4583 +    guint sz = argv->len;
4584 +
4585 +    const gchar *content = sz > 0 ? argv_idx(argv, 0) : NULL;
4586 +    const gchar *content_uri = sz > 2 ? argv_idx(argv, 1) : NULL;
4587 +    const gchar *base_uri = sz > 2 ? argv_idx(argv, 2) : NULL;
4588 +
4589 +    webkit_web_view_load_alternate_html(page, content, content_uri, base_uri);
4590 +}
4591 +
4592 +void
4593 +save(WebKitWebView *page, GArray *argv, GString *result) {
4594 +    (void) result;
4595 +    guint sz = argv->len;
4596 +
4597 +    const gchar *mode_str = sz > 0 ? argv_idx(argv, 0) : NULL;
4598 +
4599 +    WebKitSaveMode mode = WEBKIT_SAVE_MODE_MHTML;
4600 +
4601 +    if (!mode) {
4602 +        mode = WEBKIT_SAVE_MODE_MHTML;
4603 +    } else if (!strcmp("mhtml", mode_str)) {
4604 +        mode = WEBKIT_SAVE_MODE_MHTML;
4605 +    }
4606 +
4607 +    if (sz > 1) {
4608 +        const gchar *path = argv_idx(argv, 1);
4609 +        GFile *gfile = g_file_new_for_path(path);
4610 +
4611 +        webkit_web_view_save_to_file(page, gfile, mode, NULL, NULL, NULL);
4612 +        /* TODO: Don't ignore the error */
4613 +        webkit_web_view_save_to_file_finish(page, NULL, NULL);
4614 +    } else {
4615 +        webkit_web_view_save(page, mode, NULL, NULL, NULL);
4616 +        /* TODO: Don't ignore the error */
4617 +        webkit_web_view_save_finish(page, NULL, NULL);
4618 +    }
4619 +}
4620 +#endif
4621 +#endif
4622 +
4623 +void
4624 +remove_all_db(WebKitWebView *page, GArray *argv, GString *result) {
4625 +    (void) page; (void) argv; (void) result;
4626 +
4627 +    webkit_remove_all_web_databases ();
4628 +}
4629 +
4630 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
4631 +void
4632 +plugin_refresh(WebKitWebView *page, GArray *argv, GString *result) {
4633 +    (void) page; (void) argv; (void) result;
4634 +
4635 +    WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
4636 +    webkit_web_plugin_database_refresh (db);
4637 +}
4638 +
4639 +static void
4640 +plugin_toggle_one(WebKitWebPlugin *plugin, const gchar *name) {
4641 +    const gchar *plugin_name = webkit_web_plugin_get_name (plugin);
4642 +
4643 +    if (!name || !g_strcmp0 (name, plugin_name)) {
4644 +        gboolean enabled = webkit_web_plugin_get_enabled (plugin);
4645 +
4646 +        webkit_web_plugin_set_enabled (plugin, !enabled);
4647 +    }
4648 +}
4649 +
4650 +void
4651 +plugin_toggle(WebKitWebView *page, GArray *argv, GString *result) {
4652 +    (void) page; (void) result;
4653 +
4654 +    WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
4655 +    GSList *plugins = webkit_web_plugin_database_get_plugins (db);
4656 +
4657 +    if (argv->len == 0) {
4658 +        g_slist_foreach (plugins, (GFunc)plugin_toggle_one, NULL);
4659 +    } else {
4660 +        guint i;
4661 +        for (i = 0; i < argv->len; ++i) {
4662 +            const gchar *plugin_name = argv_idx (argv, i);
4663 +
4664 +            g_slist_foreach (plugins, (GFunc)plugin_toggle_one, &plugin_name);
4665 +        }
4666 +    }
4667 +
4668 +    webkit_web_plugin_database_plugins_list_free (plugins);
4669 +}
4670 +#endif
4671 +
4672  void
4673  include(WebKitWebView *page, GArray *argv, GString *result) {
4674      (void) page; (void) result;
4675 @@ -348,6 +514,103 @@ show_inspector(WebKitWebView *page, GArray *argv, GString *result) {
4676  }
4677  
4678  void
4679 +inspector(WebKitWebView *page, GArray *argv, GString *result) {
4680 +    (void) page; (void) result;
4681 +
4682 +    if (argv->len < 1) {
4683 +        return;
4684 +    }
4685 +
4686 +    const gchar* command = argv_idx (argv, 0);
4687 +
4688 +    if (!g_strcmp0 (command, "show")) {
4689 +        webkit_web_inspector_show (uzbl.gui.inspector);
4690 +    } else if (!g_strcmp0 (command, "close")) {
4691 +        webkit_web_inspector_close (uzbl.gui.inspector);
4692 +    } else if (!g_strcmp0 (command, "coord")) {
4693 +        if (argv->len < 3) {
4694 +            return;
4695 +        }
4696 +
4697 +        gdouble x = strtod (argv_idx (argv, 1), NULL);
4698 +        gdouble y = strtod (argv_idx (argv, 2), NULL);
4699 +
4700 +        /* Let's not tempt the dragons. */
4701 +        if (errno == ERANGE) {
4702 +            return;
4703 +        }
4704 +
4705 +        webkit_web_inspector_inspect_coordinates (uzbl.gui.inspector, x, y);
4706 +#if WEBKIT_CHECK_VERSION (1, 3, 17)
4707 +    } else if (!g_strcmp0 (command, "node")) {
4708 +        /* TODO: Implement */
4709 +#endif
4710 +    }
4711 +}
4712 +
4713 +#if WEBKIT_CHECK_VERSION (1, 5, 1)
4714 +void
4715 +spell_checker(WebKitWebView *page, GArray *argv, GString *result) {
4716 +    (void) page;
4717 +
4718 +    if (argv->len < 1) {
4719 +        return;
4720 +    }
4721 +
4722 +    GObject *obj = webkit_get_text_checker ();
4723 +
4724 +    if (!obj) {
4725 +        return;
4726 +    }
4727 +    if (!WEBKIT_IS_SPELL_CHECKER (obj)) {
4728 +        return;
4729 +    }
4730 +
4731 +    WebKitSpellChecker *checker = WEBKIT_SPELL_CHECKER (obj);
4732 +
4733 +    const gchar* command = argv_idx (argv, 0);
4734 +
4735 +    if (!g_strcmp0 (command, "ignore")) {
4736 +        if (argv->len < 2) {
4737 +            return;
4738 +        }
4739 +
4740 +        guint i;
4741 +        for (i = 1; i < argv->len; ++i) {
4742 +            const gchar *word = argv_idx (argv, i);
4743 +
4744 +            webkit_spell_checker_ignore_word (checker, word);
4745 +        }
4746 +    } else if (!g_strcmp0 (command, "learn")) {
4747 +        if (argv->len < 2) {
4748 +            return;
4749 +        }
4750 +
4751 +        guint i;
4752 +        for (i = 1; i < argv->len; ++i) {
4753 +            const gchar *word = argv_idx (argv, i);
4754 +
4755 +            webkit_spell_checker_learn_word (checker, word);
4756 +        }
4757 +    } else if (!g_strcmp0 (command, "autocorrect")) {
4758 +        if (argv->len != 2) {
4759 +            return;
4760 +        }
4761 +
4762 +        gchar *word = argv_idx (argv, 1);
4763 +
4764 +        char *new_word = webkit_spell_checker_get_autocorrect_suggestions_for_misspelled_word (checker, word);
4765 +
4766 +        g_string_assign (result, new_word);
4767 +
4768 +        free (new_word);
4769 +    } else if (!g_strcmp0 (command, "guesses")) {
4770 +        /* TODO Implement */
4771 +    }
4772 +}
4773 +#endif
4774 +
4775 +void
4776  add_cookie(WebKitWebView *page, GArray *argv, GString *result) {
4777      (void) page; (void) result;
4778      gchar *host, *path, *name, *value, *scheme;
4779 @@ -583,3 +846,18 @@ act_dump_config_as_events(WebKitWebView *web_view, GArray *argv, GString *result
4780      (void)web_view; (void) argv; (void)result;
4781      dump_config_as_events();
4782  }
4783 +
4784 +void
4785 +auth(WebKitWebView *page, GArray *argv, GString *result) {
4786 +    (void) page; (void) result;
4787 +    gchar *info, *username, *password;
4788 +
4789 +    if(argv->len != 3)
4790 +        return;
4791 +
4792 +    info = argv_idx (argv, 0);
4793 +    username = argv_idx (argv, 1);
4794 +    password = argv_idx (argv, 2);
4795 +
4796 +    authenticate (info, username, password);
4797 +}
4798 diff --git a/src/commands.h b/src/commands.h
4799 index 38bd5f2..6a14b9b 100644
4800 --- a/src/commands.h
4801 +++ b/src/commands.h
4802 @@ -4,7 +4,11 @@
4803  #ifndef __COMMANDS__
4804  #define __COMMANDS__
4805  
4806 +#ifdef USE_WEBKIT2
4807 +#include <webkit2/webkit2.h>
4808 +#else
4809  #include <webkit/webkit.h>
4810 +#endif
4811  
4812  typedef void (*Command)(WebKitWebView*, GArray *argv, GString *result);
4813  
4814 @@ -51,8 +55,29 @@ void        search_reverse_text (WebKitWebView *page, GArray *argv, GString *res
4815  void        search_clear(WebKitWebView *page, GArray *argv, GString *result);
4816  void        dehilight (WebKitWebView *page, GArray *argv, GString *result);
4817  void        hardcopy(WebKitWebView *page, GArray *argv, GString *result);
4818 +#ifndef USE_WEBKIT2
4819 +#if WEBKIT_CHECK_VERSION (1, 9, 6)
4820 +void        snapshot(WebKitWebView *page, GArray *argv, GString *result);
4821 +#endif
4822 +#endif
4823 +#ifdef USE_WEBKIT2
4824 +#if WEBKIT_CHECK_VERSION (1, 9, 90)
4825 +void        load(WebKitWebView *page, GArray *argv, GString *result);
4826 +void        save(WebKitWebView *page, GArray *argv, GString *result);
4827 +#endif
4828 +#endif
4829 +void        remove_all_db(WebKitWebView *page, GArray *argv, GString *result);
4830 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
4831 +void        plugin_refresh(WebKitWebView *page, GArray *argv, GString *result);
4832 +void        plugin_toggle(WebKitWebView *page, GArray *argv, GString *result);
4833 +#endif
4834  void        include(WebKitWebView *page, GArray *argv, GString *result);
4835 +/* Deprecated (use inspector instead) */
4836  void        show_inspector(WebKitWebView *page, GArray *argv, GString *result);
4837 +void        inspector(WebKitWebView *page, GArray *argv, GString *result);
4838 +#if WEBKIT_CHECK_VERSION (1, 5, 1)
4839 +void        spell_checker(WebKitWebView *page, GArray *argv, GString *result);
4840 +#endif
4841  void        add_cookie(WebKitWebView *page, GArray *argv, GString *result);
4842  void        delete_cookie(WebKitWebView *page, GArray *argv, GString *result);
4843  void        clear_cookies(WebKitWebView *pag, GArray *argv, GString *result);
4844 @@ -65,5 +90,6 @@ void        toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result
4845  void        toggle_status (WebKitWebView* page, GArray *argv, GString *result);
4846  void        act_dump_config(WebKitWebView* page, GArray *argv, GString *result);
4847  void        act_dump_config_as_events(WebKitWebView* page, GArray *argv, GString *result);
4848 +void        auth(WebKitWebView* page, GArray *argv, GString *result);
4849  
4850  #endif
4851 diff --git a/src/cookie-jar.c b/src/cookie-jar.c
4852 index 2f6be83..83facd5 100644
4853 --- a/src/cookie-jar.c
4854 +++ b/src/cookie-jar.c
4855 @@ -41,21 +41,21 @@ changed(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie) {
4856       * propagated to other uzbl instances using add/delete_cookie. */
4857      if(!uzbl_jar->in_manual_add) {
4858          gchar *scheme = cookie->secure
4859 -               ? cookie->http_only ? "httpsOnly" : "https"
4860 -               : cookie->http_only ? "httpOnly"  : "http";
4861 +                ? cookie->http_only ? "httpsOnly" : "https"
4862 +                : cookie->http_only ? "httpOnly"  : "http";
4863  
4864          gchar *expires = NULL;
4865          if(cookie->expires)
4866              expires = g_strdup_printf ("%ld", (long)soup_date_to_time_t (cookie->expires));
4867  
4868 -               send_event (new_cookie ? ADD_COOKIE : DELETE_COOKIE, NULL,
4869 -                       TYPE_STR, cookie->domain,
4870 -                       TYPE_STR, cookie->path,
4871 -                       TYPE_STR, cookie->name,
4872 -                       TYPE_STR, cookie->value,
4873 -                       TYPE_STR, scheme,
4874 -                       TYPE_STR, expires ? expires : "",
4875 -                       NULL);
4876 +            send_event (new_cookie ? ADD_COOKIE : DELETE_COOKIE, NULL,
4877 +                    TYPE_STR, cookie->domain,
4878 +                    TYPE_STR, cookie->path,
4879 +                    TYPE_STR, cookie->name,
4880 +                    TYPE_STR, cookie->value,
4881 +                    TYPE_STR, scheme,
4882 +                    TYPE_STR, expires ? expires : "",
4883 +                    NULL);
4884  
4885          if(expires)
4886              g_free(expires);
4887 diff --git a/src/events.c b/src/events.c
4888 index 081a942..8fc8573 100644
4889 --- a/src/events.c
4890 +++ b/src/events.c
4891 @@ -21,7 +21,9 @@ const char *event_table[LAST_EVENT] = {
4892       "LOAD_COMMIT"      ,
4893       "LOAD_FINISH"      ,
4894       "LOAD_ERROR"       ,
4895 +     "REQUEST_QUEUED"   ,
4896       "REQUEST_STARTING" ,
4897 +     "REQUEST_FINISHED" ,
4898       "KEY_PRESS"        ,
4899       "KEY_RELEASE"      ,
4900       "MOD_PRESS"        ,
4901 @@ -32,6 +34,7 @@ const char *event_table[LAST_EVENT] = {
4902       "GEOMETRY_CHANGED" ,
4903       "WEBINSPECTOR"     ,
4904       "NEW_WINDOW"       ,
4905 +     "CLOSE_WINDOW"     ,
4906       "SELECTION_CHANGED",
4907       "VARIABLE_SET"     ,
4908       "FIFO_SET"         ,
4909 @@ -48,7 +51,6 @@ const char *event_table[LAST_EVENT] = {
4910       "PLUG_CREATED"     ,
4911       "COMMAND_ERROR"    ,
4912       "BUILTINS"         ,
4913 -     "PTR_MOVE"         ,
4914       "SCROLL_VERT"      ,
4915       "SCROLL_HORIZ"     ,
4916       "DOWNLOAD_STARTED" ,
4917 @@ -57,7 +59,8 @@ const char *event_table[LAST_EVENT] = {
4918       "ADD_COOKIE"       ,
4919       "DELETE_COOKIE"    ,
4920       "FOCUS_ELEMENT"    ,
4921 -     "BLUR_ELEMENT"
4922 +     "BLUR_ELEMENT"     ,
4923 +     "AUTHENTICATE"
4924  };
4925  
4926  /* for now this is just a alias for GString */
4927 @@ -168,6 +171,10 @@ vformat_event(int type, const gchar *custom_event, va_list vargs) {
4928              g_string_append_printf (event_message, "%d", va_arg (vargs, int));
4929              break;
4930  
4931 +        case TYPE_ULL:
4932 +            g_string_append_printf (event_message, "%llu", va_arg (vargs, unsigned long long));
4933 +            break;
4934 +
4935          case TYPE_STR:
4936              /* a string that needs to be escaped */
4937              g_string_append_c (event_message, '\'');
4938 @@ -284,10 +291,10 @@ get_modifier_mask(guint state) {
4939              g_string_append(modifiers, "Ctrl|");
4940          if(state & GDK_MOD1_MASK)
4941              g_string_append(modifiers,"Mod1|");
4942 -               /* Mod2 is usually Num_Luck. Ignore it as it messes up keybindings.
4943 +        /* Mod2 is usually Num_Luck. Ignore it as it messes up keybindings.
4944          if(state & GDK_MOD2_MASK)
4945              g_string_append(modifiers,"Mod2|");
4946 -               */
4947 +        */
4948          if(state & GDK_MOD3_MASK)
4949              g_string_append(modifiers,"Mod3|");
4950          if(state & GDK_MOD4_MASK)
4951 @@ -349,10 +356,10 @@ guint key_to_modifier(guint keyval) {
4952      }
4953  }
4954  
4955 -guint button_to_modifier(guint buttonval) {
4956 -       if(buttonval <= 5)
4957 -               return 1 << (7 + buttonval);
4958 -       return 0;
4959 +guint button_to_modifier (guint buttonval) {
4960 +    if(buttonval <= 5)
4961 +        return 1 << (7 + buttonval);
4962 +    return 0;
4963  }
4964  
4965  /* Transform gdk key events to our own events */
4966 diff --git a/src/events.h b/src/events.h
4967 index 73d0712..bd9df33 100644
4968 --- a/src/events.h
4969 +++ b/src/events.h
4970 @@ -12,20 +12,21 @@
4971  /* Event system */
4972  enum event_type {
4973      LOAD_START, LOAD_COMMIT, LOAD_FINISH, LOAD_ERROR,
4974 -    REQUEST_STARTING,
4975 +    REQUEST_QUEUED, REQUEST_STARTING, REQUEST_FINISHED,
4976      KEY_PRESS, KEY_RELEASE, MOD_PRESS, MOD_RELEASE,
4977      COMMAND_EXECUTED,
4978      LINK_HOVER, TITLE_CHANGED, GEOMETRY_CHANGED,
4979 -    WEBINSPECTOR, NEW_WINDOW, SELECTION_CHANGED,
4980 +    WEBINSPECTOR, NEW_WINDOW, CLOSE_WINDOW, SELECTION_CHANGED,
4981      VARIABLE_SET, FIFO_SET, SOCKET_SET,
4982      INSTANCE_START, INSTANCE_EXIT, LOAD_PROGRESS,
4983      LINK_UNHOVER, FORM_ACTIVE, ROOT_ACTIVE,
4984      FOCUS_LOST, FOCUS_GAINED, FILE_INCLUDED,
4985      PLUG_CREATED, COMMAND_ERROR, BUILTINS,
4986 -    PTR_MOVE, SCROLL_VERT, SCROLL_HORIZ,
4987 +    SCROLL_VERT, SCROLL_HORIZ,
4988      DOWNLOAD_STARTED, DOWNLOAD_PROGRESS, DOWNLOAD_COMPLETE,
4989      ADD_COOKIE, DELETE_COOKIE,
4990      FOCUS_ELEMENT, BLUR_ELEMENT,
4991 +    AUTHENTICATE,
4992  
4993      /* must be last entry */
4994      LAST_EVENT
4995 diff --git a/src/inspector.c b/src/inspector.c
4996 index d0d86b9..d584c77 100644
4997 --- a/src/inspector.c
4998 +++ b/src/inspector.c
4999 @@ -8,14 +8,14 @@
5000  #include "callbacks.h"
5001  #include "type.h"
5002  
5003 -void
5004 +static void
5005  hide_window_cb(GtkWidget *widget, gpointer data) {
5006      (void) data;
5007  
5008      gtk_widget_hide(widget);
5009  }
5010  
5011 -WebKitWebView*
5012 +static WebKitWebView *
5013  create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){
5014      (void) data;
5015      (void) page;
5016 @@ -44,7 +44,7 @@ create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpo
5017      return WEBKIT_WEB_VIEW(new_web_view);
5018  }
5019  
5020 -gboolean
5021 +static gboolean
5022  inspector_show_window_cb (WebKitWebInspector* inspector){
5023      (void) inspector;
5024      gtk_widget_show(uzbl.gui.inspector_window);
5025 @@ -54,32 +54,32 @@ inspector_show_window_cb (WebKitWebInspector* inspector){
5026  }
5027  
5028  /* TODO: Add variables and code to make use of these functions */
5029 -gboolean
5030 +static gboolean
5031  inspector_close_window_cb (WebKitWebInspector* inspector){
5032      (void) inspector;
5033      send_event(WEBINSPECTOR, NULL, TYPE_NAME, "close", NULL);
5034      return TRUE;
5035  }
5036  
5037 -gboolean
5038 +static gboolean
5039  inspector_attach_window_cb (WebKitWebInspector* inspector){
5040      (void) inspector;
5041      return FALSE;
5042  }
5043  
5044 -gboolean
5045 +static gboolean
5046  inspector_detach_window_cb (WebKitWebInspector* inspector){
5047      (void) inspector;
5048      return FALSE;
5049  }
5050  
5051 -gboolean
5052 +static gboolean
5053  inspector_uri_changed_cb (WebKitWebInspector* inspector){
5054      (void) inspector;
5055      return FALSE;
5056  }
5057  
5058 -gboolean
5059 +static gboolean
5060  inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){
5061      (void) inspector;
5062      return FALSE;
5063 diff --git a/src/io.c b/src/io.c
5064 index ff418ef..ca3fcf6 100644
5065 --- a/src/io.c
5066 +++ b/src/io.c
5067 @@ -47,7 +47,7 @@ control_fifo(GIOChannel *gio, GIOCondition condition) {
5068  }
5069  
5070  
5071 -gboolean
5072 +static gboolean
5073  attach_fifo(gchar *path) {
5074      GError *error = NULL;
5075      /* we don't really need to write to the file, but if we open the
5076 @@ -259,7 +259,7 @@ control_client_socket(GIOChannel *clientchan) {
5077  }
5078  
5079  
5080 -gboolean
5081 +static gboolean
5082  attach_socket(gchar *path, struct sockaddr_un *local) {
5083      GIOChannel *chan = NULL;
5084      int sock = socket (AF_UNIX, SOCK_STREAM, 0);
5085 diff --git a/src/menu.c b/src/menu.c
5086 index 451b35a..1d9c5b4 100644
5087 --- a/src/menu.c
5088 +++ b/src/menu.c
5089 @@ -2,7 +2,7 @@
5090  #include "util.h"
5091  #include "uzbl-core.h"
5092  
5093 -void
5094 +static void
5095  add_to_menu(GArray *argv, guint context) {
5096      GUI *g = &uzbl.gui;
5097      MenuItem *m;
5098 @@ -68,7 +68,7 @@ menu_add_edit(WebKitWebView *page, GArray *argv, GString *result) {
5099  }
5100  
5101  
5102 -void
5103 +static void
5104  add_separator_to_menu(GArray *argv, guint context) {
5105      GUI *g = &uzbl.gui;
5106      MenuItem *m;
5107 @@ -127,7 +127,7 @@ menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result) {
5108  }
5109  
5110  
5111 -void
5112 +static void
5113  remove_from_menu(GArray *argv, guint context) {
5114      GUI *g = &uzbl.gui;
5115      MenuItem *mi;
5116 diff --git a/src/menu.h b/src/menu.h
5117 index 03055e5..c4ca047 100644
5118 --- a/src/menu.h
5119 +++ b/src/menu.h
5120 @@ -1,14 +1,18 @@
5121  #ifndef __MENU__
5122  #define __MENU__
5123  
5124 +#ifdef USE_WEBKIT2
5125 +#include <webkit2/webkit2.h>
5126 +#else
5127  #include <webkit/webkit.h>
5128 +#endif
5129  
5130  typedef struct {
5131      gchar*   name;
5132      gchar*   cmd;
5133      gboolean issep;
5134      guint    context;
5135 -    WebKitHitTestResult* hittest;
5136 +    gchar*   argument;
5137  } MenuItem;
5138  
5139  void    menu_add(WebKitWebView *page, GArray *argv, GString *result);
5140 diff --git a/src/soup.c b/src/soup.c
5141 new file mode 100644
5142 index 0000000..8430018
5143 --- /dev/null
5144 +++ b/src/soup.c
5145 @@ -0,0 +1,183 @@
5146 +#include "uzbl-core.h"
5147 +#include "util.h"
5148 +#include "events.h"
5149 +#include "type.h"
5150 +
5151 +static void handle_authentication    (SoupSession *session,
5152 +                                      SoupMessage *msg,
5153 +                                      SoupAuth    *auth,
5154 +                                      gboolean     retrying,
5155 +                                      gpointer     user_data);
5156 +
5157 +static void handle_request_queued    (SoupSession *session,
5158 +                                      SoupMessage *msg,
5159 +                                      gpointer user_data);
5160 +
5161 +static void handle_request_started   (SoupSession *session,
5162 +                                      SoupMessage *msg,
5163 +                                      gpointer user_data);
5164 +
5165 +static void handle_request_finished (SoupMessage *msg,
5166 +                                     gpointer user_data);
5167 +
5168 +struct _PendingAuth
5169 +{
5170 +    SoupAuth *auth;
5171 +    GList    *messages;
5172 +};
5173 +typedef struct _PendingAuth PendingAuth;
5174 +
5175 +static PendingAuth *pending_auth_new         (SoupAuth *auth);
5176 +static void         pending_auth_free        (PendingAuth *self);
5177 +static void         pending_auth_add_message (PendingAuth *self,
5178 +                                              SoupMessage *message);
5179 +
5180 +void
5181 +uzbl_soup_init (SoupSession *session)
5182 +{
5183 +    uzbl.net.soup_cookie_jar = uzbl_cookie_jar_new ();
5184 +    uzbl.net.pending_auths = g_hash_table_new_full (
5185 +        g_str_hash, g_str_equal,
5186 +        g_free, pending_auth_free
5187 +    );
5188 +
5189 +    soup_session_add_feature (
5190 +        session,
5191 +        SOUP_SESSION_FEATURE (uzbl.net.soup_cookie_jar)
5192 +    );
5193 +
5194 +    g_signal_connect (
5195 +        session, "request-queued",
5196 +        G_CALLBACK (handle_request_queued), NULL
5197 +    );
5198 +
5199 +    g_signal_connect (
5200 +        session, "request-started",
5201 +        G_CALLBACK (handle_request_started), NULL
5202 +    );
5203 +
5204 +    g_signal_connect (
5205 +       session, "authenticate",
5206 +        G_CALLBACK (handle_authentication), NULL
5207 +    );
5208 +}
5209 +
5210 +void authenticate (const char *authinfo,
5211 +                   const char *username,
5212 +                   const char *password)
5213 +{
5214 +    PendingAuth *pending = g_hash_table_lookup (
5215 +        uzbl.net.pending_auths,
5216 +        authinfo
5217 +    );
5218 +
5219 +    if (pending == NULL) {
5220 +        return;
5221 +    }
5222 +
5223 +    soup_auth_authenticate (pending->auth, username, password);
5224 +    for(GList *l = pending->messages; l != NULL; l = l->next) {
5225 +        soup_session_unpause_message (
5226 +            uzbl.net.soup_session,
5227 +            SOUP_MESSAGE (l->data)
5228 +        );
5229 +    }
5230 +
5231 +    g_hash_table_remove (uzbl.net.pending_auths, authinfo);
5232 +}
5233 +
5234 +static void
5235 +handle_request_queued (SoupSession *session,
5236 +                       SoupMessage *msg,
5237 +                       gpointer     user_data)
5238 +{
5239 +    (void) session; (void) user_data;
5240 +
5241 +    send_event (
5242 +        REQUEST_QUEUED, NULL,
5243 +        TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
5244 +        NULL
5245 +    );
5246 +}
5247 +
5248 +static void
5249 +handle_request_started (SoupSession *session,
5250 +                        SoupMessage *msg,
5251 +                        gpointer     user_data)
5252 +{
5253 +    (void) session; (void) user_data;
5254 +
5255 +    send_event (
5256 +        REQUEST_STARTING, NULL,
5257 +        TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
5258 +        NULL
5259 +    );
5260 +
5261 +    g_signal_connect (
5262 +        G_OBJECT (msg), "finished",
5263 +        G_CALLBACK (handle_request_finished), NULL
5264 +    );
5265 +}
5266 +
5267 +static void
5268 +handle_request_finished (SoupMessage *msg, gpointer user_data)
5269 +{
5270 +    (void) user_data;
5271 +
5272 +    send_event (
5273 +        REQUEST_FINISHED, NULL,
5274 +        TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
5275 +        NULL
5276 +    );
5277 +}
5278 +
5279 +static void
5280 +handle_authentication (SoupSession *session,
5281 +                       SoupMessage *msg,
5282 +                       SoupAuth    *auth,
5283 +                       gboolean     retrying,
5284 +                       gpointer     user_data)
5285 +{
5286 +    (void) user_data;
5287 +    PendingAuth *pending;
5288 +    char *authinfo = soup_auth_get_info (auth);
5289 +
5290 +    pending = g_hash_table_lookup (uzbl.net.pending_auths, authinfo);
5291 +    if (pending == NULL) {
5292 +        pending = pending_auth_new (auth);
5293 +        g_hash_table_insert (uzbl.net.pending_auths, authinfo, pending);
5294 +    }
5295 +
5296 +    pending_auth_add_message (pending, msg);
5297 +    soup_session_pause_message (session, msg);
5298 +
5299 +    send_event (
5300 +        AUTHENTICATE, NULL,
5301 +        TYPE_STR, authinfo,
5302 +        TYPE_STR, soup_auth_get_host (auth),
5303 +        TYPE_STR, soup_auth_get_realm (auth),
5304 +        TYPE_STR, (retrying ? "retrying" : ""),
5305 +        NULL
5306 +    );
5307 +}
5308 +
5309 +static PendingAuth *pending_auth_new (SoupAuth *auth)
5310 +{
5311 +    PendingAuth *self = g_new (PendingAuth, 1);
5312 +    self->auth = auth;
5313 +    self->messages = NULL;
5314 +    g_object_ref (auth);
5315 +    return self;
5316 +}
5317 +
5318 +static void pending_auth_free (PendingAuth *self)
5319 +{
5320 +    g_object_unref (self->auth);
5321 +    g_list_free_full (self->messages, g_object_unref);
5322 +    g_free (self);
5323 +}
5324 +
5325 +static void pending_auth_add_message (PendingAuth *self, SoupMessage *message)
5326 +{
5327 +    self->messages = g_list_append (self->messages, g_object_ref (message));
5328 +}
5329 diff --git a/src/soup.h b/src/soup.h
5330 new file mode 100644
5331 index 0000000..ce7d605
5332 --- /dev/null
5333 +++ b/src/soup.h
5334 @@ -0,0 +1,18 @@
5335 +/**
5336 + * Uzbl tweaks and extension for soup
5337 + */
5338 +
5339 +#ifndef __UZBL_SOUP__
5340 +#define __UZBL_SOUP__
5341 +
5342 +#include <libsoup/soup.h>
5343 +
5344 +/**
5345 + * Attach uzbl specific behaviour to the given SoupSession
5346 + */
5347 +void uzbl_soup_init (SoupSession *session);
5348 +
5349 +void authenticate   (const char  *authinfo,
5350 +                     const char  *username,
5351 +                     const char  *password);
5352 +#endif
5353 diff --git a/src/status-bar.h b/src/status-bar.h
5354 index cae8905..1fd4ef2 100644
5355 --- a/src/status-bar.h
5356 +++ b/src/status-bar.h
5357 @@ -10,7 +10,7 @@
5358  #define UZBL_IS_STATUS_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UZBL_TYPE_STATUS_BAR))
5359  #define UZBL_STATUS_BAR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), UZBL_TYPE_STATUS_BAR, UzblStatusBarClass))
5360  
5361 -typedef struct _UzblStatusBar        UzblStatusBar;
5362 +typedef struct _UzblStatusBar       UzblStatusBar;
5363  typedef struct _UzblStatusBarClass  UzblStatusBarClass;
5364  
5365  struct _UzblStatusBar {
5366 diff --git a/src/type.h b/src/type.h
5367 index eda02c1..826ca3b 100644
5368 --- a/src/type.h
5369 +++ b/src/type.h
5370 @@ -9,6 +9,7 @@ enum ptr_type {
5371      TYPE_INT = 1,
5372      TYPE_STR,
5373      TYPE_FLOAT,
5374 +    TYPE_ULL,
5375      TYPE_NAME,
5376      // used by send_event
5377      TYPE_FORMATTEDSTR,
5378 @@ -24,6 +25,7 @@ enum ptr_type {
5379  typedef union uzbl_value_ptr_t {
5380      int   *i;
5381      float *f;
5382 +    unsigned long long *ull;
5383      gchar **s;
5384  } uzbl_value_ptr;
5385  
5386 diff --git a/src/util.c b/src/util.c
5387 index 1468d23..3f7f093 100644
5388 --- a/src/util.c
5389 +++ b/src/util.c
5390 @@ -195,3 +195,9 @@ append_escaped (GString *dest, const gchar *src) {
5391      }
5392      return dest;
5393  }
5394 +
5395 +void
5396 +sharg_append (GArray *a, const gchar *str) {
5397 +    const gchar *s = (str ? str : "");
5398 +    g_array_append_val(a, s);
5399 +}
5400 diff --git a/src/util.h b/src/util.h
5401 index cc29247..e0a8bbd 100644
5402 --- a/src/util.h
5403 +++ b/src/util.h
5404 @@ -12,7 +12,11 @@ char*       str_replace(const char* search, const char* replace, const char* str
5405  gboolean    for_each_line_in_file(const gchar *path, void (*callback)(const gchar *l, void *c), void *user_data);
5406  gchar*      find_existing_file(const gchar*);
5407  gchar*      argv_idx(const GArray*, const guint);
5408 +
5409  /**
5410   * appends `src' to `dest' with backslash, single-quotes and newlines in
5411 - * `src' escaped */
5412 + * `src' escaped
5413 + */
5414  GString *   append_escaped (GString *dest, const gchar *src);
5415 +
5416 +void        sharg_append (GArray *array, const gchar *str);
5417 diff --git a/src/uzbl-core.c b/src/uzbl-core.c
5418 index 1288f22..748202f 100644
5419 --- a/src/uzbl-core.c
5420 +++ b/src/uzbl-core.c
5421 @@ -39,6 +39,7 @@
5422  #include "io.h"
5423  #include "variables.h"
5424  #include "type.h"
5425 +#include "soup.h"
5426  
5427  UzblCore uzbl;
5428  
5429 @@ -279,22 +280,6 @@ clean_up(void) {
5430      }
5431  }
5432  
5433 -gint
5434 -get_click_context() {
5435 -    GUI *g = &uzbl.gui;
5436 -    WebKitHitTestResult *ht;
5437 -    guint context;
5438 -
5439 -    if(!uzbl.state.last_button)
5440 -        return -1;
5441 -
5442 -    ht = webkit_web_view_get_hit_test_result (g->web_view, uzbl.state.last_button);
5443 -    g_object_get (ht, "context", &context, NULL);
5444 -       g_object_unref (ht);
5445 -
5446 -    return (gint)context;
5447 -}
5448 -
5449  /* --- SIGNALS --- */
5450  sigfunc*
5451  setup_signal(int signr, sigfunc *shandler) {
5452 @@ -310,7 +295,7 @@ setup_signal(int signr, sigfunc *shandler) {
5453      return NULL;
5454  }
5455  
5456 -void
5457 +static void
5458  empty_event_buffer(int s) {
5459      (void) s;
5460      if(uzbl.state.event_buffer) {
5461 @@ -356,12 +341,12 @@ parse_cmd_line_cb(const char *line, void *user_data) {
5462  }
5463  
5464  void
5465 -run_command_file(const gchar *path) {
5466 -       if(!for_each_line_in_file(path, parse_cmd_line_cb, NULL)) {
5467 -               gchar *tmp = g_strdup_printf("File %s can not be read.", path);
5468 -               send_event(COMMAND_ERROR, NULL, TYPE_STR, tmp, NULL);
5469 -               g_free(tmp);
5470 -       }
5471 +run_command_file (const gchar *path) {
5472 +    if(!for_each_line_in_file(path, parse_cmd_line_cb, NULL)) {
5473 +        gchar *tmp = g_strdup_printf("File %s can not be read.", path);
5474 +        send_event(COMMAND_ERROR, NULL, TYPE_STR, tmp, NULL);
5475 +        g_free(tmp);
5476 +    }
5477  }
5478  
5479  /* Javascript*/
5480 @@ -463,12 +448,6 @@ search_text (WebKitWebView *page, const gchar *key, const gboolean forward) {
5481      }
5482  }
5483  
5484 -void
5485 -sharg_append(GArray *a, const gchar *str) {
5486 -    const gchar *s = (str ? str : "");
5487 -    g_array_append_val(a, s);
5488 -}
5489 -
5490  /* make sure that the args string you pass can properly be interpreted (eg
5491   * properly escaped against whitespace, quotes etc) */
5492  gboolean
5493 @@ -517,7 +496,7 @@ run_command (const gchar *command, const gchar **args, const gboolean sync,
5494      return result;
5495  }
5496  
5497 -/*@null@*/ gchar**
5498 +/*@null@*/ static gchar**
5499  split_quoted(const gchar* src, const gboolean unquote) {
5500      /* split on unquoted space or tab, return array of strings;
5501         remove a layer of quotes and backslashes if unquote */
5502 @@ -769,7 +748,6 @@ create_scrolled_win() {
5503        "signal::key-release-event",                    (GCallback)key_release_cb,          NULL,
5504        "signal::button-press-event",                   (GCallback)button_press_cb,         NULL,
5505        "signal::button-release-event",                 (GCallback)button_release_cb,       NULL,
5506 -      "signal::motion-notify-event",                  (GCallback)motion_notify_cb,        NULL,
5507        "signal::notify::title",                        (GCallback)title_change_cb,         NULL,
5508        "signal::notify::progress",                     (GCallback)progress_change_cb,      NULL,
5509        "signal::notify::load-status",                  (GCallback)load_status_change_cb,   NULL,
5510 @@ -777,12 +755,16 @@ create_scrolled_win() {
5511        "signal::load-error",                           (GCallback)load_error_cb,           NULL,
5512        "signal::hovering-over-link",                   (GCallback)link_hover_cb,           NULL,
5513        "signal::navigation-policy-decision-requested", (GCallback)navigation_decision_cb,  NULL,
5514 -      "signal::new-window-policy-decision-requested", (GCallback)new_window_cb,           NULL,
5515 +      "signal::close-web-view",                       (GCallback)close_web_view_cb,       NULL,
5516        "signal::download-requested",                   (GCallback)download_cb,             NULL,
5517        "signal::create-web-view",                      (GCallback)create_web_view_cb,      NULL,
5518        "signal::mime-type-policy-decision-requested",  (GCallback)mime_policy_cb,          NULL,
5519        "signal::resource-request-starting",            (GCallback)request_starting_cb,     NULL,
5520 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
5521 +      "signal::context-menu",                         (GCallback)context_menu_cb,         NULL,
5522 +#else
5523        "signal::populate-popup",                       (GCallback)populate_popup_cb,       NULL,
5524 +#endif
5525        "signal::focus-in-event",                       (GCallback)focus_cb,                NULL,
5526        "signal::focus-out-event",                      (GCallback)focus_cb,                NULL,
5527        "signal::window-object-cleared",                (GCallback)window_object_cleared_cb,NULL,
5528 @@ -822,7 +804,6 @@ create_plug() {
5529  void
5530  settings_init () {
5531      State*   s = &uzbl.state;
5532 -    Network* n = &uzbl.net;
5533      int      i;
5534  
5535      /* Load default config */
5536 @@ -841,70 +822,13 @@ settings_init () {
5537  
5538      /* Load config file, if any */
5539      if (s->config_file) {
5540 -               run_command_file(s->config_file);
5541 +        run_command_file(s->config_file);
5542          g_setenv("UZBL_CONFIG", s->config_file, TRUE);
5543      } else if (uzbl.state.verbose)
5544          printf ("No configuration file loaded.\n");
5545  
5546      if (s->connect_socket_names)
5547          init_connect_socket();
5548 -
5549 -    g_signal_connect(n->soup_session, "authenticate", G_CALLBACK(handle_authentication), NULL);
5550 -}
5551 -
5552 -
5553 -void handle_authentication (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data) {
5554 -    (void) user_data;
5555 -
5556 -    if (uzbl.behave.authentication_handler && *uzbl.behave.authentication_handler != 0) {
5557 -        soup_session_pause_message(session, msg);
5558 -
5559 -        GString *result = g_string_new ("");
5560 -
5561 -        gchar *info  = g_strdup(soup_auth_get_info(auth));
5562 -        gchar *host  = g_strdup(soup_auth_get_host(auth));
5563 -        gchar *realm = g_strdup(soup_auth_get_realm(auth));
5564 -
5565 -        GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
5566 -        const CommandInfo *c = parse_command_parts(uzbl.behave.authentication_handler, a);
5567 -        if(c) {
5568 -            sharg_append(a, info);
5569 -            sharg_append(a, host);
5570 -            sharg_append(a, realm);
5571 -            sharg_append(a, retrying ? "TRUE" : "FALSE");
5572 -
5573 -            run_parsed_command(c, a, result);
5574 -        }
5575 -        g_array_free(a, TRUE);
5576 -
5577 -        if (result->len > 0) {
5578 -            char  *username, *password;
5579 -            int    number_of_endls=0;
5580 -
5581 -            username = result->str;
5582 -
5583 -            gchar *p;
5584 -            for (p = result->str; *p; p++) {
5585 -                if (*p == '\n') {
5586 -                    *p = '\0';
5587 -                    if (++number_of_endls == 1)
5588 -                        password = p + 1;
5589 -                }
5590 -            }
5591 -
5592 -            /* If stdout was correct (contains exactly two lines of text) do
5593 -             * authenticate. */
5594 -            if (number_of_endls == 2)
5595 -                soup_auth_authenticate(auth, username, password);
5596 -        }
5597 -
5598 -        soup_session_unpause_message(session, msg);
5599 -
5600 -        g_string_free(result, TRUE);
5601 -        g_free(info);
5602 -        g_free(host);
5603 -        g_free(realm);
5604 -    }
5605  }
5606  
5607  /* Set up gtk, gobject, variable defaults and other things that tests and other
5608 @@ -922,11 +846,19 @@ initialize(int argc, char** argv) {
5609      uzbl.info.webkit_major     = webkit_major_version();
5610      uzbl.info.webkit_minor     = webkit_minor_version();
5611      uzbl.info.webkit_micro     = webkit_micro_version();
5612 +#ifdef USE_WEBKIT2
5613 +    uzbl.info.webkit2          = 1;
5614 +#else
5615 +    uzbl.info.webkit2          = 0;
5616 +#endif
5617      uzbl.info.arch             = ARCH;
5618      uzbl.info.commit           = COMMIT;
5619  
5620      uzbl.state.last_result  = NULL;
5621  
5622 +    /* BUG There isn't a getter for this; need to maintain separately. */
5623 +    uzbl.behave.maintain_history = TRUE;
5624 +
5625      /* Parse commandline arguments */
5626      GOptionContext* context = g_option_context_new ("[ uri ] - load a uri by default");
5627      g_option_context_add_main_entries(context, entries, NULL);
5628 @@ -956,10 +888,8 @@ initialize(int argc, char** argv) {
5629      event_buffer_timeout(10);
5630  
5631      /* HTTP client */
5632 -    uzbl.net.soup_session      = webkit_get_default_session();
5633 -    uzbl.net.soup_cookie_jar   = uzbl_cookie_jar_new();
5634 -
5635 -    soup_session_add_feature(uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_cookie_jar));
5636 +    uzbl.net.soup_session = webkit_get_default_session();
5637 +    uzbl_soup_init (uzbl.net.soup_session);
5638  
5639      commands_hash();
5640      variables_hash();
5641 @@ -1081,6 +1011,13 @@ main (int argc, char* argv[]) {
5642  
5643      gboolean verbose_override = uzbl.state.verbose;
5644  
5645 +    /* Finally show the window */
5646 +    if (uzbl.gui.main_window) {
5647 +        gtk_widget_show_all (uzbl.gui.main_window);
5648 +    } else {
5649 +        gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug));
5650 +    }
5651 +
5652      /* Read configuration file */
5653      settings_init();
5654  
5655 @@ -1099,13 +1036,6 @@ main (int argc, char* argv[]) {
5656          g_free(uri_override);
5657      }
5658  
5659 -    /* Finally show the window */
5660 -    if (uzbl.gui.main_window) {
5661 -        gtk_widget_show_all (uzbl.gui.main_window);
5662 -    } else {
5663 -        gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug));
5664 -    }
5665 -
5666      /* Verbose feedback */
5667      if (uzbl.state.verbose) {
5668          printf("Uzbl start location: %s\n", argv[0]);
5669 diff --git a/src/uzbl-core.h b/src/uzbl-core.h
5670 index 1f9613e..6f27818 100644
5671 --- a/src/uzbl-core.h
5672 +++ b/src/uzbl-core.h
5673 @@ -23,7 +23,11 @@
5674  #include <sys/un.h>
5675  #include <sys/utsname.h>
5676  #include <sys/time.h>
5677 +#ifdef USE_WEBKIT2
5678 +#include <webkit2/webkit2.h>
5679 +#else
5680  #include <webkit/webkit.h>
5681 +#endif
5682  #include <libsoup/soup.h>
5683  #include <JavaScriptCore/JavaScript.h>
5684  
5685 @@ -78,6 +82,7 @@ typedef struct {
5686      WebKitWebInspector* inspector;
5687  
5688      /* Custom context menu item */
5689 +    gboolean       custom_context_menu;
5690      GPtrArray*     menu_items;
5691  } GUI;
5692  
5693 @@ -114,6 +119,9 @@ typedef struct {
5694      gboolean        handle_multi_button;
5695      GPtrArray*      event_buffer;
5696      gchar**         connect_socket_names;
5697 +
5698 +    /* Temporary web view used when a new window is opened */
5699 +    WebKitWebView* _tmp_web_view;
5700  } State;
5701  
5702  
5703 @@ -121,6 +129,7 @@ typedef struct {
5704  typedef struct {
5705      SoupSession*    soup_session;
5706      UzblCookieJar*  soup_cookie_jar;
5707 +    GHashTable*     pending_auths;
5708      SoupLogger*     soup_logger;
5709      char*           proxy_url;
5710      char*           useragent;
5711 @@ -129,12 +138,6 @@ typedef struct {
5712      gint            max_conns_host;
5713  } Network;
5714  
5715 -/* ssl */
5716 -typedef struct {
5717 -    gchar *ca_file;
5718 -    gchar *verify_cert;
5719 -} Ssl;
5720 -
5721  /* Behaviour */
5722  typedef struct {
5723      /* Status bar */
5724 @@ -161,6 +164,7 @@ typedef struct {
5725      guint    http_debug;
5726      gchar*   shell_cmd;
5727      guint    view_source;
5728 +    gboolean maintain_history;
5729  
5730      gboolean print_version;
5731  
5732 @@ -176,6 +180,7 @@ typedef struct {
5733      int     webkit_major;
5734      int     webkit_minor;
5735      int     webkit_micro;
5736 +    int     webkit2;
5737      gchar*  arch;
5738      gchar*  commit;
5739  
5740 @@ -189,7 +194,6 @@ typedef struct {
5741      GUI           gui;
5742      State         state;
5743      Network       net;
5744 -    Ssl           ssl;
5745      Behaviour     behave;
5746      Communication comm;
5747      Info          info;
5748 @@ -245,19 +249,12 @@ void        search_text (WebKitWebView *page, const gchar *key, const gboolean f
5749  void        eval_js(WebKitWebView *web_view, const gchar *script, GString *result, const gchar *script_file);
5750  
5751  /* Network functions */
5752 -void        handle_authentication (SoupSession *session,
5753 -                            SoupMessage *msg,
5754 -                            SoupAuth    *auth,
5755 -                            gboolean     retrying,
5756 -                            gpointer     user_data);
5757 -
5758  void        init_connect_socket();
5759  gboolean    remove_socket_from_array(GIOChannel *chan);
5760  
5761  /* Window */
5762  void        retrieve_geometry();
5763  void        scroll(GtkAdjustment* bar, gchar *amount_str);
5764 -gint        get_click_context();
5765  
5766  
5767  #endif
5768 diff --git a/src/variables.c b/src/variables.c
5769 index e4763bc..749a176 100644
5770 --- a/src/variables.c
5771 +++ b/src/variables.c
5772 @@ -5,6 +5,8 @@
5773  #include "io.h"
5774  #include "util.h"
5775  
5776 +#include <stdlib.h>
5777 +
5778  uzbl_cmdprop *
5779  get_var_c(const gchar *name) {
5780      return g_hash_table_lookup(uzbl.behave.proto_var, name);
5781 @@ -32,6 +34,13 @@ send_set_var_event(const char *name, const uzbl_cmdprop *c) {
5782              TYPE_INT, get_var_value_int_c(c),
5783              NULL);
5784          break;
5785 +    case TYPE_ULL:
5786 +        send_event (VARIABLE_SET, NULL,
5787 +            TYPE_NAME, name,
5788 +            TYPE_NAME, "ull",
5789 +            TYPE_ULL, get_var_value_ull_c(c),
5790 +            NULL);
5791 +        break;
5792      case TYPE_FLOAT:
5793          send_event (VARIABLE_SET, NULL,
5794              TYPE_NAME, name,
5795 @@ -78,6 +87,14 @@ set_var_value_int_c(uzbl_cmdprop *c, int i) {
5796  }
5797  
5798  void
5799 +set_var_value_ull_c(uzbl_cmdprop *c, unsigned long long ull) {
5800 +    if(c->setter)
5801 +        ((void (*)(unsigned long long))c->setter)(ull);
5802 +    else
5803 +        *(c->ptr.ull) = ull;
5804 +}
5805 +
5806 +void
5807  set_var_value_float_c(uzbl_cmdprop *c, float f) {
5808      if(c->setter)
5809          ((void (*)(float))c->setter)(f);
5810 @@ -100,10 +117,16 @@ set_var_value(const gchar *name, gchar *val) {
5811              break;
5812          case TYPE_INT:
5813          {
5814 -            int i = (int)strtoul(val, NULL, 10);
5815 +            int i = (int)strtol(val, NULL, 10);
5816              set_var_value_int_c(c, i);
5817              break;
5818          }
5819 +        case TYPE_ULL:
5820 +        {
5821 +            unsigned long long ull = strtoull(val, NULL, 10);
5822 +            set_var_value_ull_c(c, ull);
5823 +            break;
5824 +        }
5825          case TYPE_FLOAT:
5826          {
5827              float f = strtod(val, NULL);
5828 @@ -184,6 +207,24 @@ get_var_value_int(const gchar *name) {
5829      return get_var_value_int_c(c);
5830  }
5831  
5832 +unsigned long long
5833 +get_var_value_ull_c(const uzbl_cmdprop *c) {
5834 +    if(!c) return 0;
5835 +
5836 +    if(c->getter) {
5837 +        return ((unsigned long long (*)())c->getter)();
5838 +    } else if(c->ptr.ull)
5839 +        return *(c->ptr.ull);
5840 +
5841 +    return 0;
5842 +}
5843 +
5844 +unsigned long long
5845 +get_var_value_ull(const gchar *name) {
5846 +    uzbl_cmdprop *c = get_var_c(name);
5847 +    return get_var_value_ull_c(c);
5848 +}
5849 +
5850  float
5851  get_var_value_float_c(const uzbl_cmdprop *c) {
5852      if(!c) return 0;
5853 @@ -202,7 +243,7 @@ get_var_value_float(const gchar *name) {
5854      return get_var_value_float_c(c);
5855  }
5856  
5857 -void
5858 +static void
5859  dump_var_hash(gpointer k, gpointer v, gpointer ud) {
5860      (void) ud;
5861      uzbl_cmdprop *c = v;
5862 @@ -214,10 +255,13 @@ dump_var_hash(gpointer k, gpointer v, gpointer ud) {
5863          gchar *v = get_var_value_string_c(c);
5864          printf("set %s = %s\n", (char *)k, v);
5865          g_free(v);
5866 -    } else if(c->type == TYPE_INT)
5867 +    } else if(c->type == TYPE_INT) {
5868          printf("set %s = %d\n", (char *)k, get_var_value_int_c(c));
5869 -    else if(c->type == TYPE_FLOAT)
5870 +    } else if(c->type == TYPE_ULL) {
5871 +        printf("set %s = %llu\n", (char *)k, get_var_value_ull_c(c));
5872 +    } else if(c->type == TYPE_FLOAT) {
5873          printf("set %s = %f\n", (char *)k, get_var_value_float_c(c));
5874 +    }
5875  }
5876  
5877  void
5878 @@ -225,7 +269,7 @@ dump_config() {
5879      g_hash_table_foreach(uzbl.behave.proto_var, dump_var_hash, NULL);
5880  }
5881  
5882 -void
5883 +static void
5884  dump_var_hash_as_event(gpointer k, gpointer v, gpointer ud) {
5885      (void) ud;
5886      uzbl_cmdprop *c = v;
5887 @@ -240,18 +284,23 @@ dump_config_as_events() {
5888  }
5889  
5890  /* is the given string made up entirely of decimal digits? */
5891 -gboolean
5892 +static gboolean
5893  string_is_integer(const char *s) {
5894      return (strspn(s, "0123456789") == strlen(s));
5895  }
5896  
5897  
5898 -GObject*
5899 +static GObject *
5900 +cookie_jar() {
5901 +    return G_OBJECT(uzbl.net.soup_cookie_jar);
5902 +}
5903 +
5904 +static GObject *
5905  view_settings() {
5906      return G_OBJECT(webkit_web_view_get_settings(uzbl.gui.web_view));
5907  }
5908  
5909 -void
5910 +static void
5911  set_window_property(const gchar* prop, const gchar* value) {
5912      if(GTK_IS_WIDGET(uzbl.gui.main_window)) {
5913          gdk_property_change(
5914 @@ -276,7 +325,7 @@ uri_change_cb (WebKitWebView *web_view, GParamSpec param_spec) {
5915      set_window_property("UZBL_URI", uzbl.state.uri);
5916  }
5917  
5918 -gchar *
5919 +static gchar *
5920  make_uri_from_user_input(const gchar *uri) {
5921      gchar *result = NULL;
5922  
5923 @@ -312,7 +361,7 @@ make_uri_from_user_input(const gchar *uri) {
5924      return g_strconcat("http://", uri, NULL);
5925  }
5926  
5927 -void
5928 +static void
5929  set_uri(const gchar *uri) {
5930      /* Strip leading whitespace */
5931      while (*uri && isspace(*uri))
5932 @@ -340,7 +389,7 @@ set_uri(const gchar *uri) {
5933      g_free (newuri);
5934  }
5935  
5936 -void
5937 +static void
5938  set_max_conns(int max_conns) {
5939      uzbl.net.max_conns = max_conns;
5940  
5941 @@ -348,7 +397,7 @@ set_max_conns(int max_conns) {
5942              SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
5943  }
5944  
5945 -void
5946 +static void
5947  set_max_conns_host(int max_conns_host) {
5948      uzbl.net.max_conns_host = max_conns_host;
5949  
5950 @@ -356,7 +405,7 @@ set_max_conns_host(int max_conns_host) {
5951              SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
5952  }
5953  
5954 -void
5955 +static void
5956  set_http_debug(int debug) {
5957      uzbl.behave.http_debug = debug;
5958  
5959 @@ -371,77 +420,258 @@ set_http_debug(int debug) {
5960              SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
5961  }
5962  
5963 -void
5964 +static void
5965  set_ca_file(gchar *path) {
5966      g_object_set (uzbl.net.soup_session, "ssl-ca-file", path, NULL);
5967  }
5968  
5969 -gchar *
5970 +static gchar *
5971  get_ca_file() {
5972      gchar *path;
5973      g_object_get (uzbl.net.soup_session, "ssl-ca-file", &path, NULL);
5974      return path;
5975  }
5976  
5977 -void
5978 -set_verify_cert(int strict) {
5979 -    g_object_set (uzbl.net.soup_session, "ssl-strict", strict, NULL);
5980 +#define EXPOSE_WEB_INSPECTOR_SETTINGS(SYM, PROPERTY, TYPE) \
5981 +static void set_##SYM(TYPE val) { \
5982 +  g_object_set(uzbl.gui.inspector, (PROPERTY), val, NULL); \
5983 +} \
5984 +static TYPE get_##SYM() { \
5985 +  TYPE val; \
5986 +  g_object_get(uzbl.gui.inspector, (PROPERTY), &val, NULL); \
5987 +  return val; \
5988  }
5989  
5990 -int
5991 -get_verify_cert() {
5992 -    int strict;
5993 -    g_object_get (uzbl.net.soup_session, "ssl-strict", &strict, NULL);
5994 -    return strict;
5995 +EXPOSE_WEB_INSPECTOR_SETTINGS(profile_js,         "javascript-profiling-enabled",       int)
5996 +EXPOSE_WEB_INSPECTOR_SETTINGS(profile_timeline,   "timeline-profiling-enabled",         gchar *)
5997 +
5998 +#undef EXPOSE_WEB_INSPECTOR_SETTINGS
5999 +
6000 +#define EXPOSE_SOUP_SESSION_SETTINGS(SYM, PROPERTY, TYPE) \
6001 +static void set_##SYM(TYPE val) { \
6002 +  g_object_set(uzbl.net.soup_session, (PROPERTY), val, NULL); \
6003 +} \
6004 +static TYPE get_##SYM() { \
6005 +  TYPE val; \
6006 +  g_object_get(uzbl.net.soup_session, (PROPERTY), &val, NULL); \
6007 +  return val; \
6008  }
6009  
6010 +EXPOSE_SOUP_SESSION_SETTINGS(verify_cert,      "ssl-strict",       int)
6011 +
6012 +#undef EXPOSE_SOUP_SESSION_SETTINGS
6013 +
6014 +#define EXPOSE_SOUP_COOKIE_JAR_SETTINGS(SYM, PROPERTY, TYPE) \
6015 +static void set_##SYM(TYPE val) { \
6016 +  g_object_set(cookie_jar(), (PROPERTY), val, NULL); \
6017 +} \
6018 +static TYPE get_##SYM() { \
6019 +  TYPE val; \
6020 +  g_object_get(cookie_jar(), (PROPERTY), &val, NULL); \
6021 +  return val; \
6022 +}
6023 +
6024 +EXPOSE_SOUP_COOKIE_JAR_SETTINGS(cookie_policy,    "accept-policy",    int)
6025 +
6026 +#undef EXPOSE_SOUP_COOKIE_JAR_SETTINGS
6027 +
6028 +#define EXPOSE_WEBKIT_VIEW_VIEW_SETTINGS(SYM, PROPERTY, TYPE) \
6029 +static void set_##SYM(TYPE val) { \
6030 +  g_object_set(uzbl.gui.web_view, (PROPERTY), val, NULL); \
6031 +} \
6032 +static TYPE get_##SYM() { \
6033 +  TYPE val; \
6034 +  g_object_get(uzbl.gui.web_view, (PROPERTY), &val, NULL); \
6035 +  return val; \
6036 +}
6037 +
6038 +EXPOSE_WEBKIT_VIEW_VIEW_SETTINGS(editable,                "editable",                                  int)
6039 +EXPOSE_WEBKIT_VIEW_VIEW_SETTINGS(transparent,             "transparent",                               int)
6040 +
6041 +#undef EXPOSE_WEBKIT_VIEW_SETTINGS
6042 +
6043  #define EXPOSE_WEBKIT_VIEW_SETTINGS(SYM, PROPERTY, TYPE) \
6044 -void set_##SYM(TYPE val) { \
6045 +static void set_##SYM(TYPE val) { \
6046    g_object_set(view_settings(), (PROPERTY), val, NULL); \
6047  } \
6048 -TYPE get_##SYM() { \
6049 +static TYPE get_##SYM() { \
6050    TYPE val; \
6051    g_object_get(view_settings(), (PROPERTY), &val, NULL); \
6052    return val; \
6053  }
6054  
6055 -EXPOSE_WEBKIT_VIEW_SETTINGS(default_font_family,    "default-font-family",    gchar *)
6056 -EXPOSE_WEBKIT_VIEW_SETTINGS(monospace_font_family,  "monospace-font-family",  gchar *)
6057 -EXPOSE_WEBKIT_VIEW_SETTINGS(sans_serif_font_family, "sans_serif-font-family", gchar *)
6058 -EXPOSE_WEBKIT_VIEW_SETTINGS(serif_font_family,      "serif-font-family",      gchar *)
6059 -EXPOSE_WEBKIT_VIEW_SETTINGS(cursive_font_family,    "cursive-font-family",    gchar *)
6060 -EXPOSE_WEBKIT_VIEW_SETTINGS(fantasy_font_family,    "fantasy-font-family",    gchar *)
6061 +/* Font settings */
6062 +EXPOSE_WEBKIT_VIEW_SETTINGS(default_font_family,          "default-font-family",                       gchar *)
6063 +EXPOSE_WEBKIT_VIEW_SETTINGS(monospace_font_family,        "monospace-font-family",                     gchar *)
6064 +EXPOSE_WEBKIT_VIEW_SETTINGS(sans_serif_font_family,       "sans_serif-font-family",                    gchar *)
6065 +EXPOSE_WEBKIT_VIEW_SETTINGS(serif_font_family,            "serif-font-family",                         gchar *)
6066 +EXPOSE_WEBKIT_VIEW_SETTINGS(cursive_font_family,          "cursive-font-family",                       gchar *)
6067 +EXPOSE_WEBKIT_VIEW_SETTINGS(fantasy_font_family,          "fantasy-font-family",                       gchar *)
6068 +
6069 +/* Font size settings */
6070 +EXPOSE_WEBKIT_VIEW_SETTINGS(minimum_font_size,            "minimum-font-size",                         int)
6071 +EXPOSE_WEBKIT_VIEW_SETTINGS(font_size,                    "default-font-size",                         int)
6072 +EXPOSE_WEBKIT_VIEW_SETTINGS(monospace_size,               "default-monospace-font-size",               int)
6073 +
6074 +/* Text settings */
6075 +EXPOSE_WEBKIT_VIEW_SETTINGS(default_encoding,             "default-encoding",                          gchar *)
6076 +EXPOSE_WEBKIT_VIEW_SETTINGS(enforce_96_dpi,               "enforce-96-dpi",                            int)
6077 +
6078 +/* Feature settings */
6079 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_plugins,               "enable-plugins",                            int)
6080 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_java_applet,           "enable-java-applet",                        int)
6081 +#if WEBKIT_CHECK_VERSION (1, 3, 14)
6082 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_webgl,                 "enable-webgl",                              int)
6083 +#endif
6084 +#if WEBKIT_CHECK_VERSION (1, 7, 5)
6085 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_webaudio,              "enable-webaudio",                           int)
6086 +#endif
6087 +#if WEBKIT_CHECK_VERSION (1, 7, 90) // Documentation says 1.7.5, but it's not there.
6088 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_3d_acceleration,       "enable-accelerated-compositing",            int)
6089 +#endif
6090 +#if WEBKIT_CHECK_VERSION (1, 11, 1)
6091 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_css_shaders,           "enable-css-shaders",                        int)
6092 +#endif
6093  
6094 -EXPOSE_WEBKIT_VIEW_SETTINGS(minimum_font_size,      "minimum-font-size",            int)
6095 -EXPOSE_WEBKIT_VIEW_SETTINGS(font_size,              "default-font-size",            int)
6096 -EXPOSE_WEBKIT_VIEW_SETTINGS(monospace_size,         "default-monospace-font-size",  int)
6097 +/* HTML5 Database settings */
6098 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_database,              "enable-html5-database",                     int)
6099 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_local_storage,         "enable-html5-local-storage",                int)
6100 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_pagecache,             "enable-page-cache",                         int)
6101 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_offline_app_cache,     "enable-offline-web-application-cache",      int)
6102 +#if WEBKIT_CHECK_VERSION (1, 5, 2)
6103 +EXPOSE_WEBKIT_VIEW_SETTINGS(local_storage_path,           "html5-local-storage-database-path",         gchar *)
6104 +#endif
6105  
6106 -EXPOSE_WEBKIT_VIEW_SETTINGS(enable_plugins, "enable-plugins", int)
6107 -EXPOSE_WEBKIT_VIEW_SETTINGS(enable_scripts, "enable-scripts", int)
6108 +/* Security settings */
6109 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_private_webkit,        "enable-private-browsing",                   int)
6110 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_universal_file_access, "enable-universal-access-from-file-uris",    int)
6111 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_cross_file_access,     "enable-file-access-from-file-uris",         int)
6112 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_hyperlink_auditing,    "enable-hyperlink-auditing",                 int)
6113 +#if WEBKIT_CHECK_VERSION (1, 3, 13)
6114 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_dns_prefetch,          "enable-dns-prefetching",                    int)
6115 +#endif
6116 +#if WEBKIT_CHECK_VERSION (1, 11, 2)
6117 +EXPOSE_WEBKIT_VIEW_SETTINGS(display_insecure_content,     "enable-display-of-insecure-content",        int)
6118 +EXPOSE_WEBKIT_VIEW_SETTINGS(run_insecure_content,         "enable-running-of-insecure-content",        int)
6119 +#endif
6120  
6121 -EXPOSE_WEBKIT_VIEW_SETTINGS(javascript_windows, "javascript-can-open-windows-automatically", int)
6122 +/* Display settings */
6123 +EXPOSE_WEBKIT_VIEW_SETTINGS(zoom_step,                    "zoom-step",                                 float)
6124 +EXPOSE_WEBKIT_VIEW_SETTINGS(caret_browsing,               "enable-caret-browsing",                     int)
6125 +EXPOSE_WEBKIT_VIEW_SETTINGS(auto_resize_window,           "auto-resize-window",                        int)
6126 +#if WEBKIT_CHECK_VERSION (1, 3, 5)
6127 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_frame_flattening,      "enable-frame-flattening",                   int)
6128 +#endif
6129 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
6130 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_fullscreen,            "enable-fullscreen",                         int)
6131 +#endif
6132 +#ifdef USE_WEBKIT2
6133 +#if WEBKIT_CHECK_VERSION (1, 7, 91)
6134 +EXPOSE_WEBKIT_VIEW_SETTINGS(zoom_text_only,               "zoom-text-only",                            int)
6135 +#endif
6136 +#endif
6137 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
6138 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_smooth_scrolling,      "enable-smooth-scrolling",                   int)
6139 +#endif
6140  
6141 -EXPOSE_WEBKIT_VIEW_SETTINGS(autoload_images,       "auto-load-images",   int)
6142 -EXPOSE_WEBKIT_VIEW_SETTINGS(autoshrink_images,     "auto-shrink-images", int)
6143 +/* Javascript settings */
6144 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_scripts,               "enable-scripts",                            int)
6145 +EXPOSE_WEBKIT_VIEW_SETTINGS(javascript_windows,           "javascript-can-open-windows-automatically", int)
6146 +EXPOSE_WEBKIT_VIEW_SETTINGS(javascript_dom_paste,         "enable-dom-paste",                          int)
6147 +#if WEBKIT_CHECK_VERSION (1, 3, 0)
6148 +EXPOSE_WEBKIT_VIEW_SETTINGS(javascript_clipboard,         "javascript-can-access-clipboard",           int)
6149 +#endif
6150  
6151 -EXPOSE_WEBKIT_VIEW_SETTINGS(enable_pagecache,   "enable-page-cache",       int)
6152 -EXPOSE_WEBKIT_VIEW_SETTINGS(enable_private,     "enable-private-browsing", int)
6153 +/* Media settings */
6154 +#if WEBKIT_CHECK_VERSION (1, 9, 3)
6155 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_inline_media,          "media-playback-allows-inline",              int)
6156 +EXPOSE_WEBKIT_VIEW_SETTINGS(require_click_to_play,        "media-playback-requires-user-gesture",      int)
6157 +#endif
6158 +#if WEBKIT_CHECK_VERSION (1, 11, 1)
6159 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_media_stream,          "enable-media-stream",                       int)
6160 +#endif
6161  
6162 -EXPOSE_WEBKIT_VIEW_SETTINGS(enable_spellcheck,     "enable-spell-checking",    int)
6163 -EXPOSE_WEBKIT_VIEW_SETTINGS(spellcheck_languages,  "spell-checking-languages", gchar *)
6164 -EXPOSE_WEBKIT_VIEW_SETTINGS(resizable_text_areas,  "resizable-text-areas",     int)
6165 +/* Image settings */
6166 +EXPOSE_WEBKIT_VIEW_SETTINGS(autoload_images,              "auto-load-images",                          int)
6167 +EXPOSE_WEBKIT_VIEW_SETTINGS(autoshrink_images,            "auto-shrink-images",                        int)
6168  
6169 -EXPOSE_WEBKIT_VIEW_SETTINGS(stylesheet_uri,   "user-stylesheet-uri", gchar *)
6170 -EXPOSE_WEBKIT_VIEW_SETTINGS(print_bg,         "print-backgrounds",   int)
6171 -EXPOSE_WEBKIT_VIEW_SETTINGS(enforce_96_dpi,   "enforce-96-dpi",      int)
6172 +/* Spell checking settings */
6173 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_spellcheck,            "enable-spell-checking",                     int)
6174  
6175 -EXPOSE_WEBKIT_VIEW_SETTINGS(caret_browsing,   "enable-caret-browsing", int)
6176 +/* Form settings */
6177 +EXPOSE_WEBKIT_VIEW_SETTINGS(resizable_text_areas,         "resizable-text-areas",                      int)
6178 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_spatial_navigation,    "enable-spatial-navigation",                 int)
6179 +EXPOSE_WEBKIT_VIEW_SETTINGS(editing_behavior,             "editing-behavior",                          int)
6180 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_tab_cycle,             "tab-key-cycles-through-elements",           int)
6181  
6182 -EXPOSE_WEBKIT_VIEW_SETTINGS(enable_cross_file_access, "enable-file-access-from-file-uris", int)
6183 +/* Customization */
6184 +EXPOSE_WEBKIT_VIEW_SETTINGS(stylesheet_uri,               "user-stylesheet-uri",                       gchar *)
6185 +#if !WEBKIT_CHECK_VERSION (1, 9, 0)
6186 +EXPOSE_WEBKIT_VIEW_SETTINGS(default_context_menu,         "enable-default-context-menu",               int)
6187 +#endif
6188  
6189 -EXPOSE_WEBKIT_VIEW_SETTINGS(default_encoding, "default-encoding", gchar *)
6190 +/* Hacks */
6191 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_site_workarounds,      "enable-site-specific-quirks",               int)
6192  
6193 -void
6194 +/* Printing settings */
6195 +EXPOSE_WEBKIT_VIEW_SETTINGS(print_bg,                     "print-backgrounds",                         int)
6196 +
6197 +#undef EXPOSE_WEBKIT_VIEW_SETTINGS
6198 +
6199 +static void
6200 +set_maintain_history (int maintain) {
6201 +    uzbl.behave.maintain_history = maintain;
6202 +
6203 +    webkit_web_view_set_maintains_back_forward_list (uzbl.gui.web_view, maintain);
6204 +}
6205 +
6206 +static int
6207 +get_maintain_history () {
6208 +    return uzbl.behave.maintain_history;
6209 +}
6210 +
6211 +static void
6212 +set_spellcheck_languages(const gchar *languages) {
6213 +  GObject *obj = webkit_get_text_checker ();
6214 +
6215 +  if (!obj) {
6216 +      return;
6217 +  }
6218 +  if (!WEBKIT_IS_SPELL_CHECKER (obj)) {
6219 +      return;
6220 +  }
6221 +
6222 +  WebKitSpellChecker *checker = WEBKIT_SPELL_CHECKER (obj);
6223 +
6224 +  webkit_spell_checker_update_spell_checking_languages (checker, languages);
6225 +  g_object_set(view_settings(), "spell-checking-languages", languages, NULL);
6226 +}
6227 +
6228 +static gchar *
6229 +get_spellcheck_languages() {
6230 +  gchar *val;
6231 +  g_object_get(view_settings(), "spell-checking-languages", &val, NULL);
6232 +  return val;
6233 +}
6234 +
6235 +static void
6236 +set_enable_private (int private) {
6237 +    const char *priv_envvar = "UZBL_PRIVATE";
6238 +
6239 +    if (private)
6240 +        setenv (priv_envvar, "true", 1);
6241 +    else
6242 +        unsetenv (priv_envvar);
6243 +
6244 +    set_enable_private_webkit (private);
6245 +}
6246 +
6247 +static int
6248 +get_enable_private () {
6249 +    return get_enable_private_webkit ();
6250 +}
6251 +
6252 +static void
6253  set_proxy_url(const gchar *proxy_url) {
6254      g_free(uzbl.net.proxy_url);
6255      uzbl.net.proxy_url = g_strdup(proxy_url);
6256 @@ -459,28 +689,45 @@ set_proxy_url(const gchar *proxy_url) {
6257          soup_uri_free(soup_uri);
6258  }
6259  
6260 -void
6261 -set_authentication_handler(const gchar *handler) {
6262 -    /* Check if WEBKIT_TYPE_SOUP_AUTH_DIALOG feature is set */
6263 -    GSList *flist = soup_session_get_features (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG);
6264 -    guint feature_is_set = g_slist_length(flist);
6265 -    g_slist_free(flist);
6266 -
6267 -    g_free(uzbl.behave.authentication_handler);
6268 -    uzbl.behave.authentication_handler = g_strdup(handler);
6269 -
6270 -    if (uzbl.behave.authentication_handler == NULL || *uzbl.behave.authentication_handler == 0) {
6271 -        if (!feature_is_set)
6272 -            soup_session_add_feature_by_type
6273 -                (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG);
6274 +/**
6275 + * Check if the webkit auth dialog is enabled for the soup session
6276 + */
6277 +int
6278 +get_enable_builtin_auth () {
6279 +    SoupSessionFeature *auth = soup_session_get_feature (
6280 +        uzbl.net.soup_session,
6281 +        (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG
6282 +    );
6283 +
6284 +    return auth != NULL;
6285 +}
6286 +
6287 +/**
6288 + * Enable/Disable the webkit auth dialog for the soup session
6289 + */
6290 +static void
6291 +set_enable_builtin_auth (int enabled) {
6292 +    SoupSessionFeature *auth = soup_session_get_feature (
6293 +        uzbl.net.soup_session,
6294 +        (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG
6295 +    );
6296 +
6297 +    if (enabled > 0) {
6298 +        if (auth == NULL) {
6299 +            soup_session_add_feature_by_type (
6300 +                uzbl.net.soup_session,
6301 +                (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG
6302 +            );
6303 +        }
6304      } else {
6305 -        if (feature_is_set)
6306 -            soup_session_remove_feature_by_type
6307 -                (uzbl.net.soup_session, (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG);
6308 +        if (auth != NULL) {
6309 +            soup_session_remove_feature (uzbl.net.soup_session, auth);
6310 +        }
6311      }
6312 +
6313  }
6314  
6315 -void
6316 +static void
6317  set_status_background(const gchar *background) {
6318      /* labels and hboxes do not draw their own background. applying this
6319       * on the vbox/main_window is ok as the statusbar is the only affected
6320 @@ -501,7 +748,7 @@ set_status_background(const gchar *background) {
6321  #endif
6322  }
6323  
6324 -void
6325 +static void
6326  set_icon(const gchar *icon) {
6327      if(file_exists(icon) && uzbl.gui.main_window) {
6328          g_free(uzbl.gui.icon);
6329 @@ -513,7 +760,7 @@ set_icon(const gchar *icon) {
6330      }
6331  }
6332  
6333 -void
6334 +static void
6335  set_window_role(const gchar *role) {
6336      if (!uzbl.gui.main_window)
6337          return;
6338 @@ -521,7 +768,7 @@ set_window_role(const gchar *role) {
6339      gtk_window_set_role(GTK_WINDOW (uzbl.gui.main_window), role);
6340  }
6341  
6342 -gchar *
6343 +static gchar *
6344  get_window_role() {
6345      if (!uzbl.gui.main_window)
6346          return NULL;
6347 @@ -585,7 +832,7 @@ get_show_status() {
6348    return gtk_widget_get_visible(uzbl.gui.status_bar);
6349  }
6350  
6351 -void
6352 +static void
6353  set_status_top(int status_top) {
6354      if (!uzbl.gui.scrolled_win && !uzbl.gui.status_bar)
6355          return;
6356 @@ -612,21 +859,27 @@ set_status_top(int status_top) {
6357          gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
6358  }
6359  
6360 -void
6361 -set_current_encoding(const gchar *encoding) {
6362 +static void
6363 +set_custom_encoding(const gchar *encoding) {
6364      if(strlen(encoding) == 0)
6365          encoding = NULL;
6366  
6367      webkit_web_view_set_custom_encoding(uzbl.gui.web_view, encoding);
6368  }
6369  
6370 -gchar *
6371 -get_current_encoding() {
6372 +static gchar *
6373 +get_custom_encoding() {
6374      const gchar *encoding = webkit_web_view_get_custom_encoding(uzbl.gui.web_view);
6375      return g_strdup(encoding);
6376  }
6377  
6378 -void
6379 +static gchar *
6380 +get_current_encoding() {
6381 +    const gchar *encoding = webkit_web_view_get_encoding (uzbl.gui.web_view);
6382 +    return g_strdup(encoding);
6383 +}
6384 +
6385 +static void
6386  set_fifo_dir(const gchar *fifo_dir) {
6387      g_free(uzbl.behave.fifo_dir);
6388  
6389 @@ -636,7 +889,7 @@ set_fifo_dir(const gchar *fifo_dir) {
6390        uzbl.behave.fifo_dir = NULL;
6391  }
6392  
6393 -void
6394 +static void
6395  set_socket_dir(const gchar *socket_dir) {
6396      g_free(uzbl.behave.socket_dir);
6397  
6398 @@ -646,26 +899,38 @@ set_socket_dir(const gchar *socket_dir) {
6399        uzbl.behave.socket_dir = NULL;
6400  }
6401  
6402 -void
6403 +#ifdef USE_WEBKIT2
6404 +static void
6405 +set_inject_text(const gchar *text) {
6406 +    webkit_web_view_load_plain_text (uzbl.gui.web_view, html, NULL);
6407 +}
6408 +#endif
6409 +
6410 +static void
6411  set_inject_html(const gchar *html) {
6412 +#ifdef USE_WEBKIT2
6413 +    webkit_web_view_load_html (uzbl.gui.web_view, html, NULL);
6414 +#else
6415      webkit_web_view_load_html_string (uzbl.gui.web_view, html, NULL);
6416 +#endif
6417  }
6418  
6419 -void
6420 +static void
6421  set_useragent(const gchar *useragent) {
6422      g_free(uzbl.net.useragent);
6423  
6424 -    if (*useragent == ' ') {
6425 +    if (!useragent || !*useragent) {
6426          uzbl.net.useragent = NULL;
6427      } else {
6428          uzbl.net.useragent = g_strdup(useragent);
6429  
6430          g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT,
6431              uzbl.net.useragent, NULL);
6432 +        g_object_set(view_settings(), "user-agent", uzbl.net.useragent, NULL);
6433      }
6434  }
6435  
6436 -void
6437 +static void
6438  set_accept_languages(const gchar *accept_languages) {
6439      g_free(uzbl.net.accept_languages);
6440  
6441 @@ -679,8 +944,7 @@ set_accept_languages(const gchar *accept_languages) {
6442      }
6443  }
6444  
6445 -/* requires webkit >=1.1.14 */
6446 -void
6447 +static void
6448  set_view_source(int view_source) {
6449      uzbl.behave.view_source = view_source;
6450  
6451 @@ -688,6 +952,7 @@ set_view_source(int view_source) {
6452              (gboolean) uzbl.behave.view_source);
6453  }
6454  
6455 +#ifndef USE_WEBKIT2
6456  void
6457  set_zoom_type (int type) {
6458      webkit_web_view_set_full_content_zoom (uzbl.gui.web_view, type);
6459 @@ -697,17 +962,207 @@ int
6460  get_zoom_type () {
6461      return webkit_web_view_get_full_content_zoom (uzbl.gui.web_view);
6462  }
6463 +#endif
6464  
6465 -void
6466 +static void
6467  set_zoom_level(float zoom_level) {
6468      webkit_web_view_set_zoom_level (uzbl.gui.web_view, zoom_level);
6469  }
6470  
6471 -float
6472 +static float
6473  get_zoom_level() {
6474      return webkit_web_view_get_zoom_level (uzbl.gui.web_view);
6475  }
6476  
6477 +static gchar *
6478 +get_cache_model() {
6479 +    WebKitCacheModel model = webkit_get_cache_model ();
6480 +
6481 +    switch (model) {
6482 +    case WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER:
6483 +        return g_strdup("document_viewer");
6484 +    case WEBKIT_CACHE_MODEL_WEB_BROWSER:
6485 +        return g_strdup("web_browser");
6486 +    case WEBKIT_CACHE_MODEL_DOCUMENT_BROWSER:
6487 +        return g_strdup("document_browser");
6488 +    default:
6489 +        return g_strdup("unknown");
6490 +    }
6491 +}
6492 +
6493 +static void
6494 +set_cache_model(const gchar *model) {
6495 +    if (!g_strcmp0 (model, "default")) {
6496 +        webkit_set_cache_model (WEBKIT_CACHE_MODEL_DEFAULT);
6497 +    } else if (!g_strcmp0 (model, "document_viewer")) {
6498 +        webkit_set_cache_model (WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
6499 +    } else if (!g_strcmp0 (model, "web_browser")) {
6500 +        webkit_set_cache_model (WEBKIT_CACHE_MODEL_WEB_BROWSER);
6501 +    } else if (!g_strcmp0 (model, "document_browser")) {
6502 +        webkit_set_cache_model (WEBKIT_CACHE_MODEL_DOCUMENT_BROWSER);
6503 +    }
6504 +}
6505 +
6506 +static gchar *
6507 +get_web_database_directory() {
6508 +    return g_strdup (webkit_get_web_database_directory_path ());
6509 +}
6510 +
6511 +static unsigned long long
6512 +get_web_database_quota () {
6513 +    return webkit_get_default_web_database_quota ();
6514 +}
6515 +
6516 +static void
6517 +set_web_database_quota (unsigned long long quota) {
6518 +    webkit_set_default_web_database_quota (quota);
6519 +}
6520 +
6521 +static void
6522 +set_web_database_directory(const gchar *path) {
6523 +    webkit_set_web_database_directory_path (path);
6524 +}
6525 +
6526 +#if WEBKIT_CHECK_VERSION (1, 3, 4)
6527 +static gchar *
6528 +get_view_mode() {
6529 +    WebKitWebViewViewMode mode = webkit_web_view_get_view_mode (uzbl.gui.web_view);
6530 +
6531 +    switch (mode) {
6532 +    case WEBKIT_WEB_VIEW_VIEW_MODE_WINDOWED:
6533 +        return g_strdup("windowed");
6534 +    case WEBKIT_WEB_VIEW_VIEW_MODE_FLOATING:
6535 +        return g_strdup("floating");
6536 +    case WEBKIT_WEB_VIEW_VIEW_MODE_FULLSCREEN:
6537 +        return g_strdup("fullscreen");
6538 +    case WEBKIT_WEB_VIEW_VIEW_MODE_MAXIMIZED:
6539 +        return g_strdup("maximized");
6540 +    case WEBKIT_WEB_VIEW_VIEW_MODE_MINIMIZED:
6541 +        return g_strdup("minimized");
6542 +    default:
6543 +        return g_strdup("unknown");
6544 +    }
6545 +}
6546 +
6547 +static void
6548 +set_view_mode(const gchar *mode) {
6549 +    if (!g_strcmp0 (mode, "windowed")) {
6550 +        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_WINDOWED);
6551 +    } else if (!g_strcmp0 (mode, "floating")) {
6552 +        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_FLOATING);
6553 +    } else if (!g_strcmp0 (mode, "fullscreen")) {
6554 +        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_FULLSCREEN);
6555 +    } else if (!g_strcmp0 (mode, "maximized")) {
6556 +        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_MAXIMIZED);
6557 +    } else if (!g_strcmp0 (mode, "minimized")) {
6558 +        webkit_web_view_set_view_mode (uzbl.gui.web_view, WEBKIT_WEB_VIEW_VIEW_MODE_MINIMIZED);
6559 +    }
6560 +}
6561 +#endif
6562 +
6563 +#if WEBKIT_CHECK_VERSION (1, 3, 17)
6564 +static gchar *
6565 +get_inspected_uri() {
6566 +    return g_strdup (webkit_web_inspector_get_inspected_uri (uzbl.gui.inspector));
6567 +}
6568 +#endif
6569 +
6570 +#if WEBKIT_CHECK_VERSION (1, 3, 13)
6571 +static gchar *
6572 +get_app_cache_directory() {
6573 +    return g_strdup (webkit_application_cache_get_database_directory_path ());
6574 +}
6575 +
6576 +static unsigned long long
6577 +get_app_cache_size() {
6578 +    return webkit_application_cache_get_maximum_size ();
6579 +}
6580 +
6581 +static void
6582 +set_app_cache_size(unsigned long long size) {
6583 +    webkit_application_cache_set_maximum_size (size);
6584 +}
6585 +#endif
6586 +
6587 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
6588 +static void
6589 +mimetype_list_append(WebKitWebPluginMIMEType *mimetype, GString *list) {
6590 +    if (*list->str != '[') {
6591 +        g_string_append_c (list, ',');
6592 +    }
6593 +
6594 +    /* Write out a JSON representation of the information */
6595 +    g_string_append_printf (list,
6596 +            "{\"name\": \"%s\","
6597 +            "\"description\": \"%s\","
6598 +            "\"extensions\": [", /* Open array for the extensions */
6599 +            mimetype->name,
6600 +            mimetype->description);
6601 +
6602 +    char **extension = mimetype->extensions;
6603 +    gboolean first = TRUE;
6604 +
6605 +    while (extension) {
6606 +        if (first) {
6607 +            first = FALSE;
6608 +        } else {
6609 +            g_string_append_c (list, ',');
6610 +        }
6611 +        g_string_append (list, *extension);
6612 +
6613 +        ++extension;
6614 +    }
6615 +
6616 +    g_string_append_c (list, '}');
6617 +}
6618 +
6619 +static void
6620 +plugin_list_append(WebKitWebPlugin *plugin, GString *list) {
6621 +    if (*list->str != '[') {
6622 +        g_string_append_c (list, ',');
6623 +    }
6624 +
6625 +    const gchar *desc = webkit_web_plugin_get_description (plugin);
6626 +    gboolean enabled = webkit_web_plugin_get_enabled (plugin);
6627 +    GSList *mimetypes = webkit_web_plugin_get_mimetypes (plugin);
6628 +    const gchar *name = webkit_web_plugin_get_name (plugin);
6629 +    const gchar *path = webkit_web_plugin_get_path (plugin);
6630 +
6631 +    /* Write out a JSON representation of the information */
6632 +    g_string_append_printf (list,
6633 +            "{\"name\": \"%s\","
6634 +            "\"description\": \"%s\","
6635 +            "\"enabled\": %s,"
6636 +            "\"path\": \"%s\","
6637 +            "\"mimetypes\": [", /* Open array for the mimetypes */
6638 +            name,
6639 +            desc,
6640 +            enabled ? "true" : "false",
6641 +            path);
6642 +
6643 +    g_slist_foreach (mimetypes, (GFunc)mimetype_list_append, list);
6644 +
6645 +    /* Close the array and the object */
6646 +    g_string_append (list, "]}");
6647 +}
6648 +
6649 +static gchar *
6650 +get_plugin_list() {
6651 +    WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
6652 +    GSList *plugins = webkit_web_plugin_database_get_plugins (db);
6653 +
6654 +    GString *list = g_string_new ("[");
6655 +
6656 +    g_slist_foreach (plugins, (GFunc)plugin_list_append, list);
6657 +
6658 +    g_string_append_c (list, ']');
6659 +
6660 +    webkit_web_plugin_database_plugins_list_free (plugins);
6661 +
6662 +    return g_string_free (list, FALSE);
6663 +}
6664 +#endif
6665 +
6666  /* abbreviations to help keep the table's width humane */
6667  
6668  /* variables */
6669 @@ -717,6 +1172,7 @@ get_zoom_level() {
6670  
6671  #define PTR_V_STR_GETSET(var)    { .type = TYPE_STR,   .dump = 1, .writeable = 1, .getter = (uzbl_fp) get_##var, .setter = (uzbl_fp)set_##var }
6672  #define PTR_V_INT_GETSET(var)    { .type = TYPE_INT,   .dump = 1, .writeable = 1, .getter = (uzbl_fp) get_##var, .setter = (uzbl_fp)set_##var }
6673 +#define PTR_V_ULL_GETSET(var)    { .type = TYPE_ULL,   .dump = 1, .writeable = 1, .getter = (uzbl_fp) get_##var, .setter = (uzbl_fp)set_##var }
6674  #define PTR_V_FLOAT_GETSET(var)  { .type = TYPE_FLOAT, .dump = 1, .writeable = 1, .getter = (uzbl_fp) get_##var, .setter = (uzbl_fp)set_##var }
6675  
6676  /* constants */
6677 @@ -724,6 +1180,11 @@ get_zoom_level() {
6678  #define PTR_C_INT(var)           { .ptr = { .i = (int*)&(var) }, .type = TYPE_INT,   .dump = 0, .writeable = 0, .getter = NULL, .setter = NULL }
6679  #define PTR_C_FLOAT(var)         { .ptr = { .f = &(var) },       .type = TYPE_FLOAT, .dump = 0, .writeable = 0, .getter = NULL, .setter = NULL }
6680  
6681 +/* programmatic constants */
6682 +#define PTR_C_STR_F(get)    { .type = TYPE_STR,   .dump = 0, .writeable = 0, .getter = (uzbl_fp)get, .setter = NULL }
6683 +#define PTR_C_INT_F(get)    { .type = TYPE_INT,   .dump = 0, .writeable = 0, .getter = (uzbl_fp)get, .setter = NULL }
6684 +#define PTR_C_FLOAT_F(get)  { .type = TYPE_FLOAT, .dump = 0, .writeable = 0, .getter = (uzbl_fp)get, .setter = NULL }
6685 +
6686  const struct var_name_to_ptr_t {
6687      const char *name;
6688      uzbl_cmdprop cp;
6689 @@ -751,7 +1212,6 @@ const struct var_name_to_ptr_t {
6690  
6691      { "forward_keys",           PTR_V_INT(uzbl.behave.forward_keys,             1,   NULL)},
6692  
6693 -    { "authentication_handler", PTR_V_STR(uzbl.behave.authentication_handler,   1,   set_authentication_handler)},
6694      { "scheme_handler",         PTR_V_STR(uzbl.behave.scheme_handler,           1,   NULL)},
6695      { "request_handler",        PTR_V_STR(uzbl.behave.request_handler,          1,   NULL)},
6696      { "download_handler",       PTR_V_STR(uzbl.behave.download_handler,         1,   NULL)},
6697 @@ -773,45 +1233,145 @@ const struct var_name_to_ptr_t {
6698      { "ssl_ca_file",            PTR_V_STR_GETSET(ca_file)},
6699      { "ssl_verify",             PTR_V_INT_GETSET(verify_cert)},
6700  
6701 -    /* exported WebKitWebSettings properties */
6702 -    { "javascript_windows",     PTR_V_INT_GETSET(javascript_windows)},
6703 -    { "zoom_level",             PTR_V_FLOAT_GETSET(zoom_level)},
6704 -    { "zoom_type",              PTR_V_INT_GETSET(zoom_type)},
6705 +    { "enable_builtin_auth",    PTR_V_INT_GETSET(enable_builtin_auth)},
6706 +    { "cache_model",            PTR_V_STR_GETSET(cache_model)},
6707 +#if WEBKIT_CHECK_VERSION (1, 3, 13)
6708 +    { "app_cache_size",         PTR_V_ULL_GETSET(app_cache_size)},
6709 +#endif
6710 +    { "web_database_directory", PTR_V_STR_GETSET(web_database_directory)},
6711 +    { "web_database_quota",     PTR_V_ULL_GETSET(web_database_quota)},
6712  
6713 +    /* exported WebKitWebSettings properties */
6714 +    /* Font settings */
6715      { "default_font_family",    PTR_V_STR_GETSET(default_font_family)},
6716      { "monospace_font_family",  PTR_V_STR_GETSET(monospace_font_family)},
6717 -    { "cursive_font_family",    PTR_V_STR_GETSET(cursive_font_family)},
6718      { "sans_serif_font_family", PTR_V_STR_GETSET(sans_serif_font_family)},
6719      { "serif_font_family",      PTR_V_STR_GETSET(serif_font_family)},
6720 +    { "cursive_font_family",    PTR_V_STR_GETSET(cursive_font_family)},
6721      { "fantasy_font_family",    PTR_V_STR_GETSET(fantasy_font_family)},
6722 -
6723 -    { "monospace_size",         PTR_V_INT_GETSET(monospace_size)},
6724 -    { "font_size",              PTR_V_INT_GETSET(font_size)},
6725 +    /* Font size settings */
6726      { "minimum_font_size",      PTR_V_INT_GETSET(minimum_font_size)},
6727 -
6728 -    { "enable_pagecache",       PTR_V_INT_GETSET(enable_pagecache)},
6729 +    { "font_size",              PTR_V_INT_GETSET(font_size)},
6730 +    { "monospace_size",         PTR_V_INT_GETSET(monospace_size)},
6731 +    /* Text settings */
6732 +    { "default_encoding",       PTR_V_STR_GETSET(default_encoding)},
6733 +    { "custom_encoding",        PTR_V_STR_GETSET(custom_encoding)},
6734 +    { "enforce_96_dpi",         PTR_V_INT_GETSET(enforce_96_dpi)},
6735 +    { "editable",               PTR_V_INT_GETSET(editable)},
6736 +    /* Feature settings */
6737      { "enable_plugins",         PTR_V_INT_GETSET(enable_plugins)},
6738 +    { "enable_java_applet",     PTR_V_INT_GETSET(enable_java_applet)},
6739 +#if WEBKIT_CHECK_VERSION (1, 3, 14)
6740 +    { "enable_webgl",           PTR_V_INT_GETSET(enable_webgl)},
6741 +#endif
6742 +#if WEBKIT_CHECK_VERSION (1, 7, 5)
6743 +    { "enable_webaudio",        PTR_V_INT_GETSET(enable_webaudio)},
6744 +#endif
6745 +#if WEBKIT_CHECK_VERSION (1, 7, 90) /* Documentation says 1.7.5, but it's not there. */
6746 +    { "enable_3d_acceleration", PTR_V_INT_GETSET(enable_3d_acceleration)},
6747 +#endif
6748 +#if WEBKIT_CHECK_VERSION (1, 11, 1)
6749 +    { "enable_css_shaders",     PTR_V_INT_GETSET(enable_css_shaders)},
6750 +#endif
6751 +    /* HTML5 Database settings */
6752 +    { "enable_database",        PTR_V_INT_GETSET(enable_database)},
6753 +    { "enable_local_storage",   PTR_V_INT_GETSET(enable_local_storage)},
6754 +    { "enable_pagecache",       PTR_V_INT_GETSET(enable_pagecache)},
6755 +    { "enable_offline_app_cache", PTR_V_INT_GETSET(enable_offline_app_cache)},
6756 +#if WEBKIT_CHECK_VERSION (1, 5, 2)
6757 +    { "local_storage_path",     PTR_V_STR_GETSET(local_storage_path)},
6758 +#endif
6759 +    /* Security settings */
6760 +    { "enable_private",         PTR_V_INT_GETSET(enable_private)},
6761 +    { "enable_universal_file_access", PTR_V_INT_GETSET(enable_universal_file_access)},
6762 +    { "enable_cross_file_access", PTR_V_INT_GETSET(enable_cross_file_access)},
6763 +    { "enable_hyperlink_auditing", PTR_V_INT_GETSET(enable_hyperlink_auditing)},
6764 +    { "cookie_policy",          PTR_V_INT_GETSET(cookie_policy)},
6765 +#if WEBKIT_CHECK_VERSION (1, 3, 13)
6766 +    { "enable_dns_prefetch",    PTR_V_INT_GETSET(enable_dns_prefetch)},
6767 +#endif
6768 +#if WEBKIT_CHECK_VERSION (1, 11, 2)
6769 +    { "display_insecure_content",PTR_V_INT_GETSET(display_insecure_content)},
6770 +    { "run_insecure_content",   PTR_V_INT_GETSET(run_insecure_content)},
6771 +#endif
6772 +    { "maintain_history",       PTR_V_INT_GETSET(maintain_history)},
6773 +    /* Display settings */
6774 +    { "transparent",            PTR_V_STR_GETSET(transparent)},
6775 +#if WEBKIT_CHECK_VERSION (1, 3, 4)
6776 +    { "view_mode",              PTR_V_STR_GETSET(view_mode)},
6777 +#endif
6778 +    { "zoom_level",             PTR_V_FLOAT_GETSET(zoom_level)},
6779 +    { "zoom_step",              PTR_V_FLOAT_GETSET(zoom_step)},
6780 +#ifndef USE_WEBKIT2
6781 +    { "zoom_type",              PTR_V_INT_GETSET(zoom_type)},
6782 +#endif
6783 +    { "caret_browsing",         PTR_V_INT_GETSET(caret_browsing)},
6784 +    { "auto_resize_window",     PTR_V_INT_GETSET(auto_resize_window)},
6785 +#if WEBKIT_CHECK_VERSION (1, 3, 5)
6786 +    { "enable_frame_flattening", PTR_V_INT_GETSET(enable_frame_flattening)},
6787 +#endif
6788 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
6789 +    { "enable_fullscreen",      PTR_V_INT_GETSET(enable_fullscreen)},
6790 +#endif
6791 +#ifdef USE_WEBKIT2
6792 +#if WEBKIT_CHECK_VERSION (1, 7, 91)
6793 +    { "zoom_text_only",         PTR_V_INT_GETSET(zoom_text_only)},
6794 +#endif
6795 +#endif
6796 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
6797 +    { "enable_smooth_scrolling",PTR_V_INT_GETSET(enable_smooth_scrolling)},
6798 +#endif
6799 +    /* Javascript settings */
6800      { "enable_scripts",         PTR_V_INT_GETSET(enable_scripts)},
6801 +    { "javascript_windows",     PTR_V_INT_GETSET(javascript_windows)},
6802 +    { "javascript_dom_paste",   PTR_V_INT_GETSET(javascript_dom_paste)},
6803 +#if WEBKIT_CHECK_VERSION (1, 3, 0)
6804 +    { "javascript_clipboard",   PTR_V_INT_GETSET(javascript_clipboard)},
6805 +#endif
6806 +    /* Media settings */
6807 +#if WEBKIT_CHECK_VERSION (1, 9, 3)
6808 +    { "enable_inline_media",    PTR_V_INT_GETSET(enable_inline_media)},
6809 +    { "require_click_to_play",  PTR_V_INT_GETSET(require_click_to_play)},
6810 +#endif
6811 +#if WEBKIT_CHECK_VERSION (1, 11, 1)
6812 +    { "enable_media_stream",    PTR_V_INT_GETSET(enable_media_stream)},
6813 +#endif
6814 +    /* Image settings */
6815      { "autoload_images",        PTR_V_INT_GETSET(autoload_images)},
6816      { "autoshrink_images",      PTR_V_INT_GETSET(autoshrink_images)},
6817 +    /* Spell checking settings */
6818      { "enable_spellcheck",      PTR_V_INT_GETSET(enable_spellcheck)},
6819      { "spellcheck_languages",   PTR_V_STR_GETSET(spellcheck_languages)},
6820 -    { "enable_private",         PTR_V_INT_GETSET(enable_private)},
6821 -    { "print_backgrounds",      PTR_V_INT_GETSET(print_bg)},
6822 -    { "stylesheet_uri",         PTR_V_STR_GETSET(stylesheet_uri)},
6823 +    /* Form settings */
6824      { "resizable_text_areas",   PTR_V_INT_GETSET(resizable_text_areas)},
6825 -    { "default_encoding",       PTR_V_STR_GETSET(default_encoding)},
6826 -    { "current_encoding",       PTR_V_STR_GETSET(current_encoding)},
6827 -    { "enforce_96_dpi",         PTR_V_INT_GETSET(enforce_96_dpi)},
6828 -    { "caret_browsing",         PTR_V_INT_GETSET(caret_browsing)},
6829 -    { "enable_cross_file_access", PTR_V_INT_GETSET(enable_cross_file_access)},
6830 +    { "enable_spatial_navigation", PTR_V_INT_GETSET(enable_spatial_navigation)},
6831 +    { "editing_behavior",       PTR_V_INT_GETSET(editing_behavior)},
6832 +    { "enable_tab_cycle",       PTR_V_INT_GETSET(enable_tab_cycle)},
6833 +    /* Customization */
6834 +    { "stylesheet_uri",         PTR_V_STR_GETSET(stylesheet_uri)},
6835 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
6836 +    { "default_context_menu",   PTR_V_INT(uzbl.gui.custom_context_menu,         1,   NULL)},
6837 +#else
6838 +    { "default_context_menu",   PTR_V_INT_GETSET(default_context_menu)},
6839 +#endif
6840 +    /* Hacks */
6841 +    { "enable_site_workarounds", PTR_V_INT_GETSET(enable_site_workarounds)},
6842 +    /* Printing settings */
6843 +    { "print_backgrounds",      PTR_V_INT_GETSET(print_bg)},
6844 +    /* Inspector settings */
6845 +    { "profile_js",             PTR_V_INT_GETSET(profile_js)},
6846 +    { "profile_timeline",       PTR_V_INT_GETSET(profile_timeline)},
6847  
6848 +#ifdef USE_WEBKIT2
6849 +    { "inject_text",            { .type = TYPE_STR, .dump = 0, .writeable = 1, .getter = NULL, .setter = (uzbl_fp) set_inject_text }},
6850 +#endif
6851      { "inject_html",            { .type = TYPE_STR, .dump = 0, .writeable = 1, .getter = NULL, .setter = (uzbl_fp) set_inject_html }},
6852  
6853      /* constants (not dumpable or writeable) */
6854      { "WEBKIT_MAJOR",           PTR_C_INT(uzbl.info.webkit_major)},
6855      { "WEBKIT_MINOR",           PTR_C_INT(uzbl.info.webkit_minor)},
6856      { "WEBKIT_MICRO",           PTR_C_INT(uzbl.info.webkit_micro)},
6857 +    { "HAS_WEBKIT2",            PTR_C_INT(uzbl.info.webkit2)},
6858      { "ARCH_UZBL",              PTR_C_STR(uzbl.info.arch)},
6859      { "COMMIT",                 PTR_C_STR(uzbl.info.commit)},
6860      { "TITLE",                  PTR_C_STR(uzbl.gui.main_title)},
6861 @@ -820,6 +1380,18 @@ const struct var_name_to_ptr_t {
6862      { "PID",                    PTR_C_STR(uzbl.info.pid_str)},
6863      { "_",                      PTR_C_STR(uzbl.state.last_result)},
6864  
6865 +    /* runtime settings */
6866 +    { "current_encoding",       PTR_C_STR_F(get_current_encoding)},
6867 +#if WEBKIT_CHECK_VERSION (1, 3, 17)
6868 +    { "inspected_uri",          PTR_C_STR_F(get_inspected_uri)},
6869 +#endif
6870 +#if WEBKIT_CHECK_VERSION (1, 3, 13)
6871 +    { "app_cache_directory",    PTR_C_STR_F(get_app_cache_directory)},
6872 +#endif
6873 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
6874 +    { "plugin_list",            PTR_C_STR_F(get_plugin_list)},
6875 +#endif
6876 +
6877      /* and we terminate the whole thing with the closest thing we have to NULL.
6878       * it's important that dump = 0. */
6879      { NULL,                     {.ptr = { .i = NULL }, .type = TYPE_INT, .dump = 0, .writeable = 0}}
6880 diff --git a/src/variables.h b/src/variables.h
6881 index dade652..0b935eb 100644
6882 --- a/src/variables.h
6883 +++ b/src/variables.h
6884 @@ -6,7 +6,11 @@
6885  #define __VARIABLES__
6886  
6887  #include <glib.h>
6888 +#ifdef USE_WEBKIT2
6889 +#include <webkit2/webkit2.h>
6890 +#else
6891  #include <webkit/webkit.h>
6892 +#endif
6893  
6894  #include "type.h"
6895  
6896 @@ -20,11 +24,14 @@ gchar *get_var_value_string_c(const uzbl_cmdprop *c);
6897  gchar *get_var_value_string(const char *name);
6898  int get_var_value_int_c(const uzbl_cmdprop *c);
6899  int get_var_value_int(const char *name);
6900 +unsigned long long get_var_value_ull_c(const uzbl_cmdprop *c);
6901 +unsigned long long get_var_value_ull(const char *name);
6902  float get_var_value_float_c(const uzbl_cmdprop *c);
6903  float get_var_value_float(const char *name);
6904  
6905  void set_var_value_string_c(uzbl_cmdprop *c, const gchar *val);
6906 -void set_var_value_int_c(uzbl_cmdprop *c, int f);
6907 +void set_var_value_int_c(uzbl_cmdprop *c, int i);
6908 +void set_var_value_ull_c(uzbl_cmdprop *c, unsigned long long ull);
6909  void set_var_value_float_c(uzbl_cmdprop *c, float f);
6910  
6911  void send_set_var_event(const char *name, const uzbl_cmdprop *c);
6912 @@ -34,8 +41,6 @@ void        dump_config_as_events();
6913  
6914  void        uri_change_cb (WebKitWebView *web_view, GParamSpec param_spec);
6915  
6916 -void        set_show_status(int);
6917 -
6918  void        set_zoom_type(int);
6919  int         get_zoom_type();
6920  
6921 diff --git a/tests/event-manager/emtest.py b/tests/event-manager/emtest.py
6922 new file mode 100644
6923 index 0000000..27ce21b
6924 --- /dev/null
6925 +++ b/tests/event-manager/emtest.py
6926 @@ -0,0 +1,30 @@
6927 +from mock import Mock
6928 +import logging
6929 +from uzbl.event_manager import Uzbl
6930 +
6931 +
6932 +class EventManagerMock(object):
6933 +    def __init__(self,
6934 +        global_plugins=(), instance_plugins=(),
6935 +        global_mock_plugins=(), instance_mock_plugins=()
6936 +    ):
6937 +        self.uzbls = {}
6938 +        self.plugins = {}
6939 +        self.instance_plugins = instance_plugins
6940 +        self.instance_mock_plugins = instance_mock_plugins
6941 +        for plugin in global_plugins:
6942 +            self.plugins[plugin] = plugin(self)
6943 +        for (plugin, mock) in global_mock_plugins:
6944 +            self.plugins[plugin] = mock() if mock else Mock(plugin)
6945 +
6946 +    def add(self):
6947 +        u = Mock(spec=Uzbl)
6948 +        u.parent = self
6949 +        u.logger = logging.getLogger('debug')
6950 +        u.plugins = {}
6951 +        for plugin in self.instance_plugins:
6952 +            u.plugins[plugin] = plugin(u)
6953 +        for (plugin, mock) in self.instance_mock_plugins:
6954 +            u.plugins[plugin] = mock() if mock else Mock(plugin)
6955 +        self.uzbls[Mock()] = u
6956 +        return u
6957 diff --git a/tests/event-manager/testarguments.py b/tests/event-manager/testarguments.py
6958 new file mode 100755
6959 index 0000000..edb102d
6960 --- /dev/null
6961 +++ b/tests/event-manager/testarguments.py
6962 @@ -0,0 +1,25 @@
6963 +#!/usr/bin/env python
6964 +
6965 +import unittest
6966 +from uzbl.arguments import Arguments
6967 +
6968 +
6969 +class ArgumentsTest(unittest.TestCase):
6970 +    def test_empty(self):
6971 +        a = Arguments('')
6972 +        self.assertEqual(len(a), 0)
6973 +        self.assertEqual(a.raw(), '')
6974 +
6975 +    def test_space(self):
6976 +        a = Arguments(' foo  bar')
6977 +        self.assertEqual(len(a), 2)
6978 +        self.assertEqual(a.raw(), 'foo  bar')
6979 +        self.assertEqual(a.raw(0, 0), 'foo')
6980 +        self.assertEqual(a.raw(1, 1), 'bar')
6981 +
6982 +    def test_tab(self):
6983 +        a = Arguments('\tfoo\t\tbar')
6984 +        self.assertEqual(len(a), 2)
6985 +        self.assertEqual(a.raw(), 'foo\t\tbar')
6986 +        self.assertEqual(a.raw(0, 0), 'foo')
6987 +        self.assertEqual(a.raw(1, 1), 'bar')
6988 diff --git a/tests/event-manager/testbind.py b/tests/event-manager/testbind.py
6989 new file mode 100644
6990 index 0000000..a2e8d70
6991 --- /dev/null
6992 +++ b/tests/event-manager/testbind.py
6993 @@ -0,0 +1,51 @@
6994 +#!/usr/bin/env python
6995 +
6996 +
6997 +import sys
6998 +if '' not in sys.path:
6999 +    sys.path.insert(0, '')
7000 +
7001 +import mock
7002 +import unittest
7003 +from emtest import EventManagerMock
7004 +from uzbl.plugins.bind import Bind, BindPlugin
7005 +from uzbl.plugins.config import Config
7006 +
7007 +
7008 +def justafunction():
7009 +    pass
7010 +
7011 +
7012 +class BindTest(unittest.TestCase):
7013 +    def test_unique_id(self):
7014 +        a = Bind('spam', 'spam')
7015 +        b = Bind('spam', 'spam')
7016 +        self.assertNotEqual(a.bid, b.bid)
7017 +
7018 +
7019 +class BindPluginTest(unittest.TestCase):
7020 +    def setUp(self):
7021 +        self.event_manager = EventManagerMock((), (Config, BindPlugin))
7022 +        self.uzbl = self.event_manager.add()
7023 +
7024 +    def test_add_bind(self):
7025 +        b = BindPlugin[self.uzbl]
7026 +        modes = 'global'
7027 +        glob = 'test'
7028 +        handler = justafunction
7029 +        b.mode_bind(modes, glob, handler)
7030 +
7031 +        binds = b.bindlet.get_binds()
7032 +        self.assertEqual(len(binds), 1)
7033 +        self.assertIs(binds[0].function, justafunction)
7034 +
7035 +    def test_parse_bind(self):
7036 +        b = BindPlugin[self.uzbl]
7037 +        modes = 'global'
7038 +        glob = 'test'
7039 +        handler = 'handler'
7040 +
7041 +        b.parse_mode_bind('%s %s = %s' % (modes, glob, handler))
7042 +        binds = b.bindlet.get_binds()
7043 +        self.assertEqual(len(binds), 1)
7044 +        self.assertEqual(binds[0].commands, [handler])
7045 diff --git a/tests/event-manager/testcompletion.py b/tests/event-manager/testcompletion.py
7046 new file mode 100644
7047 index 0000000..8ae32a2
7048 --- /dev/null
7049 +++ b/tests/event-manager/testcompletion.py
7050 @@ -0,0 +1,118 @@
7051 +#!/usr/bin/env python
7052 +# vi: set et ts=4:
7053 +
7054 +
7055 +
7056 +import unittest
7057 +from emtest import EventManagerMock
7058 +from uzbl.plugins.config import Config
7059 +from uzbl.plugins.keycmd import KeyCmd
7060 +from uzbl.plugins.completion import CompletionPlugin
7061 +
7062 +
7063 +class DummyFormatter(object):
7064 +    def format(self, partial, completions):
7065 +        return '[%s] %s' % (partial, ', '.join(completions))
7066 +
7067 +class TestAdd(unittest.TestCase):
7068 +    def setUp(self):
7069 +        self.event_manager = EventManagerMock(
7070 +            (), (CompletionPlugin,),
7071 +            (), ((Config, dict), (KeyCmd, None))
7072 +        )
7073 +        self.uzbl = self.event_manager.add()
7074 +
7075 +    def test_builtins(self):
7076 +        c = CompletionPlugin[self.uzbl]
7077 +        c.add_builtins(('spam', 'egg'))
7078 +        self.assertIn('spam', c.completion)
7079 +        self.assertIn('egg', c.completion)
7080 +
7081 +    def test_config(self):
7082 +        c = CompletionPlugin[self.uzbl]
7083 +        c.add_config_key('spam', 'SPAM')
7084 +        self.assertIn('@spam', c.completion)
7085 +
7086 +
7087 +class TestCompletion(unittest.TestCase):
7088 +    def setUp(self):
7089 +        self.event_manager = EventManagerMock(
7090 +            (), (KeyCmd, CompletionPlugin),
7091 +            (), ((Config, dict),)
7092 +        )
7093 +        self.uzbl = self.event_manager.add()
7094 +
7095 +        c = CompletionPlugin[self.uzbl]
7096 +        c.listformatter = DummyFormatter()
7097 +        c.add_builtins(('spam', 'egg', 'bar', 'baz'))
7098 +        c.add_config_key('spam', 'SPAM')
7099 +        c.add_config_key('Something', 'Else')
7100 +
7101 +    def test_incomplete_keyword(self):
7102 +        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
7103 +        k.keylet.keycmd = 'sp'
7104 +        k.keylet.cursor = len(k.keylet.keycmd)
7105 +
7106 +        r = c.get_incomplete_keyword()
7107 +        self.assertEqual(r, ('sp', False))
7108 +
7109 +    def test_incomplete_keyword_var(self):
7110 +        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
7111 +        k.keylet.keycmd = 'set @sp'
7112 +        k.keylet.cursor = len(k.keylet.keycmd)
7113 +
7114 +        r = c.get_incomplete_keyword()
7115 +        self.assertEqual(r, ('@sp', False))
7116 +
7117 +    def test_incomplete_keyword_var_noat(self):
7118 +        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
7119 +        k.keylet.keycmd = 'set Some'
7120 +        k.keylet.cursor = len(k.keylet.keycmd)
7121 +
7122 +        r = c.get_incomplete_keyword()
7123 +        self.assertEqual(r, ('@Some', True))
7124 +
7125 +    def test_stop_completion(self):
7126 +        config, c = Config[self.uzbl], CompletionPlugin[self.uzbl]
7127 +        c.completion.level = 99
7128 +        config['completion_list'] = 'test'
7129 +
7130 +        c.stop_completion()
7131 +        self.assertNotIn('completion_list', config)
7132 +        self.assertEqual(c.completion.level, 0)
7133 +
7134 +    def test_completion(self):
7135 +        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
7136 +        config = Config[self.uzbl]
7137 +
7138 +        comp = (
7139 +            ('sp', 'spam '),
7140 +            ('e', 'egg '),
7141 +            ('set @sp', 'set @spam '),
7142 +        )
7143 +
7144 +        for i, o in comp:
7145 +            k.keylet.keycmd = i
7146 +            k.keylet.cursor = len(k.keylet.keycmd)
7147 +
7148 +            c.start_completion()
7149 +            self.assertEqual(k.keylet.keycmd, o)
7150 +            c.start_completion()
7151 +            self.assertNotIn('completion_list', config)
7152 +
7153 +    def test_completion_list(self):
7154 +        k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
7155 +        config = Config[self.uzbl]
7156 +
7157 +        comp = (
7158 +            ('b', 'ba', '[ba] bar, baz'),
7159 +        )
7160 +
7161 +        for i, o, l in comp:
7162 +            k.keylet.keycmd = i
7163 +            k.keylet.cursor = len(k.keylet.keycmd)
7164 +
7165 +            c.start_completion()
7166 +            self.assertEqual(k.keylet.keycmd, o)
7167 +            c.start_completion()
7168 +            self.assertEqual(config['completion_list'], l)
7169 diff --git a/tests/event-manager/testconfig.py b/tests/event-manager/testconfig.py
7170 new file mode 100644
7171 index 0000000..d2a891e
7172 --- /dev/null
7173 +++ b/tests/event-manager/testconfig.py
7174 @@ -0,0 +1,77 @@
7175 +
7176 +
7177 +import unittest
7178 +from emtest import EventManagerMock
7179 +
7180 +from uzbl.plugins.config import Config
7181 +
7182 +
7183 +class ConfigTest(unittest.TestCase):
7184 +    def setUp(self):
7185 +        self.event_manager = EventManagerMock((), (Config,))
7186 +        self.uzbl = self.event_manager.add()
7187 +
7188 +    def test_set(self):
7189 +        cases = (
7190 +            (True, '1'),
7191 +            (False, '0'),
7192 +            ("test", "test"),
7193 +            (5, '5')
7194 +        )
7195 +        c = Config[self.uzbl]
7196 +        for input, expected in cases:
7197 +            c.set('foo', input)
7198 +            self.uzbl.send.assert_called_once_with(
7199 +                'set foo = ' + expected)
7200 +            self.uzbl.send.reset_mock()
7201 +
7202 +    def test_set_invalid(self):
7203 +        cases = (
7204 +            ("foo\nbar", AssertionError),  # Better Exception type
7205 +            ("bad'key", AssertionError)
7206 +        )
7207 +        c = Config[self.uzbl]
7208 +        for input, exception in cases:
7209 +            self.assertRaises(exception, c.set, input)
7210 +
7211 +    def test_parse(self):
7212 +        cases = (
7213 +            ('foo str value', 'foo', 'value'),
7214 +            ('foo str "ba ba"', 'foo', 'ba ba'),
7215 +            ('foo float 5', 'foo', 5.0)
7216 +        )
7217 +        c = Config[self.uzbl]
7218 +        for input, ekey, evalue in cases:
7219 +            c.parse_set_event(input)
7220 +            self.assertIn(ekey, c)
7221 +            self.assertEqual(c[ekey], evalue)
7222 +            self.uzbl.event.assert_called_once_with(
7223 +                'CONFIG_CHANGED', ekey, evalue)
7224 +            self.uzbl.event.reset_mock()
7225 +
7226 +    def test_parse_null(self):
7227 +        cases = (
7228 +            ('foo str', 'foo'),
7229 +            ('foo str ""', 'foo'),
7230 +            #('foo int', 'foo')  # Not sure if this input is valid
7231 +        )
7232 +        c = Config[self.uzbl]
7233 +        for input, ekey in cases:
7234 +            c.update({'foo': '-'})
7235 +            c.parse_set_event(input)
7236 +            self.assertNotIn(ekey, c)
7237 +            self.uzbl.event.assert_called_once_with(
7238 +                'CONFIG_CHANGED', ekey, '')
7239 +            self.uzbl.event.reset_mock()
7240 +
7241 +    def test_parse_invalid(self):
7242 +        cases = (
7243 +            ('foo bar', AssertionError),  # TypeError?
7244 +            ('foo bad^key', AssertionError),
7245 +            ('', Exception),
7246 +            ('foo int z', ValueError)
7247 +        )
7248 +        c = Config[self.uzbl]
7249 +        for input, exception in cases:
7250 +            self.assertRaises(exception, c.parse_set_event, input)
7251 +            self.assertEqual(len(list(c.keys())), 0)
7252 diff --git a/tests/event-manager/testcookie.py b/tests/event-manager/testcookie.py
7253 new file mode 100755
7254 index 0000000..c33ba3b
7255 --- /dev/null
7256 +++ b/tests/event-manager/testcookie.py
7257 @@ -0,0 +1,67 @@
7258 +#!/usr/bin/env python
7259 +
7260 +
7261 +import sys
7262 +if '' not in sys.path:
7263 +    sys.path.insert(0, '')
7264 +
7265 +import unittest
7266 +from emtest import EventManagerMock
7267 +
7268 +from uzbl.plugins.cookies import Cookies
7269 +
7270 +cookies = (
7271 +    r'".nyan.cat" "/" "__utmb" "183192761.1.10.1313990640" "http" "1313992440"',
7272 +    r'".twitter.com" "/" "guest_id" "v1%3A131399064036991891" "http" "1377104460"'
7273 +)
7274 +
7275 +
7276 +class CookieFilterTest(unittest.TestCase):
7277 +    def setUp(self):
7278 +        self.event_manager = EventManagerMock((), (Cookies,))
7279 +        self.uzbl = self.event_manager.add()
7280 +        self.other = self.event_manager.add()
7281 +
7282 +    def test_add_cookie(self):
7283 +        c = Cookies[self.uzbl]
7284 +        c.add_cookie(cookies[0])
7285 +        self.other.send.assert_called_once_with(
7286 +            'add_cookie ' + cookies[0])
7287 +
7288 +    def test_whitelist_block(self):
7289 +        c = Cookies[self.uzbl]
7290 +        c.whitelist_cookie(r'domain "nyan\.cat$"')
7291 +        c.add_cookie(cookies[1])
7292 +        self.uzbl.send.assert_called_once_with(
7293 +            'delete_cookie ' + cookies[1])
7294 +
7295 +    def test_whitelist_accept(self):
7296 +        c = Cookies[self.uzbl]
7297 +        c.whitelist_cookie(r'domain "nyan\.cat$"')
7298 +        c.add_cookie(cookies[0])
7299 +        self.other.send.assert_called_once_with(
7300 +            'add_cookie ' + cookies[0])
7301 +
7302 +    def test_blacklist_block(self):
7303 +        c = Cookies[self.uzbl]
7304 +        c.blacklist_cookie(r'domain "twitter\.com$"')
7305 +        c.add_cookie(cookies[1])
7306 +        self.uzbl.send.assert_called_once_with(
7307 +            'delete_cookie ' + cookies[1])
7308 +
7309 +    def test_blacklist_accept(self):
7310 +        c = Cookies[self.uzbl]
7311 +        c.blacklist_cookie(r'domain "twitter\.com$"')
7312 +        c.add_cookie(cookies[0])
7313 +        self.other.send.assert_called_once_with(
7314 +            'add_cookie ' + cookies[0])
7315 +
7316 +    def test_filter_numeric(self):
7317 +        c = Cookies[self.uzbl]
7318 +        c.blacklist_cookie(r'0 "twitter\.com$"')
7319 +        c.add_cookie(cookies[1])
7320 +        self.uzbl.send.assert_called_once_with(
7321 +            'delete_cookie ' + cookies[1])
7322 +
7323 +if __name__ == '__main__':
7324 +    unittest.main()
7325 diff --git a/tests/event-manager/testcore.py b/tests/event-manager/testcore.py
7326 new file mode 100644
7327 index 0000000..1a8b4fa
7328 --- /dev/null
7329 +++ b/tests/event-manager/testcore.py
7330 @@ -0,0 +1,90 @@
7331 +#!/usr/bin/env python
7332 +# vi: set et ts=4:
7333 +
7334 +
7335 +
7336 +import unittest
7337 +from mock import Mock
7338 +from uzbl.core import Uzbl
7339 +
7340 +
7341 +class TestUzbl(unittest.TestCase):
7342 +    def setUp(self):
7343 +        options = Mock()
7344 +        options.print_events = False
7345 +        self.em = Mock()
7346 +        self.proto = Mock()
7347 +        self.uzbl = Uzbl(self.em, self.proto, options)
7348 +
7349 +    def test_repr(self):
7350 +        r = '%r' % self.uzbl
7351 +        self.assertRegex(r, r'<uzbl\(.*\)>')
7352 +
7353 +    def test_event_handler(self):
7354 +        handler = Mock()
7355 +        event, arg = 'FOO', 'test'
7356 +        self.uzbl.connect(event, handler)
7357 +        self.uzbl.event(event, arg)
7358 +        handler.assert_called_once_with(arg)
7359 +
7360 +    def test_parse_sets_name(self):
7361 +        name = 'spam'
7362 +        self.uzbl.parse_msg(' '.join(['EVENT', name, 'FOO', 'BAR']))
7363 +        self.assertEqual(self.uzbl.name, name)
7364 +
7365 +    def test_parse_sends_event(self):
7366 +        handler = Mock()
7367 +        event, arg = 'FOO', 'test'
7368 +        self.uzbl.connect(event, handler)
7369 +        self.uzbl.parse_msg(' '.join(['EVENT', 'instance-name', event, arg]))
7370 +        handler.assert_called_once_with(arg)
7371 +
7372 +    def test_malformed_message(self):
7373 +        # Should not crash
7374 +        self.uzbl.parse_msg('asdaf')
7375 +        self.assertTrue(True)
7376 +
7377 +    def test_send(self):
7378 +        self.uzbl.send('hello  ')
7379 +        self.proto.push.assert_called_once_with('hello\n'.encode('utf-8'))
7380 +
7381 +    def test_close_calls_remove_instance(self):
7382 +        self.uzbl.close()
7383 +        self.em.remove_instance.assert_called_once_with(self.proto.socket)
7384 +
7385 +    def test_close_cleans_plugins(self):
7386 +        p1, p2 = Mock(), Mock()
7387 +        self.uzbl._plugin_instances = (p1, p2)
7388 +        self.uzbl.plugins = {}
7389 +        self.uzbl.close()
7390 +        p1.cleanup.assert_called_once_with()
7391 +        p2.cleanup.assert_called_once_with()
7392 +
7393 +    def test_close_connection_closes_protocol(self):
7394 +        self.uzbl.close_connection(Mock())
7395 +        self.proto.close.assert_called_once_with()
7396 +
7397 +    def test_exit_triggers_close(self):
7398 +        self.uzbl.parse_msg(' '.join(['EVENT', 'spam', 'INSTANCE_EXIT']))
7399 +        self.em.remove_instance.assert_called_once_with(self.proto.socket)
7400 +
7401 +    def test_instance_start(self):
7402 +        pid = 1234
7403 +        self.em.plugind.per_instance_plugins = []
7404 +        self.uzbl.parse_msg(
7405 +            ' '.join(['EVENT', 'spam', 'INSTANCE_START', str(pid)])
7406 +        )
7407 +        self.assertEqual(self.uzbl.pid, pid)
7408 +
7409 +    def test_init_plugins(self):
7410 +        u = self.uzbl
7411 +        class FooPlugin(object):
7412 +            def __init__(self, uzbl): pass
7413 +        class BarPlugin(object):
7414 +            def __init__(self, uzbl): pass
7415 +        self.em.plugind.per_instance_plugins = [FooPlugin, BarPlugin]
7416 +        u.init_plugins()
7417 +        self.assertEqual(len(u.plugins), 2)
7418 +        for t in (FooPlugin, BarPlugin):
7419 +            self.assertIn(t, u.plugins)
7420 +            self.assertTrue(isinstance(u.plugins[t], t))
7421 diff --git a/tests/event-manager/testdoc.py b/tests/event-manager/testdoc.py
7422 new file mode 100755
7423 index 0000000..0f3b8eb
7424 --- /dev/null
7425 +++ b/tests/event-manager/testdoc.py
7426 @@ -0,0 +1,22 @@
7427 +#!/usr/bin/env python
7428 +# vi: set et ts=4:
7429 +
7430 +import sys
7431 +if '' not in sys.path:
7432 +    sys.path.insert(0, '')
7433 +
7434 +import unittest
7435 +from doctest import DocTestSuite
7436 +keycmd_tests = DocTestSuite('uzbl.plugins.keycmd')
7437 +arguments_tests = DocTestSuite('uzbl.arguments')
7438 +
7439 +
7440 +def load_tests(loader, standard, pattern):
7441 +    tests = unittest.TestSuite()
7442 +    tests.addTest(keycmd_tests)
7443 +    tests.addTest(arguments_tests)
7444 +    return tests
7445 +
7446 +if __name__ == '__main__':
7447 +    runner = unittest.TextTestRunner()
7448 +    runner.run()
7449 diff --git a/tests/event-manager/testdownload.py b/tests/event-manager/testdownload.py
7450 new file mode 100644
7451 index 0000000..49ed726
7452 --- /dev/null
7453 +++ b/tests/event-manager/testdownload.py
7454 @@ -0,0 +1,29 @@
7455 +# vi: set et ts=4:
7456 +
7457 +
7458 +
7459 +import unittest
7460 +from emtest import EventManagerMock
7461 +
7462 +from uzbl.plugins.config import Config
7463 +from uzbl.plugins.downloads import Downloads
7464 +
7465 +
7466 +class DownloadsTest(unittest.TestCase):
7467 +    def setUp(self):
7468 +        self.event_manager = EventManagerMock((), (Downloads, Config))
7469 +        self.uzbl = self.event_manager.add()
7470 +
7471 +    def test_start(self):
7472 +        cases = (
7473 +            ('foo', 'foo', 'foo (0%)'),
7474 +            ('"b@r"', 'b@r', 'b@r (0%'),
7475 +        )
7476 +        d = Downloads[self.uzbl]
7477 +        for input, key, section in cases:
7478 +            d.download_started(input)
7479 +            self.assertIn(key, d.active_downloads)
7480 +            self.assertEqual(d.active_downloads[key], 0)
7481 +            self.uzbl.send.assert_called_once()
7482 +            self.assertIn(section, self.uzbl.send.call_args[0][0])
7483 +            self.uzbl.reset_mock()
7484 diff --git a/tests/event-manager/testhistory.py b/tests/event-manager/testhistory.py
7485 new file mode 100644
7486 index 0000000..0817d9f
7487 --- /dev/null
7488 +++ b/tests/event-manager/testhistory.py
7489 @@ -0,0 +1,154 @@
7490 +#!/usr/bin/env python
7491 +# vi: set et ts=4:
7492 +
7493 +
7494 +
7495 +import sys
7496 +if '' not in sys.path:
7497 +    sys.path.insert(0, '')
7498 +
7499 +import unittest
7500 +from emtest import EventManagerMock
7501 +
7502 +from uzbl.plugins.history import History, SharedHistory
7503 +from uzbl.plugins.keycmd import Keylet, KeyCmd
7504 +from uzbl.plugins.on_set import OnSetPlugin
7505 +from uzbl.plugins.config import Config
7506 +
7507 +
7508 +class SharedHistoryTest(unittest.TestCase):
7509 +    def setUp(self):
7510 +        self.event_manager = EventManagerMock((SharedHistory,), ())
7511 +        self.uzbl = self.event_manager.add()
7512 +        self.other = self.event_manager.add()
7513 +
7514 +    def test_instance(self):
7515 +        a = SharedHistory[self.uzbl]
7516 +        b = SharedHistory[self.other]
7517 +        self.assertIs(a, b)
7518 +
7519 +    def test_add_and_get(self):
7520 +        s = SharedHistory[self.uzbl]
7521 +        s.addline('foo', 'bar')
7522 +        s.addline('foo', 'baz')
7523 +        s.addline('foo', 'bap')
7524 +        self.assertEqual(s.get_line_number('foo'), 3)
7525 +        self.assertEqual(s.get_line_number('other'), 0)
7526 +        self.assertEqual(s.getline('foo', 0), 'bar')
7527 +        self.assertEqual(s.getline('foo', 1), 'baz')
7528 +        self.assertEqual(s.getline('foo', 2), 'bap')
7529 +        self.assertEqual(s.getline('foo', -1), 'bap')
7530 +
7531 +    def test_empty_line_number(self):
7532 +        s = SharedHistory[self.uzbl]
7533 +        s.addline('foo', 'bar')
7534 +        self.assertEqual(s.get_line_number(''), 0)
7535 +        self.assertEqual(s.get_line_number('other'), 0)
7536 +
7537 +    def test_get_missing_prompt(self):
7538 +        s = SharedHistory[self.uzbl]
7539 +        s.addline('foo', 'bar')
7540 +        self.assertRaises(IndexError, s.getline, 'bar', 0)
7541 +
7542 +
7543 +class HistoryTest(unittest.TestCase):
7544 +    def setUp(self):
7545 +        self.event_manager = EventManagerMock(
7546 +            (SharedHistory,),
7547 +            (OnSetPlugin, KeyCmd, Config, History)
7548 +        )
7549 +        self.uzbl = self.event_manager.add()
7550 +        self.other = self.event_manager.add()
7551 +        s = SharedHistory[self.uzbl]
7552 +        data = (
7553 +            ('', 'woop'),
7554 +            ('', 'doop'),
7555 +            ('', 'bar'),
7556 +            ('', 'foo'),
7557 +            ('git', 'spam'),
7558 +            ('git', 'egg'),
7559 +            ('foo', 'foo')
7560 +        )
7561 +        for prompt, input in data:
7562 +            s.addline(prompt, input)
7563 +
7564 +    def test_step(self):
7565 +        h = History[self.uzbl]
7566 +        self.assertEqual('', next(h))
7567 +        self.assertEqual('', next(h))
7568 +        self.assertEqual('foo', h.prev())
7569 +        self.assertEqual('bar', h.prev())
7570 +        self.assertEqual('foo', next(h))
7571 +        self.assertEqual('bar', h.prev())
7572 +        self.assertEqual('doop', h.prev())
7573 +        self.assertEqual('woop', h.prev())
7574 +        self.assertTrue(len(h.prev()) > 0)
7575 +        self.assertTrue(len(h.prev()) > 0)
7576 +        self.assertEqual('woop', next(h))
7577 +
7578 +    def test_step_prompt(self):
7579 +        h = History[self.uzbl]
7580 +        h.change_prompt('git')
7581 +        self.assertEqual('', next(h))
7582 +        self.assertEqual('', next(h))
7583 +        self.assertEqual('egg', h.prev())
7584 +        self.assertEqual('spam', h.prev())
7585 +        self.assertTrue(len(h.prev()) > 0)
7586 +        self.assertTrue(len(h.prev()) > 0)
7587 +        self.assertEqual('spam', next(h))
7588 +
7589 +    def test_change_prompt(self):
7590 +        h = History[self.uzbl]
7591 +        self.assertEqual('foo', h.prev())
7592 +        self.assertEqual('bar', h.prev())
7593 +        h.change_prompt('git')
7594 +        self.assertEqual('egg', h.prev())
7595 +        self.assertEqual('spam', h.prev())
7596 +
7597 +    def test_exec(self):
7598 +        modstate = set()
7599 +        keylet = Keylet()
7600 +        keylet.set_keycmd('foo')
7601 +        History[self.uzbl].keycmd_exec(modstate, keylet)
7602 +        s = SharedHistory[self.uzbl]
7603 +        self.assertEqual(s.getline('', -1), 'foo')
7604 +
7605 +    def test_exec_from_history(self):
7606 +        h = History[self.uzbl]
7607 +        self.assertEqual('foo', h.prev())
7608 +        self.assertEqual('bar', h.prev())
7609 +        self.assertEqual('doop', h.prev())
7610 +        modstate = set()
7611 +        keylet = Keylet()
7612 +        keylet.set_keycmd('doop')
7613 +        h.keycmd_exec(modstate, keylet)
7614 +        self.assertEqual('doop', h.prev())
7615 +        self.assertEqual('foo', h.prev())
7616 +        self.assertEqual('bar', h.prev())
7617 +        # do we really want this one here ?
7618 +        self.assertEqual('doop', h.prev())
7619 +        self.assertEqual('woop', h.prev())
7620 +
7621 +    def test_search(self):
7622 +        h = History[self.uzbl]
7623 +        h.search('oop')
7624 +        self.assertEqual('doop', h.prev())
7625 +        self.assertEqual('woop', h.prev())
7626 +        self.assertTrue(len(h.prev()) > 0)
7627 +        self.assertEqual('woop', next(h))
7628 +        self.assertEqual('doop', next(h))
7629 +        # this reset the search
7630 +        self.assertEqual('', next(h))
7631 +        self.assertEqual('foo', h.prev())
7632 +
7633 +    def test_temp(self):
7634 +        kl = KeyCmd[self.uzbl].keylet
7635 +        kl.set_keycmd('uzbl')
7636 +        h = History[self.uzbl]
7637 +        h.change_prompt('foo')
7638 +        # Why is the preserve current logic in this method?
7639 +        h.history_prev(None)
7640 +        self.assertTrue(len(h.prev()) > 0)
7641 +        self.assertEqual('foo', next(h))
7642 +        self.assertEqual('uzbl', next(h))
7643 +        self.assertEqual('', next(h))  # this clears the keycmd
7644 diff --git a/tests/event-manager/testkeycmd.py b/tests/event-manager/testkeycmd.py
7645 new file mode 100644
7646 index 0000000..bc82c6d
7647 --- /dev/null
7648 +++ b/tests/event-manager/testkeycmd.py
7649 @@ -0,0 +1,45 @@
7650 +#!/usr/bin/env python
7651 +
7652 +import re
7653 +import mock
7654 +import unittest
7655 +from emtest import EventManagerMock
7656 +from uzbl.plugins.keycmd import KeyCmd
7657 +from uzbl.plugins.config import Config
7658 +
7659 +
7660 +def getkeycmd(s):
7661 +    return re.match(r'@\[([^\]]*)\]@', s).group(1)
7662 +
7663 +class KeyCmdTest(unittest.TestCase):
7664 +    def setUp(self):
7665 +        self.event_manager = EventManagerMock(
7666 +            (), (KeyCmd,),
7667 +            (), ((Config, dict),)
7668 +        )
7669 +        self.uzbl = self.event_manager.add()
7670 +
7671 +    def test_press_key(self):
7672 +        c, k = Config[self.uzbl], KeyCmd[self.uzbl]
7673 +        k.key_press(('', 'a'))
7674 +        self.assertEqual(c.get('modcmd', ''), '')
7675 +        keycmd = getkeycmd(c['keycmd'])
7676 +        self.assertEqual(keycmd, 'a')
7677 +
7678 +    def test_press_keys(self):
7679 +        c, k = Config[self.uzbl], KeyCmd[self.uzbl]
7680 +        string = 'uzbl'
7681 +        for char in string:
7682 +            k.key_press(('', char))
7683 +        self.assertEqual(c.get('modcmd', ''), '')
7684 +        keycmd = getkeycmd(c['keycmd'])
7685 +        self.assertEqual(keycmd, string)
7686 +
7687 +    def test_press_unicode_keys(self):
7688 +        c, k = Config[self.uzbl], KeyCmd[self.uzbl]
7689 +        string = '\u5927\u962a\u5e02'
7690 +        for char in string:
7691 +            k.key_press(('', char))
7692 +        self.assertEqual(c.get('modcmd', ''), '')
7693 +        keycmd = getkeycmd(c['keycmd'])
7694 +        self.assertEqual(keycmd, string)
7695 diff --git a/tests/event-manager/testmode.py b/tests/event-manager/testmode.py
7696 new file mode 100644
7697 index 0000000..d198c32
7698 --- /dev/null
7699 +++ b/tests/event-manager/testmode.py
7700 @@ -0,0 +1,91 @@
7701 +#!/usr/bin/env python
7702 +# vi: set et ts=4:
7703 +
7704 +
7705 +
7706 +import unittest
7707 +from emtest import EventManagerMock
7708 +from uzbl.plugins.config import Config
7709 +from uzbl.plugins.mode import ModePlugin
7710 +from uzbl.plugins.on_set import OnSetPlugin
7711 +
7712 +class ModeParseTest(unittest.TestCase):
7713 +    def setUp(self):
7714 +        self.event_manager = EventManagerMock(
7715 +            (), (OnSetPlugin, ModePlugin),
7716 +            (), ((Config, dict),)
7717 +        )
7718 +        self.uzbl = self.event_manager.add()
7719 +
7720 +    def test_parse_config(self):
7721 +        uzbl = self.uzbl
7722 +        m = ModePlugin[uzbl]
7723 +
7724 +        mode, key, value = 'foo', 'x', 'y'
7725 +        m.parse_mode_config((mode, key, '=', value))
7726 +        self.assertIn(mode, m.mode_config)
7727 +        self.assertIn(key, m.mode_config[mode])
7728 +        self.assertEqual(m.mode_config[mode][key], value)
7729 +
7730 +
7731 +class ModeTest(unittest.TestCase):
7732 +    def setUp(self):
7733 +        self.event_manager = EventManagerMock(
7734 +            (), (OnSetPlugin, ModePlugin),
7735 +            (), ((Config, dict),)
7736 +        )
7737 +        self.uzbl = self.event_manager.add()
7738 +
7739 +        mode = ModePlugin[self.uzbl]
7740 +        config = Config[self.uzbl]
7741 +
7742 +        mode.parse_mode_config(('mode0', 'foo', '=', 'default'))
7743 +
7744 +        mode.parse_mode_config(('mode1', 'foo', '=', 'xxx'))
7745 +        mode.parse_mode_config('mode1 bar = "spam spam"')
7746 +        mode.parse_mode_config('mode1 baz = foo="baz"')
7747 +
7748 +        mode.parse_mode_config(('mode2', 'foo', '=', 'XXX'))
7749 +        mode.parse_mode_config(('mode2', 'spam', '=', 'spam'))
7750 +
7751 +        config['default_mode'] = 'mode0'
7752 +        mode.default_mode_updated(None, 'mode0')
7753 +
7754 +    def test_mode_sets_vars(self):
7755 +        mode, config = ModePlugin[self.uzbl], Config[self.uzbl]
7756 +        mode.mode_updated(None, 'mode1')
7757 +
7758 +        self.assertIn('foo', config)
7759 +        self.assertIn('bar', config)
7760 +        self.assertIn('baz', config)
7761 +        self.assertEqual(config['foo'], 'xxx')
7762 +        self.assertEqual(config['bar'], 'spam spam')
7763 +        self.assertEqual(config['baz'], 'foo="baz"')
7764 +
7765 +    def test_mode_overwrite_vars(self):
7766 +        mode, config = ModePlugin[self.uzbl], Config[self.uzbl]
7767 +        config['mode'] = 'mode1'
7768 +        mode.mode_updated(None, 'mode1')
7769 +        config['mode'] = 'mode2'
7770 +        mode.mode_updated(None, 'mode2')
7771 +
7772 +        self.assertIn('foo', config)
7773 +        self.assertIn('bar', config)
7774 +        self.assertIn('baz', config)
7775 +        self.assertIn('spam', config)
7776 +        self.assertEqual(config['foo'], 'XXX')
7777 +        self.assertEqual(config['bar'], 'spam spam')
7778 +        self.assertEqual(config['baz'], 'foo="baz"')
7779 +        self.assertEqual(config['spam'], 'spam')
7780 +
7781 +    def test_default_mode(self):
7782 +        ''' Setting to mode to nothing should enter the default mode'''
7783 +        mode, config = ModePlugin[self.uzbl], Config[self.uzbl]
7784 +
7785 +        config['foo'] = 'nthth'
7786 +        config['mode'] = ''
7787 +        mode.mode_updated(None, '')
7788 +        self.assertEqual(config['mode'], 'mode0')
7789 +        mode.mode_updated(None, config['mode'])
7790 +        self.assertEqual(config['mode'], 'mode0')
7791 +        self.assertEqual(config['foo'], 'default')
7792 diff --git a/tests/event-manager/testonevent.py b/tests/event-manager/testonevent.py
7793 new file mode 100644
7794 index 0000000..7d80dbd
7795 --- /dev/null
7796 +++ b/tests/event-manager/testonevent.py
7797 @@ -0,0 +1,59 @@
7798 +#!/usr/bin/env python
7799 +# vi: set et ts=4:
7800 +
7801 +
7802 +
7803 +import unittest
7804 +from emtest import EventManagerMock
7805 +from uzbl.plugins.config import Config
7806 +from uzbl.plugins.mode import ModePlugin
7807 +from uzbl.plugins.on_event import OnEventPlugin
7808 +
7809 +
7810 +class OnEventTest(unittest.TestCase):
7811 +    def setUp(self):
7812 +        self.event_manager = EventManagerMock(
7813 +            (), (OnEventPlugin,),
7814 +        )
7815 +        self.uzbl = self.event_manager.add()
7816 +
7817 +    def test_command(self):
7818 +        oe = OnEventPlugin[self.uzbl]
7819 +        event, command = 'FOO', 'test test'
7820 +
7821 +        oe.on_event(event, [], command)
7822 +        oe.event_handler('', on_event=event)
7823 +        self.uzbl.send.assert_called_once_with(command)
7824 +
7825 +    def test_matching_pattern(self):
7826 +        oe = OnEventPlugin[self.uzbl]
7827 +        event, pattern, command = 'FOO', ['BAR'], 'test test'
7828 +
7829 +        oe.on_event(event, pattern, command)
7830 +        oe.event_handler('BAR else', on_event=event)
7831 +        self.uzbl.send.assert_called_once_with(command)
7832 +
7833 +    def test_non_matching_pattern(self):
7834 +        oe = OnEventPlugin[self.uzbl]
7835 +        event, pattern, command = 'FOO', ['BAR'], 'test test'
7836 +
7837 +        oe.on_event(event, pattern, command)
7838 +        oe.event_handler('FOO else', on_event=event)
7839 +        self.assertFalse(self.uzbl.send.called)
7840 +
7841 +    def test_parse(self):
7842 +        oe = OnEventPlugin[self.uzbl]
7843 +        event, command = 'FOO', 'test test'
7844 +
7845 +        oe.parse_on_event((event, command))
7846 +        self.assertIn(event, oe.events)
7847 +
7848 +    def test_parse_pattern(self):
7849 +        oe = OnEventPlugin[self.uzbl]
7850 +        event, pattern, command = 'FOO', 'BAR', 'test test'
7851 +
7852 +        oe.parse_on_event((event, '[', pattern, ']', command))
7853 +        self.assertIn(event, oe.events)
7854 +        commands = oe.events[event]
7855 +        self.assertIn(command, commands)
7856 +        self.assertEqual(commands[command], [pattern])
7857 diff --git a/tests/event-manager/testprogressbar.py b/tests/event-manager/testprogressbar.py
7858 new file mode 100644
7859 index 0000000..93ebaa8
7860 --- /dev/null
7861 +++ b/tests/event-manager/testprogressbar.py
7862 @@ -0,0 +1,94 @@
7863 +#!/usr/bin/env python
7864 +# vi: set et ts=4:
7865 +
7866 +
7867 +
7868 +import sys
7869 +if '' not in sys.path:
7870 +    sys.path.insert(0, '')
7871 +
7872 +import unittest
7873 +from emtest import EventManagerMock
7874 +from uzbl.plugins.config import Config
7875 +from uzbl.plugins.progress_bar import ProgressBar
7876 +
7877 +
7878 +class ProgressBarTest(unittest.TestCase):
7879 +    def setUp(self):
7880 +        self.event_manager = EventManagerMock(
7881 +            (), (ProgressBar,),
7882 +            (), ((Config, dict),)
7883 +        )
7884 +        self.uzbl = self.event_manager.add()
7885 +
7886 +    def test_percent_done(self):
7887 +        uzbl = self.uzbl
7888 +        p, c = ProgressBar[uzbl], Config[uzbl]
7889 +        c['progress.format'] = '%c'
7890 +
7891 +        p.update_progress()
7892 +        inout = (
7893 +            (9,   '9%'),
7894 +            (99,  '99%'),
7895 +            (100, '100%'),
7896 +            #(101, '100%') # TODO
7897 +        )
7898 +
7899 +        for i, o in inout:
7900 +            p.update_progress(i)
7901 +            self.assertEqual(c['progress.output'], o)
7902 +
7903 +    def test_done_char(self):
7904 +        uzbl = self.uzbl
7905 +        p, c = ProgressBar[uzbl], Config[uzbl]
7906 +        c['progress.format'] = '%d'
7907 +
7908 +        p.update_progress()
7909 +        inout = (
7910 +            (9,   '='),
7911 +            (50,  '===='),
7912 +            (99,  '========'),
7913 +            (100, '========'),
7914 +            (101, '========')
7915 +        )
7916 +
7917 +        for i, o in inout:
7918 +            p.update_progress(i)
7919 +            self.assertEqual(c['progress.output'], o)
7920 +
7921 +    def test_pending_char(self):
7922 +        uzbl = self.uzbl
7923 +        p, c = ProgressBar[uzbl], Config[uzbl]
7924 +        c['progress.format'] = '%p'
7925 +        c['progress.pending'] = '-'
7926 +
7927 +        p.update_progress()
7928 +        inout = (
7929 +            (9,   '-------'),
7930 +            (50,  '----'),
7931 +            (99,  ''),
7932 +            (100, ''),
7933 +            (101, '')
7934 +        )
7935 +
7936 +        for i, o in inout:
7937 +            p.update_progress(i)
7938 +            self.assertEqual(c['progress.output'], o)
7939 +
7940 +    def test_percent_pending(self):
7941 +        uzbl = self.uzbl
7942 +        p, c = ProgressBar[uzbl], Config[uzbl]
7943 +        c['progress.format'] = '%t'
7944 +
7945 +        p.update_progress()
7946 +        inout = (
7947 +            (9,   '91%'),
7948 +            (50,  '50%'),
7949 +            (99,  '1%'),
7950 +            (100, '0%'),
7951 +            #(101, '0%')  # TODO
7952 +        )
7953 +
7954 +        for i, o in inout:
7955 +            p.update_progress(i)
7956 +            self.assertEqual(c['progress.output'], o)
7957 diff --git a/uzbl/__init__.py b/uzbl/__init__.py
7958 new file mode 100644
7959 index 0000000..58f0d85
7960 --- /dev/null
7961 +++ b/uzbl/__init__.py
7962 @@ -0,0 +1,6 @@
7963 +'''
7964 +Event manager for uzbl
7965 +
7966 +A event manager for uzbl that supports plugins and multiple simultaneus
7967 +connections from uzbl-core(s)
7968 +'''
7969 diff --git a/uzbl/arguments.py b/uzbl/arguments.py
7970 new file mode 100644
7971 index 0000000..7f71b74
7972 --- /dev/null
7973 +++ b/uzbl/arguments.py
7974 @@ -0,0 +1,103 @@
7975 +'''
7976 +Arguments parser
7977 +
7978 +provides argument parsing for event handlers
7979 +'''
7980 +
7981 +import re
7982 +import ast
7983 +
7984 +
7985 +class Arguments(tuple):
7986 +    '''
7987 +    Given a argument line gives access to the split parts
7988 +    honoring common quotation and escaping rules
7989 +
7990 +    >>> Arguments(r"simple 'quoted string'")
7991 +    ('simple', 'quoted string')
7992 +    '''
7993 +
7994 +    _splitquoted = re.compile("(\s+|\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')")
7995 +
7996 +    def __new__(cls, s):
7997 +        '''
7998 +        >>> Arguments(r"one two three")
7999 +        ('one', 'two', 'three')
8000 +        >>> Arguments(r"spam 'escaping \\'works\\''")
8001 +        ('spam', "escaping 'works'")
8002 +        >>> # For testing purposes we can pass a preparsed tuple
8003 +        >>> Arguments(('foo', 'bar', 'baz az'))
8004 +        ('foo', 'bar', 'baz az')
8005 +        '''
8006 +        if isinstance(s, tuple):
8007 +            self = tuple.__new__(cls, s)
8008 +            self._raw, self._ref = s, list(range(len(s)))
8009 +            return self
8010 +        raw = cls._splitquoted.split(s)
8011 +        ref = []
8012 +        self = tuple.__new__(cls, cls.parse(raw, ref))
8013 +        self._raw, self._ref = raw, ref
8014 +        return self
8015 +
8016 +    @classmethod
8017 +    def parse(cls, raw, ref):
8018 +        '''
8019 +        Generator used to initialise the arguments tuple
8020 +
8021 +        Indexes to where in source list the arguments start will be put in 'ref'
8022 +        '''
8023 +        c = None
8024 +        for i, part in enumerate(raw):
8025 +            if re.match('\s+', part):
8026 +                # Whitespace ends the current argument, leading ws is ignored
8027 +                if c is not None:
8028 +                    yield c
8029 +                    c = None
8030 +            else:
8031 +                f = unquote(part)
8032 +                if c is None:
8033 +                    # Mark the start of the argument in the raw input
8034 +                    if part != '':
8035 +                        ref.append(i)
8036 +                        c = f
8037 +                else:
8038 +                    c += f
8039 +        if c is not None:
8040 +            yield c
8041 +
8042 +    def raw(self, frm=0, to=None):
8043 +        '''
8044 +        Returs the portion of the raw input that yielded arguments
8045 +        from 'frm' to 'to'
8046 +
8047 +        >>> args = Arguments(r"'spam, spam' egg sausage   and 'spam'")
8048 +        >>> args
8049 +        ('spam, spam', 'egg', 'sausage', 'and', 'spam')
8050 +        >>> args.raw(1)
8051 +        "egg sausage   and 'spam'"
8052 +        '''
8053 +        if len(self._ref) < 1:
8054 +            return ''
8055 +        rfrm = self._ref[frm]
8056 +        if to is None or len(self._ref) <= to + 1:
8057 +            rto = len(self._raw)
8058 +        else:
8059 +            rto = self._ref[to + 1] - 1
8060 +        return ''.join(self._raw[rfrm:rto])
8061 +
8062 +splitquoted = Arguments  # or define a function?
8063 +
8064 +
8065 +def is_quoted(s):
8066 +    return s and s[0] == s[-1] and s[0] in "'\""
8067 +
8068 +
8069 +def unquote(s):
8070 +    '''
8071 +        Returns the input string without quotations and with
8072 +        escape sequences interpreted
8073 +    '''
8074 +
8075 +    if is_quoted(s):
8076 +        return ast.literal_eval(s)
8077 +    return ast.literal_eval('"' + s + '"')
8078 diff --git a/uzbl/core.py b/uzbl/core.py
8079 new file mode 100644
8080 index 0000000..d0f9010
8081 --- /dev/null
8082 +++ b/uzbl/core.py
8083 @@ -0,0 +1,153 @@
8084 +import time
8085 +import logging
8086 +from collections import defaultdict
8087 +
8088 +
8089 +class Uzbl(object):
8090 +
8091 +    def __init__(self, parent, proto, options):
8092 +        proto.target = self
8093 +        self.opts = options
8094 +        self.parent = parent
8095 +        self.proto = proto
8096 +        self.time = time.time()
8097 +        self.pid = None
8098 +        self.name = None
8099 +
8100 +        # Flag if the instance has raised the INSTANCE_START event.
8101 +        self.instance_start = False
8102 +
8103 +        # Use name "unknown" until name is discovered.
8104 +        self.logger = logging.getLogger('uzbl-instance[]')
8105 +
8106 +        # Plugin instances
8107 +        self._plugin_instances = []
8108 +        self.plugins = {}
8109 +
8110 +        # Track plugin event handlers
8111 +        self.handlers = defaultdict(list)
8112 +
8113 +        # Internal vars
8114 +        self._depth = 0
8115 +        self._buffer = ''
8116 +
8117 +    def __repr__(self):
8118 +        return '<uzbl(%s)>' % ', '.join([
8119 +            'pid=%s' % (self.pid if self.pid else "Unknown"),
8120 +            'name=%s' % ('%r' % self.name if self.name else "Unknown"),
8121 +            'uptime=%f' % (time.time() - self.time),
8122 +            '%d handlers' % sum([len(l) for l in list(self.handlers.values())])])
8123 +
8124 +    def init_plugins(self):
8125 +        '''Creates instances of per-instance plugins'''
8126 +
8127 +        for plugin in self.parent.plugind.per_instance_plugins:
8128 +            pinst = plugin(self)
8129 +            self._plugin_instances.append(pinst)
8130 +            self.plugins[plugin] = pinst
8131 +
8132 +    def send(self, msg):
8133 +        '''Send a command to the uzbl instance via the child socket
8134 +        instance.'''
8135 +
8136 +        msg = msg.strip()
8137 +
8138 +        if self.opts.print_events:
8139 +            print(('%s<-- %s' % ('  ' * self._depth, msg)))
8140 +
8141 +        self.proto.push((msg+'\n').encode('utf-8'))
8142 +
8143 +    def parse_msg(self, line):
8144 +        '''Parse an incoming message from a uzbl instance. Event strings
8145 +        will be parsed into `self.event(event, args)`.'''
8146 +
8147 +        # Split by spaces (and fill missing with nulls)
8148 +        elems = (line.split(' ', 3) + [''] * 3)[:4]
8149 +
8150 +        # Ignore non-event messages.
8151 +        if elems[0] != 'EVENT':
8152 +            self.logger.info('non-event message: %r', line)
8153 +            if self.opts.print_events:
8154 +                print(('--- %s' % line))
8155 +            return
8156 +
8157 +        # Check event string elements
8158 +        (name, event, args) = elems[1:]
8159 +        assert name and event, 'event string missing elements'
8160 +        if not self.name:
8161 +            self.name = name
8162 +            self.logger = logging.getLogger('uzbl-instance%s' % name)
8163 +            self.logger.info('found instance name %r', name)
8164 +
8165 +        assert self.name == name, 'instance name mismatch'
8166 +
8167 +        # Handle the event with the event handlers through the event method
8168 +        self.event(event, args)
8169 +
8170 +    def event(self, event, *args, **kargs):
8171 +        '''Raise an event.'''
8172 +
8173 +        event = event.upper()
8174 +
8175 +        if not self.opts.daemon_mode and self.opts.print_events:
8176 +            elems = [event]
8177 +            if args:
8178 +                elems.append(str(args))
8179 +            if kargs:
8180 +                elems.append(str(kargs))
8181 +            print(('%s--> %s' % ('  ' * self._depth, ' '.join(elems))))
8182 +
8183 +        if event == "INSTANCE_START" and args:
8184 +            assert not self.instance_start, 'instance already started'
8185 +
8186 +            self.pid = int(args[0])
8187 +            self.logger.info('found instance pid %r', self.pid)
8188 +
8189 +            self.init_plugins()
8190 +
8191 +        elif event == "INSTANCE_EXIT":
8192 +            self.logger.info('uzbl instance exit')
8193 +            self.close()
8194 +
8195 +        if event not in self.handlers:
8196 +            return
8197 +
8198 +        for handler in self.handlers[event]:
8199 +            self._depth += 1
8200 +            try:
8201 +                handler(*args, **kargs)
8202 +
8203 +            except Exception:
8204 +                self.logger.error('error in handler', exc_info=True)
8205 +
8206 +            self._depth -= 1
8207 +
8208 +    def close_connection(self, child_socket):
8209 +        '''Close child socket and delete the uzbl instance created for that
8210 +        child socket connection.'''
8211 +        self.proto.close()
8212 +
8213 +    def close(self):
8214 +        '''Close the client socket and call the plugin cleanup hooks.'''
8215 +
8216 +        self.logger.debug('called close method')
8217 +
8218 +        # Remove self from parent uzbls dict.
8219 +        self.logger.debug('removing self from uzbls list')
8220 +        self.parent.remove_instance(self.proto.socket)
8221 +
8222 +        for plugin in self._plugin_instances:
8223 +            plugin.cleanup()
8224 +        del self.plugins  # to avoid cyclic links
8225 +        del self._plugin_instances
8226 +
8227 +        self.logger.info('removed %r', self)
8228 +
8229 +    def connect(self, name, handler):
8230 +        """Attach event handler
8231 +
8232 +        No extra arguments added. Use bound methods and partials to have
8233 +        extra arguments.
8234 +        """
8235 +        self.handlers[name].append(handler)
8236 +
8237 diff --git a/uzbl/event_manager.py b/uzbl/event_manager.py
8238 new file mode 100755
8239 index 0000000..fcf9a47
8240 --- /dev/null
8241 +++ b/uzbl/event_manager.py
8242 @@ -0,0 +1,540 @@
8243 +#!/usr/bin/env python3
8244 +
8245 +
8246 +# Event Manager for Uzbl
8247 +# Copyright (c) 2009-2010, Mason Larobina <mason.larobina@gmail.com>
8248 +# Copyright (c) 2009, Dieter Plaetinck <dieter@plaetinck.be>
8249 +#
8250 +# This program is free software: you can redistribute it and/or modify
8251 +# it under the terms of the GNU General Public License as published by
8252 +# the Free Software Foundation, either version 3 of the License, or
8253 +# (at your option) any later version.
8254 +#
8255 +# This program is distributed in the hope that it will be useful,
8256 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
8257 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
8258 +# GNU General Public License for more details.
8259 +#
8260 +# You should have received a copy of the GNU General Public License
8261 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
8262 +
8263 +'''
8264 +
8265 +E V E N T _ M A N A G E R . P Y
8266 +===============================
8267 +
8268 +Event manager for uzbl written in python.
8269 +
8270 +'''
8271 +
8272 +import atexit
8273 +import imp
8274 +import logging
8275 +import os
8276 +import sys
8277 +import time
8278 +import weakref
8279 +import re
8280 +import errno
8281 +import asyncore
8282 +from collections import defaultdict
8283 +from functools import partial
8284 +from glob import glob
8285 +from itertools import count
8286 +from optparse import OptionParser
8287 +from select import select
8288 +from signal import signal, SIGTERM, SIGINT, SIGKILL
8289 +from traceback import format_exc
8290 +
8291 +from uzbl.net import Listener, Protocol
8292 +from uzbl.core import Uzbl
8293 +
8294 +def xdghome(key, default):
8295 +    '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise
8296 +    use $HOME and the default path.'''
8297 +
8298 +    xdgkey = "XDG_%s_HOME" % key
8299 +    if xdgkey in list(os.environ.keys()) and os.environ[xdgkey]:
8300 +        return os.environ[xdgkey]
8301 +
8302 +    return os.path.join(os.environ['HOME'], default)
8303 +
8304 +# Setup xdg paths.
8305 +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
8306 +CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
8307 +
8308 +# Define some globals.
8309 +SCRIPTNAME = os.path.basename(sys.argv[0])
8310 +
8311 +logger = logging.getLogger(SCRIPTNAME)
8312 +
8313 +
8314 +def get_exc():
8315 +    '''Format `format_exc` for logging.'''
8316 +    return "\n%s" % format_exc().rstrip()
8317 +
8318 +
8319 +def expandpath(path):
8320 +    '''Expand and realpath paths.'''
8321 +    return os.path.realpath(os.path.expandvars(path))
8322 +
8323 +
8324 +
8325 +def daemonize():
8326 +    '''Daemonize the process using the Stevens' double-fork magic.'''
8327 +
8328 +    logger.info('entering daemon mode')
8329 +
8330 +    try:
8331 +        if os.fork():
8332 +            os._exit(0)
8333 +
8334 +    except OSError:
8335 +        logger.critical('failed to daemonize', exc_info=True)
8336 +        sys.exit(1)
8337 +
8338 +    os.chdir('/')
8339 +    os.setsid()
8340 +    os.umask(0)
8341 +
8342 +    try:
8343 +        if os.fork():
8344 +            os._exit(0)
8345 +
8346 +    except OSError:
8347 +        logger.critical('failed to daemonize', exc_info=True)
8348 +        sys.exit(1)
8349 +
8350 +    if sys.stdout.isatty():
8351 +        sys.stdout.flush()
8352 +        sys.stderr.flush()
8353 +
8354 +    devnull = '/dev/null'
8355 +    stdin = open(devnull, 'r')
8356 +    stdout = open(devnull, 'a+')
8357 +    stderr = open(devnull, 'a+')
8358 +
8359 +    os.dup2(stdin.fileno(), sys.stdin.fileno())
8360 +    os.dup2(stdout.fileno(), sys.stdout.fileno())
8361 +    os.dup2(stderr.fileno(), sys.stderr.fileno())
8362 +
8363 +    logger.info('entered daemon mode')
8364 +
8365 +
8366 +def make_dirs(path):
8367 +    '''Make all basedirs recursively as required.'''
8368 +
8369 +    try:
8370 +        dirname = os.path.dirname(path)
8371 +        if not os.path.isdir(dirname):
8372 +            logger.debug('creating directories %r', dirname)
8373 +            os.makedirs(dirname)
8374 +
8375 +    except OSError:
8376 +        logger.error('failed to create directories', exc_info=True)
8377 +
8378 +
8379 +class PluginDirectory(object):
8380 +    def __init__(self):
8381 +        self.global_plugins = []
8382 +        self.per_instance_plugins = []
8383 +
8384 +    def load(self):
8385 +        ''' Import plugin files '''
8386 +
8387 +        import uzbl.plugins
8388 +        import pkgutil
8389 +
8390 +        path = uzbl.plugins.__path__
8391 +        for impr, name, ispkg in pkgutil.iter_modules(path, 'uzbl.plugins.'):
8392 +            __import__(name, globals(), locals())
8393 +
8394 +        from uzbl.ext import global_registry, per_instance_registry
8395 +        self.global_plugins.extend(global_registry)
8396 +        self.per_instance_plugins.extend(per_instance_registry)
8397 +
8398 +
8399 +class UzblEventDaemon(object):
8400 +    def __init__(self, listener, plugind):
8401 +        listener.target = self
8402 +        self.opts = opts
8403 +        self.listener = listener
8404 +        self.plugind = plugind
8405 +        self._quit = False
8406 +
8407 +        # Hold uzbl instances
8408 +        # {child socket: Uzbl instance, ..}
8409 +        self.uzbls = {}
8410 +
8411 +        self.plugins = {}
8412 +
8413 +        # Register that the event daemon server has started by creating the
8414 +        # pid file.
8415 +        make_pid_file(opts.pid_file)
8416 +
8417 +        # Register a function to clean up the socket and pid file on exit.
8418 +        atexit.register(self.quit)
8419 +
8420 +        # Add signal handlers.
8421 +        for sigint in [SIGTERM, SIGINT]:
8422 +            signal(sigint, self.quit)
8423 +
8424 +        # Scan plugin directory for plugins
8425 +        self.plugind.load()
8426 +
8427 +        # Initialise global plugins with instances in self.plugins
8428 +        self.init_plugins()
8429 +
8430 +    def init_plugins(self):
8431 +        '''Initialise event manager plugins.'''
8432 +        self._plugin_instances = []
8433 +        self.plugins = {}
8434 +
8435 +        for plugin in self.plugind.global_plugins:
8436 +            pinst = plugin(self)
8437 +            self._plugin_instances.append(pinst)
8438 +            self.plugins[plugin] = pinst
8439 +
8440 +    def run(self):
8441 +        '''Main event daemon loop.'''
8442 +
8443 +        logger.debug('entering main loop')
8444 +
8445 +        if opts.daemon_mode:
8446 +            # Daemonize the process
8447 +            daemonize()
8448 +
8449 +            # Update the pid file
8450 +            make_pid_file(opts.pid_file)
8451 +
8452 +        asyncore.loop()
8453 +
8454 +        # Clean up and exit
8455 +        self.quit()
8456 +
8457 +        logger.debug('exiting main loop')
8458 +
8459 +    def add_instance(self, sock):
8460 +        proto = Protocol(sock)
8461 +        uzbl = Uzbl(self, proto, opts)
8462 +        self.uzbls[sock] = uzbl
8463 +
8464 +    def remove_instance(self, sock):
8465 +        if sock in self.uzbls:
8466 +            del self.uzbls[sock]
8467 +        if not self.uzbls and opts.auto_close:
8468 +            self.quit()
8469 +
8470 +    def close_server_socket(self):
8471 +        '''Close and delete the server socket.'''
8472 +
8473 +        try:
8474 +            self.listener.close()
8475 +
8476 +        except:
8477 +            logger.error('failed to close server socket', exc_info=True)
8478 +
8479 +    def quit(self, sigint=None, *args):
8480 +        '''Close all instance socket objects, server socket and delete the
8481 +        pid file.'''
8482 +
8483 +        if sigint == SIGTERM:
8484 +            logger.critical('caught SIGTERM, exiting')
8485 +
8486 +        elif sigint == SIGINT:
8487 +            logger.critical('caught SIGINT, exiting')
8488 +
8489 +        elif not self._quit:
8490 +            logger.debug('shutting down event manager')
8491 +
8492 +        self.close_server_socket()
8493 +
8494 +        for uzbl in list(self.uzbls.values()):
8495 +            uzbl.close()
8496 +
8497 +        if not self._quit:
8498 +            for plugin in self._plugin_instances:
8499 +                plugin.cleanup()
8500 +            del self.plugins  # to avoid cyclic links
8501 +            del self._plugin_instances
8502 +
8503 +        del_pid_file(opts.pid_file)
8504 +
8505 +        if not self._quit:
8506 +            logger.info('event manager shut down')
8507 +            self._quit = True
8508 +            raise SystemExit()
8509 +
8510 +
8511 +def make_pid_file(pid_file):
8512 +    '''Creates a pid file at `pid_file`, fails silently.'''
8513 +
8514 +    try:
8515 +        logger.debug('creating pid file %r', pid_file)
8516 +        make_dirs(pid_file)
8517 +        pid = os.getpid()
8518 +        fileobj = open(pid_file, 'w')
8519 +        fileobj.write('%d' % pid)
8520 +        fileobj.close()
8521 +        logger.info('created pid file %r with pid %d', pid_file, pid)
8522 +
8523 +    except:
8524 +        logger.error('failed to create pid file', exc_info=True)
8525 +
8526 +
8527 +def del_pid_file(pid_file):
8528 +    '''Deletes a pid file at `pid_file`, fails silently.'''
8529 +
8530 +    if os.path.isfile(pid_file):
8531 +        try:
8532 +            logger.debug('deleting pid file %r', pid_file)
8533 +            os.remove(pid_file)
8534 +            logger.info('deleted pid file %r', pid_file)
8535 +
8536 +        except:
8537 +            logger.error('failed to delete pid file', exc_info=True)
8538 +
8539 +
8540 +def get_pid(pid_file):
8541 +    '''Reads a pid from pid file `pid_file`, fails None.'''
8542 +
8543 +    try:
8544 +        logger.debug('reading pid file %r', pid_file)
8545 +        fileobj = open(pid_file, 'r')
8546 +        pid = int(fileobj.read())
8547 +        fileobj.close()
8548 +        logger.info('read pid %d from pid file %r', pid, pid_file)
8549 +        return pid
8550 +
8551 +    except (IOError, ValueError):
8552 +        logger.error('failed to read pid', exc_info=True)
8553 +        return None
8554 +
8555 +
8556 +def pid_running(pid):
8557 +    '''Checks if a process with a pid `pid` is running.'''
8558 +
8559 +    try:
8560 +        os.kill(pid, 0)
8561 +    except OSError:
8562 +        return False
8563 +    else:
8564 +        return True
8565 +
8566 +
8567 +def term_process(pid):
8568 +    '''Asks nicely then forces process with pid `pid` to exit.'''
8569 +
8570 +    try:
8571 +        logger.info('sending SIGTERM to process with pid %r', pid)
8572 +        os.kill(pid, SIGTERM)
8573 +
8574 +    except OSError:
8575 +        logger.error(get_exc())
8576 +
8577 +    logger.debug('waiting for process with pid %r to exit', pid)
8578 +    start = time.time()
8579 +    while True:
8580 +        if not pid_running(pid):
8581 +            logger.debug('process with pid %d exit', pid)
8582 +            return True
8583 +
8584 +        if (time.time() - start) > 5:
8585 +            logger.warning('process with pid %d failed to exit', pid)
8586 +            logger.info('sending SIGKILL to process with pid %d', pid)
8587 +            try:
8588 +                os.kill(pid, SIGKILL)
8589 +            except:
8590 +                logger.critical('failed to kill %d', pid, exc_info=True)
8591 +                raise
8592 +
8593 +        if (time.time() - start) > 10:
8594 +            logger.critical('unable to kill process with pid %d', pid)
8595 +            raise OSError
8596 +
8597 +        time.sleep(0.25)
8598 +
8599 +
8600 +def stop_action():
8601 +    '''Stop the event manager daemon.'''
8602 +
8603 +    pid_file = opts.pid_file
8604 +    if not os.path.isfile(pid_file):
8605 +        logger.error('could not find running event manager with pid file %r',
8606 +            pid_file)
8607 +        return
8608 +
8609 +    pid = get_pid(pid_file)
8610 +    if not pid_running(pid):
8611 +        logger.debug('no process with pid %r', pid)
8612 +        del_pid_file(pid_file)
8613 +        return
8614 +
8615 +    logger.debug('terminating process with pid %r', pid)
8616 +    term_process(pid)
8617 +    del_pid_file(pid_file)
8618 +    logger.info('stopped event manager process with pid %d', pid)
8619 +
8620 +
8621 +def start_action():
8622 +    '''Start the event manager daemon.'''
8623 +
8624 +    pid_file = opts.pid_file
8625 +    if os.path.isfile(pid_file):
8626 +        pid = get_pid(pid_file)
8627 +        if pid_running(pid):
8628 +            logger.error('event manager already started with pid %d', pid)
8629 +            return
8630 +
8631 +        logger.info('no process with pid %d', pid)
8632 +        del_pid_file(pid_file)
8633 +
8634 +    listener = Listener(opts.server_socket)
8635 +    listener.start()
8636 +    plugind = PluginDirectory()
8637 +    daemon = UzblEventDaemon(listener, plugind)
8638 +    daemon.run()
8639 +
8640 +
8641 +def restart_action():
8642 +    '''Restart the event manager daemon.'''
8643 +
8644 +    stop_action()
8645 +    start_action()
8646 +
8647 +
8648 +def list_action():
8649 +    '''List all the plugins that would be loaded in the current search
8650 +    dirs.'''
8651 +
8652 +    from types import ModuleType
8653 +    import uzbl.plugins
8654 +    import pkgutil
8655 +    for line in pkgutil.iter_modules(uzbl.plugins.__path__, 'uzbl.plugins.'):
8656 +        imp, name, ispkg = line
8657 +        print(name)
8658 +
8659 +
8660 +def make_parser():
8661 +    parser = OptionParser('usage: %prog [options] {start|stop|restart|list}')
8662 +    add = parser.add_option
8663 +
8664 +    add('-v', '--verbose',
8665 +        dest='verbose', default=2, action='count',
8666 +        help='increase verbosity')
8667 +
8668 +    socket_location = os.path.join(CACHE_DIR, 'event_daemon')
8669 +
8670 +    add('-s', '--server-socket',
8671 +        dest='server_socket', metavar="SOCKET", default=socket_location,
8672 +        help='server AF_UNIX socket location')
8673 +
8674 +    add('-p', '--pid-file',
8675 +        metavar="FILE", dest='pid_file',
8676 +        help='pid file location, defaults to server socket + .pid')
8677 +
8678 +    add('-n', '--no-daemon',
8679 +        dest='daemon_mode', action='store_false', default=True,
8680 +        help='do not daemonize the process')
8681 +
8682 +    add('-a', '--auto-close',
8683 +        dest='auto_close', action='store_true', default=False,
8684 +        help='auto close after all instances disconnect')
8685 +
8686 +    add('-o', '--log-file',
8687 +        dest='log_file', metavar='FILE',
8688 +        help='write logging output to a file, defaults to server socket +'
8689 +        ' .log')
8690 +
8691 +    add('-q', '--quiet-events',
8692 +        dest='print_events', action="store_false", default=True,
8693 +        help="silence the printing of events to stdout")
8694 +
8695 +    return parser
8696 +
8697 +
8698 +def init_logger():
8699 +    log_level = logging.CRITICAL - opts.verbose * 10
8700 +    logger = logging.getLogger()
8701 +    logger.setLevel(max(log_level, 10))
8702 +
8703 +    # Console
8704 +    handler = logging.StreamHandler()
8705 +    handler.setLevel(max(log_level + 10, 10))
8706 +    handler.setFormatter(logging.Formatter(
8707 +        '%(name)s: %(levelname)s: %(message)s'))
8708 +    logger.addHandler(handler)
8709 +
8710 +    # Logfile
8711 +    handler = logging.FileHandler(opts.log_file, 'w', 'utf-8', 1)
8712 +    handler.setLevel(max(log_level, 10))
8713 +    handler.setFormatter(logging.Formatter(
8714 +        '[%(created)f] %(name)s: %(levelname)s: %(message)s'))
8715 +    logger.addHandler(handler)
8716 +
8717 +
8718 +def main():
8719 +    global opts
8720 +
8721 +    parser = make_parser()
8722 +
8723 +    (opts, args) = parser.parse_args()
8724 +
8725 +    opts.server_socket = expandpath(opts.server_socket)
8726 +
8727 +    # Set default pid file location
8728 +    if not opts.pid_file:
8729 +        opts.pid_file = "%s.pid" % opts.server_socket
8730 +
8731 +    else:
8732 +        opts.pid_file = expandpath(opts.pid_file)
8733 +
8734 +    # Set default log file location
8735 +    if not opts.log_file:
8736 +        opts.log_file = "%s.log" % opts.server_socket
8737 +
8738 +    else:
8739 +        opts.log_file = expandpath(opts.log_file)
8740 +
8741 +    # Logging setup
8742 +    init_logger()
8743 +    logger.info('logging to %r', opts.log_file)
8744 +
8745 +    if opts.auto_close:
8746 +        logger.debug('will auto close')
8747 +    else:
8748 +        logger.debug('will not auto close')
8749 +
8750 +    if opts.daemon_mode:
8751 +        logger.debug('will daemonize')
8752 +    else:
8753 +        logger.debug('will not daemonize')
8754 +
8755 +    # init like {start|stop|..} daemon actions
8756 +    daemon_actions = {'start': start_action, 'stop': stop_action,
8757 +        'restart': restart_action, 'list': list_action}
8758 +
8759 +    if len(args) == 1:
8760 +        action = args[0]
8761 +        if action not in daemon_actions:
8762 +            parser.error('invalid action: %r' % action)
8763 +
8764 +    elif not args:
8765 +        action = 'start'
8766 +        logger.warning('no daemon action given, assuming %r', action)
8767 +
8768 +    else:
8769 +        parser.error('invalid action argument: %r' % args)
8770 +
8771 +    logger.info('daemon action %r', action)
8772 +    # Do action
8773 +    daemon_actions[action]()
8774 +
8775 +    logger.debug('process CPU time: %f', time.clock())
8776 +
8777 +
8778 +if __name__ == "__main__":
8779 +    main()
8780 +
8781 +
8782 +# vi: set et ts=4:
8783 diff --git a/uzbl/ext.py b/uzbl/ext.py
8784 new file mode 100644
8785 index 0000000..b2795ed
8786 --- /dev/null
8787 +++ b/uzbl/ext.py
8788 @@ -0,0 +1,92 @@
8789 +from .event_manager import Uzbl
8790 +import logging
8791 +
8792 +
8793 +per_instance_registry = []
8794 +global_registry = []
8795 +
8796 +
8797 +class PluginMeta(type):
8798 +    """Registers plugin in registry so that it instantiates when needed"""
8799 +
8800 +    def __init__(self, name, bases, dic):
8801 +        super(PluginMeta, self).__init__(name, bases, dic)
8802 +        # Sorry, a bit of black magick
8803 +        if bases == (object,) or bases == (BasePlugin,):
8804 +            # base classes for the plugins
8805 +            return
8806 +        if issubclass(self, PerInstancePlugin):
8807 +            per_instance_registry.append(self)
8808 +        elif issubclass(self, GlobalPlugin):
8809 +            global_registry.append(self)
8810 +
8811 +    def __getitem__(self, owner):
8812 +        """This method returns instance of plugin corresponding to owner
8813 +
8814 +        :param owner: can be uzbl or event manager
8815 +
8816 +        If you will try to get instance of :class:`GlobalPlugin` on uzbl
8817 +        instance it will find instance on it's parent. If you will try to
8818 +        find instance of a :class:`PerInstancePlugin` it will raise
8819 +        :class:`ValueError`
8820 +        """
8821 +        return self._get_instance(owner)
8822 +
8823 +
8824 +class BasePlugin(object, metaclass=PluginMeta):
8825 +    """Base class for all uzbl plugins"""
8826 +
8827 +
8828 +class PerInstancePlugin(BasePlugin):
8829 +    """Base class for plugins which instantiate once per uzbl instance"""
8830 +
8831 +    def __init__(self, uzbl):
8832 +        self.uzbl = uzbl
8833 +        self.logger = uzbl.logger  # we can also append plugin name to logger
8834 +
8835 +    def cleanup(self):
8836 +        """Cleanup state after instance is gone
8837 +
8838 +        Default function avoids cyclic refrences, so don't forget to call
8839 +        super() if overriding
8840 +        """
8841 +        del self.uzbl
8842 +
8843 +    @classmethod
8844 +    def _get_instance(cls, owner):
8845 +        """Returns instance of the plugin
8846 +
8847 +        This method should be private to not violate TOOWTDI
8848 +        """
8849 +        if not isinstance(owner, Uzbl):
8850 +            raise ValueError("Can only get {0} instance for uzbl, not {1}"
8851 +                .format(cls.__name__, type(owner).__name__))
8852 +        # TODO(tailhook) probably subclasses can be returned as well
8853 +        return owner.plugins[cls]
8854 +
8855 +
8856 +class GlobalPlugin(BasePlugin):
8857 +    """Base class for plugins which instantiate once per daemon"""
8858 +
8859 +    def __init__(self, event_manager):
8860 +        self.event_manager = event_manager
8861 +        self.logger = logging.getLogger(self.__module__)
8862 +
8863 +    @classmethod
8864 +    def _get_instance(cls, owner):
8865 +        """Returns instance of the plugin
8866 +
8867 +        This method should be private to not violate TOOWTDI
8868 +        """
8869 +        if isinstance(owner, Uzbl):
8870 +            owner = owner.parent
8871 +        # TODO(tailhook) probably subclasses can be returned as well
8872 +        return owner.plugins[cls]
8873 +
8874 +    def cleanup(self):
8875 +        """Cleanup state after instance is gone
8876 +
8877 +        Default function avoids cyclic refrences, so don't forget to call
8878 +        super() if overriding
8879 +        """
8880 +        del self.event_manager
8881 diff --git a/uzbl/net.py b/uzbl/net.py
8882 new file mode 100644
8883 index 0000000..2c34e7b
8884 --- /dev/null
8885 +++ b/uzbl/net.py
8886 @@ -0,0 +1,109 @@
8887 +# Network communication classes
8888 +# vi: set et ts=4:
8889 +import asyncore
8890 +import asynchat
8891 +import socket
8892 +import os
8893 +import logging
8894 +
8895 +logger = logging.getLogger('uzbl.net')
8896 +
8897 +
8898 +class NoTargetSet(Exception):
8899 +    pass
8900 +
8901 +
8902 +class TargetAlreadySet(Exception):
8903 +    pass
8904 +
8905 +
8906 +class WithTarget(object):
8907 +    '''
8908 +        Mixin that adds a property 'target' than can only be set once and
8909 +        raises an exception if not set when accesed
8910 +    '''
8911 +
8912 +    @property
8913 +    def target(self):
8914 +        try:
8915 +            return self._target
8916 +        except AttributeError as e:
8917 +            raise NoTargetSet("No target for %r" % self, e)
8918 +
8919 +    @target.setter
8920 +    def target(self, value):
8921 +        if hasattr(self, '_target') and self._target is not None:
8922 +            raise TargetAlreadySet(
8923 +                "target of listener already set (%r)" % self._target
8924 +            )
8925 +        self._target = value
8926 +
8927 +
8928 +class Listener(asyncore.dispatcher, WithTarget):
8929 +    ''' Waits for new connections and accept()s them '''
8930 +
8931 +    def __init__(self, addr, target=None):
8932 +        asyncore.dispatcher.__init__(self)
8933 +        self.addr = addr
8934 +        self.target = target
8935 +
8936 +    def start(self):
8937 +        self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
8938 +        self.knock()
8939 +        self.set_reuse_addr()
8940 +        self.bind(self.addr)
8941 +        self.listen(5)
8942 +
8943 +    def knock(self):
8944 +        '''Unlink existing socket if it's stale'''
8945 +
8946 +        if os.path.exists(self.addr):
8947 +            logger.info('socket already exists, checking if active')
8948 +            s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
8949 +            try:
8950 +                s.connect(self.addr)
8951 +            except socket.error as e:
8952 +                logger.info('unlinking %r', self.addr)
8953 +                os.unlink(self.addr)
8954 +
8955 +    def writable(self):
8956 +        return False
8957 +
8958 +    def handle_accept(self):
8959 +        try:
8960 +            sock, addr = self.accept()
8961 +        except socket.error:
8962 +            return
8963 +        else:
8964 +            self.target.add_instance(sock)
8965 +
8966 +    def close(self):
8967 +        super(Listener, self).close()
8968 +        if os.path.exists(self.addr):
8969 +            logger.info('unlinking %r', self.addr)
8970 +            os.unlink(self.addr)
8971 +
8972 +    def handle_error(self):
8973 +        raise
8974 +
8975 +
8976 +class Protocol(asynchat.async_chat):
8977 +    ''' A connection with a single client '''
8978 +
8979 +    def __init__(self, socket, target=None):
8980 +        asynchat.async_chat.__init__(self, socket)
8981 +        self.socket = socket
8982 +        self.target = target
8983 +        self.buffer = bytearray()
8984 +        self.set_terminator(b'\n')
8985 +
8986 +    def collect_incoming_data(self, data):
8987 +        self.buffer += data
8988 +
8989 +    def found_terminator(self):
8990 +        val = self.buffer.decode('utf-8')
8991 +        del self.buffer[:]
8992 +        self.target.parse_msg(val)
8993 +
8994 +    def handle_error(self):
8995 +        raise
8996 diff --git a/uzbl/plugins/__init__.py b/uzbl/plugins/__init__.py
8997 new file mode 100644
8998 index 0000000..84cf2ec
8999 --- /dev/null
9000 +++ b/uzbl/plugins/__init__.py
9001 @@ -0,0 +1,14 @@
9002 +'''
9003 +Plugins collection
9004 +
9005 +plugins for use with uzbl-event-manager
9006 +'''
9007 +
9008 +import os.path
9009 +
9010 +plugin_path = os.environ.get("UZBL_PLUGIN_PATH",
9011 +    "~/.local/share/uzbl/plugins:/usr/share/uzbl/site-plugins",
9012 +    ).split(":")
9013 +if plugin_path:
9014 +    __path__ = list(map(os.path.expanduser, plugin_path)) + __path__
9015 +
9016 diff --git a/uzbl/plugins/bind.py b/uzbl/plugins/bind.py
9017 new file mode 100644
9018 index 0000000..f40da36
9019 --- /dev/null
9020 +++ b/uzbl/plugins/bind.py
9021 @@ -0,0 +1,463 @@
9022 +'''Plugin provides support for binds in uzbl.
9023 +
9024 +For example:
9025 +  event BIND ZZ = exit          -> bind('ZZ', 'exit')
9026 +  event BIND o _ = uri %s       -> bind('o _', 'uri %s')
9027 +  event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'")
9028 +
9029 +And it is also possible to execute a function on activation:
9030 +  bind('DD', myhandler)
9031 +'''
9032 +
9033 +import sys
9034 +import re
9035 +from functools import partial
9036 +from itertools import count
9037 +
9038 +from uzbl.arguments import unquote, splitquoted
9039 +from uzbl.ext import PerInstancePlugin
9040 +from .cmd_expand import cmd_expand
9041 +from .config import Config
9042 +from .keycmd import KeyCmd
9043 +import collections
9044 +
9045 +# Commonly used regular expressions.
9046 +MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match
9047 +# Matches <x:y>, <'x':y>, <:'y'>, <x!y>, <'x'!y>, ...
9048 +PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>'
9049 +FIND_PROMPTS = re.compile(PROMPTS).split
9050 +VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match
9051 +
9052 +# For accessing a bind glob stack.
9053 +ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = list(range(5))
9054 +
9055 +
9056 +# Custom errors.
9057 +class ArgumentError(Exception): pass
9058 +
9059 +
9060 +class Bindlet(object):
9061 +    '''Per-instance bind status/state tracker.'''
9062 +
9063 +    def __init__(self, uzbl):
9064 +        self.binds = {'global': {}}
9065 +        self.uzbl = uzbl
9066 +        self.uzbl_config = Config[uzbl]
9067 +        self.depth = 0
9068 +        self.args = []
9069 +        self.last_mode = None
9070 +        self.after_cmds = None
9071 +        self.stack_binds = []
9072 +
9073 +        # A subset of the global mode binds containing non-stack and modkey
9074 +        # activiated binds for use in the stack mode.
9075 +        self.globals = []
9076 +
9077 +
9078 +    def __getitem__(self, key):
9079 +        return self.get_binds(key)
9080 +
9081 +
9082 +    def reset(self):
9083 +        '''Reset the tracker state and return to last mode.'''
9084 +
9085 +        self.depth = 0
9086 +        self.args = []
9087 +        self.after_cmds = None
9088 +        self.stack_binds = []
9089 +
9090 +        if self.last_mode:
9091 +            mode, self.last_mode = self.last_mode, None
9092 +            self.uzbl_config['mode'] = mode
9093 +
9094 +        del self.uzbl_config['keycmd_prompt']
9095 +
9096 +
9097 +    def stack(self, bind, args, depth):
9098 +        '''Enter or add new bind in the next stack level.'''
9099 +
9100 +        if self.depth != depth:
9101 +            if bind not in self.stack_binds:
9102 +                self.stack_binds.append(bind)
9103 +
9104 +            return
9105 +
9106 +        mode = self.uzbl_config.get('mode', None)
9107 +        if mode != 'stack':
9108 +            self.last_mode = mode
9109 +            self.uzbl_config['mode'] = 'stack'
9110 +
9111 +        self.stack_binds = [bind,]
9112 +        self.args += args
9113 +        self.depth += 1
9114 +        self.after_cmds = bind.prompts[depth]
9115 +
9116 +
9117 +    def after(self):
9118 +        '''If a stack was triggered then set the prompt and default value.'''
9119 +
9120 +        if self.after_cmds is None:
9121 +            return
9122 +
9123 +        (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None
9124 +
9125 +        KeyCmd[self.uzbl].clear_keycmd()
9126 +        if prompt:
9127 +            self.uzbl_config['keycmd_prompt'] = prompt
9128 +
9129 +        if set and is_cmd:
9130 +            self.uzbl.send(set)
9131 +
9132 +        elif set and not is_cmd:
9133 +            self.uzbl.send('event SET_KEYCMD %s' % set)
9134 +
9135 +
9136 +    def get_binds(self, mode=None):
9137 +        '''Return the mode binds + globals. If we are stacked then return
9138 +        the filtered stack list and modkey & non-stack globals.'''
9139 +
9140 +        if mode is None:
9141 +            mode = self.uzbl_config.get('mode', None)
9142 +
9143 +        if not mode:
9144 +            mode = 'global'
9145 +
9146 +        if self.depth:
9147 +            return self.stack_binds + self.globals
9148 +
9149 +        globals = self.binds['global']
9150 +        if mode not in self.binds or mode == 'global':
9151 +            return [_f for _f in list(globals.values()) if _f]
9152 +
9153 +        binds = dict(list(globals.items()) + list(self.binds[mode].items()))
9154 +        return [_f for _f in list(binds.values()) if _f]
9155 +
9156 +
9157 +    def add_bind(self, mode, glob, bind=None):
9158 +        '''Insert (or override) a bind into the mode bind dict.'''
9159 +
9160 +        if mode not in self.binds:
9161 +            self.binds[mode] = {glob: bind}
9162 +            return
9163 +
9164 +        binds = self.binds[mode]
9165 +        binds[glob] = bind
9166 +
9167 +        if mode == 'global':
9168 +            # Regen the global-globals list.
9169 +            self.globals = []
9170 +            for bind in list(binds.values()):
9171 +                if bind is not None and bind.is_global:
9172 +                    self.globals.append(bind)
9173 +
9174 +
9175 +def ismodbind(glob):
9176 +    '''Return True if the glob specifies a modbind.'''
9177 +
9178 +    return bool(MOD_START(glob))
9179 +
9180 +
9181 +def split_glob(glob):
9182 +    '''Take a string of the form "<Mod1><Mod2>cmd _" and return a list of the
9183 +    modkeys in the glob and the command.'''
9184 +
9185 +    mods = set()
9186 +    while True:
9187 +        match = MOD_START(glob)
9188 +        if not match:
9189 +            break
9190 +
9191 +        end = match.span()[1]
9192 +        mods.add(glob[:end])
9193 +        glob = glob[end:]
9194 +
9195 +    return (mods, glob)
9196 +
9197 +
9198 +class Bind(object):
9199 +
9200 +    # unique id generator
9201 +    nextid = count().__next__
9202 +
9203 +    def __init__(self, glob, handler, *args, **kargs):
9204 +        self.is_callable = isinstance(handler, collections.Callable)
9205 +        self._repr_cache = None
9206 +
9207 +        if not glob:
9208 +            raise ArgumentError('glob cannot be blank')
9209 +
9210 +        if self.is_callable:
9211 +            self.function = handler
9212 +            self.args = args
9213 +            self.kargs = kargs
9214 +
9215 +        elif kargs:
9216 +            raise ArgumentError('cannot supply kargs for uzbl commands')
9217 +
9218 +        elif not isinstance(handler, str):
9219 +            self.commands = handler
9220 +
9221 +        else:
9222 +            self.commands = [handler,] + list(args)
9223 +
9224 +        self.glob = glob
9225 +
9226 +        # Assign unique id.
9227 +        self.bid = self.nextid()
9228 +
9229 +        self.split = split = FIND_PROMPTS(glob)
9230 +        self.prompts = []
9231 +        for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]):
9232 +            prompt, set = list(map(unquote, [prompt, set]))
9233 +            cmd = True if cmd == '!' else False
9234 +            if prompt and prompt[-1] != ":":
9235 +                prompt = "%s:" % prompt
9236 +
9237 +            self.prompts.append((prompt, cmd, set))
9238 +
9239 +        # Check that there is nothing like: fl*<int:>*
9240 +        for glob in split[:-1:4]:
9241 +            if glob.endswith('*'):
9242 +                msg = "token '*' not at the end of a prompt bind: %r" % split
9243 +                raise SyntaxError(msg)
9244 +
9245 +        # Check that there is nothing like: fl<prompt1:><prompt2:>_
9246 +        for glob in split[4::4]:
9247 +            if not glob:
9248 +                msg = 'found null segment after first prompt: %r' % split
9249 +                raise SyntaxError(msg)
9250 +
9251 +        stack = []
9252 +        for (index, glob) in enumerate(reversed(split[::4])):
9253 +            # Is the binding a MODCMD or KEYCMD:
9254 +            mod_cmd = ismodbind(glob)
9255 +
9256 +            # Do we execute on UPDATES or EXEC events?
9257 +            on_exec = True if glob[-1] in ['!', '_'] else False
9258 +
9259 +            # Does the command take arguments?
9260 +            has_args = True if glob[-1] in ['*', '_'] else False
9261 +
9262 +            glob = glob[:-1] if has_args or on_exec else glob
9263 +            mods, glob = split_glob(glob)
9264 +            stack.append((on_exec, has_args, mods, glob, index))
9265 +
9266 +        self.stack = list(reversed(stack))
9267 +        self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD])
9268 +
9269 +
9270 +    def __getitem__(self, depth):
9271 +        '''Get bind info at a depth.'''
9272 +
9273 +        if self.is_global:
9274 +            return self.stack[0]
9275 +
9276 +        return self.stack[depth]
9277 +
9278 +
9279 +    def __repr__(self):
9280 +        if self._repr_cache:
9281 +            return self._repr_cache
9282 +
9283 +        args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
9284 +
9285 +        if self.is_callable:
9286 +            args.append('function=%r' % self.function)
9287 +            if self.args:
9288 +                args.append('args=%r' % self.args)
9289 +
9290 +            if self.kargs:
9291 +                args.append('kargs=%r' % self.kargs)
9292 +
9293 +        else:
9294 +            cmdlen = len(self.commands)
9295 +            cmds = self.commands[0] if cmdlen == 1 else self.commands
9296 +            args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds))
9297 +
9298 +        self._repr_cache = '<Bind(%s)>' % ', '.join(args)
9299 +        return self._repr_cache
9300 +
9301 +
9302 +class BindPlugin(PerInstancePlugin):
9303 +    def __init__(self, uzbl):
9304 +        '''Export functions and connect handlers to events.'''
9305 +        super(BindPlugin, self).__init__(uzbl)
9306 +
9307 +        self.bindlet = Bindlet(uzbl)
9308 +
9309 +        uzbl.connect('BIND', self.parse_bind)
9310 +        uzbl.connect('MODE_BIND', self.parse_mode_bind)
9311 +        uzbl.connect('MODE_CHANGED', self.mode_changed)
9312 +
9313 +        # Connect key related events to the key_event function.
9314 +        events = [['KEYCMD_UPDATE', 'KEYCMD_EXEC'],
9315 +                  ['MODCMD_UPDATE', 'MODCMD_EXEC']]
9316 +
9317 +        for mod_cmd in range(2):
9318 +            for on_exec in range(2):
9319 +                event = events[mod_cmd][on_exec]
9320 +                handler = partial(self.key_event,
9321 +                    mod_cmd=bool(mod_cmd),
9322 +                    on_exec=bool(on_exec))
9323 +                uzbl.connect(event, handler)
9324 +
9325 +    def exec_bind(self, bind, *args, **kargs):
9326 +        '''Execute bind objects.'''
9327 +
9328 +        self.uzbl.event("EXEC_BIND", bind, args, kargs)
9329 +
9330 +        if bind.is_callable:
9331 +            args += bind.args
9332 +            kargs = dict(list(bind.kargs.items())+list(kargs.items()))
9333 +            bind.function(self.uzbl, *args, **kargs)
9334 +            return
9335 +
9336 +        if kargs:
9337 +            raise ArgumentError('cannot supply kargs for uzbl commands')
9338 +
9339 +        commands = []
9340 +        for cmd in bind.commands:
9341 +            cmd = cmd_expand(cmd, args)
9342 +            self.uzbl.send(cmd)
9343 +
9344 +    def mode_bind(self, modes, glob, handler=None, *args, **kargs):
9345 +        '''Add a mode bind.'''
9346 +
9347 +        bindlet = self.bindlet
9348 +
9349 +        if isinstance(modes, str):
9350 +            modes = modes.split(',')
9351 +
9352 +        # Sort and filter binds.
9353 +        modes = [_f for _f in map(str.strip, modes) if _f]
9354 +
9355 +        if isinstance(handler, collections.Callable) or (handler is not None and handler.strip()):
9356 +            bind = Bind(glob, handler, *args, **kargs)
9357 +
9358 +        else:
9359 +            bind = None
9360 +
9361 +        for mode in modes:
9362 +            if not VALID_MODE(mode):
9363 +                raise NameError('invalid mode name: %r' % mode)
9364 +
9365 +        for mode in modes:
9366 +            if mode[0] == '-':
9367 +                mode, bind = mode[1:], None
9368 +
9369 +            bindlet.add_bind(mode, glob, bind)
9370 +            self.uzbl.event('ADDED_MODE_BIND', mode, glob, bind)
9371 +        self.logger.info('added bind %s %s %s', mode, glob, bind)
9372 +
9373 +    def bind(self, glob, handler, *args, **kargs):
9374 +        '''Legacy bind function.'''
9375 +
9376 +        self.mode_bind('global', glob, handler, *args, **kargs)
9377 +
9378 +    def parse_mode_bind(self, args):
9379 +        '''Parser for the MODE_BIND event.
9380 +
9381 +        Example events:
9382 +            MODE_BIND <mode>         <bind>        = <command>
9383 +            MODE_BIND command        o<location:>_ = uri %s
9384 +            MODE_BIND insert,command <BackSpace>   = ...
9385 +            MODE_BIND global         ...           = ...
9386 +            MODE_BIND global,-insert ...           = ...
9387 +        '''
9388 +
9389 +        args = splitquoted(args)
9390 +        if len(args) < 2:
9391 +            raise ArgumentError('missing mode or bind section: %r' % args.raw())
9392 +
9393 +        modes = args[0].split(',')
9394 +        for i, g in enumerate(args[1:]):
9395 +            if g == '=':
9396 +                glob = args.raw(1, i)
9397 +                command = args.raw(i+2)
9398 +                break
9399 +        else:
9400 +            raise ArgumentError('missing delimiter in bind section: %r' % args.raw())
9401 +
9402 +        self.mode_bind(modes, glob, command)
9403 +
9404 +    def parse_bind(self, args):
9405 +        '''Legacy parsing of the BIND event and conversion to the new format.
9406 +
9407 +        Example events:
9408 +            request BIND <bind>        = <command>
9409 +            request BIND o<location:>_ = uri %s
9410 +            request BIND <BackSpace>   = ...
9411 +            request BIND ...           = ...
9412 +        '''
9413 +
9414 +        self.parse_mode_bind("global %s" % args)
9415 +
9416 +    def mode_changed(self, mode):
9417 +        '''Clear the stack on all non-stack mode changes.'''
9418 +
9419 +        if mode != 'stack':
9420 +            self.bindlet.reset()
9421 +
9422 +    def match_and_exec(self, bind, depth, modstate, keylet, bindlet):
9423 +        (on_exec, has_args, mod_cmd, glob, more) = bind[depth]
9424 +        cmd = keylet.modcmd if mod_cmd else keylet.keycmd
9425 +
9426 +        if mod_cmd and modstate != mod_cmd:
9427 +            return False
9428 +
9429 +        if has_args:
9430 +            if not cmd.startswith(glob):
9431 +                return False
9432 +
9433 +            args = [cmd[len(glob):],]
9434 +
9435 +        elif cmd != glob:
9436 +            return False
9437 +
9438 +        else:
9439 +            args = []
9440 +
9441 +        if bind.is_global or (not more and depth == 0):
9442 +            self.exec_bind(bind, *args)
9443 +            if not has_args:
9444 +                KeyCmd[self.uzbl].clear_current()
9445 +
9446 +            return True
9447 +
9448 +        elif more:
9449 +            bindlet.stack(bind, args, depth)
9450 +            (on_exec, has_args, mod_cmd, glob, more) = bind[depth+1]
9451 +            if not on_exec and has_args and not glob and not more:
9452 +                self.exec_bind(uzbl, bind, *(args+['',]))
9453 +
9454 +            return False
9455 +
9456 +        args = bindlet.args + args
9457 +        self.exec_bind(bind, *args)
9458 +        if not has_args or on_exec:
9459 +            config = Config[self.uzbl]
9460 +            del config['mode']
9461 +            bindlet.reset()
9462 +
9463 +        return True
9464 +
9465 +    def key_event(self, modstate, keylet, mod_cmd=False, on_exec=False):
9466 +        bindlet = self.bindlet
9467 +        depth = bindlet.depth
9468 +        for bind in bindlet.get_binds():
9469 +            t = bind[depth]
9470 +            if (bool(t[MOD_CMD]) != mod_cmd) or (t[ON_EXEC] != on_exec):
9471 +                continue
9472 +
9473 +            if self.match_and_exec(bind, depth, modstate, keylet, bindlet):
9474 +                return
9475 +
9476 +        bindlet.after()
9477 +
9478 +        # Return to the previous mode if the KEYCMD_EXEC keycmd doesn't match any
9479 +        # binds in the stack mode.
9480 +        if on_exec and not mod_cmd and depth and depth == bindlet.depth:
9481 +            config = Config[uzbl]
9482 +            del config['mode']
9483 +
9484 +# vi: set et ts=4:
9485 diff --git a/uzbl/plugins/cmd_expand.py b/uzbl/plugins/cmd_expand.py
9486 new file mode 100644
9487 index 0000000..46e1e67
9488 --- /dev/null
9489 +++ b/uzbl/plugins/cmd_expand.py
9490 @@ -0,0 +1,36 @@
9491 +def escape(str):
9492 +    for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]:
9493 +        str = str.replace(char, (level * '\\') + char)
9494 +
9495 +    return str
9496 +
9497 +
9498 +def cmd_expand(cmd, args):
9499 +    '''Exports a function that provides the following
9500 +    expansions in any uzbl command string:
9501 +
9502 +        %s = replace('%s', ' '.join(args))
9503 +        %r = replace('%r', "'%s'" % escaped(' '.join(args)))
9504 +        %1 = replace('%1', arg[0])
9505 +        %2 = replace('%2', arg[1])
9506 +        %n = replace('%n', arg[n-1])
9507 +    '''
9508 +
9509 +    # Ensure (1) all string representable and (2) correct string encoding.
9510 +    args = list(map(str, args))
9511 +
9512 +    # Direct string replace.
9513 +    if '%s' in cmd:
9514 +        cmd = cmd.replace('%s', ' '.join(args))
9515 +
9516 +    # Escaped and quoted string replace.
9517 +    if '%r' in cmd:
9518 +        cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args)))
9519 +
9520 +    # Arg index string replace.
9521 +    for (index, arg) in enumerate(args):
9522 +        index += 1
9523 +        if '%%%d' % index in cmd:
9524 +            cmd = cmd.replace('%%%d' % index, str(arg))
9525 +
9526 +    return cmd
9527 diff --git a/uzbl/plugins/completion.py b/uzbl/plugins/completion.py
9528 new file mode 100644
9529 index 0000000..ef2f277
9530 --- /dev/null
9531 +++ b/uzbl/plugins/completion.py
9532 @@ -0,0 +1,186 @@
9533 +'''Keycmd completion.'''
9534 +
9535 +import re
9536 +
9537 +from uzbl.arguments import splitquoted
9538 +from uzbl.ext import PerInstancePlugin
9539 +from .config import Config
9540 +from .keycmd import KeyCmd
9541 +
9542 +# Completion level
9543 +NONE, ONCE, LIST, COMPLETE = list(range(4))
9544 +
9545 +# The reverse keyword finding re.
9546 +FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall
9547 +
9548 +
9549 +def escape(str):
9550 +    return str.replace("@", "\@")
9551 +
9552 +
9553 +class Completions(set):
9554 +    def __init__(self):
9555 +        set.__init__(self)
9556 +        self.locked = False
9557 +        self.level = NONE
9558 +
9559 +    def lock(self):
9560 +        self.locked = True
9561 +
9562 +    def unlock(self):
9563 +        self.locked = False
9564 +
9565 +    def add_var(self, var):
9566 +        self.add('@' + var)
9567 +
9568 +
9569 +class CompletionListFormatter(object):
9570 +    LIST_FORMAT = "<span> %s </span>"
9571 +    ITEM_FORMAT = "<span @hint_style>%s</span>%s"
9572 +
9573 +    def format(self, partial, completions):
9574 +        p = len(partial)
9575 +        completions.sort()
9576 +        return self.LIST_FORMAT % ' '.join(
9577 +            [self.ITEM_FORMAT % (escape(h[:p]), h[p:]) for h in completions]
9578 +        )
9579 +
9580 +
9581 +class CompletionPlugin(PerInstancePlugin):
9582 +    def __init__(self, uzbl):
9583 +        '''Export functions and connect handlers to events.'''
9584 +        super(CompletionPlugin, self).__init__(uzbl)
9585 +
9586 +        self.completion = Completions()
9587 +        self.listformatter = CompletionListFormatter()
9588 +
9589 +        uzbl.connect('BUILTINS', self.add_builtins)
9590 +        uzbl.connect('CONFIG_CHANGED', self.add_config_key)
9591 +        uzbl.connect('KEYCMD_CLEARED', self.stop_completion)
9592 +        uzbl.connect('KEYCMD_EXEC', self.stop_completion)
9593 +        uzbl.connect('KEYCMD_UPDATE', self.update_completion_list)
9594 +        uzbl.connect('START_COMPLETION', self.start_completion)
9595 +        uzbl.connect('STOP_COMPLETION', self.stop_completion)
9596 +
9597 +        uzbl.send('dump_config_as_events')
9598 +
9599 +    def get_incomplete_keyword(self):
9600 +        '''Gets the segment of the keycmd leading up to the cursor position and
9601 +        uses a regular expression to search backwards finding parially completed
9602 +        keywords or @variables. Returns a null string if the correct completion
9603 +        conditions aren't met.'''
9604 +
9605 +        keylet = KeyCmd[self.uzbl].keylet
9606 +        left_segment = keylet.keycmd[:keylet.cursor]
9607 +        partial = (FIND_SEGMENT(left_segment) + ['', ])[0].lstrip()
9608 +        if partial.startswith('set '):
9609 +            return ('@' + partial[4:].lstrip(), True)
9610 +
9611 +        return (partial, False)
9612 +
9613 +    def stop_completion(self, *args):
9614 +        '''Stop command completion and return the level to NONE.'''
9615 +
9616 +        self.completion.level = NONE
9617 +        if 'completion_list' in Config[self.uzbl]:
9618 +            del Config[self.uzbl]['completion_list']
9619 +
9620 +    def complete_completion(self, partial, hint, set_completion=False):
9621 +        '''Inject the remaining porition of the keyword into the keycmd then stop
9622 +        the completioning.'''
9623 +
9624 +        if set_completion:
9625 +            remainder = "%s = " % hint[len(partial):]
9626 +
9627 +        else:
9628 +            remainder = "%s " % hint[len(partial):]
9629 +
9630 +        KeyCmd[self.uzbl].inject_keycmd(remainder)
9631 +        self.stop_completion()
9632 +
9633 +    def partial_completion(self, partial, hint):
9634 +        '''Inject a common portion of the hints into the keycmd.'''
9635 +
9636 +        remainder = hint[len(partial):]
9637 +        KeyCmd[self.uzbl].inject_keycmd(remainder)
9638 +
9639 +    def update_completion_list(self, *args):
9640 +        '''Checks if the user still has a partially completed keyword under his
9641 +        cursor then update the completion hints list.'''
9642 +
9643 +        partial = self.get_incomplete_keyword()[0]
9644 +        if not partial:
9645 +            return self.stop_completion()
9646 +
9647 +        if self.completion.level < LIST:
9648 +            return
9649 +
9650 +        config = Config[self.uzbl]
9651 +
9652 +        hints = [h for h in self.completion if h.startswith(partial)]
9653 +        if not hints:
9654 +            del config['completion_list']
9655 +            return
9656 +
9657 +        config['completion_list'] = self.listformatter.format(partial, hints)
9658 +
9659 +    def start_completion(self, *args):
9660 +        if self.completion.locked:
9661 +            return
9662 +
9663 +        (partial, set_completion) = self.get_incomplete_keyword()
9664 +        if not partial:
9665 +            return self.stop_completion()
9666 +
9667 +        if self.completion.level < COMPLETE:
9668 +            self.completion.level += 1
9669 +
9670 +        hints = [h for h in self.completion if h.startswith(partial)]
9671 +        if not hints:
9672 +            return
9673 +
9674 +        elif len(hints) == 1:
9675 +            self.completion.lock()
9676 +            self.complete_completion(partial, hints[0], set_completion)
9677 +            self.completion.unlock()
9678 +            return
9679 +
9680 +        elif partial in hints and completion.level == COMPLETE:
9681 +            self.completion.lock()
9682 +            self.complete_completion(partial, partial, set_completion)
9683 +            self.completion.unlock()
9684 +            return
9685 +
9686 +        smalllen, smallest = sorted([(len(h), h) for h in hints])[0]
9687 +        common = ''
9688 +        for i in range(len(partial), smalllen):
9689 +            char, same = smallest[i], True
9690 +            for hint in hints:
9691 +                if hint[i] != char:
9692 +                    same = False
9693 +                    break
9694 +
9695 +            if not same:
9696 +                break
9697 +
9698 +            common += char
9699 +
9700 +        if common:
9701 +            self.completion.lock()
9702 +            self.partial_completion(partial, partial + common)
9703 +            self.completion.unlock()
9704 +
9705 +        self.update_completion_list()
9706 +
9707 +    def add_builtins(self, builtins):
9708 +        '''Pump the space delimited list of builtin commands into the
9709 +        builtin list.'''
9710 +
9711 +        builtins = splitquoted(builtins)
9712 +        self.completion.update(builtins)
9713 +
9714 +    def add_config_key(self, key, value):
9715 +        '''Listen on the CONFIG_CHANGED event and add config keys to the variable
9716 +        list for @var<Tab> like expansion support.'''
9717 +
9718 +        self.completion.add_var(key)
9719 diff --git a/uzbl/plugins/config.py b/uzbl/plugins/config.py
9720 new file mode 100644
9721 index 0000000..4f00315
9722 --- /dev/null
9723 +++ b/uzbl/plugins/config.py
9724 @@ -0,0 +1,109 @@
9725 +from re import compile
9726 +
9727 +from uzbl.arguments import splitquoted
9728 +from uzbl.ext import PerInstancePlugin
9729 +
9730 +types = {'int': int, 'float': float, 'str': str}
9731 +
9732 +valid_key = compile('^[A-Za-z0-9_\.]+$').match
9733 +
9734 +class Config(PerInstancePlugin):
9735 +    """Configuration plugin, has dictionary interface for config access
9736 +
9737 +    This class is currenty not inherited from either UserDict or abc.Mapping
9738 +    because not sure what version of python we want to support. It's not
9739 +    hard to implement all needed methods either.
9740 +    """
9741 +
9742 +    def __init__(self, uzbl):
9743 +        super(Config, self).__init__(uzbl)
9744 +
9745 +        self.data = {}
9746 +        uzbl.connect('VARIABLE_SET', self.parse_set_event)
9747 +        assert not 'a' in self.data
9748 +
9749 +    def __getitem__(self, key):
9750 +        return self.data[key]
9751 +
9752 +    def __setitem__(self, key, value):
9753 +        self.set(key, value)
9754 +
9755 +    def __delitem__(self, key):
9756 +        self.set(key)
9757 +
9758 +    def get(self, key, default=None):
9759 +        return self.data.get(key, default)
9760 +
9761 +    def __contains__(self, key):
9762 +        return key in self.data
9763 +
9764 +    def keys(self):
9765 +        return iter(self.data.keys())
9766 +
9767 +    def items(self):
9768 +        return iter(self.data.items())
9769 +
9770 +    def update(self, other=None, **kwargs):
9771 +        if other is None:
9772 +            other = {}
9773 +
9774 +        for (key, value) in list(dict(other).items()) + list(kwargs.items()):
9775 +            self[key] = value
9776 +
9777 +
9778 +    def set(self, key, value='', force=False):
9779 +        '''Generates a `set <key> = <value>` command string to send to the
9780 +        current uzbl instance.
9781 +
9782 +        Note that the config dict isn't updated by this function. The config
9783 +        dict is only updated after a successful `VARIABLE_SET ..` event
9784 +        returns from the uzbl instance.'''
9785 +
9786 +        assert valid_key(key)
9787 +
9788 +        if isinstance(value, bool):
9789 +            value = int(value)
9790 +
9791 +        else:
9792 +            value = str(value)
9793 +            assert '\n' not in value
9794 +
9795 +        if not force and key in self and self[key] == value:
9796 +            return
9797 +
9798 +        self.uzbl.send('set %s = %s' % (key, value))
9799 +
9800 +
9801 +    def parse_set_event(self, args):
9802 +        '''Parse `VARIABLE_SET <var> <type> <value>` event and load the
9803 +        (key, value) pair into the `uzbl.config` dict.'''
9804 +
9805 +        args = splitquoted(args)
9806 +        if len(args) == 2:
9807 +            key, type, raw_value = args[0], args[1], ''
9808 +        elif len(args) == 3:
9809 +            key, type, raw_value = args
9810 +        else:
9811 +            raise Exception('Invalid number of arguments')
9812 +
9813 +        assert valid_key(key)
9814 +        assert type in types
9815 +
9816 +        new_value = types[type](raw_value)
9817 +        old_value = self.data.get(key, None)
9818 +
9819 +        # Update new value.
9820 +        self.data[key] = new_value
9821 +
9822 +        if old_value != new_value:
9823 +            self.uzbl.event('CONFIG_CHANGED', key, new_value)
9824 +
9825 +        # Cleanup null config values.
9826 +        if type == 'str' and not new_value:
9827 +            del self.data[key]
9828 +
9829 +    def cleanup(self):
9830 +        # not sure it's needed, but safer for cyclic links
9831 +        self.data.clear()
9832 +        super(Config, self).cleanup()
9833 +
9834 diff --git a/uzbl/plugins/cookies.py b/uzbl/plugins/cookies.py
9835 new file mode 100644
9836 index 0000000..8875e99
9837 --- /dev/null
9838 +++ b/uzbl/plugins/cookies.py
9839 @@ -0,0 +1,226 @@
9840 +""" Basic cookie manager
9841 +    forwards cookies to all other instances connected to the event manager"""
9842 +
9843 +from collections import defaultdict
9844 +import os, re, stat
9845 +
9846 +from uzbl.arguments import splitquoted
9847 +from uzbl.ext import GlobalPlugin, PerInstancePlugin
9848 +
9849 +# these are symbolic names for the components of the cookie tuple
9850 +symbolic = {'domain': 0, 'path':1, 'name':2, 'value':3, 'scheme':4, 'expires':5}
9851 +
9852 +# allows for partial cookies
9853 +# ? allow wildcard in key
9854 +def match(key, cookie):
9855 +    for k,c in zip(key,cookie):
9856 +        if k != c:
9857 +            return False
9858 +    return True
9859 +
9860 +def match_list(_list, cookie):
9861 +    for matcher in _list:
9862 +        for component, match in matcher:
9863 +            if match(cookie[component]) is None:
9864 +                break
9865 +        else:
9866 +            return True
9867 +    return False
9868 +
9869 +def add_cookie_matcher(_list, arg):
9870 +    ''' add a cookie matcher to a whitelist or a blacklist.
9871 +        a matcher is a list of (component, re) tuples that matches a cookie when the
9872 +        "component" part of the cookie matches the regular expression "re".
9873 +        "component" is one of the keys defined in the variable "symbolic" above,
9874 +        or the index of a component of a cookie tuple.
9875 +    '''
9876 +
9877 +    args = splitquoted(arg)
9878 +    mlist = []
9879 +    for (component, regexp) in zip(args[0::2], args[1::2]):
9880 +        try:
9881 +            component = symbolic[component]
9882 +        except KeyError:
9883 +            component = int(component)
9884 +        assert component <= 5
9885 +        mlist.append((component, re.compile(regexp).search))
9886 +    _list.append(mlist)
9887 +
9888 +class NullStore(object):
9889 +    def add_cookie(self, rawcookie, cookie):
9890 +        pass
9891 +
9892 +    def delete_cookie(self, rkey, key):
9893 +        pass
9894 +
9895 +class ListStore(list):
9896 +    def add_cookie(self, rawcookie, cookie):
9897 +        self.append(rawcookie)
9898 +
9899 +    def delete_cookie(self, rkey, key):
9900 +        self[:] = [x for x in self if not match(key, splitquoted(x))]
9901 +
9902 +class TextStore(object):
9903 +    def __init__(self, filename):
9904 +        self.filename = filename
9905 +        try:
9906 +          # make sure existing cookie jar is not world-open
9907 +          perm_mode = os.stat(self.filename).st_mode
9908 +          if (perm_mode & (stat.S_IRWXO | stat.S_IRWXG)) > 0:
9909 +              safe_perm = stat.S_IMODE(perm_mode) & ~(stat.S_IRWXO | stat.S_IRWXG)
9910 +              os.chmod(self.filename, safe_perm)
9911 +        except OSError:
9912 +            pass
9913 +
9914 +    def as_event(self, cookie):
9915 +        """Convert cookie.txt row to uzbls cookie event format"""
9916 +        scheme = {
9917 +            'TRUE'  : 'https',
9918 +            'FALSE' : 'http'
9919 +        }
9920 +        extra = ''
9921 +        if cookie[0].startswith("#HttpOnly_"):
9922 +            extra = 'Only'
9923 +            domain = cookie[0][len("#HttpOnly_"):]
9924 +        elif cookie[0].startswith('#'):
9925 +            return None
9926 +        else:
9927 +            domain = cookie[0]
9928 +        try:
9929 +            return (domain,
9930 +                cookie[2],
9931 +                cookie[5],
9932 +                cookie[6],
9933 +                scheme[cookie[3]] + extra,
9934 +                cookie[4])
9935 +        except (KeyError,IndexError):
9936 +            # Let malformed rows pass through like comments
9937 +            return None
9938 +
9939 +    def as_file(self, cookie):
9940 +        """Convert cookie event to cookie.txt row"""
9941 +        secure = {
9942 +            'https' : 'TRUE',
9943 +            'http'  : 'FALSE',
9944 +            'httpsOnly' : 'TRUE',
9945 +            'httpOnly'  : 'FALSE'
9946 +        }
9947 +        http_only = {
9948 +            'https' : '',
9949 +            'http'  : '',
9950 +            'httpsOnly' : '#HttpOnly_',
9951 +            'httpOnly'  : '#HttpOnly_'
9952 +        }
9953 +        return (http_only[cookie[4]] + cookie[0],
9954 +            'TRUE' if cookie[0].startswith('.') else 'FALSE',
9955 +            cookie[1],
9956 +            secure[cookie[4]],
9957 +            cookie[5],
9958 +            cookie[2],
9959 +            cookie[3])
9960 +
9961 +    def add_cookie(self, rawcookie, cookie):
9962 +        assert len(cookie) == 6
9963 +
9964 +        # delete equal cookies (ignoring expire time, value and secure flag)
9965 +        self.delete_cookie(None, cookie[:-3])
9966 +
9967 +        # restrict umask before creating the cookie jar
9968 +        curmask=os.umask(0)
9969 +        os.umask(curmask| stat.S_IRWXO | stat.S_IRWXG)
9970 +
9971 +        first = not os.path.exists(self.filename)
9972 +        with open(self.filename, 'a') as f:
9973 +            if first:
9974 +                print("# HTTP Cookie File", file=f)
9975 +            print('\t'.join(self.as_file(cookie)), file=f)
9976 +
9977 +    def delete_cookie(self, rkey, key):
9978 +        if not os.path.exists(self.filename):
9979 +            return
9980 +
9981 +        # restrict umask before creating the cookie jar
9982 +        curmask=os.umask(0)
9983 +        os.umask(curmask | stat.S_IRWXO | stat.S_IRWXG)
9984 +
9985 +        # read all cookies
9986 +        with open(self.filename, 'r') as f:
9987 +            cookies = f.readlines()
9988 +
9989 +        # write those that don't match the cookie to delete
9990 +        with open(self.filename, 'w') as f:
9991 +            for l in cookies:
9992 +                c = self.as_event(l.split('\t'))
9993 +                if c is None or not match(key, c):
9994 +                    print(l, end='', file=f)
9995 +        os.umask(curmask)
9996 +
9997 +xdg_data_home = os.environ.get('XDG_DATA_HOME', os.path.join(os.environ['HOME'], '.local/share'))
9998 +DefaultStore = TextStore(os.path.join(xdg_data_home, 'uzbl/cookies.txt'))
9999 +SessionStore = TextStore(os.path.join(xdg_data_home, 'uzbl/session-cookies.txt'))
10000 +
10001 +class Cookies(PerInstancePlugin):
10002 +    def __init__(self, uzbl):
10003 +        super(Cookies, self).__init__(uzbl)
10004 +
10005 +        self.whitelist = []
10006 +        self.blacklist = []
10007 +
10008 +        uzbl.connect('ADD_COOKIE', self.add_cookie)
10009 +        uzbl.connect('DELETE_COOKIE', self.delete_cookie)
10010 +        uzbl.connect('BLACKLIST_COOKIE', self.blacklist_cookie)
10011 +        uzbl.connect('WHITELIST_COOKIE', self.whitelist_cookie)
10012 +
10013 +    # accept a cookie only when:
10014 +    # a. there is no whitelist and the cookie is in the blacklist
10015 +    # b. the cookie is in the whitelist and not in the blacklist
10016 +    def accept_cookie(self, cookie):
10017 +        if self.whitelist:
10018 +            if match_list(self.whitelist, cookie):
10019 +                return not match_list(self.blacklist, cookie)
10020 +            return False
10021 +
10022 +        return not match_list(self.blacklist, cookie)
10023 +
10024 +    def expires_with_session(self, cookie):
10025 +        return cookie[5] == ''
10026 +
10027 +    def get_recipents(self):
10028 +        """ get a list of Uzbl instances to send the cookie too. """
10029 +        # This could be a lot more interesting
10030 +        return [u for u in list(self.uzbl.parent.uzbls.values()) if u is not self.uzbl]
10031 +
10032 +    def get_store(self, session=False):
10033 +        if session:
10034 +            return SessionStore
10035 +        return DefaultStore
10036 +
10037 +    def add_cookie(self, cookie):
10038 +        cookie = splitquoted(cookie)
10039 +        if self.accept_cookie(cookie):
10040 +            for u in self.get_recipents():
10041 +                u.send('add_cookie %s' % cookie.raw())
10042 +
10043 +            self.get_store(self.expires_with_session(cookie)).add_cookie(cookie.raw(), cookie)
10044 +        else:
10045 +            self.logger.debug('cookie %r is blacklisted', cookie)
10046 +            self.uzbl.send('delete_cookie %s' % cookie.raw())
10047 +
10048 +    def delete_cookie(self, cookie):
10049 +        cookie = splitquoted(cookie)
10050 +        for u in self.get_recipents():
10051 +            u.send('delete_cookie %s' % cookie.raw())
10052 +
10053 +        if len(cookie) == 6:
10054 +            self.get_store(self.expires_with_session(cookie)).delete_cookie(cookie.raw(), cookie)
10055 +        else:
10056 +            for store in set([self.get_store(session) for session in (True, False)]):
10057 +                store.delete_cookie(cookie.raw(), cookie)
10058 +
10059 +    def blacklist_cookie(self, arg):
10060 +        add_cookie_matcher(self.blacklist, arg)
10061 +
10062 +    def whitelist_cookie(self, arg):
10063 +        add_cookie_matcher(self.whitelist, arg)
10064 +
10065 +# vi: set et ts=4:
10066 diff --git a/uzbl/plugins/downloads.py b/uzbl/plugins/downloads.py
10067 new file mode 100644
10068 index 0000000..208dbda
10069 --- /dev/null
10070 +++ b/uzbl/plugins/downloads.py
10071 @@ -0,0 +1,83 @@
10072 +# this plugin does a very simple display of download progress. to use it, add
10073 +# @downloads to your status_format.
10074 +
10075 +import os
10076 +import html
10077 +
10078 +from uzbl.arguments import splitquoted
10079 +from .config import Config
10080 +from uzbl.ext import PerInstancePlugin
10081 +
10082 +class Downloads(PerInstancePlugin):
10083 +
10084 +    def __init__(self, uzbl):
10085 +        super(Downloads, self).__init__(uzbl)
10086 +        uzbl.connect('DOWNLOAD_STARTED', self.download_started)
10087 +        uzbl.connect('DOWNLOAD_PROGRESS', self.download_progress)
10088 +        uzbl.connect('DOWNLOAD_COMPLETE', self.download_complete)
10089 +        self.active_downloads = {}
10090 +
10091 +    def update_download_section(self):
10092 +        """after a download's status has changed this
10093 +           is called to update the status bar
10094 +        """
10095 +
10096 +        if self.active_downloads:
10097 +            # add a newline before we list downloads
10098 +            result = '&#10;downloads:'
10099 +            for path, progress in list(self.active_downloads.items()):
10100 +                # add each download
10101 +                fn = os.path.basename(path)
10102 +
10103 +                dl = " %s (%d%%)" % (fn, progress * 100)
10104 +
10105 +                # replace entities to make sure we don't break our markup
10106 +                # (this could be done with an @[]@ expansion in uzbl, but then we
10107 +                # can't use the &#10; above to make a new line)
10108 +                dl = html.escape(dl)
10109 +                result += dl
10110 +        else:
10111 +            result = ''
10112 +
10113 +        # and the result gets saved to an uzbl variable that can be used in
10114 +        # status_format
10115 +        config = Config[self.uzbl]
10116 +        if config.get('downloads', '') != result:
10117 +              config['downloads'] = result
10118 +
10119 +    def download_started(self, args):
10120 +        # parse the arguments
10121 +        args = splitquoted(args)
10122 +        destination_path = args[0]
10123 +
10124 +        # add to the list of active downloads
10125 +        self.active_downloads[destination_path] = 0.0
10126 +
10127 +        # update the progress
10128 +        self.update_download_section()
10129 +
10130 +    def download_progress(self, args):
10131 +        # parse the arguments
10132 +        args = splitquoted(args)
10133 +        destination_path = args[0]
10134 +        progress = float(args[1])
10135 +
10136 +        # update the progress
10137 +        self.active_downloads[destination_path] = progress
10138 +
10139 +        # update the status bar variable
10140 +        self.update_download_section()
10141 +
10142 +    def download_complete(self, args):
10143 +        # TODO(tailhook) be more userfriendly: show download for some time!
10144 +
10145 +        # parse the arguments
10146 +        args = splitquoted(args)
10147 +        destination_path = args[0]
10148 +
10149 +        # remove from the list of active downloads
10150 +        del self.active_downloads[destination_path]
10151 +
10152 +        # update the status bar variable
10153 +        self.update_download_section()
10154 +
10155 diff --git a/uzbl/plugins/history.py b/uzbl/plugins/history.py
10156 new file mode 100644
10157 index 0000000..1a0e0fb
10158 --- /dev/null
10159 +++ b/uzbl/plugins/history.py
10160 @@ -0,0 +1,148 @@
10161 +import random
10162 +
10163 +from .on_set import OnSetPlugin
10164 +from .keycmd import KeyCmd
10165 +from uzbl.ext import GlobalPlugin, PerInstancePlugin
10166 +
10167 +class SharedHistory(GlobalPlugin):
10168 +
10169 +    def __init__(self, event_manager):
10170 +        super(SharedHistory, self).__init__(event_manager)
10171 +        self.history = {}  #TODO(tailhook) save and load from file
10172 +
10173 +    def get_line_number(self, prompt):
10174 +        try:
10175 +            return len(self.history[prompt])
10176 +        except KeyError:
10177 +            return 0
10178 +
10179 +    def addline(self, prompt, entry):
10180 +        lst = self.history.get(prompt)
10181 +        if lst is None:
10182 +            self.history[prompt] = [entry]
10183 +        else:
10184 +            lst.append(entry)
10185 +
10186 +    def getline(self, prompt, index):
10187 +        try:
10188 +            return self.history[prompt][index]
10189 +        except KeyError:
10190 +            # not existent list is same as empty one
10191 +            raise IndexError()
10192 +
10193 +
10194 +class History(PerInstancePlugin):
10195 +
10196 +    def __init__(self, uzbl):
10197 +        super(History, self).__init__(uzbl)
10198 +        self._tail = ''
10199 +        self.prompt = ''
10200 +        self.cursor = None
10201 +        self.search_key = None
10202 +        uzbl.connect('KEYCMD_EXEC', self.keycmd_exec)
10203 +        uzbl.connect('HISTORY_PREV', self.history_prev)
10204 +        uzbl.connect('HISTORY_NEXT', self.history_next)
10205 +        uzbl.connect('HISTORY_SEARCH', self.history_search)
10206 +        OnSetPlugin[uzbl].on_set('keycmd_prompt',
10207 +            lambda uzbl, k, v: self.change_prompt(v))
10208 +
10209 +    def prev(self):
10210 +        shared = SharedHistory[self.uzbl]
10211 +        if self.cursor is None:
10212 +            self.cursor = shared.get_line_number(self.prompt) - 1
10213 +        else:
10214 +            self.cursor -= 1
10215 +
10216 +        if self.search_key:
10217 +            while self.cursor >= 0:
10218 +                line = shared.getline(self.prompt, self.cursor)
10219 +                if self.search_key in line:
10220 +                    return line
10221 +                self.cursor -= 1
10222 +
10223 +        if self.cursor >= 0:
10224 +            return shared.getline(self.prompt, self.cursor)
10225 +
10226 +        self.cursor = -1
10227 +        return random.choice(end_messages)
10228 +
10229 +    def __next__(self):
10230 +        if self.cursor is None:
10231 +            return ''
10232 +        shared = SharedHistory[self.uzbl]
10233 +
10234 +        self.cursor += 1
10235 +
10236 +        num = shared.get_line_number(self.prompt)
10237 +        if self.search_key:
10238 +            while self.cursor < num:
10239 +                line = shared.getline(self.prompt, self.cursor)
10240 +                if self.search_key in line:
10241 +                    return line
10242 +                self.cursor += 1
10243 +
10244 +        if self.cursor >= num:
10245 +            self.cursor = None
10246 +            self.search_key = None
10247 +            if self._tail:
10248 +                value = self._tail
10249 +                self._tail = None
10250 +                return value
10251 +            return ''
10252 +        return shared.getline(self.prompt, self.cursor)
10253 +
10254 +    def change_prompt(self, prompt):
10255 +        self.prompt = prompt
10256 +        self._tail = None
10257 +
10258 +    def search(self, key):
10259 +        self.search_key = key
10260 +        self.cursor = None
10261 +
10262 +    def __str__(self):
10263 +        return "(History %s, %s)" % (self.cursor, self.prompt)
10264 +
10265 +    def keycmd_exec(self, modstate, keylet):
10266 +        cmd = keylet.get_keycmd()
10267 +        if cmd:
10268 +            SharedHistory[self.uzbl].addline(self.prompt, cmd)
10269 +        self._tail = None
10270 +        self.cursor = None
10271 +        self.search_key = None
10272 +
10273 +    def history_prev(self, _x):
10274 +        cmd = KeyCmd[self.uzbl].keylet.get_keycmd()
10275 +        if self.cursor is None and cmd:
10276 +            self._tail = cmd
10277 +        val = self.prev()
10278 +        KeyCmd[self.uzbl].set_keycmd(val)
10279 +
10280 +    def history_next(self, _x):
10281 +        KeyCmd[self.uzbl].set_keycmd(next(self))
10282 +
10283 +    def history_search(self, key):
10284 +        self.search(key)
10285 +        self.uzbl.send('event HISTORY_PREV')
10286 +
10287 +end_messages = (
10288 +    'Look behind you, A three-headed monkey!',
10289 +    'error #4: static from nylon underwear.',
10290 +    'error #5: static from plastic slide rules.',
10291 +    'error #6: global warming.',
10292 +    'error #9: doppler effect.',
10293 +    'error #16: somebody was calculating pi on the server.',
10294 +    'error #19: floating point processor overflow.',
10295 +    'error #21: POSIX compliance problem.',
10296 +    'error #25: Decreasing electron flux.',
10297 +    'error #26: first Saturday after first full moon in Winter.',
10298 +    'error #64: CPU needs recalibration.',
10299 +    'error #116: the real ttys became pseudo ttys and vice-versa.',
10300 +    'error #229: wrong polarity of neutron flow.',
10301 +    'error #330: quantum decoherence.',
10302 +    'error #388: Bad user karma.',
10303 +    'error #407: Route flapping at the NAP.',
10304 +    'error #435: Internet shut down due to maintenance.',
10305 +    )
10306 +
10307 +
10308 +# vi: set et ts=4:
10309 diff --git a/uzbl/plugins/keycmd.py b/uzbl/plugins/keycmd.py
10310 new file mode 100644
10311 index 0000000..2020fda
10312 --- /dev/null
10313 +++ b/uzbl/plugins/keycmd.py
10314 @@ -0,0 +1,503 @@
10315 +import re
10316 +
10317 +from uzbl.arguments import splitquoted
10318 +from uzbl.ext import PerInstancePlugin
10319 +from .config import Config
10320 +
10321 +# Keycmd format which includes the markup for the cursor.
10322 +KEYCMD_FORMAT = "%s<span @cursor_style>%s</span>%s"
10323 +MODCMD_FORMAT = "<span> %s </span>"
10324 +
10325 +
10326 +# FIXME, add utility functions to shared module
10327 +def escape(str):
10328 +    for char in ['\\', '@']:
10329 +        str = str.replace(char, '\\'+char)
10330 +
10331 +    return str
10332 +
10333 +
10334 +def uzbl_escape(str):
10335 +    return "@[%s]@" % escape(str) if str else ''
10336 +
10337 +
10338 +def inject_str(str, index, inj):
10339 +    '''Inject a string into string at at given index.'''
10340 +
10341 +    return "%s%s%s" % (str[:index], inj, str[index:])
10342 +
10343 +
10344 +class Keylet(object):
10345 +    '''Small per-instance object that tracks characters typed.
10346 +
10347 +        >>> k = Keylet()
10348 +        >>> k.set_keycmd('spam')
10349 +        >>> print(k)
10350 +        <keylet(keycmd='spam')>
10351 +        >>> k.append_keycmd(' and egg')
10352 +        >>> print(k)
10353 +        <keylet(keycmd='spam and egg')>
10354 +        >>> print(k.cursor)
10355 +        12
10356 +    '''
10357 +
10358 +    def __init__(self):
10359 +        # Modcmd tracking
10360 +        self.modcmd = ''
10361 +        self.is_modcmd = False
10362 +
10363 +        # Keycmd tracking
10364 +        self.keycmd = ''
10365 +        self.cursor = 0
10366 +
10367 +    def get_keycmd(self):
10368 +        ''' Get the keycmd-part of the keylet. '''
10369 +
10370 +        return self.keycmd
10371 +
10372 +    def clear_keycmd(self):
10373 +        ''' Clears the keycmd part of the keylet '''
10374 +
10375 +        self.keycmd = ''
10376 +        self.cursor = 0
10377 +
10378 +    def get_modcmd(self):
10379 +        ''' Get the modcmd-part of the keylet. '''
10380 +
10381 +        if not self.is_modcmd:
10382 +            return ''
10383 +
10384 +        return self.modcmd
10385 +
10386 +    def clear_modcmd(self):
10387 +        self.modcmd = ''
10388 +        self.is_modcmd = False
10389 +
10390 +    def set_keycmd(self, keycmd):
10391 +        self.keycmd = keycmd
10392 +        self.cursor = len(keycmd)
10393 +
10394 +    def insert_keycmd(self, s):
10395 +        ''' Inserts string at the current position
10396 +
10397 +            >>> k = Keylet()
10398 +            >>> k.set_keycmd('spam')
10399 +            >>> k.cursor = 1
10400 +            >>> k.insert_keycmd('egg')
10401 +            >>> print(k)
10402 +            <keylet(keycmd='seggpam')>
10403 +            >>> print(k.cursor)
10404 +            4
10405 +        '''
10406 +
10407 +        self.keycmd = inject_str(self.keycmd, self.cursor, s)
10408 +        self.cursor += len(s)
10409 +
10410 +    def append_keycmd(self, s):
10411 +        ''' Appends string to to end of keycmd and moves the cursor
10412 +
10413 +            >>> k = Keylet()
10414 +            >>> k.set_keycmd('spam')
10415 +            >>> k.cursor = 1
10416 +            >>> k.append_keycmd('egg')
10417 +            >>> print(k)
10418 +            <keylet(keycmd='spamegg')>
10419 +            >>> print(k.cursor)
10420 +            7
10421 +        '''
10422 +
10423 +        self.keycmd += s
10424 +        self.cursor = len(self.keycmd)
10425 +
10426 +    def backspace(self):
10427 +        ''' Removes the character at the cursor position. '''
10428 +        if not self.keycmd or not self.cursor:
10429 +            return False
10430 +
10431 +        self.keycmd = self.keycmd[:self.cursor-1] + self.keycmd[self.cursor:]
10432 +        self.cursor -= 1
10433 +        return True
10434 +
10435 +    def delete(self):
10436 +        ''' Removes the character after the cursor position. '''
10437 +        if not self.keycmd:
10438 +            return False
10439 +
10440 +        self.keycmd = self.keycmd[:self.cursor] + self.keycmd[self.cursor+1:]
10441 +        return True
10442 +
10443 +    def strip_word(self, seps=' '):
10444 +        ''' Removes the last word from the keycmd, similar to readline ^W
10445 +            returns the part removed or None
10446 +
10447 +            >>> k = Keylet()
10448 +            >>> k.set_keycmd('spam and egg')
10449 +            >>> k.strip_word()
10450 +            'egg'
10451 +            >>> print(k)
10452 +            <keylet(keycmd='spam and ')>
10453 +            >>> k.strip_word()
10454 +            'and'
10455 +            >>> print(k)
10456 +            <keylet(keycmd='spam ')>
10457 +        '''
10458 +        if not self.keycmd:
10459 +            return None
10460 +
10461 +        head, tail = self.keycmd[:self.cursor].rstrip(seps), self.keycmd[self.cursor:]
10462 +        rfind = -1
10463 +        for sep in seps:
10464 +            p = head.rfind(sep)
10465 +            if p >= 0 and rfind < p + 1:
10466 +                rfind = p + 1
10467 +        if rfind == len(head) and head[-1] in seps:
10468 +            rfind -= 1
10469 +        self.keycmd = head[:rfind] if rfind + 1 else '' + tail
10470 +        self.cursor = len(head)
10471 +        return head[rfind:]
10472 +
10473 +    def set_cursor_pos(self, index):
10474 +        ''' Sets the cursor position, Supports negative indexing and relative
10475 +            stepping with '+' and '-'.
10476 +            Returns the new cursor position
10477 +
10478 +            >>> k = Keylet()
10479 +            >>> k.set_keycmd('spam and egg')
10480 +            >>> k.set_cursor_pos(2)
10481 +            2
10482 +            >>> k.set_cursor_pos(-3)
10483 +            10
10484 +            >>> k.set_cursor_pos('+')
10485 +            11
10486 +        '''
10487 +
10488 +        if index == '-':
10489 +            cursor = self.cursor - 1
10490 +
10491 +        elif index == '+':
10492 +            cursor = self.cursor + 1
10493 +
10494 +        else:
10495 +            cursor = int(index)
10496 +            if cursor < 0:
10497 +                cursor = len(self.keycmd) + cursor + 1
10498 +
10499 +        if cursor < 0:
10500 +            cursor = 0
10501 +
10502 +        if cursor > len(self.keycmd):
10503 +            cursor = len(self.keycmd)
10504 +
10505 +        self.cursor = cursor
10506 +        return self.cursor
10507 +
10508 +    def markup(self):
10509 +        ''' Returns the keycmd with the cursor in pango markup spliced in
10510 +
10511 +        >>> k = Keylet()
10512 +        >>> k.set_keycmd('spam and egg')
10513 +        >>> k.set_cursor_pos(4)
10514 +        4
10515 +        >>> k.markup()
10516 +        '@[spam]@<span @cursor_style>@[ ]@</span>@[and egg]@'
10517 +    '''
10518 +
10519 +        if self.cursor < len(self.keycmd):
10520 +            curchar = self.keycmd[self.cursor]
10521 +        else:
10522 +            curchar = ' '
10523 +        chunks = [self.keycmd[:self.cursor], curchar, self.keycmd[self.cursor+1:]]
10524 +        return KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks))
10525 +
10526 +    def __repr__(self):
10527 +        ''' Return a string representation of the keylet. '''
10528 +
10529 +        l = []
10530 +        if self.is_modcmd:
10531 +            l.append('modcmd=%r' % self.get_modcmd())
10532 +
10533 +        if self.keycmd:
10534 +            l.append('keycmd=%r' % self.get_keycmd())
10535 +
10536 +        return '<keylet(%s)>' % ', '.join(l)
10537 +
10538 +
10539 +class KeyCmd(PerInstancePlugin):
10540 +    def __init__(self, uzbl):
10541 +        '''Export functions and connect handlers to events.'''
10542 +        super(KeyCmd, self).__init__(uzbl)
10543 +
10544 +        self.keylet = Keylet()
10545 +        self.modmaps = {}
10546 +        self.ignores = {}
10547 +
10548 +        uzbl.connect('APPEND_KEYCMD', self.append_keycmd)
10549 +        uzbl.connect('IGNORE_KEY', self.add_key_ignore)
10550 +        uzbl.connect('INJECT_KEYCMD', self.inject_keycmd)
10551 +        uzbl.connect('KEYCMD_BACKSPACE', self.keycmd_backspace)
10552 +        uzbl.connect('KEYCMD_DELETE', self.keycmd_delete)
10553 +        uzbl.connect('KEYCMD_EXEC_CURRENT', self.keycmd_exec_current)
10554 +        uzbl.connect('KEYCMD_STRIP_WORD', self.keycmd_strip_word)
10555 +        uzbl.connect('KEYCMD_CLEAR', self.clear_keycmd)
10556 +        uzbl.connect('KEY_PRESS', self.key_press)
10557 +        uzbl.connect('KEY_RELEASE', self.key_release)
10558 +        uzbl.connect('MOD_PRESS', self.key_press)
10559 +        uzbl.connect('MOD_RELEASE', self.key_release)
10560 +        uzbl.connect('MODMAP', self.modmap_parse)
10561 +        uzbl.connect('SET_CURSOR_POS', self.set_cursor_pos)
10562 +        uzbl.connect('SET_KEYCMD', self.set_keycmd)
10563 +
10564 +    def modmap_key(self, key):
10565 +        '''Make some obscure names for some keys friendlier.'''
10566 +
10567 +        if key in self.modmaps:
10568 +            return self.modmaps[key]
10569 +
10570 +        elif key.endswith('_L') or key.endswith('_R'):
10571 +            # Remove left-right discrimination and try again.
10572 +            return self.modmap_key(key[:-2])
10573 +
10574 +        else:
10575 +            return key
10576 +
10577 +
10578 +    def key_ignored(self, key):
10579 +        '''Check if the given key is ignored by any ignore rules.'''
10580 +
10581 +        for (glob, match) in list(self.ignores.items()):
10582 +            if match(key):
10583 +                return True
10584 +
10585 +        return False
10586 +
10587 +    def add_modmap(self, key, map):
10588 +        '''Add modmaps.
10589 +
10590 +        Examples:
10591 +            set modmap = request MODMAP
10592 +            @modmap <Control> <Ctrl>
10593 +            @modmap <ISO_Left_Tab> <Shift-Tab>
10594 +            ...
10595 +
10596 +        Then:
10597 +            @bind <Shift-Tab> = <command1>
10598 +            @bind <Ctrl>x = <command2>
10599 +            ...
10600 +
10601 +        '''
10602 +
10603 +        assert len(key)
10604 +        modmaps = self.modmaps
10605 +
10606 +        modmaps[key.strip('<>')] = map.strip('<>')
10607 +        self.uzbl.event("NEW_MODMAP", key, map)
10608 +
10609 +    def modmap_parse(self, map):
10610 +        '''Parse a modmap definiton.'''
10611 +
10612 +        split = splitquoted(map)
10613 +
10614 +        if not split or len(split) > 2:
10615 +            raise Exception('Invalid modmap arugments: %r' % map)
10616 +
10617 +        self.add_modmap(*split)
10618 +
10619 +    def add_key_ignore(self, glob):
10620 +        '''Add an ignore definition.
10621 +
10622 +        Examples:
10623 +            set ignore_key = request IGNORE_KEY
10624 +            @ignore_key <Shift>
10625 +            @ignore_key <ISO_*>
10626 +            ...
10627 +        '''
10628 +
10629 +        assert len(glob) > 1
10630 +        ignores = self.ignores
10631 +
10632 +        glob = "<%s>" % glob.strip("<> ")
10633 +        restr = glob.replace('*', '[^\s]*')
10634 +        match = re.compile(restr).match
10635 +
10636 +        ignores[glob] = match
10637 +        self.uzbl.event('NEW_KEY_IGNORE', glob)
10638 +
10639 +    def clear_keycmd(self, *args):
10640 +        '''Clear the keycmd for this uzbl instance.'''
10641 +
10642 +        self.keylet.clear_keycmd()
10643 +        config = Config[self.uzbl]
10644 +        del config['keycmd']
10645 +        self.uzbl.event('KEYCMD_CLEARED')
10646 +
10647 +    def clear_modcmd(self):
10648 +        '''Clear the modcmd for this uzbl instance.'''
10649 +
10650 +        self.keylet.clear_modcmd()
10651 +
10652 +        config = Config[self.uzbl]
10653 +        del config['modcmd']
10654 +        self.uzbl.event('MODCMD_CLEARED')
10655 +
10656 +    def clear_current(self):
10657 +        '''Clear the modcmd if is_modcmd else clear keycmd.'''
10658 +
10659 +        if self.keylet.is_modcmd:
10660 +            self.clear_modcmd()
10661 +
10662 +        else:
10663 +            self.clear_keycmd()
10664 +
10665 +    def update_event(self, modstate, k, execute=True):
10666 +        '''Raise keycmd & modcmd update events.'''
10667 +
10668 +        keycmd, modcmd = k.get_keycmd(), ''.join(modstate) + k.get_modcmd()
10669 +
10670 +        if k.is_modcmd:
10671 +            self.logger.debug('modcmd_update, %s', modcmd)
10672 +            self.uzbl.event('MODCMD_UPDATE', modstate, k)
10673 +
10674 +        else:
10675 +            self.logger.debug('keycmd_update, %s', keycmd)
10676 +            self.uzbl.event('KEYCMD_UPDATE', modstate, k)
10677 +
10678 +        config = Config[self.uzbl]
10679 +        if config.get('modcmd_updates', '1') == '1':
10680 +            new_modcmd = ''.join(modstate) + k.get_modcmd()
10681 +            if not new_modcmd or not k.is_modcmd:
10682 +                if 'modcmd' in config:
10683 +                    del config['modcmd']
10684 +
10685 +            elif new_modcmd == modcmd:
10686 +                config['modcmd'] = MODCMD_FORMAT % uzbl_escape(modcmd)
10687 +
10688 +        if config.get('keycmd_events', '1') != '1':
10689 +            return
10690 +
10691 +        new_keycmd = k.get_keycmd()
10692 +        if not new_keycmd:
10693 +            del config['keycmd']
10694 +
10695 +        elif new_keycmd == keycmd:
10696 +            # Generate the pango markup for the cursor in the keycmd.
10697 +            config['keycmd'] = str(k.markup())
10698 +
10699 +    def parse_key_event(self, key):
10700 +        ''' Build a set from the modstate part of the event, and pass all keys through modmap '''
10701 +
10702 +        modstate, key = splitquoted(key)
10703 +        modstate = set(['<%s>' % self.modmap_key(k) for k in modstate.split('|') if k])
10704 +
10705 +        key = self.modmap_key(key)
10706 +        return modstate, key
10707 +
10708 +    def key_press(self, key):
10709 +        '''Handle KEY_PRESS events. Things done by this function include:
10710 +
10711 +        1. Ignore all shift key presses (shift can be detected by capital chars)
10712 +        2. In non-modcmd mode:
10713 +             a. append char to keycmd
10714 +        3. If not in modcmd mode and a modkey was pressed set modcmd mode.
10715 +        4. Keycmd is updated and events raised if anything is changed.'''
10716 +
10717 +        k = self.keylet
10718 +        config = Config[self.uzbl]
10719 +        modstate, key = self.parse_key_event(key)
10720 +        k.is_modcmd = any(not self.key_ignored(m) for m in modstate)
10721 +
10722 +        self.logger.debug('key press modstate=%s', modstate)
10723 +        if key.lower() == 'space' and not k.is_modcmd and k.keycmd:
10724 +            k.insert_keycmd(' ')
10725 +
10726 +        elif not k.is_modcmd and len(key) == 1:
10727 +            if config.get('keycmd_events', '1') != '1':
10728 +                # TODO, make a note on what's going on here
10729 +                k.keycmd = ''
10730 +                k.cursor = 0
10731 +                del config['keycmd']
10732 +                return
10733 +
10734 +            k.insert_keycmd(key)
10735 +
10736 +        elif len(key) == 1:
10737 +            k.modcmd += key
10738 +
10739 +        else:
10740 +            if not self.key_ignored('<%s>' % key):
10741 +                modstate.add('<%s>' % key)
10742 +                k.is_modcmd = True
10743 +
10744 +        self.update_event(modstate, k)
10745 +
10746 +    def key_release(self, key):
10747 +        '''Respond to KEY_RELEASE event. Things done by this function include:
10748 +
10749 +        1. If in a mod-command then raise a MODCMD_EXEC.
10750 +        2. Update the keycmd uzbl variable if anything changed.'''
10751 +        k = self.keylet
10752 +        modstate, key = self.parse_key_event(key)
10753 +
10754 +        if len(key) > 1:
10755 +            if k.is_modcmd:
10756 +                self.uzbl.event('MODCMD_EXEC', modstate, k)
10757 +
10758 +            self.clear_modcmd()
10759 +
10760 +    def set_keycmd(self, keycmd):
10761 +        '''Allow setting of the keycmd externally.'''
10762 +
10763 +        self.keylet.set_keycmd(keycmd)
10764 +        self.update_event(set(), self.keylet, False)
10765 +
10766 +    def inject_keycmd(self, keycmd):
10767 +        '''Allow injecting of a string into the keycmd at the cursor position.'''
10768 +
10769 +        self.keylet.insert_keycmd(keycmd)
10770 +        self.update_event(set(), self.keylet, False)
10771 +
10772 +    def append_keycmd(self, keycmd):
10773 +        '''Allow appening of a string to the keycmd.'''
10774 +
10775 +        self.keylet.append_keycmd(keycmd)
10776 +        self.update_event(set(), self.keylet, False)
10777 +
10778 +    def keycmd_strip_word(self, args):
10779 +        ''' Removes the last word from the keycmd, similar to readline ^W '''
10780 +
10781 +        args = splitquoted(args)
10782 +        assert len(args) <= 1
10783 +        self.logger.debug('STRIPWORD %r %r', args, self.keylet)
10784 +        if self.keylet.strip_word(*args):
10785 +            self.update_event(set(), self.keylet, False)
10786 +
10787 +    def keycmd_backspace(self, *args):
10788 +        '''Removes the character at the cursor position in the keycmd.'''
10789 +
10790 +        if self.keylet.backspace():
10791 +            self.update_event(set(), self.keylet, False)
10792 +
10793 +    def keycmd_delete(self, *args):
10794 +        '''Removes the character after the cursor position in the keycmd.'''
10795 +
10796 +        if self.keylet.delete():
10797 +            self.update_event(set(), self.keylet, False)
10798 +
10799 +    def keycmd_exec_current(self, *args):
10800 +        '''Raise a KEYCMD_EXEC with the current keylet and then clear the
10801 +        keycmd.'''
10802 +
10803 +        self.uzbl.event('KEYCMD_EXEC', set(), self.keylet)
10804 +        self.clear_keycmd()
10805 +
10806 +    def set_cursor_pos(self, args):
10807 +        '''Allow setting of the cursor position externally. Supports negative
10808 +        indexing and relative stepping with '+' and '-'.'''
10809 +
10810 +        args = splitquoted(args)
10811 +        assert len(args) == 1
10812 +
10813 +        self.keylet.set_cursor_pos(args[0])
10814 +        self.update_event(set(), self.keylet, False)
10815 +
10816 +# vi: set et ts=4:
10817 +
10818 diff --git a/uzbl/plugins/mode.py b/uzbl/plugins/mode.py
10819 new file mode 100644
10820 index 0000000..6eaf009
10821 --- /dev/null
10822 +++ b/uzbl/plugins/mode.py
10823 @@ -0,0 +1,69 @@
10824 +from collections import defaultdict
10825 +
10826 +from .on_set import OnSetPlugin
10827 +from .config import Config
10828 +from uzbl.arguments import splitquoted, is_quoted
10829 +from uzbl.ext import PerInstancePlugin
10830 +
10831 +
10832 +class ModePlugin(PerInstancePlugin):
10833 +    def __init__(self, uzbl):
10834 +        super(ModePlugin, self).__init__(uzbl)
10835 +        self.mode_config = defaultdict(dict)
10836 +        uzbl.connect('MODE_CONFIG', self.parse_mode_config)
10837 +        uzbl.connect('MODE_CONFIRM', self.confirm_change)
10838 +        OnSetPlugin[uzbl].on_set('mode', self.mode_updated, False)
10839 +        OnSetPlugin[uzbl].on_set('default_mode', self.default_mode_updated, False)
10840 +
10841 +    def cleanup(self):
10842 +        self.mode_config.clear()
10843 +
10844 +    def parse_mode_config(self, args):
10845 +        '''Parse `MODE_CONFIG <mode> <var> = <value>` event and update config
10846 +        if the `<mode>` is the current mode.'''
10847 +
10848 +        args = splitquoted(args)
10849 +        assert len(args) >= 3, 'missing mode config args %r' % args
10850 +        mode = args[0]
10851 +        key = args[1]
10852 +        assert args[2] == '=', 'invalid mode config set syntax'
10853 +
10854 +        # Use the rest of the line verbatim as the value unless it's a
10855 +        # single properly quoted string
10856 +        if len(args) == 4 and is_quoted(args.raw(3)):
10857 +            value = args[3]
10858 +        else:
10859 +            value = args.raw(3).strip()
10860 +
10861 +        self.logger.debug('value %r', value)
10862 +
10863 +        self.mode_config[mode][key] = value
10864 +        config = Config[self.uzbl]
10865 +        if config.get('mode', None) == mode:
10866 +            config[key] = value
10867 +
10868 +    def default_mode_updated(self, var, mode):
10869 +        config = Config[self.uzbl]
10870 +        if mode and not config.get('mode', None):
10871 +            self.logger.debug('setting mode to default %r' % mode)
10872 +            config['mode'] = mode
10873 +
10874 +    def mode_updated(self, var, mode):
10875 +        config = Config[self.uzbl]
10876 +        if not mode:
10877 +            mode = config.get('default_mode', 'command')
10878 +            self.logger.debug('setting mode to default %r' % mode)
10879 +            config['mode'] = mode
10880 +            return
10881 +
10882 +        # Load mode config
10883 +        mode_config = self.mode_config.get(mode, None)
10884 +        if mode_config:
10885 +            config.update(mode_config)
10886 +
10887 +        self.uzbl.send('event MODE_CONFIRM %s' % mode)
10888 +
10889 +    def confirm_change(self, mode):
10890 +        config = Config[self.uzbl]
10891 +        if mode and config.get('mode', None) == mode:
10892 +            self.uzbl.event('MODE_CHANGED', mode)
10893 diff --git a/uzbl/plugins/on_event.py b/uzbl/plugins/on_event.py
10894 new file mode 100644
10895 index 0000000..cf33799
10896 --- /dev/null
10897 +++ b/uzbl/plugins/on_event.py
10898 @@ -0,0 +1,106 @@
10899 +'''Plugin provides arbitrary binding of uzbl events to uzbl commands.
10900 +
10901 +Formatting options:
10902 +  %s = space separated string of the arguments
10903 +  %r = escaped and quoted version of %s
10904 +  %1 = argument 1
10905 +  %2 = argument 2
10906 +  %n = argument n
10907 +
10908 +Usage:
10909 +  request ON_EVENT LINK_HOVER set selected_uri = $1
10910 +    --> LINK_HOVER http://uzbl.org/
10911 +    <-- set selected_uri = http://uzbl.org/
10912 +
10913 +  request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2
10914 +    --> CONFIG_CHANGED selected_uri http://uzbl.org/
10915 +    <-- print Config changed: selected_uri = http://uzbl.org/
10916 +'''
10917 +
10918 +import re
10919 +import fnmatch
10920 +from functools import partial
10921 +
10922 +from uzbl.arguments import splitquoted
10923 +from .cmd_expand import cmd_expand
10924 +from uzbl.ext import PerInstancePlugin
10925 +
10926 +def match_args(pattern, args):
10927 +    if len(pattern) > len(args):
10928 +        return False
10929 +    for p, a in zip(pattern, args):
10930 +        if not fnmatch.fnmatch(a, p):
10931 +            return False
10932 +    return True
10933 +
10934 +
10935 +class OnEventPlugin(PerInstancePlugin):
10936 +
10937 +    def __init__(self, uzbl):
10938 +        '''Export functions and connect handlers to events.'''
10939 +        super(OnEventPlugin, self).__init__(uzbl)
10940 +
10941 +        self.events = {}
10942 +
10943 +        uzbl.connect('ON_EVENT', self.parse_on_event)
10944 +
10945 +    def event_handler(self, *args, **kargs):
10946 +        '''This function handles all the events being watched by various
10947 +        on_event definitions and responds accordingly.'''
10948 +
10949 +        # Could be connected to a EM internal event that can use anything as args
10950 +        if len(args) == 1 and isinstance(args[0], str):
10951 +            args = splitquoted(args[0])
10952 +
10953 +        event = kargs['on_event']
10954 +        if event not in self.events:
10955 +            return
10956 +
10957 +        commands = self.events[event]
10958 +        for cmd, pattern in list(commands.items()):
10959 +            if not pattern or match_args(pattern, args):
10960 +                cmd = cmd_expand(cmd, args)
10961 +                self.uzbl.send(cmd)
10962 +
10963 +    def on_event(self, event, pattern, cmd):
10964 +        '''Add a new event to watch and respond to.'''
10965 +
10966 +        event = event.upper()
10967 +        self.logger.debug('new event handler %r %r %r', event, pattern, cmd)
10968 +        if event not in self.events:
10969 +            self.uzbl.connect(event,
10970 +                partial(self.event_handler, on_event=event))
10971 +            self.events[event] = {}
10972 +
10973 +        cmds = self.events[event]
10974 +        if cmd not in cmds:
10975 +            cmds[cmd] = pattern
10976 +
10977 +    def parse_on_event(self, args):
10978 +        '''Parse ON_EVENT events and pass them to the on_event function.
10979 +
10980 +        Syntax: "event ON_EVENT <EVENT_NAME> commands".'''
10981 +
10982 +        args = splitquoted(args)
10983 +        assert args, 'missing on event arguments'
10984 +
10985 +        # split arguments into event name, optional argument pattern and command
10986 +        event = args[0]
10987 +        pattern = []
10988 +        if args[1] == '[':
10989 +            for i, arg in enumerate(args[2:]):
10990 +                if arg == ']':
10991 +                    break
10992 +                pattern.append(arg)
10993 +            command = args.raw(3+i)
10994 +        else:
10995 +            command = args.raw(1)
10996 +
10997 +        assert event and command, 'missing on event command'
10998 +        self.on_event(event, pattern, command)
10999 +
11000 +    def cleanup(self):
11001 +        self.events.clear()
11002 +        super(OnEventPlugin, self).cleanup()
11003 +
11004 +# vi: set et ts=4:
11005 diff --git a/uzbl/plugins/on_set.py b/uzbl/plugins/on_set.py
11006 new file mode 100644
11007 index 0000000..f6b9229
11008 --- /dev/null
11009 +++ b/uzbl/plugins/on_set.py
11010 @@ -0,0 +1,85 @@
11011 +from re import compile
11012 +from functools import partial
11013 +
11014 +import uzbl.plugins.config
11015 +from .cmd_expand import cmd_expand
11016 +from uzbl.arguments import splitquoted
11017 +from uzbl.ext import PerInstancePlugin
11018 +import collections
11019 +
11020 +valid_glob = compile('^[A-Za-z0-9_\*\.]+$').match
11021 +
11022 +def make_matcher(glob):
11023 +    '''Make matcher function from simple glob.'''
11024 +
11025 +    pattern = "^%s$" % glob.replace('*', '[^\s]*')
11026 +    return compile(pattern).match
11027 +
11028 +
11029 +class OnSetPlugin(PerInstancePlugin):
11030 +
11031 +    def __init__(self, uzbl):
11032 +        super(OnSetPlugin, self).__init__(uzbl)
11033 +        self.on_sets = {}
11034 +        uzbl.connect('ON_SET', self.parse_on_set)
11035 +        uzbl.connect('CONFIG_CHANGED', self.check_for_handlers)
11036 +
11037 +    def _exec_handlers(self, handlers, key, arg):
11038 +        '''Execute the on_set handlers that matched the key.'''
11039 +
11040 +        for handler in handlers:
11041 +            if isinstance(handler, collections.Callable):
11042 +                handler(key, arg)
11043 +            else:
11044 +                self.uzbl.send(cmd_expand(handler, [key, arg]))
11045 +
11046 +    def check_for_handlers(self, key, arg):
11047 +        '''Check for handlers for the current key.'''
11048 +
11049 +        for (matcher, handlers) in list(self.on_sets.values()):
11050 +            if matcher(key):
11051 +                self._exec_handlers(handlers, key, arg)
11052 +
11053 +    def on_set(self, glob, handler, prepend=True):
11054 +        '''Add a new handler for a config key change.
11055 +
11056 +        Structure of the `self.on_sets` dict:
11057 +          { glob : ( glob matcher function, handlers list ), .. }
11058 +        '''
11059 +
11060 +        assert valid_glob(glob)
11061 +
11062 +        while '**' in glob:
11063 +            glob = glob.replace('**', '*')
11064 +
11065 +        if isinstance(handler, collections.Callable):
11066 +            orig_handler = handler
11067 +            if prepend:
11068 +                handler = partial(handler, self.uzbl)
11069 +
11070 +        else:
11071 +            orig_handler = handler = str(handler)
11072 +
11073 +        if glob in self.on_sets:
11074 +            (matcher, handlers) = self.on_sets[glob]
11075 +            handlers.append(handler)
11076 +
11077 +        else:
11078 +            matcher = make_matcher(glob)
11079 +            self.on_sets[glob] = (matcher, [handler,])
11080 +
11081 +        self.logger.info('on set %r call %r' % (glob, orig_handler))
11082 +
11083 +
11084 +    def parse_on_set(self, args):
11085 +        '''Parse `ON_SET <glob> <command>` event then pass arguments to the
11086 +        `on_set(..)` function.'''
11087 +
11088 +        args = splitquoted(args)
11089 +        assert len(args) >= 2
11090 +        glob = args[0]
11091 +        command = args.raw(1)
11092 +
11093 +        assert glob and command and valid_glob(glob)
11094 +        self.on_set(glob, command)
11095 +
11096 diff --git a/uzbl/plugins/progress_bar.py b/uzbl/plugins/progress_bar.py
11097 new file mode 100644
11098 index 0000000..5eb10d3
11099 --- /dev/null
11100 +++ b/uzbl/plugins/progress_bar.py
11101 @@ -0,0 +1,92 @@
11102 +import re
11103 +from .config import Config
11104 +
11105 +from uzbl.ext import PerInstancePlugin
11106 +
11107 +class ProgressBar(PerInstancePlugin):
11108 +    splitfrmt = re.compile(r'(%[A-Z][^%]|%[^%])').split
11109 +
11110 +    def __init__(self, uzbl):
11111 +        super(ProgressBar, self).__init__(uzbl)
11112 +        uzbl.connect('LOAD_COMMIT', lambda uri: self.update_progress())
11113 +        uzbl.connect('LOAD_PROGRESS', self.update_progress)
11114 +        self.updates = 0
11115 +
11116 +    def update_progress(self, progress=None):
11117 +        '''Updates the progress.output variable on LOAD_PROGRESS update.
11118 +
11119 +        The current substitution options are:
11120 +            %d = done char * done
11121 +            %p = pending char * remaining
11122 +            %c = percent done
11123 +            %i = int done
11124 +            %s = -\|/ spinner
11125 +            %t = percent pending
11126 +            %o = int pending
11127 +            %r = sprites
11128 +
11129 +        Default configuration options:
11130 +            progress.format  = [%d>%p]%c
11131 +            progress.width   = 8
11132 +            progress.done    = =
11133 +            progress.pending =
11134 +            progress.spinner = -\|/
11135 +            progress.sprites = loading
11136 +        '''
11137 +
11138 +        if progress is None:
11139 +            self.updates = 0
11140 +            progress = 100
11141 +
11142 +        else:
11143 +            self.updates += 1
11144 +            progress = int(progress)
11145 +
11146 +        # Get progress config vars.
11147 +        config = Config[self.uzbl]
11148 +        frmt = config.get('progress.format', '[%d>%p]%c')
11149 +        width = int(config.get('progress.width', 8))
11150 +        done_symbol = config.get('progress.done', '=')
11151 +        pend = config.get('progress.pending', None)
11152 +        pending_symbol = pend if pend else ' '
11153 +
11154 +        # Get spinner character
11155 +        spinner = config.get('progress.spinner', '-\\|/')
11156 +        index = 0 if progress == 100 else self.updates % len(spinner)
11157 +        spinner = '\\\\' if spinner[index] == '\\' else spinner[index]
11158 +
11159 +        # get sprite character
11160 +        sprites = config.get('progress.sprites', 'loading')
11161 +        index = int(((progress/100.0)*len(sprites))+0.5)-1
11162 +        sprite = '%r' % ('\\\\' if sprites[index] == '\\' else sprites[index])
11163 +
11164 +        # Inflate the done and pending bars to stop the progress bar
11165 +        # jumping around.
11166 +        if '%c' in frmt or '%i' in frmt:
11167 +            count = frmt.count('%c') + frmt.count('%i')
11168 +            width += (3-len(str(progress))) * count
11169 +
11170 +        if '%t' in frmt or '%o' in frmt:
11171 +            count = frmt.count('%t') + frmt.count('%o')
11172 +            width += (3-len(str(100-progress))) * count
11173 +
11174 +        done = int(((progress/100.0)*width)+0.5)
11175 +        pending = width - done
11176 +
11177 +        # values to replace with
11178 +        values = {
11179 +            'd': done_symbol * done,
11180 +            'p': pending_symbol * pending,
11181 +            'c': '%d%%' % progress,
11182 +            'i': '%d' % progress,
11183 +            't': '%d%%' % (100 - progress),
11184 +            'o': '%d' % (100 - progress),
11185 +            's': spinner,
11186 +            'r': sprite
11187 +        }
11188 +
11189 +        frmt = ''.join([str(values[k[1:]]) if k.startswith('%') else k for
11190 +                k in self.splitfrmt(frmt)])
11191 +
11192 +        if config.get('progress.output', None) != frmt:
11193 +            config['progress.output'] = frmt
11194 --- uzbl-2012.05.14/Makefile~   2013-12-08 16:55:13.000000000 +0100
11195 +++ uzbl-2012.05.14/Makefile    2013-12-08 16:56:12.412142265 +0100
11196 @@ -189,7 +189,7 @@
11197         install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core
11198  
11199  install-event-manager: install-dirs
11200 -       $(PYTHON) setup.py install --prefix=$(PREFIX) --install-scripts=$(INSTALLDIR)/bin $(PYINSTALL_EXTRA)
11201 +       $(PYTHON) setup.py install --prefix=$(PREFIX) --root=$(DESTDIR) --install-scripts=$(PREFIX)/bin $(PYINSTALL_EXTRA)
11202  
11203  install-uzbl-browser: install-dirs install-uzbl-core install-event-manager
11204         sed 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' < bin/uzbl-browser > $(INSTALLDIR)/bin/uzbl-browser
This page took 1.317738 seconds and 3 git commands to generate.