]> git.pld-linux.org Git - packages/uzbl.git/blame - uzbl-git.patch
- up to 2013.12.08 git snap (which doesn't hang on some analytics js)
[packages/uzbl.git] / uzbl-git.patch
CommitLineData
d2f1f677
AM
1diff --git a/.gitignore b/.gitignore
2index 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
16diff --git a/AUTHORS b/AUTHORS
17index 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
28diff --git a/Makefile b/Makefile
29index 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
244diff --git a/README b/README
245index 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
614diff --git a/bin/uzbl-browser b/bin/uzbl-browser
615index 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
631diff --git a/bin/uzbl-event-manager b/bin/uzbl-event-manager
632index 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()
1650diff --git a/bin/uzbl-tabbed b/bin/uzbl-tabbed
1651index 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:
1792diff --git a/examples/config/config b/examples/config/config
1793index 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
1851diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py
1852deleted file mode 100644
1853index 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:
2319diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py
2320deleted file mode 100644
2321index 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)
2365diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py
2366deleted file mode 100644
2367index 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')
2550diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py
2551deleted file mode 100644
2552index 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()
2647diff --git a/examples/data/plugins/cookies.py b/examples/data/plugins/cookies.py
2648deleted file mode 100644
2649index 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:
2875diff --git a/examples/data/plugins/downloads.py b/examples/data/plugins/downloads.py
2876deleted file mode 100644
2877index 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- })
2958diff --git a/examples/data/plugins/history.py b/examples/data/plugins/history.py
2959deleted file mode 100644
2960index 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:
3093diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py
3094deleted file mode 100644
3095index 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:
3522diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py
3523deleted file mode 100644
3524index 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()
3596diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py
3597deleted file mode 100644
3598index 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:
3690diff --git a/examples/data/plugins/on_set.py b/examples/data/plugins/on_set.py
3691deleted file mode 100644
3692index 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()
3788diff --git a/examples/data/plugins/progress_bar.py b/examples/data/plugins/progress_bar.py
3789deleted file mode 100644
3790index 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- })
3887diff --git a/examples/data/scripts/auth.py b/examples/data/scripts/auth.py
3888index 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)
3922diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js
3923index 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
3934diff --git a/examples/data/scripts/formfiller.js b/examples/data/scripts/formfiller.js
3935index 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
3944diff --git a/examples/data/scripts/history.sh b/examples/data/scripts/history.sh
3945index 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
3956diff --git a/examples/data/scripts/scheme.py b/examples/data/scripts/scheme.py
3957index 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]
3982diff --git a/misc/env.sh b/misc/env.sh
3983index 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
3998diff --git a/setup.py b/setup.py
3999new file mode 100644
4000index 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+ )
4015diff --git a/src/callbacks.c b/src/callbacks.c
4016index 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
4388diff --git a/src/callbacks.h b/src/callbacks.h
4389index 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
4439diff --git a/src/commands.c b/src/commands.c
4440index 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+}
4798diff --git a/src/commands.h b/src/commands.h
4799index 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
4851diff --git a/src/cookie-jar.c b/src/cookie-jar.c
4852index 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);
4887diff --git a/src/events.c b/src/events.c
4888index 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 */
4966diff --git a/src/events.h b/src/events.h
4967index 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
4995diff --git a/src/inspector.c b/src/inspector.c
4996index 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;
5063diff --git a/src/io.c b/src/io.c
5064index 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);
5085diff --git a/src/menu.c b/src/menu.c
5086index 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;
5116diff --git a/src/menu.h b/src/menu.h
5117index 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);
5140diff --git a/src/soup.c b/src/soup.c
5141new file mode 100644
5142index 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+}
5329diff --git a/src/soup.h b/src/soup.h
5330new file mode 100644
5331index 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
5353diff --git a/src/status-bar.h b/src/status-bar.h
5354index 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 {
5366diff --git a/src/type.h b/src/type.h
5367index 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
5386diff --git a/src/util.c b/src/util.c
5387index 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+}
5400diff --git a/src/util.h b/src/util.h
5401index 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);
5417diff --git a/src/uzbl-core.c b/src/uzbl-core.c
5418index 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]);
5669diff --git a/src/uzbl-core.h b/src/uzbl-core.h
5670index 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
5768diff --git a/src/variables.c b/src/variables.c
5769index 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}}
6880diff --git a/src/variables.h b/src/variables.h
6881index 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
6921diff --git a/tests/event-manager/emtest.py b/tests/event-manager/emtest.py
6922new file mode 100644
6923index 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
6957diff --git a/tests/event-manager/testarguments.py b/tests/event-manager/testarguments.py
6958new file mode 100755
6959index 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')
6988diff --git a/tests/event-manager/testbind.py b/tests/event-manager/testbind.py
6989new file mode 100644
6990index 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])
7045diff --git a/tests/event-manager/testcompletion.py b/tests/event-manager/testcompletion.py
7046new file mode 100644
7047index 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)
7169diff --git a/tests/event-manager/testconfig.py b/tests/event-manager/testconfig.py
7170new file mode 100644
7171index 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)
7252diff --git a/tests/event-manager/testcookie.py b/tests/event-manager/testcookie.py
7253new file mode 100755
7254index 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()
7325diff --git a/tests/event-manager/testcore.py b/tests/event-manager/testcore.py
7326new file mode 100644
7327index 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))
7421diff --git a/tests/event-manager/testdoc.py b/tests/event-manager/testdoc.py
7422new file mode 100755
7423index 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()
7449diff --git a/tests/event-manager/testdownload.py b/tests/event-manager/testdownload.py
7450new file mode 100644
7451index 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()
7484diff --git a/tests/event-manager/testhistory.py b/tests/event-manager/testhistory.py
7485new file mode 100644
7486index 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
7644diff --git a/tests/event-manager/testkeycmd.py b/tests/event-manager/testkeycmd.py
7645new file mode 100644
7646index 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)
7695diff --git a/tests/event-manager/testmode.py b/tests/event-manager/testmode.py
7696new file mode 100644
7697index 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')
7792diff --git a/tests/event-manager/testonevent.py b/tests/event-manager/testonevent.py
7793new file mode 100644
7794index 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])
7857diff --git a/tests/event-manager/testprogressbar.py b/tests/event-manager/testprogressbar.py
7858new file mode 100644
7859index 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)
7957diff --git a/uzbl/__init__.py b/uzbl/__init__.py
7958new file mode 100644
7959index 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+'''
7969diff --git a/uzbl/arguments.py b/uzbl/arguments.py
7970new file mode 100644
7971index 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 + '"')
8078diff --git a/uzbl/core.py b/uzbl/core.py
8079new file mode 100644
8080index 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+
8237diff --git a/uzbl/event_manager.py b/uzbl/event_manager.py
8238new file mode 100755
8239index 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:
8783diff --git a/uzbl/ext.py b/uzbl/ext.py
8784new file mode 100644
8785index 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
8881diff --git a/uzbl/net.py b/uzbl/net.py
8882new file mode 100644
8883index 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
8996diff --git a/uzbl/plugins/__init__.py b/uzbl/plugins/__init__.py
8997new file mode 100644
8998index 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+
9016diff --git a/uzbl/plugins/bind.py b/uzbl/plugins/bind.py
9017new file mode 100644
9018index 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:
9485diff --git a/uzbl/plugins/cmd_expand.py b/uzbl/plugins/cmd_expand.py
9486new file mode 100644
9487index 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
9527diff --git a/uzbl/plugins/completion.py b/uzbl/plugins/completion.py
9528new file mode 100644
9529index 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)
9719diff --git a/uzbl/plugins/config.py b/uzbl/plugins/config.py
9720new file mode 100644
9721index 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+
9834diff --git a/uzbl/plugins/cookies.py b/uzbl/plugins/cookies.py
9835new file mode 100644
9836index 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:
10066diff --git a/uzbl/plugins/downloads.py b/uzbl/plugins/downloads.py
10067new file mode 100644
10068index 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+
10155diff --git a/uzbl/plugins/history.py b/uzbl/plugins/history.py
10156new file mode 100644
10157index 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:
10309diff --git a/uzbl/plugins/keycmd.py b/uzbl/plugins/keycmd.py
10310new file mode 100644
10311index 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+
10818diff --git a/uzbl/plugins/mode.py b/uzbl/plugins/mode.py
10819new file mode 100644
10820index 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)
10893diff --git a/uzbl/plugins/on_event.py b/uzbl/plugins/on_event.py
10894new file mode 100644
10895index 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:
11005diff --git a/uzbl/plugins/on_set.py b/uzbl/plugins/on_set.py
11006new file mode 100644
11007index 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+
11096diff --git a/uzbl/plugins/progress_bar.py b/uzbl/plugins/progress_bar.py
11097new file mode 100644
11098index 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.572967 seconds and 4 git commands to generate.