1 diff --git a/.gitignore b/.gitignore
2 index 8c08dc0..092909f 100644
11 @@ -6,3 +7,4 @@ uzbl-core
16 diff --git a/AUTHORS b/AUTHORS
17 index b3ce2e2..22d3c9b 100644
20 @@ -46,6 +46,7 @@ In alphabetical order:
21 Gregor Uhlenheuer (kongo2002) <kongo2002@googlemail.com> - uzbl vim syntax & related files
22 Helmut Grohne (helmut) - move void **ptr to union, various fixes
23 Henri Kemppainen (DuClare) <email is akarinotengoku AT THE DOMAIN OF gmail.com> - many contributions, mostly old handler code
24 + HÃ¥kan Jerning - uzbl-tabbed: autosave_session patch
25 Igor Bogomazov - mouse ptr events
26 Jake Probst <jake.probst@gmail.com> - uzbl_tabbed: multiline tablist, new window opening patches
27 James Campos (aeosynth) <james.r.campos@gmail.com> - Re-orderable gtk notebook tabs in uzbl-tabbed
28 diff --git a/Makefile b/Makefile
29 index a11fc8d..ba74e57 100644
33 +# Create a local.mk file to store default local settings to override the
35 +include $(wildcard local.mk)
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
41 -INSTALLDIR?=$(DESTDIR)$(PREFIX)
42 -DOCDIR?=$(INSTALLDIR)/share/uzbl/docs
43 -RUN_PREFIX?=$(PREFIX)
45 +INSTALLDIR ?= $(DESTDIR)$(PREFIX)
46 +DOCDIR ?= $(INSTALLDIR)/share/uzbl/docs
47 +RUN_PREFIX ?= $(PREFIX)
53 +PYTHONV=$(shell $(PYTHON) --version | sed -n /[0-9].[0-9]/p)
54 +COVERAGE=$(shell which coverage)
56 +# --- configuration ends here ---
58 +ifeq ($(ENABLE_WEBKIT2),auto)
59 +ENABLE_WEBKIT2 := $(shell pkg-config --exists webkit2gtk-3.0 && echo yes)
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)
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
77 +ifeq ($(ENABLE_GTK3),yes)
78 +REQ_PKGS += 'webkitgtk-3.0 >= 1.2.4' javascriptcoregtk-3.0
80 - REQ_PKGS += gtk+-2.0 webkit-1.0 javascriptcoregtk-1.0
82 +REQ_PKGS += 'webkit-1.0 >= 1.2.4' javascriptcoregtk-1.0
86 -# --- configuration ends here ---
87 +ifeq ($(ENABLE_GTK3),yes)
89 +CPPFLAGS += -DG_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
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
97 ARCH:=$(shell uname -m)
99 @@ -33,10 +61,11 @@ LDLIBS:=$(shell pkg-config --libs $(REQ_PKGS) x11)
101 CFLAGS += -std=c99 $(PKG_CFLAGS) -ggdb -W -Wall -Wextra -pedantic -pthread
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)
112 @@ -46,7 +75,13 @@ ${OBJ}: ${HEAD}
116 -uzbl-browser: uzbl-core
117 +uzbl-browser: uzbl-core uzbl-event-manager
120 + $(PYTHON) setup.py build
122 +.PHONY: uzbl-event-manager
123 +uzbl-event-manager: build
125 # the 'tests' target can never be up to date
127 @@ -61,38 +96,43 @@ tests: ${LOBJ} force
128 $(CC) -shared -Wl ${LOBJ} -o ./tests/libuzbl-core.so
131 +test-event-manager: force
132 + ${PYTHON} -m unittest discover tests/event-manager -v
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
141 test-uzbl-core: uzbl-core
142 ./uzbl-core --uri http://www.uzbl.org --verbose
144 test-uzbl-browser: uzbl-browser
145 ./bin/uzbl-browser --uri http://www.uzbl.org --verbose
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
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
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
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
188 @@ -102,18 +142,44 @@ clean:
189 find ./examples/ -name "*.pyc" -delete
190 cd ./tests/; $(MAKE) clean
192 + $(PYTHON) setup.py clean
195 @echo Stripping binary
201 + RUN_PREFIX=`pwd`/sandbox/usr/local\
202 + PYINSTALL_EXTRA='--prefix=./sandbox/usr/local --install-scripts=./sandbox/usr/local/bin'
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
209 +sandbox-install-uzbl-browser:
210 + make ${SANDBOXOPTS} install-uzbl-browser
212 +sandbox-install-uzbl-tabbed:
213 + make ${SANDBOXOPTS} install-uzbl-tabbed
215 +sandbox-install-uzbl-core:
216 + make ${SANDBOXOPTS} install-uzbl-core
218 +sandbox-install-event-manager:
219 + make ${SANDBOXOPTS} install-event-manager
221 +sandbox-install-example-data:
222 + make ${SANDBOXOPTS} install-example-data
224 install: install-uzbl-core install-uzbl-browser install-uzbl-tabbed
227 [ -d "$(INSTALLDIR)/bin" ] || install -d -m755 $(INSTALLDIR)/bin
229 -install-uzbl-core: all install-dirs
230 +install-uzbl-core: uzbl-core install-dirs
231 install -d $(INSTALLDIR)/share/uzbl/
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
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)
242 install-uzbl-browser: install-dirs install-uzbl-core install-event-manager
243 sed 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' < bin/uzbl-browser > $(INSTALLDIR)/bin/uzbl-browser
244 diff --git a/README b/README
245 index b124fb4..5241aba 100644
248 @@ -252,18 +252,42 @@ The following commands are recognized:
249 - Open the print dialog.
251 - Read contents of `<file>` and interpret as a set of `uzbl` commands.
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 >=
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.
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.
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.
285 + - Removes all of the web databases from the current database directory path.
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.
292 ### VARIABLES AND CONSTANTS
294 @@ -283,8 +307,10 @@ file).
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
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
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
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
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
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
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
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.
444 #### Constants (not dumpable or writeable)
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 >=
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.
458 ### VARIABLE EXPANSION AND COMMAND / JAVASCRIPT SUBSTITUTION
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.
466 -Handler scripts (`download_handler`, `cookie_handler`, `scheme_handler`,
467 -`request_handler`, and `authentication_handler`) are called with special
469 +Handler scripts (`download_handler`, `scheme_handler`, and `request_handler`)
470 +are called with special arguments:
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.
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.
490 - `$1 URI` of the page to be navigated to
491 @@ -550,13 +661,6 @@ arguments:
493 - `$1 URI` of the resource which is being requested
495 -* authentication handler:
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
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
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.
512 -You can use the authentication_handler variable to denote how http
513 -authentication should be handled.
514 -If this variable is:
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.
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
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.
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
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:
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"`
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)
562 -[ "$4" == "TRUE ] && exit
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"
571 ### WINDOW MANAGER INTEGRATION
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`.
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
582 Events have this format:
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
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
598 * `EVENT [uzbl_instance_name] REQUEST_STARTING uri`: http resource gets
600 +* `EVENT [uzbl_instance_name] REQUEST_FINISHED uri`: http resource has finished
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
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`
612 Events/requests which the EM and its plugins listens for
614 diff --git a/bin/uzbl-browser b/bin/uzbl-browser
615 index fb9a368..4381050 100755
616 --- a/bin/uzbl-browser
617 +++ b/bin/uzbl-browser
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 ]
624 +if [ ! -f "$DAEMON_SOCKET".pid ]
626 ${UZBL_EVENT_MANAGER:-uzbl-event-manager -va start}
630 exec uzbl-core "$@" ${config_file:+--config "$config_file"} --connect-socket $DAEMON_SOCKET
631 diff --git a/bin/uzbl-event-manager b/bin/uzbl-event-manager
632 index 56253ef..221fa73 100755
633 --- a/bin/uzbl-event-manager
634 +++ b/bin/uzbl-event-manager
636 -#!/usr/bin/env python2
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>
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.
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.
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/>.
657 -E V E N T _ M A N A G E R . P Y
658 -===============================
660 -Event manager for uzbl written in python.
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
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.'''
688 - xdgkey = "XDG_%s_HOME" % key
689 - if xdgkey in os.environ.keys() and os.environ[xdgkey]:
690 - return os.environ[xdgkey]
692 - return os.path.join(os.environ['HOME'], default)
694 -# `make install` will put the correct value here for your system
695 -PREFIX = '/usr/local/'
698 -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
699 -CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
701 -# Define some globals.
702 -SCRIPTNAME = os.path.basename(sys.argv[0])
704 -logger = logging.getLogger(SCRIPTNAME)
708 - '''Format `format_exc` for logging.'''
709 - return "\n%s" % format_exc().rstrip()
712 -def expandpath(path):
713 - '''Expand and realpath paths.'''
714 - return os.path.realpath(os.path.expandvars(path))
718 - '''Convert unicode strings into ascii for transmission over
719 - ascii-only streams/sockets/devices.'''
720 - return u.encode('utf-8')
724 - '''Daemonize the process using the Stevens' double-fork magic.'''
726 - logger.info('entering daemon mode')
733 - logger.critical('failed to daemonize', exc_info=True)
745 - logger.critical('failed to daemonize', exc_info=True)
748 - if sys.stdout.isatty():
752 - devnull = '/dev/null'
753 - stdin = file(devnull, 'r')
754 - stdout = file(devnull, 'a+')
755 - stderr = file(devnull, 'a+', 0)
757 - os.dup2(stdin.fileno(), sys.stdin.fileno())
758 - os.dup2(stdout.fileno(), sys.stdout.fileno())
759 - os.dup2(stderr.fileno(), sys.stderr.fileno())
761 - logger.info('entered daemon mode')
764 -def make_dirs(path):
765 - '''Make all basedirs recursively as required.'''
768 - dirname = os.path.dirname(path)
769 - if not os.path.isdir(dirname):
770 - logger.debug('creating directories %r', dirname)
771 - os.makedirs(dirname)
774 - logger.error('failed to create directories', exc_info=True)
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.'''
781 - nextid = count().next
783 - def __init__(self, plugin, event, callback, args, kwargs):
784 - self.id = self.nextid()
785 - self.plugin = plugin
787 - self.callback = callback
789 - self.kwargs = kwargs
791 - def __repr__(self):
792 - elems = ['id=%d' % self.id, 'event=%s' % self.event,
793 - 'callback=%r' % self.callback]
796 - elems.append('args=%s' % repr(self.args))
799 - elems.append('kwargs=%s' % repr(self.kwargs))
801 - elems.append('plugin=%s' % self.plugin.name)
802 - return u'<handler(%s)>' % ', '.join(elems)
804 - def call(self, uzbl, *args, **kwargs):
805 - '''Execute the handler function and merge argument lists.'''
807 - args = args + self.args
808 - kwargs = dict(self.kwargs.items() + kwargs.items())
809 - self.callback(uzbl, *args, **kwargs)
812 -class Plugin(object):
813 - '''Plugin module wrapper object.'''
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']
820 - def __init__(self, parent, name, path, plugin):
821 - self.parent = parent
824 - self.plugin = plugin
825 - self.logger = logging.getLogger('plugin.%s' % name)
827 - # Weakrefs to all handlers created by this plugin
828 - self.handlers = set([])
830 - # Plugins init hook
831 - init = getattr(plugin, 'init', None)
832 - self.init = init if callable(init) else None
834 - # Plugins optional after hook
835 - after = getattr(plugin, 'after', None)
836 - self.after = after if callable(after) else None
838 - # Plugins optional cleanup hook
839 - cleanup = getattr(plugin, 'cleanup', None)
840 - self.cleanup = cleanup if callable(cleanup) else None
842 - assert init or after or cleanup, "missing hooks in plugin"
844 - # Export plugin's instance methods to plugin namespace
845 - for attr in self.special_functions:
846 - plugin.__dict__[attr] = getattr(self, attr)
848 - def __repr__(self):
849 - return u'<plugin(%r)>' % self.plugin
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
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.
861 - assert attr not in uzbl.exports, "attr %r already exported by %r" %\
862 - (attr, uzbl.exports[attr][0])
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)
870 - def export_dict(self, uzbl, exports):
871 - for (attr, object) in exports.items():
872 - self.export(uzbl, attr, object)
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.'''
879 - self.handlers -= set(filter(lambda ref: not ref(), self.handlers))
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:
887 - def connect(self, uzbl, event, callback, *args, **kwargs):
888 - '''Create an event handler object which handles `event` events.
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.
894 - All handler functions must behave like a `uzbl` instance-method (that
895 - means `uzbl` is prepended to the callback call arguments).'''
897 - # Sanitise and check event name
898 - event = event.upper().strip()
899 - assert event and ' ' not in event
901 - assert callable(callback), 'callback must be callable'
903 - # Check if an identical handler already exists
904 - handler = self.find_handler(event, callback, args, kwargs)
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)
911 - uzbl.handlers[event].append(handler)
912 - uzbl.logger.info('connected %r', handler)
915 - def connect_dict(self, uzbl, connects):
916 - for (event, callback) in connects.items():
917 - self.connect(uzbl, event, callback)
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.'''
923 - assert plugin in self.parent.plugins, self.logger.critical(
924 - 'plugin %r required by plugin %r', plugin, self.name)
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 ['"', "'"]:
932 - return s.encode('utf-8').decode('string_escape').decode('utf-8')
934 - _splitquoted = re.compile("( |\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')")
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()]
944 - def __init__(self, parent, child_socket):
946 - self.parent = parent
947 - self.child_socket = child_socket
948 - self.child_buffer = []
949 - self.time = time.time()
953 - # Flag if the instance has raised the INSTANCE_START event.
954 - self.instance_start = False
956 - # Use name "unknown" until name is discovered.
957 - self.logger = logging.getLogger('uzbl-instance[]')
959 - # Track plugin event handlers and exported functions.
961 - self.handlers = defaultdict(list)
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()])])
975 - def init_plugins(self):
976 - '''Call the init and after hooks in all loaded plugins for this
979 - # Initialise each plugin with the current uzbl instance.
980 - for plugin in self.parent.plugins.values():
982 - self.logger.debug('calling %r plugin init hook', plugin.name)
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():
989 - self.logger.debug('calling %r plugin after hook', plugin.name)
992 - def send(self, msg):
993 - '''Send a command to the uzbl instance via the child socket
997 - assert self.child_socket, "socket inactive"
999 - if opts.print_events:
1000 - print ascii(u'%s<-- %s' % (' ' * self._depth, msg))
1002 - self.child_buffer.append(ascii("%s\n" % msg))
1004 - def do_send(self):
1005 - data = ''.join(self.child_buffer)
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]
1013 - self.logger.error('failed to send', exc_info=True)
1014 - return self.close()
1017 - self.logger.debug('write end of connection closed')
1019 - elif bsent < len(data):
1020 - self.child_buffer = [data[bsent:]]
1022 - del self.child_buffer[:]
1025 - '''Read data from the child socket and pass lines to the parse_msg
1029 - raw = unicode(self.child_socket.recv(8192), 'utf-8', 'ignore')
1031 - self.logger.debug('read null byte')
1032 - return self.close()
1035 - self.logger.error('failed to read', exc_info=True)
1036 - return self.close()
1038 - lines = (self._buffer + raw).split('\n')
1039 - self._buffer = lines.pop()
1041 - for line in filter(None, map(unicode.strip, lines)):
1043 - self.parse_msg(line.strip())
1046 - self.logger.error(get_exc())
1047 - self.logger.error('erroneous event: %r' % line)
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)`.'''
1053 - # Split by spaces (and fill missing with nulls)
1054 - elems = (line.split(' ', 3) + [''] * 3)[:4]
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)
1063 - # Check event string elements
1064 - (name, event, args) = elems[1:]
1065 - assert name and event, 'event string missing elements'
1068 - self.logger = logging.getLogger('uzbl-instance%s' % name)
1069 - self.logger.info('found instance name %r', name)
1071 - assert self.name == name, 'instance name mismatch'
1073 - # Handle the event with the event handlers through the event method
1074 - self.event(event, args)
1076 - def event(self, event, *args, **kargs):
1077 - '''Raise an event.'''
1079 - event = event.upper()
1081 - if not opts.daemon_mode and opts.print_events:
1084 - elems.append(unicode(args))
1086 - elems.append(unicode(kargs))
1087 - print ascii(u'%s--> %s' % (' ' * self._depth, ' '.join(elems)))
1089 - if event == "INSTANCE_START" and args:
1090 - assert not self.instance_start, 'instance already started'
1092 - self.pid = int(args[0])
1093 - self.logger.info('found instance pid %r', self.pid)
1095 - self.init_plugins()
1097 - elif event == "INSTANCE_EXIT":
1098 - self.logger.info('uzbl instance exit')
1101 - if event not in self.handlers:
1104 - for handler in self.handlers[event]:
1107 - handler.call(self, *args, **kargs)
1110 - self.logger.error('error in handler', exc_info=True)
1114 - def close_connection(self, child_socket):
1115 - '''Close child socket and delete the uzbl instance created for that
1116 - child socket connection.'''
1119 - '''Close the client socket and call the plugin cleanup hooks.'''
1121 - self.logger.debug('called close method')
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]
1129 - if self.child_socket:
1130 - self.logger.debug('closing child socket')
1131 - self.child_socket.close()
1134 - self.logger.error('failed to close socket', exc_info=True)
1137 - self.child_socket = None
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',
1144 - plugin.cleanup(self)
1146 - logger.info('removed %r', self)
1149 -class UzblEventDaemon(object):
1150 - def __init__(self):
1152 - self.server_socket = None
1153 - self._quit = False
1155 - # Hold uzbl instances
1156 - # {child socket: Uzbl instance, ..}
1160 - # {plugin name: Plugin instance, ..}
1163 - # Register that the event daemon server has started by creating the
1165 - make_pid_file(opts.pid_file)
1167 - # Register a function to clean up the socket and pid file on exit.
1168 - atexit.register(self.quit)
1170 - # Add signal handlers.
1171 - for sigint in [SIGTERM, SIGINT]:
1172 - signal(sigint, self.quit)
1174 - # Load plugins into self.plugins
1175 - self.load_plugins(opts.plugins)
1177 - def load_plugins(self, plugins):
1178 - '''Load event manager plugins.'''
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
1185 - info = imp.find_module(name, [dir])
1186 - module = imp.load_module(name, *info)
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
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)
1198 - def create_server_socket(self):
1199 - '''Create the event manager daemon socket for uzbl instance duplex
1202 - # Close old socket.
1203 - self.close_server_socket()
1205 - sock = socket(AF_UNIX, SOCK_STREAM)
1206 - sock.bind(opts.server_socket)
1209 - self.server_socket = sock
1210 - logger.debug('bound server socket to %r', opts.server_socket)
1213 - '''Main event daemon loop.'''
1215 - logger.debug('entering main loop')
1217 - # Create and listen on the server socket
1218 - self.create_server_socket()
1220 - if opts.daemon_mode:
1221 - # Daemonize the process
1224 - # Update the pid file
1225 - make_pid_file(opts.pid_file)
1228 - # Accept incoming connections and listen for incoming data
1232 - if not self._quit:
1233 - logger.critical('failed to listen', exc_info=True)
1235 - # Clean up and exit
1238 - logger.debug('exiting main loop')
1241 - '''Accept incoming connections and constantly poll instance sockets
1242 - for incoming data.'''
1244 - logger.info('listening on %r', opts.server_socket)
1246 - # Count accepted connections
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)
1254 - if self.server_socket in reads:
1255 - reads.remove(self.server_socket)
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)
1263 - for uzbl in [self.uzbls[s] for s in writes if s in self.uzbls ]:
1266 - for uzbl in [self.uzbls[s] for s in reads if s in self.uzbls]:
1269 - for uzbl in [self.uzbls[s] for s in errors if s in self.uzbls]:
1270 - uzbl.logger.error('socket read error')
1273 - logger.info('auto closing')
1275 - def close_server_socket(self):
1276 - '''Close and delete the server socket.'''
1279 - if self.server_socket:
1280 - logger.debug('closing server socket')
1281 - self.server_socket.close()
1282 - self.server_socket = None
1284 - if os.path.exists(opts.server_socket):
1285 - logger.info('unlinking %r', opts.server_socket)
1286 - os.unlink(opts.server_socket)
1289 - logger.error('failed to close server socket', exc_info=True)
1291 - def quit(self, sigint=None, *args):
1292 - '''Close all instance socket objects, server socket and delete the
1295 - if sigint == SIGTERM:
1296 - logger.critical('caught SIGTERM, exiting')
1298 - elif sigint == SIGINT:
1299 - logger.critical('caught SIGINT, exiting')
1301 - elif not self._quit:
1302 - logger.debug('shutting down event manager')
1304 - self.close_server_socket()
1306 - for uzbl in self.uzbls.values():
1309 - del_pid_file(opts.pid_file)
1311 - if not self._quit:
1312 - logger.info('event manager shut down')
1316 -def make_pid_file(pid_file):
1317 - '''Creates a pid file at `pid_file`, fails silently.'''
1320 - logger.debug('creating pid file %r', pid_file)
1321 - make_dirs(pid_file)
1323 - fileobj = open(pid_file, 'w')
1324 - fileobj.write('%d' % pid)
1326 - logger.info('created pid file %r with pid %d', pid_file, pid)
1329 - logger.error('failed to create pid file', exc_info=True)
1332 -def del_pid_file(pid_file):
1333 - '''Deletes a pid file at `pid_file`, fails silently.'''
1335 - if os.path.isfile(pid_file):
1337 - logger.debug('deleting pid file %r', pid_file)
1338 - os.remove(pid_file)
1339 - logger.info('deleted pid file %r', pid_file)
1342 - logger.error('failed to delete pid file', exc_info=True)
1345 -def get_pid(pid_file):
1346 - '''Reads a pid from pid file `pid_file`, fails None.'''
1349 - logger.debug('reading pid file %r', pid_file)
1350 - fileobj = open(pid_file, 'r')
1351 - pid = int(fileobj.read())
1353 - logger.info('read pid %d from pid file %r', pid, pid_file)
1356 - except (IOError, ValueError):
1357 - logger.error('failed to read pid', exc_info=True)
1361 -def pid_running(pid):
1362 - '''Checks if a process with a pid `pid` is running.'''
1372 -def term_process(pid):
1373 - '''Asks nicely then forces process with pid `pid` to exit.'''
1376 - logger.info('sending SIGTERM to process with pid %r', pid)
1377 - os.kill(pid, SIGTERM)
1380 - logger.error(get_exc())
1382 - logger.debug('waiting for process with pid %r to exit', pid)
1383 - start = time.time()
1385 - if not pid_running(pid):
1386 - logger.debug('process with pid %d exit', pid)
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)
1393 - os.kill(pid, SIGKILL)
1395 - logger.critical('failed to kill %d', pid, exc_info=True)
1398 - if (time.time() - start) > 10:
1399 - logger.critical('unable to kill process with pid %d', pid)
1406 - '''Stop the event manager daemon.'''
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',
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)
1420 - logger.debug('terminating process with pid %r', pid)
1422 - del_pid_file(pid_file)
1423 - logger.info('stopped event manager process with pid %d', pid)
1426 -def start_action():
1427 - '''Start the event manager daemon.'''
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)
1436 - logger.info('no process with pid %d', pid)
1437 - del_pid_file(pid_file)
1439 - UzblEventDaemon().run()
1442 -def restart_action():
1443 - '''Restart the event manager daemon.'''
1450 - '''List all the plugins that would be loaded in the current search
1454 - for plugin in opts.plugins:
1455 - (head, tail) = os.path.split(plugin)
1456 - if tail not in names:
1457 - names[tail] = plugin
1459 - for plugin in sorted(names.values()):
1464 - parser = OptionParser('usage: %prog [options] {start|stop|restart|list}')
1465 - add = parser.add_option
1467 - add('-v', '--verbose',
1468 - dest='verbose', default=2, action='count',
1469 - help='increase verbosity')
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"`')
1475 - add('-l', '--load-plugin',
1476 - dest='load_plugins', action='append', metavar="PLUGIN", default=[],
1477 - help='load plugin, loads before plugins in search dirs')
1479 - socket_location = os.path.join(CACHE_DIR, 'event_daemon')
1481 - add('-s', '--server-socket',
1482 - dest='server_socket', metavar="SOCKET", default=socket_location,
1483 - help='server AF_UNIX socket location')
1485 - add('-p', '--pid-file',
1486 - metavar="FILE", dest='pid_file',
1487 - help='pid file location, defaults to server socket + .pid')
1489 - add('-n', '--no-daemon',
1490 - dest='daemon_mode', action='store_false', default=True,
1491 - help='do not daemonize the process')
1493 - add('-a', '--auto-close',
1494 - dest='auto_close', action='store_true', default=False,
1495 - help='auto close after all instances disconnect')
1497 - add('-i', '--no-default-dirs',
1498 - dest='default_dirs', action='store_false', default=True,
1499 - help='ignore the default plugin search dirs')
1501 - add('-o', '--log-file',
1502 - dest='log_file', metavar='FILE',
1503 - help='write logging output to a file, defaults to server socket +'
1506 - add('-q', '--quiet-events',
1507 - dest='print_events', action="store_false", default=True,
1508 - help="silence the printing of events to stdout")
1514 - log_level = logging.CRITICAL - opts.verbose * 10
1515 - logger = logging.getLogger()
1516 - logger.setLevel(max(log_level, 10))
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)
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)
1536 - parser = make_parser()
1538 - (opts, args) = parser.parse_args()
1540 - opts.server_socket = expandpath(opts.server_socket)
1542 - # Set default pid file location
1543 - if not opts.pid_file:
1544 - opts.pid_file = "%s.pid" % opts.server_socket
1547 - opts.pid_file = expandpath(opts.pid_file)
1549 - # Set default log file location
1550 - if not opts.log_file:
1551 - opts.log_file = "%s.log" % opts.server_socket
1554 - opts.log_file = expandpath(opts.log_file)
1558 - logger.info('logging to %r', opts.log_file)
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)
1567 - parser.error('cannot find plugin(s): %r' % path)
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
1576 - logger.debug('ignoring plugin: %r', plugin)
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/')]
1585 - logger.debug('ignoring default plugin dirs')
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
1598 - logger.debug('ignoring plugin: %r', plugin)
1600 - plugins = plugins.values()
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)
1607 - if opts.auto_close:
1608 - logger.debug('will auto close')
1610 - logger.debug('will not auto close')
1612 - if opts.daemon_mode:
1613 - logger.debug('will daemonize')
1615 - logger.debug('will not daemonize')
1617 - opts.plugins = plugins
1619 - # init like {start|stop|..} daemon actions
1620 - daemon_actions = {'start': start_action, 'stop': stop_action,
1621 - 'restart': restart_action, 'list': list_action}
1623 - if len(args) == 1:
1625 - if action not in daemon_actions:
1626 - parser.error('invalid action: %r' % action)
1630 - logger.warning('no daemon action given, assuming %r', action)
1633 - parser.error('invalid action argument: %r' % args)
1635 - logger.info('daemon action %r', action)
1637 - daemon_actions[action]()
1639 - logger.debug('process CPU time: %f', time.clock())
1642 -if __name__ == "__main__":
1648 +from uzbl import event_manager
1649 +event_manager.main()
1650 diff --git a/bin/uzbl-tabbed b/bin/uzbl-tabbed
1651 index b78a54a..12fa249 100755
1652 --- a/bin/uzbl-tabbed
1653 +++ b/bin/uzbl-tabbed
1656 # Simon Lipp (sloonz)
1660 +# Wrote autosave_session patch to have a saved session even if
1661 +# uzbl-tabbed is closed unexpectedly.
1668 # session_file = $HOME/.local/share/uzbl/session
1669 +# autosave_session = 0
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
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"',
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,
1690 } # End of config dict.
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)
1697 - def new_tab_bg(self, uri = ''):
1698 + def new_bg_tab(self, uri = ''):
1699 self.uzbl_tabbed.new_tab(uri, switch = False)
1701 def new_tab_next(self, uri = ''):
1702 @@ -434,6 +445,15 @@ class GlobalEventDispatcher(EventDispatcher):
1704 self.uzbl_tabbed.goto_tab(-1)
1706 + def move_current_tab(self, index=0):
1707 + self.uzbl_tabbed.move_current_tab(absolute=int(index))
1709 + def move_current_tab_left(self):
1710 + self.uzbl_tabbed.move_current_tab(relative=-1)
1712 + def move_current_tab_right(self):
1713 + self.uzbl_tabbed.move_current_tab(relative=1)
1715 def preset_tabs(self, *args):
1716 self.uzbl_tabbed.run_preset_command(*args)
1718 @@ -889,6 +909,9 @@ class UzblTabbed:
1720 cmd = cmd + ['--uri', str(uri)]
1722 + if config['explicit_config_file'] is not None:
1723 + cmd = cmd + ['-c', config['explicit_config_file']]
1725 gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
1727 uzbl = UzblInstance(self, name, uri, title, switch)
1728 @@ -968,6 +991,18 @@ class UzblTabbed:
1729 while tabn < 0: tabn += ntabs
1732 + def move_tab(self, tab, index):
1733 + '''Move tab to position.'''
1734 + self.notebook.reorder_child(tab, index)
1735 + self.update_tablist()
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)
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)
1752 + if config['save_session'] and config['autosave_session']:
1753 + if len(list(self.notebook)) > 1:
1754 + self.save_session()
1759 @@ -1038,6 +1078,7 @@ class UzblTabbed:
1761 for tab in self.notebook:
1762 self.tabs[tab].title_changed(True)
1763 + self.update_tablist()
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")
1774 # Parse command line options
1775 (options, uris) = parser.parse_args()
1776 @@ -1275,6 +1318,15 @@ if __name__ == "__main__":
1778 sys.stderr.write("%s\n" % pprint.pformat(config))
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)
1787 + config['explicit_config_file'] = options.config_file
1791 if options.socketdir:
1792 diff --git a/examples/config/config b/examples/config/config
1793 index 11f1d82..d607cb4 100644
1794 --- a/examples/config/config
1795 +++ b/examples/config/config
1796 @@ -8,6 +8,7 @@ set prefix = @(echo $PREFIX)@
1797 set data_home = @(echo $XDG_DATA_HOME)@
1798 set cache_home = @(echo $XDG_CACHE_HOME)@
1799 set config_home = @(echo $XDG_CONFIG_HOME)@
1800 +set local_storage_path = @data_home/uzbl/databases/
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>
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
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
1816 +@on_event AUTHENTICATE spawn @scripts_dir/auth.py "%1" "%2" "%3"
1818 # Example CONFIG_CHANGED event handler
1819 #@on_event CONFIG_CHANGED print Config changed: %1 = %2
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
1825 +# If WebKits builtin authentication dialog should be used, if enabling remember
1826 +# to disable external authentication handlers
1827 +set enable_builtin_auth = 0
1831 set status_background = #303030
1832 @@ -138,6 +144,8 @@ set useragent = Uzbl (Webkit @{WEBKIT_MAJOR}.@{WEBKIT_MINOR}) (@(+uname
1834 # === Configure cookie blacklist ========================================================
1836 +set cookie_policy = 0
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 '^$'
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
1850 set preset = event PRESET_TABS
1851 diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py
1852 deleted file mode 100644
1853 index fc8b392..0000000
1854 --- a/examples/data/plugins/bind.py
1857 -'''Plugin provides support for binds in uzbl.
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'")
1864 -And it is also possible to execute a function on activation:
1865 - bind('DD', myhandler)
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
1878 -# For accessing a bind glob stack.
1879 -ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5)
1883 -class ArgumentError(Exception): pass
1886 -class Bindlet(object):
1887 - '''Per-instance bind status/state tracker.'''
1889 - def __init__(self, uzbl):
1890 - self.binds = {'global': {}}
1894 - self.last_mode = None
1895 - self.after_cmds = None
1896 - self.stack_binds = []
1898 - # A subset of the global mode binds containing non-stack and modkey
1899 - # activiated binds for use in the stack mode.
1903 - def __getitem__(self, key):
1904 - return self.get_binds(key)
1908 - '''Reset the tracker state and return to last mode.'''
1912 - self.after_cmds = None
1913 - self.stack_binds = []
1915 - if self.last_mode:
1916 - mode, self.last_mode = self.last_mode, None
1917 - self.uzbl.config['mode'] = mode
1919 - del self.uzbl.config['keycmd_prompt']
1922 - def stack(self, bind, args, depth):
1923 - '''Enter or add new bind in the next stack level.'''
1925 - if self.depth != depth:
1926 - if bind not in self.stack_binds:
1927 - self.stack_binds.append(bind)
1931 - mode = self.uzbl.config.get('mode', None)
1932 - if mode != 'stack':
1933 - self.last_mode = mode
1934 - self.uzbl.config['mode'] = 'stack'
1936 - self.stack_binds = [bind,]
1939 - self.after_cmds = bind.prompts[depth]
1943 - '''If a stack was triggered then set the prompt and default value.'''
1945 - if self.after_cmds is None:
1948 - (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None
1950 - self.uzbl.clear_keycmd()
1952 - self.uzbl.config['keycmd_prompt'] = prompt
1954 - if set and is_cmd:
1955 - self.uzbl.send(set)
1957 - elif set and not is_cmd:
1958 - self.uzbl.send('event SET_KEYCMD %s' % set)
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.'''
1966 - mode = self.uzbl.config.get('mode', None)
1972 - return self.stack_binds + self.globals
1974 - globals = self.binds['global']
1975 - if mode not in self.binds or mode == 'global':
1976 - return filter(None, globals.values())
1978 - binds = dict(globals.items() + self.binds[mode].items())
1979 - return filter(None, binds.values())
1982 - def add_bind(self, mode, glob, bind=None):
1983 - '''Insert (or override) a bind into the mode bind dict.'''
1985 - if mode not in self.binds:
1986 - self.binds[mode] = {glob: bind}
1989 - binds = self.binds[mode]
1990 - binds[glob] = bind
1992 - if mode == 'global':
1993 - # Regen the global-globals list.
1995 - for bind in binds.values():
1996 - if bind is not None and bind.is_global:
1997 - self.globals.append(bind)
2000 -def ismodbind(glob):
2001 - '''Return True if the glob specifies a modbind.'''
2003 - return bool(MOD_START(glob))
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.'''
2012 - match = MOD_START(glob)
2016 - end = match.span()[1]
2017 - mods.add(glob[:end])
2020 - return (mods, glob)
2023 -class Bind(object):
2025 - # Class attribute to hold the number of Bind classes created.
2028 - def __init__(self, glob, handler, *args, **kargs):
2029 - self.is_callable = callable(handler)
2030 - self._repr_cache = None
2033 - raise ArgumentError('glob cannot be blank')
2035 - if self.is_callable:
2036 - self.function = handler
2038 - self.kargs = kargs
2041 - raise ArgumentError('cannot supply kargs for uzbl commands')
2043 - elif hasattr(handler, '__iter__'):
2044 - self.commands = handler
2047 - self.commands = [handler,] + list(args)
2051 - # Assign unique id.
2052 - self.counter[0] += 1
2053 - self.bid = self.counter[0]
2055 - self.split = split = FIND_PROMPTS(glob)
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
2063 - self.prompts.append((prompt, cmd, set))
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)
2071 - # Check that there is nothing like: fl<prompt1:><prompt2:>_
2072 - for glob in split[4::4]:
2074 - msg = 'found null segment after first prompt: %r' % split
2075 - raise SyntaxError(msg)
2078 - for (index, glob) in enumerate(reversed(split[::4])):
2079 - # Is the binding a MODCMD or KEYCMD:
2080 - mod_cmd = ismodbind(glob)
2082 - # Do we execute on UPDATES or EXEC events?
2083 - on_exec = True if glob[-1] in ['!', '_'] else False
2085 - # Does the command take arguments?
2086 - has_args = True if glob[-1] in ['*', '_'] else False
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))
2092 - self.stack = list(reversed(stack))
2093 - self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD])
2096 - def __getitem__(self, depth):
2097 - '''Get bind info at a depth.'''
2099 - if self.is_global:
2100 - return self.stack[0]
2102 - return self.stack[depth]
2105 - def __repr__(self):
2106 - if self._repr_cache:
2107 - return self._repr_cache
2109 - args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
2111 - if self.is_callable:
2112 - args.append('function=%r' % self.function)
2114 - args.append('args=%r' % self.args)
2117 - args.append('kargs=%r' % self.kargs)
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))
2124 - self._repr_cache = '<Bind(%s)>' % ', '.join(args)
2125 - return self._repr_cache
2128 -def exec_bind(uzbl, bind, *args, **kargs):
2129 - '''Execute bind objects.'''
2131 - uzbl.event("EXEC_BIND", bind, args, kargs)
2133 - if bind.is_callable:
2135 - kargs = dict(bind.kargs.items()+kargs.items())
2136 - bind.function(uzbl, *args, **kargs)
2140 - raise ArgumentError('cannot supply kargs for uzbl commands')
2143 - cmd_expand = uzbl.cmd_expand
2144 - for cmd in bind.commands:
2145 - cmd = cmd_expand(cmd, args)
2149 -def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs):
2150 - '''Add a mode bind.'''
2152 - bindlet = uzbl.bindlet
2154 - if not hasattr(modes, '__iter__'):
2155 - modes = unicode(modes).split(',')
2157 - # Sort and filter binds.
2158 - modes = filter(None, map(unicode.strip, modes))
2160 - if callable(handler) or (handler is not None and handler.strip()):
2161 - bind = Bind(glob, handler, *args, **kargs)
2166 - for mode in modes:
2167 - if not VALID_MODE(mode):
2168 - raise NameError('invalid mode name: %r' % mode)
2170 - for mode in modes:
2171 - if mode[0] == '-':
2172 - mode, bind = mode[1:], None
2174 - bindlet.add_bind(mode, glob, bind)
2175 - uzbl.event('ADDED_MODE_BIND', mode, glob, bind)
2178 -def bind(uzbl, glob, handler, *args, **kargs):
2179 - '''Legacy bind function.'''
2181 - mode_bind(uzbl, 'global', glob, handler, *args, **kargs)
2184 -def parse_mode_bind(uzbl, args):
2185 - '''Parser for the MODE_BIND event.
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 ... = ...
2196 - raise ArgumentError('missing bind arguments')
2198 - split = map(unicode.strip, args.split(' ', 1))
2199 - if len(split) != 2:
2200 - raise ArgumentError('missing mode or bind section: %r' % args)
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)
2207 - glob, command = split
2208 - mode_bind(uzbl, modes, glob, command)
2211 -def parse_bind(uzbl, args):
2212 - '''Legacy parsing of the BIND event and conversion to the new format.
2215 - request BIND <bind> = <command>
2216 - request BIND o<location:>_ = uri %s
2217 - request BIND <BackSpace> = ...
2218 - request BIND ... = ...
2221 - parse_mode_bind(uzbl, "global %s" % args)
2224 -def mode_changed(uzbl, mode):
2225 - '''Clear the stack on all non-stack mode changes.'''
2227 - if mode != 'stack':
2228 - uzbl.bindlet.reset()
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
2235 - if mod_cmd and modstate != mod_cmd:
2239 - if not cmd.startswith(glob):
2242 - args = [cmd[len(glob):],]
2250 - if bind.is_global or (not more and depth == 0):
2251 - exec_bind(uzbl, bind, *args)
2253 - uzbl.clear_current()
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+['',]))
2265 - args = bindlet.args + args
2266 - exec_bind(uzbl, bind, *args)
2267 - if not has_args or on_exec:
2268 - del uzbl.config['mode']
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():
2279 - if (bool(t[MOD_CMD]) != mod_cmd) or (t[ON_EXEC] != on_exec):
2282 - if match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet):
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']
2295 - '''Export functions and connect handlers to events.'''
2297 - connect_dict(uzbl, {
2298 - 'BIND': parse_bind,
2299 - 'MODE_BIND': parse_mode_bind,
2300 - 'MODE_CHANGED': mode_changed,
2303 - # Connect key related events to the key_event function.
2304 - events = [['KEYCMD_UPDATE', 'KEYCMD_EXEC'],
2305 - ['MODCMD_UPDATE', 'MODCMD_EXEC']]
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))
2312 - export_dict(uzbl, {
2314 - 'mode_bind': mode_bind,
2315 - 'bindlet': Bindlet(uzbl),
2319 diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py
2320 deleted file mode 100644
2321 index b007975..0000000
2322 --- a/examples/data/plugins/cmd_expand.py
2326 - for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]:
2327 - str = str.replace(char, (level * '\\') + char)
2332 -def cmd_expand(uzbl, cmd, args):
2333 - '''Exports a function that provides the following
2334 - expansions in any uzbl command string:
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])
2343 - # Ensure (1) all string representable and (2) correct string encoding.
2344 - args = map(unicode, args)
2346 - # Direct string replace.
2348 - cmd = cmd.replace('%s', ' '.join(args))
2350 - # Escaped and quoted string replace.
2352 - cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args)))
2354 - # Arg index string replace.
2355 - for (index, arg) in enumerate(args):
2357 - if '%%%d' % index in cmd:
2358 - cmd = cmd.replace('%%%d' % index, unicode(arg))
2364 - export(uzbl, 'cmd_expand', cmd_expand)
2365 diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py
2366 deleted file mode 100644
2367 index e8c7f34..0000000
2368 --- a/examples/data/plugins/completion.py
2371 -'''Keycmd completion.'''
2376 -NONE, ONCE, LIST, COMPLETE = range(4)
2378 -# The reverse keyword finding re.
2379 -FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall
2382 -LIST_FORMAT = "<span> %s </span>"
2383 -ITEM_FORMAT = "<span @hint_style>%s</span>%s"
2386 - return str.replace("@", "\@")
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.'''
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)
2401 - return (partial, False)
2404 -def stop_completion(uzbl, *args):
2405 - '''Stop command completion and return the level to NONE.'''
2407 - uzbl.completion.level = NONE
2408 - del uzbl.config['completion_list']
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.'''
2415 - if set_completion:
2416 - remainder = "%s = " % hint[len(partial):]
2419 - remainder = "%s " % hint[len(partial):]
2421 - uzbl.inject_keycmd(remainder)
2422 - stop_completion(uzbl)
2425 -def partial_completion(uzbl, partial, hint):
2426 - '''Inject a common portion of the hints into the keycmd.'''
2428 - remainder = hint[len(partial):]
2429 - uzbl.inject_keycmd(remainder)
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.'''
2436 - partial = get_incomplete_keyword(uzbl)[0]
2438 - return stop_completion(uzbl)
2440 - if uzbl.completion.level < LIST:
2443 - hints = filter(lambda h: h.startswith(partial), uzbl.completion)
2445 - del uzbl.config['completion_list']
2449 - l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)]
2450 - uzbl.config['completion_list'] = LIST_FORMAT % ' '.join(l)
2453 -def start_completion(uzbl, *args):
2455 - comp = uzbl.completion
2459 - (partial, set_completion) = get_incomplete_keyword(uzbl)
2461 - return stop_completion(uzbl)
2463 - if comp.level < COMPLETE:
2466 - hints = filter(lambda h: h.startswith(partial), comp)
2470 - elif len(hints) == 1:
2472 - complete_completion(uzbl, partial, hints[0], set_completion)
2476 - elif partial in hints and comp.level == COMPLETE:
2478 - complete_completion(uzbl, partial, partial, set_completion)
2482 - smalllen, smallest = sorted([(len(h), h) for h in hints])[0]
2484 - for i in range(len(partial), smalllen):
2485 - char, same = smallest[i], True
2486 - for hint in hints:
2487 - if hint[i] != char:
2498 - partial_completion(uzbl, partial, partial+common)
2501 - update_completion_list(uzbl)
2504 -def add_builtins(uzbl, builtins):
2505 - '''Pump the space delimited list of builtin commands into the
2508 - uzbl.completion.update(builtins.split())
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.'''
2515 - uzbl.completion.add("@%s" % key)
2518 -class Completions(set):
2519 - def __init__(self):
2520 - set.__init__(self)
2521 - self.locked = False
2525 - self.locked = True
2528 - self.locked = False
2532 - '''Export functions and connect handlers to events.'''
2534 - export_dict(uzbl, {
2535 - 'completion': Completions(),
2536 - 'start_completion': start_completion,
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,
2549 - uzbl.send('dump_config_as_events')
2550 diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py
2551 deleted file mode 100644
2552 index c9bdf67..0000000
2553 --- a/examples/data/plugins/config.py
2556 -from re import compile
2557 -from types import BooleanType
2558 -from UserDict import DictMixin
2560 -valid_key = compile('^[A-Za-z0-9_\.]+$').match
2562 -class Config(DictMixin):
2563 - def __init__(self, uzbl):
2566 - # Create the base dict and map allowed methods to `self`.
2567 - self.data = data = {}
2569 - methods = ['__contains__', '__getitem__', '__iter__',
2570 - '__len__', 'get', 'has_key', 'items', 'iteritems',
2571 - 'iterkeys', 'itervalues', 'values']
2573 - for method in methods:
2574 - setattr(self, method, getattr(data, method))
2577 - def __setitem__(self, key, value):
2578 - self.set(key, value)
2580 - def __delitem__(self, key):
2583 - def update(self, other=None, **kwargs):
2587 - for (key, value) in dict(other).items() + kwargs.items():
2591 - def set(self, key, value='', force=False):
2592 - '''Generates a `set <key> = <value>` command string to send to the
2593 - current uzbl instance.
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.'''
2599 - assert valid_key(key)
2601 - if type(value) == BooleanType:
2602 - value = int(value)
2605 - value = unicode(value)
2606 - assert '\n' not in value
2608 - if not force and key in self and self[key] == value:
2611 - self.uzbl.send(u'set %s = %s' % (key, value))
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.'''
2618 - (key, type, raw_value) = (args.split(' ', 2) + ['',])[:3]
2620 - assert valid_key(key)
2621 - assert type in types
2623 - new_value = types[type](raw_value)
2624 - old_value = uzbl.config.get(key, None)
2626 - # Update new value.
2627 - uzbl.config.data[key] = new_value
2629 - if old_value != new_value:
2630 - uzbl.event('CONFIG_CHANGED', key, new_value)
2632 - # Cleanup null config values.
2633 - if type == 'str' and not new_value:
2634 - del uzbl.config.data[key]
2640 - types = {'int': int, 'float': float, 'str': unquote}
2641 - export(uzbl, 'config', Config(uzbl))
2642 - connect(uzbl, 'VARIABLE_SET', parse_set_event)
2644 -# plugin cleanup hook
2646 - uzbl.config.data.clear()
2647 diff --git a/examples/data/plugins/cookies.py b/examples/data/plugins/cookies.py
2648 deleted file mode 100644
2649 index bf59e96..0000000
2650 --- a/examples/data/plugins/cookies.py
2653 -""" Basic cookie manager
2654 - forwards cookies to all other instances connected to the event manager"""
2656 -from collections import defaultdict
2657 -import os, re, stat
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}
2662 -# allows for partial cookies
2663 -# ? allow wildcard in key
2664 -def match(key, cookie):
2665 - for k,c in zip(key,cookie):
2670 -class NullStore(object):
2671 - def add_cookie(self, rawcookie, cookie):
2674 - def delete_cookie(self, rkey, key):
2677 -class ListStore(list):
2678 - def add_cookie(self, rawcookie, cookie):
2679 - self.append(rawcookie)
2681 - def delete_cookie(self, rkey, key):
2682 - self[:] = [x for x in self if not match(key, splitquoted(x))]
2684 -class TextStore(object):
2685 - def __init__(self, filename):
2686 - self.filename = filename
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)
2696 - def as_event(self, cookie):
2697 - """Convert cookie.txt row to uzbls cookie event format"""
2703 - if cookie[0].startswith("#HttpOnly_"):
2705 - domain = cookie[0][len("#HttpOnly_"):]
2706 - elif cookie[0].startswith('#'):
2709 - domain = cookie[0]
2715 - scheme[cookie[3]] + extra,
2717 - except (KeyError,IndexError):
2718 - # Let malformed rows pass through like comments
2721 - def as_file(self, cookie):
2722 - """Convert cookie event to cookie.txt row"""
2726 - 'httpsOnly' : 'TRUE',
2727 - 'httpOnly' : 'FALSE'
2732 - 'httpsOnly' : '#HttpOnly_',
2733 - 'httpOnly' : '#HttpOnly_'
2735 - return (http_only[cookie[4]] + cookie[0],
2736 - 'TRUE' if cookie[0].startswith('.') else 'FALSE',
2738 - secure[cookie[4]],
2743 - def add_cookie(self, rawcookie, cookie):
2744 - assert len(cookie) == 6
2746 - # delete equal cookies (ignoring expire time, value and secure flag)
2747 - self.delete_cookie(None, cookie[:-3])
2749 - # restrict umask before creating the cookie jar
2750 - curmask=os.umask(0)
2751 - os.umask(curmask| stat.S_IRWXO | stat.S_IRWXG)
2753 - first = not os.path.exists(self.filename)
2754 - with open(self.filename, 'a') as f:
2756 - print >> f, "# HTTP Cookie File"
2757 - print >> f, '\t'.join(self.as_file(cookie))
2760 - def delete_cookie(self, rkey, key):
2761 - if not os.path.exists(self.filename):
2764 - # restrict umask before creating the cookie jar
2765 - curmask=os.umask(0)
2766 - os.umask(curmask | stat.S_IRWXO | stat.S_IRWXG)
2768 - # read all cookies
2769 - with open(self.filename, 'r') as f:
2770 - cookies = f.readlines()
2772 - # write those that don't match the cookie to delete
2773 - with open(self.filename, 'w') as f:
2775 - c = self.as_event(l.split('\t'))
2776 - if c is None or not match(key, c):
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'))
2784 -def match_list(_list, cookie):
2785 - for matcher in _list:
2786 - for component, match in matcher:
2787 - if match(cookie[component]) is None:
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)
2802 - return not match_list(uzbl.cookie_blacklist, cookie)
2804 -def expires_with_session(uzbl, cookie):
2805 - return cookie[5] == ''
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]
2812 -def get_store(uzbl, session=False):
2814 - return SessionStore
2815 - return DefaultStore
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)
2823 - get_store(uzbl, expires_with_session(uzbl, splitted)).add_cookie(cookie, splitted)
2825 - logger.debug('cookie %r is blacklisted' % splitted)
2826 - uzbl.send('delete_cookie %s' % cookie)
2828 -def delete_cookie(uzbl, cookie):
2829 - for u in get_recipents(uzbl):
2830 - u.send('delete_cookie %s' % cookie)
2832 - splitted = splitquoted(cookie)
2833 - if len(splitted) == 6:
2834 - get_store(uzbl, expires_with_session(uzbl, splitted)).delete_cookie(cookie, splitted)
2836 - for store in set([get_store(uzbl, session) for session in (True, False)]):
2837 - store.delete_cookie(cookie, splitted)
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)
2847 - for (component, regexp) in zip(args[0::2], args[1::2]):
2849 - component = symbolic[component]
2851 - component = int(component)
2852 - assert component <= 5
2853 - mlist.append((component, re.compile(regexp).search))
2854 - _list.append(mlist)
2856 -def blacklist(uzbl, arg):
2857 - add_cookie_matcher(uzbl.cookie_blacklist, arg)
2859 -def whitelist(uzbl, arg):
2860 - add_cookie_matcher(uzbl.cookie_whitelist, arg)
2863 - connect_dict(uzbl, {
2864 - 'ADD_COOKIE': add_cookie,
2865 - 'DELETE_COOKIE': delete_cookie,
2866 - 'BLACKLIST_COOKIE': blacklist,
2867 - 'WHITELIST_COOKIE': whitelist
2869 - export_dict(uzbl, {
2870 - 'cookie_blacklist' : [],
2871 - 'cookie_whitelist' : []
2875 diff --git a/examples/data/plugins/downloads.py b/examples/data/plugins/downloads.py
2876 deleted file mode 100644
2877 index 8d796ce..0000000
2878 --- a/examples/data/plugins/downloads.py
2881 -# this plugin does a very simple display of download progress. to use it, add
2882 -# @downloads to your status_format.
2885 -ACTIVE_DOWNLOADS = {}
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
2891 - if len(ACTIVE_DOWNLOADS):
2892 - # add a newline before we list downloads
2893 - result = ' downloads:'
2894 - for path in ACTIVE_DOWNLOADS:
2895 - # add each download
2896 - fn = os.path.basename(path)
2897 - progress, = ACTIVE_DOWNLOADS[path]
2899 - dl = " %s (%d%%)" % (fn, progress * 100)
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 above to make a new line)
2904 - dl = dl.replace("&", "&").replace("<", "<")
2909 - # and the result gets saved to an uzbl variable that can be used in
2911 - if uzbl.config.get('downloads', '') != result:
2912 - uzbl.config['downloads'] = result
2914 -def download_started(uzbl, args):
2915 - # parse the arguments
2916 - args = splitquoted(args)
2917 - destination_path = args[0]
2919 - # add to the list of active downloads
2920 - global ACTIVE_DOWNLOADS
2921 - ACTIVE_DOWNLOADS[destination_path] = (0.0,)
2923 - # update the progress
2924 - update_download_section(uzbl)
2926 -def download_progress(uzbl, args):
2927 - # parse the arguments
2928 - args = splitquoted(args)
2929 - destination_path = args[0]
2930 - progress = float(args[1])
2932 - # update the progress
2933 - global ACTIVE_DOWNLOADS
2934 - ACTIVE_DOWNLOADS[destination_path] = (progress,)
2936 - # update the status bar variable
2937 - update_download_section(uzbl)
2939 -def download_complete(uzbl, args):
2940 - # parse the arguments
2941 - args = splitquoted(args)
2942 - destination_path = args[0]
2944 - # remove from the list of active downloads
2945 - global ACTIVE_DOWNLOADS
2946 - del ACTIVE_DOWNLOADS[destination_path]
2948 - # update the status bar variable
2949 - update_download_section(uzbl)
2953 - connect_dict(uzbl, {
2954 - 'DOWNLOAD_STARTED': download_started,
2955 - 'DOWNLOAD_PROGRESS': download_progress,
2956 - 'DOWNLOAD_COMPLETE': download_complete,
2958 diff --git a/examples/data/plugins/history.py b/examples/data/plugins/history.py
2959 deleted file mode 100644
2960 index f42f86f..0000000
2961 --- a/examples/data/plugins/history.py
2966 -shared_history = {'':[]}
2968 -class History(object):
2969 - def __init__(self, uzbl):
2971 - self._temporary = []
2973 - self.cursor = None
2974 - self.__temp_tail = False
2975 - self.search_key = None
2978 - if self.cursor is None:
2979 - self.cursor = len(self) - 1
2983 - if self.search_key:
2984 - while self.cursor >= 0 and self.search_key not in self[self.cursor]:
2987 - if self.cursor < 0 or len(self) == 0:
2989 - return random.choice(end_messages)
2991 - return self[self.cursor]
2994 - if self.cursor is None:
2999 - if self.search_key:
3000 - while self.cursor < len(self) and self.search_key not in self[self.cursor]:
3003 - if self.cursor >= len(shared_history[self.prompt]):
3004 - self.cursor = None
3005 - self.search_key = None
3007 - if self._temporary:
3008 - return self._temporary.pop()
3011 - return self[self.cursor]
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] = []
3020 - def search(self, key):
3021 - self.search_key = key
3022 - self.cursor = None
3024 - def add(self, cmd):
3025 - if self._temporary:
3026 - self._temporary.pop()
3028 - shared_history[self.prompt].append(cmd)
3029 - self.cursor = None
3030 - self.search_key = None
3032 - def add_temporary(self, cmd):
3033 - assert not self._temporary
3035 - self._temporary.append(cmd)
3036 - self.cursor = len(self) - 1
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]
3043 - def __len__(self):
3044 - return len(shared_history[self.prompt]) + len(self._temporary)
3046 - def __str__(self):
3047 - return "(History %s, %s)" % (self.cursor, self.prompt)
3049 -def keycmd_exec(uzbl, modstate, keylet):
3050 - cmd = keylet.get_keycmd()
3052 - uzbl.history.add(cmd)
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)
3059 - uzbl.set_keycmd(uzbl.history.prev())
3060 - uzbl.logger.debug('PREV %s' % uzbl.history)
3062 -def history_next(uzbl, _x):
3063 - cmd = uzbl.keylet.get_keycmd()
3065 - uzbl.set_keycmd(uzbl.history.next())
3066 - uzbl.logger.debug('NEXT %s' % uzbl.history)
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))
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.')
3077 - connect_dict(uzbl, {
3078 - 'KEYCMD_EXEC': keycmd_exec,
3079 - 'HISTORY_PREV': history_prev,
3080 - 'HISTORY_NEXT': history_next,
3081 - 'HISTORY_SEARCH': history_search
3084 - export_dict(uzbl, {
3085 - 'history' : History(uzbl)
3088 -# plugin after hook
3090 - uzbl.on_set('keycmd_prompt', lambda uzbl, k, v: uzbl.history.change_prompt(v))
3093 diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py
3094 deleted file mode 100644
3095 index 1bb70e3..0000000
3096 --- a/examples/data/plugins/keycmd.py
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>"
3107 - for char in ['\\', '@']:
3108 - str = str.replace(char, '\\'+char)
3113 -def uzbl_escape(str):
3114 - return "@[%s]@" % escape(str) if str else ''
3117 -class Keylet(object):
3118 - '''Small per-instance object that tracks characters typed.'''
3120 - def __init__(self):
3123 - self.is_modcmd = False
3133 - def get_keycmd(self):
3134 - '''Get the keycmd-part of the keylet.'''
3136 - return self.keycmd
3139 - def get_modcmd(self):
3140 - '''Get the modcmd-part of the keylet.'''
3142 - if not self.is_modcmd:
3145 - return self.modcmd
3148 - def modmap_key(self, key):
3149 - '''Make some obscure names for some keys friendlier.'''
3151 - if key in self.modmaps:
3152 - return self.modmaps[key]
3154 - elif key.endswith('_L') or key.endswith('_R'):
3155 - # Remove left-right discrimination and try again.
3156 - return self.modmap_key(key[:-2])
3162 - def key_ignored(self, key):
3163 - '''Check if the given key is ignored by any ignore rules.'''
3165 - for (glob, match) in self.ignores.items():
3172 - def __repr__(self):
3173 - '''Return a string representation of the keylet.'''
3176 - if self.is_modcmd:
3177 - l.append('modcmd=%r' % self.get_modcmd())
3180 - l.append('keycmd=%r' % self.get_keycmd())
3182 - return '<keylet(%s)>' % ', '.join(l)
3185 -def add_modmap(uzbl, key, map):
3189 - set modmap = request MODMAP
3190 - @modmap <Control> <Ctrl>
3191 - @modmap <ISO_Left_Tab> <Shift-Tab>
3195 - @bind <Shift-Tab> = <command1>
3196 - @bind <Ctrl>x = <command2>
3202 - modmaps = uzbl.keylet.modmaps
3204 - modmaps[key.strip('<>')] = map.strip('<>')
3205 - uzbl.event("NEW_MODMAP", key, map)
3208 -def modmap_parse(uzbl, map):
3209 - '''Parse a modmap definiton.'''
3211 - split = [s.strip() for s in map.split(' ') if s.split()]
3213 - if not split or len(split) > 2:
3214 - raise Exception('Invalid modmap arugments: %r' % map)
3216 - add_modmap(uzbl, *split)
3219 -def add_key_ignore(uzbl, glob):
3220 - '''Add an ignore definition.
3223 - set ignore_key = request IGNORE_KEY
3224 - @ignore_key <Shift>
3225 - @ignore_key <ISO_*>
3229 - assert len(glob) > 1
3230 - ignores = uzbl.keylet.ignores
3232 - glob = "<%s>" % glob.strip("<> ")
3233 - restr = glob.replace('*', '[^\s]*')
3234 - match = re.compile(restr).match
3236 - ignores[glob] = match
3237 - uzbl.event('NEW_KEY_IGNORE', glob)
3240 -def clear_keycmd(uzbl, *args):
3241 - '''Clear the keycmd for this uzbl instance.'''
3246 - del uzbl.config['keycmd']
3247 - uzbl.event('KEYCMD_CLEARED')
3250 -def clear_modcmd(uzbl):
3251 - '''Clear the modcmd for this uzbl instance.'''
3255 - k.is_modcmd = False
3257 - del uzbl.config['modcmd']
3258 - uzbl.event('MODCMD_CLEARED')
3261 -def clear_current(uzbl):
3262 - '''Clear the modcmd if is_modcmd else clear keycmd.'''
3264 - if uzbl.keylet.is_modcmd:
3265 - clear_modcmd(uzbl)
3268 - clear_keycmd(uzbl)
3271 -def update_event(uzbl, modstate, k, execute=True):
3272 - '''Raise keycmd & modcmd update events.'''
3274 - keycmd, modcmd = k.get_keycmd(), ''.join(modstate) + k.get_modcmd()
3277 - logger.debug('modcmd_update, %s' % modcmd)
3278 - uzbl.event('MODCMD_UPDATE', modstate, k)
3281 - logger.debug('keycmd_update, %s' % keycmd)
3282 - uzbl.event('KEYCMD_UPDATE', modstate, k)
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']
3289 - elif new_modcmd == modcmd:
3290 - uzbl.config['modcmd'] = MODCMD_FORMAT % uzbl_escape(modcmd)
3292 - if uzbl.config.get('keycmd_events', '1') != '1':
3295 - new_keycmd = k.get_keycmd()
3296 - if not new_keycmd:
3297 - del uzbl.config['keycmd']
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))
3305 - uzbl.config['keycmd'] = value
3308 -def inject_str(str, index, inj):
3309 - '''Inject a string into string at at given index.'''
3311 - return "%s%s%s" % (str[:index], inj, str[index:])
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
3318 - modstate, key = splitquoted(key)
3319 - modstate = set(['<%s>' % keylet.modmap_key(k) for k in modstate.split('|') if k])
3321 - key = keylet.modmap_key(key)
3322 - return modstate, key
3325 -def key_press(uzbl, key):
3326 - '''Handle KEY_PRESS events. Things done by this function include:
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.'''
3335 - modstate, key = parse_key_event(uzbl, key)
3336 - k.is_modcmd = any(not k.key_ignored(m) for m in modstate)
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, ' ')
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
3348 - del uzbl.config['keycmd']
3351 - k.keycmd = inject_str(k.keycmd, k.cursor, key)
3354 - elif len(key) == 1:
3358 - if not k.key_ignored('<%s>' % key):
3359 - modstate.add('<%s>' % key)
3360 - k.is_modcmd = True
3362 - update_event(uzbl, modstate, k)
3365 -def key_release(uzbl, key):
3366 - '''Respond to KEY_RELEASE event. Things done by this function include:
3368 - 1. If in a mod-command then raise a MODCMD_EXEC.
3369 - 2. Update the keycmd uzbl variable if anything changed.'''
3371 - modstate, key = parse_key_event(uzbl, key)
3375 - uzbl.event('MODCMD_EXEC', modstate, k)
3377 - clear_modcmd(uzbl)
3380 -def set_keycmd(uzbl, keycmd):
3381 - '''Allow setting of the keycmd externally.'''
3385 - k.cursor = len(keycmd)
3386 - update_event(uzbl, set(), k, False)
3389 -def inject_keycmd(uzbl, keycmd):
3390 - '''Allow injecting of a string into the keycmd at the cursor position.'''
3393 - k.keycmd = inject_str(k.keycmd, k.cursor, keycmd)
3394 - k.cursor += len(keycmd)
3395 - update_event(uzbl, set(), k, False)
3398 -def append_keycmd(uzbl, keycmd):
3399 - '''Allow appening of a string to the keycmd.'''
3402 - k.keycmd += keycmd
3403 - k.cursor = len(k.keycmd)
3404 - update_event(uzbl, set(), k, False)
3407 -def keycmd_strip_word(uzbl, seps):
3408 - ''' Removes the last word from the keycmd, similar to readline ^W '''
3410 - seps = seps or ' '
3415 - head, tail = k.keycmd[:k.cursor].rstrip(seps), k.keycmd[k.cursor:]
3418 - p = head.rfind(sep)
3419 - if p >= 0 and rfind < p + 1:
3421 - if rfind == len(head) and head[-1] in seps:
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)
3429 -def keycmd_backspace(uzbl, *args):
3430 - '''Removes the character at the cursor position in the keycmd.'''
3433 - if not k.keycmd or not k.cursor:
3436 - k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:]
3438 - update_event(uzbl, set(), k, False)
3441 -def keycmd_delete(uzbl, *args):
3442 - '''Removes the character after the cursor position in the keycmd.'''
3448 - k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:]
3449 - update_event(uzbl, set(), k, False)
3452 -def keycmd_exec_current(uzbl, *args):
3453 - '''Raise a KEYCMD_EXEC with the current keylet and then clear the
3456 - uzbl.event('KEYCMD_EXEC', set(), uzbl.keylet)
3457 - clear_keycmd(uzbl)
3460 -def set_cursor_pos(uzbl, index):
3461 - '''Allow setting of the cursor position externally. Supports negative
3462 - indexing and relative stepping with '+' and '-'.'''
3466 - cursor = k.cursor - 1
3468 - elif index == '+':
3469 - cursor = k.cursor + 1
3472 - cursor = int(index.strip())
3474 - cursor = len(k.keycmd) + cursor + 1
3479 - if cursor > len(k.keycmd):
3480 - cursor = len(k.keycmd)
3483 - update_event(uzbl, set(), k, False)
3488 - '''Export functions and connect handlers to events.'''
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,
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,
3522 diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py
3523 deleted file mode 100644
3524 index e0de706..0000000
3525 --- a/examples/data/plugins/mode.py
3528 -from collections import defaultdict
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.'''
3534 - ustrip = unicode.strip
3535 - args = unicode(args)
3537 - assert args.strip(), "missing mode config args"
3538 - (mode, args) = map(ustrip, (args.strip().split(' ', 1) + ['',])[:2])
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"
3544 - uzbl.mode_config[mode][key] = value
3545 - if uzbl.config.get('mode', None) == mode:
3546 - uzbl.config[key] = value
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
3555 -def mode_updated(uzbl, var, mode):
3557 - mode = uzbl.config.get('default_mode', 'command')
3558 - logger.debug('setting mode to default %r' % mode)
3559 - uzbl.config['mode'] = mode
3562 - # Load mode config
3563 - mode_config = uzbl.mode_config.get(mode, None)
3565 - uzbl.config.update(mode_config)
3567 - uzbl.send('event MODE_CONFIRM %s' % mode)
3570 -def confirm_change(uzbl, mode):
3571 - if mode and uzbl.config.get('mode', None) == mode:
3572 - uzbl.event('MODE_CHANGED', mode)
3580 - # Usage `uzbl.mode_config[mode][key] = value`
3581 - export(uzbl, 'mode_config', defaultdict(dict))
3583 - connect_dict(uzbl, {
3584 - 'MODE_CONFIG': parse_mode_config,
3585 - 'MODE_CONFIRM': confirm_change,
3588 -# plugin after hook
3590 - uzbl.on_set('mode', mode_updated)
3591 - uzbl.on_set('default_mode', default_mode_updated)
3593 -# plugin cleanup hook
3595 - uzbl.mode_config.clear()
3596 diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py
3597 deleted file mode 100644
3598 index 32f09e2..0000000
3599 --- a/examples/data/plugins/on_event.py
3602 -'''Plugin provides arbitrary binding of uzbl events to uzbl commands.
3604 -Formatting options:
3605 - %s = space separated string of the arguments
3606 - %r = escaped and quoted version of %s
3612 - request ON_EVENT LINK_HOVER set selected_uri = $1
3613 - --> LINK_HOVER http://uzbl.org/
3614 - <-- set selected_uri = http://uzbl.org/
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/
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.'''
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])
3632 - events = uzbl.on_events
3633 - event = kargs['on_event']
3634 - if event not in events:
3637 - commands = events[event]
3638 - cmd_expand = uzbl.cmd_expand
3639 - for cmd in commands:
3640 - cmd = cmd_expand(cmd, args)
3644 -def on_event(uzbl, event, cmd):
3645 - '''Add a new event to watch and respond to.'''
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] = []
3653 - cmds = events[event]
3654 - if cmd not in cmds:
3658 -def parse_on_event(uzbl, args):
3659 - '''Parse ON_EVENT events and pass them to the on_event function.
3661 - Syntax: "event ON_EVENT <EVENT_NAME> commands".'''
3663 - args = args.strip()
3664 - assert args, 'missing on event arguments'
3666 - (event, command) = (args.split(' ', 1) + ['',])[:2]
3667 - assert event and command, 'missing on event command'
3668 - on_event(uzbl, event, command)
3673 - '''Export functions and connect handlers to events.'''
3675 - connect(uzbl, 'ON_EVENT', parse_on_event)
3677 - export_dict(uzbl, {
3678 - 'on_event': on_event,
3682 -# plugin cleanup hook
3684 - for handlers in uzbl.on_events.values():
3687 - uzbl.on_events.clear()
3690 diff --git a/examples/data/plugins/on_set.py b/examples/data/plugins/on_set.py
3691 deleted file mode 100644
3692 index 130b816..0000000
3693 --- a/examples/data/plugins/on_set.py
3696 -from re import compile
3697 -from functools import partial
3699 -valid_glob = compile('^[A-Za-z0-9_\*\.]+$').match
3701 -def make_matcher(glob):
3702 - '''Make matcher function from simple glob.'''
3704 - pattern = "^%s$" % glob.replace('*', '[^\s]*')
3705 - return compile(pattern).match
3708 -def exec_handlers(uzbl, handlers, key, arg):
3709 - '''Execute the on_set handlers that matched the key.'''
3711 - for handler in handlers:
3712 - if callable(handler):
3716 - uzbl.send(uzbl.cmd_expand(handler, [key, arg]))
3719 -def check_for_handlers(uzbl, key, arg):
3720 - '''Check for handlers for the current key.'''
3722 - for (matcher, handlers) in uzbl.on_sets.values():
3724 - exec_handlers(uzbl, handlers, key, arg)
3727 -def on_set(uzbl, glob, handler, prepend=True):
3728 - '''Add a new handler for a config key change.
3730 - Structure of the `uzbl.on_sets` dict:
3731 - { glob : ( glob matcher function, handlers list ), .. }
3734 - assert valid_glob(glob)
3736 - while '**' in glob:
3737 - glob = glob.replace('**', '*')
3739 - if callable(handler):
3740 - orig_handler = handler
3742 - handler = partial(handler, uzbl)
3745 - orig_handler = handler = unicode(handler)
3747 - if glob in uzbl.on_sets:
3748 - (matcher, handlers) = uzbl.on_sets[glob]
3749 - handlers.append(handler)
3752 - matcher = make_matcher(glob)
3753 - uzbl.on_sets[glob] = (matcher, [handler,])
3755 - uzbl.logger.info('on set %r call %r' % (glob, orig_handler))
3758 -def parse_on_set(uzbl, args):
3759 - '''Parse `ON_SET <glob> <command>` event then pass arguments to the
3760 - `on_set(..)` function.'''
3762 - (glob, command) = (args.split(' ', 1) + [None,])[:2]
3763 - assert glob and command and valid_glob(glob)
3764 - on_set(uzbl, glob, command)
3767 -# plugins init hook
3770 - require('cmd_expand')
3772 - export_dict(uzbl, {
3777 - connect_dict(uzbl, {
3778 - 'ON_SET': parse_on_set,
3779 - 'CONFIG_CHANGED': check_for_handlers,
3782 -# plugins cleanup hook
3784 - for (matcher, handlers) in uzbl.on_sets.values():
3787 - uzbl.on_sets.clear()
3788 diff --git a/examples/data/plugins/progress_bar.py b/examples/data/plugins/progress_bar.py
3789 deleted file mode 100644
3790 index b2edffc..0000000
3791 --- a/examples/data/plugins/progress_bar.py
3796 -def update_progress(uzbl, progress=None):
3797 - '''Updates the progress.output variable on LOAD_PROGRESS update.
3799 - The current substitution options are:
3800 - %d = done char * done
3801 - %p = pending char * remaining
3805 - %t = percent pending
3809 - Default configuration options:
3810 - progress.format = [%d>%p]%c
3811 - progress.width = 8
3813 - progress.pending =
3814 - progress.spinner = -\|/
3815 - progress.sprites = loading
3820 - if progress is None:
3826 - progress = int(progress)
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 ' '
3835 - # Inflate the done and pending bars to stop the progress bar
3837 - if '%c' in format or '%i' in format:
3838 - count = format.count('%c') + format.count('%i')
3839 - width += (3-len(str(progress))) * count
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
3845 - done = int(((progress/100.0)*width)+0.5)
3846 - pending = width - done
3848 - if '%d' in format:
3849 - format = format.replace('%d', done_symbol * done)
3851 - if '%p' in format:
3852 - format = format.replace('%p', pending_symbol * pending)
3854 - if '%c' in format:
3855 - format = format.replace('%c', '%d%%' % progress)
3857 - if '%i' in format:
3858 - format = format.replace('%i', '%d' % progress)
3860 - if '%t' in format:
3861 - format = format.replace('%t', '%d%%' % (100-progress))
3863 - if '%o' in format:
3864 - format = format.replace('%o', '%d' % (100-progress))
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)
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)
3878 - if uzbl.config.get('progress.output', None) != format:
3879 - uzbl.config['progress.output'] = format
3883 - connect_dict(uzbl, {
3884 - 'LOAD_COMMIT': lambda uzbl, uri: update_progress(uzbl),
3885 - 'LOAD_PROGRESS': update_progress,
3887 diff --git a/examples/data/scripts/auth.py b/examples/data/scripts/auth.py
3888 index 49fa41e..dbf58dc 100755
3889 --- a/examples/data/scripts/auth.py
3890 +++ b/examples/data/scripts/auth.py
3892 #!/usr/bin/env python
3898 @@ -41,13 +42,20 @@ def getText(authInfo, authHost, authRealm):
3902 - output = login.get_text() + "\n" + password.get_text()
3904 + 'username': login.get_text(),
3905 + 'password': password.get_text()
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):
3917 + print >> fifo, 'auth "%s" "%s" "%s"' % (
3918 + info, output['username'], output['password']
3922 diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js
3923 index 83bbf55..0afeec3 100644
3924 --- a/examples/data/scripts/follow.js
3925 +++ b/examples/data/scripts/follow.js
3929 uzbldivid = 'uzbl_link_hints';
3930 +var uzbl = uzbl || {};
3932 uzbl.follow = function() {
3934 diff --git a/examples/data/scripts/formfiller.js b/examples/data/scripts/formfiller.js
3935 index 06db648..9c56f7b 100644
3936 --- a/examples/data/scripts/formfiller.js
3937 +++ b/examples/data/scripts/formfiller.js
3939 +var uzbl = uzbl || {};
3943 // this is pointlessly duplicated in uzbl.follow
3944 diff --git a/examples/data/scripts/history.sh b/examples/data/scripts/history.sh
3945 index 0709b5e..2d96a07 100755
3946 --- a/examples/data/scripts/history.sh
3947 +++ b/examples/data/scripts/history.sh
3951 +[ -n "$UZBL_PRIVATE" ] && exit 0
3953 . "$UZBL_UTIL_DIR/uzbl-dir.sh"
3955 >> "$UZBL_HISTORY_FILE" || exit 1
3956 diff --git a/examples/data/scripts/scheme.py b/examples/data/scripts/scheme.py
3957 index 4b0b7ca..c652478 100755
3958 --- a/examples/data/scripts/scheme.py
3959 +++ b/examples/data/scripts/scheme.py
3961 #!/usr/bin/env python
3963 -import os, subprocess, sys, urlparse
3964 +import os, subprocess, sys
3967 + import urllib.parse as urlparse
3968 +except ImportError:
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)
3976 subprocess.Popen(cmd)
3980 if __name__ == '__main__':
3982 diff --git a/misc/env.sh b/misc/env.sh
3983 index f815c44..d5f0db8 100755
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"
3991 +PYTHONLIB=$(python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib(prefix="/usr/local"))')
3993 +UZBL_PLUGIN_PATH="$(pwd)/sandbox/$PYTHONLIB/uzbl/plugins"
3994 +export UZBL_PLUGIN_PATH
3996 +PYTHONPATH="$(pwd)/sandbox/$PYTHONLIB/"
3998 diff --git a/setup.py b/setup.py
3999 new file mode 100644
4000 index 0000000..5dd6b59
4004 +from distutils.core import setup
4007 + version='201100808',
4008 + description='Uzbl event daemon',
4009 + url='http://uzbl.org',
4010 + packages=['uzbl', 'uzbl.plugins'],
4012 + 'bin/uzbl-event-manager',
4015 diff --git a/src/callbacks.c b/src/callbacks.c
4016 index eee9f69..370f679 100644
4017 --- a/src/callbacks.c
4018 +++ b/src/callbacks.c
4021 #include "variables.h"
4023 +#include <gdk/gdk.h>
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;
4033 +get_click_context() {
4034 + WebKitHitTestResult *ht;
4037 + if(!uzbl.state.last_button)
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);
4044 + return (gint)context;
4048 button_press_cb (GtkWidget* window, GdkEventButton* event) {
4050 @@ -233,22 +250,9 @@ button_release_cb (GtkWidget* window, GdkEventButton* event) {
4054 -motion_notify_cb(GtkWidget* window, GdkEventMotion* event, gpointer user_data) {
4059 - send_event (PTR_MOVE, NULL,
4060 - TYPE_FLOAT, event->x,
4061 - TYPE_FLOAT, event->y,
4062 - TYPE_INT, event->state,
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) {
4075 (void) navigation_action;
4076 @@ -288,39 +292,16 @@ navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNe
4081 -new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
4082 - WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
4083 - WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
4086 - (void) navigation_action;
4087 - (void) policy_decision;
4090 - if (uzbl.state.verbose)
4091 - printf ("New window requested -> %s \n", webkit_network_request_get_uri (request));
4093 - /* This event function causes troubles with `target="_blank"` anchors.
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
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.
4102 - * We are leaving this uncommented as we would rather links open twice
4103 - * than not at all.
4105 - send_event (NEW_WINDOW, NULL, TYPE_STR, webkit_network_request_get_uri (request), NULL);
4107 - webkit_web_policy_decision_ignore (policy_decision);
4110 +close_web_view_cb(WebKitWebView *webview, gpointer user_data) {
4111 + (void) webview; (void) user_data;
4112 + send_event (CLOSE_WINDOW, NULL, NULL);
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) {
4123 @@ -345,11 +326,17 @@ request_starting_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitWebRes
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);
4132 + SoupURI *soup_uri = soup_uri_new (uri);
4133 + soup_message_set_first_party (message, soup_uri);
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);
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
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) {
4152 webkit_web_view_stop_loading(web_view);
4154 const gchar* uri = webkit_web_view_get_uri(web_view);
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));
4162 send_event(NEW_WINDOW, NULL, TYPE_STR, uri, NULL);
4164 @@ -394,14 +380,29 @@ create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer us
4168 - if (uzbl.state.verbose)
4169 - printf("New web view -> javascript link...\n");
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.
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().)
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
4183 + if(!uzbl.state._tmp_web_view) {
4184 + uzbl.state._tmp_web_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
4186 + g_object_connect (uzbl.state._tmp_web_view, "signal::notify::uri",
4187 + G_CALLBACK(create_web_view_got_uri_cb), NULL, NULL);
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);
4194 - g_object_connect (new_view, "signal::notify::uri",
4195 - G_CALLBACK(create_web_view_js_cb), NULL, NULL);
4197 + return uzbl.state._tmp_web_view;
4201 @@ -464,7 +465,21 @@ download_cb(WebKitWebView *web_view, WebKitDownload *download, gpointer user_dat
4203 /* get a reasonable suggestion for a filename */
4204 const gchar *suggested_filename;
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);
4211 + suggested_filename = webkit_uri_response_get_suggested_filename(respose);
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);
4218 g_object_get(download, "suggested-filename", &suggested_filename, NULL);
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) {
4226 if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
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);
4232 parse_cmd_line(cmd, NULL);
4236 - g_object_unref(mi->hittest);
4237 + g_free(mi->argument);
4240 parse_cmd_line(mi->cmd, NULL);
4245 +populate_context_menu (GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gint context) {
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);
4254 + gboolean contexts_match = (context & mi->context);
4256 + if(!contexts_match) {
4260 + if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
4261 + g_object_get(hit_test_result, "image-uri", &(mi->argument), NULL);
4265 + item = gtk_separator_menu_item_new();
4267 + item = gtk_menu_item_new_with_label(mi->name);
4268 + g_signal_connect(item, "activate",
4269 + G_CALLBACK(run_menu_command), mi);
4272 + gtk_menu_shell_append(GTK_MENU_SHELL(default_menu), item);
4273 + gtk_widget_show(item);
4279 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
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;
4285 + if(!uzbl.gui.menu_items)
4288 + /* check context */
4289 + if((context = get_click_context()) == -1)
4292 + /* display the default menu with our modifications. */
4293 + return populate_context_menu(default_menu, hit_test_result, context);
4297 populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) {
4299 - GUI *g = &uzbl.gui;
4303 - gint context, hit=0;
4306 - if(!g->menu_items)
4307 + if(!uzbl.gui.menu_items)
4311 if((context = get_click_context()) == -1)
4314 - for(i=0; i < uzbl.gui.menu_items->len; i++) {
4316 - mi = g_ptr_array_index(uzbl.gui.menu_items, i);
4317 + WebKitHitTestResult *hit_test_result;
4318 + GdkEventButton ev;
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)))),
4327 + gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(v)), &x, &y, NULL);
4331 + hit_test_result = webkit_web_view_get_hit_test_result(v, &ev);
4333 - if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
4334 - GdkEventButton ev;
4336 - gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(v)), &x, &y, NULL);
4339 - mi->hittest = webkit_web_view_get_hit_test_result(v, &ev);
4341 + populate_context_menu(m, hit_test_result, context);
4343 - if((mi->context > WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) &&
4344 - (context & mi->context)) {
4346 - item = gtk_separator_menu_item_new();
4347 - gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
4348 - gtk_widget_show(item);
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);
4360 - if((mi->context == WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) &&
4361 - (context <= WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) &&
4364 - item = gtk_separator_menu_item_new();
4365 - gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
4366 - gtk_widget_show(item);
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);
4377 + g_object_unref(hit_test_result);
4382 window_object_cleared_cb(WebKitWebView *webview, WebKitWebFrame *frame,
4383 - JSGlobalContextRef *context, JSObjectRef *object) {
4384 + JSGlobalContextRef *context, JSObjectRef *object) {
4385 (void) frame; (void) context; (void) object;
4386 #if WEBKIT_CHECK_VERSION (1, 3, 13)
4387 // Take this opportunity to set some callbacks on the DOM
4388 diff --git a/src/callbacks.h b/src/callbacks.h
4389 index 6a10205..56bf612 100644
4390 --- a/src/callbacks.h
4391 +++ b/src/callbacks.h
4392 @@ -31,19 +31,11 @@ gboolean
4393 key_release_cb (GtkWidget* window, GdkEventKey* event);
4396 -motion_notify_cb(GtkWidget* window, GdkEventMotion* event, gpointer user_data);
4399 navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
4400 WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
4401 WebKitWebPolicyDecision *policy_decision, gpointer user_data);
4404 -new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame,
4405 - WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action,
4406 - WebKitWebPolicyDecision *policy_decision, gpointer user_data);
4409 mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request,
4410 gchar *mime_type, WebKitWebPolicyDecision *policy_decision, gpointer user_data);
4412 @@ -57,8 +49,13 @@ create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer us
4414 download_cb (WebKitWebView *web_view, WebKitDownload *download, gpointer user_data);
4416 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
4418 +context_menu_cb (WebKitWebView *web_view, GtkWidget *default_menu, WebKitHitTestResult *hit_test_result, gboolean triggered_with_keyboard, gpointer user_data);
4421 populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c);
4425 button_press_cb (GtkWidget* window, GdkEventButton* event);
4426 @@ -76,8 +73,11 @@ gboolean
4427 scroll_horiz_cb(GtkAdjustment *adjust, void *w);
4430 +close_web_view_cb(WebKitWebView *webview, gpointer user_data);
4433 window_object_cleared_cb(WebKitWebView *webview, WebKitWebFrame *frame,
4434 - JSGlobalContextRef *context, JSObjectRef *object);
4435 + JSGlobalContextRef *context, JSObjectRef *object);
4437 #if WEBKIT_CHECK_VERSION (1, 3, 13)
4439 diff --git a/src/commands.c b/src/commands.c
4440 index b3546a9..6769189 100644
4441 --- a/src/commands.c
4442 +++ b/src/commands.c
4444 #include "callbacks.h"
4445 #include "variables.h"
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 },
4461 +#if WEBKIT_CHECK_VERSION (1, 9, 90)
4462 + { "load", load, TRUE },
4463 + { "save", save, TRUE },
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 },
4471 { "include", include, TRUE },
4473 { "show_inspector", show_inspector, 0 },
4474 + { "inspector", inspector, TRUE },
4475 +#if WEBKIT_CHECK_VERSION (1, 5, 1)
4476 + { "spell_checker", spell_checker, TRUE },
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 }
4487 @@ -190,7 +213,11 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
4488 uzbl_cmdprop *c = get_var_c(var_name);
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));
4495 + set_var_value(var_name, "1");
4500 @@ -230,18 +257,18 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
4501 if(argv->len >= 3) {
4504 - int first = strtoul(argv_idx(argv, 1), NULL, 10);
4505 + int first = strtol(argv_idx(argv, 1), NULL, 10);
4508 const gchar *next_s = argv_idx(argv, 2);
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);
4517 - next = strtoul(next_s, NULL, 10);
4518 + next = strtol(next_s, NULL, 10);
4522 @@ -250,6 +277,34 @@ toggle_var(WebKitWebView *page, GArray *argv, GString *result) {
4523 set_var_value_int_c(c, next);
4528 + unsigned long long current = get_var_value_int_c(c);
4529 + unsigned long long next;
4531 + if(argv->len >= 3) {
4534 + unsigned long long first = strtoull(argv_idx(argv, 1), NULL, 10);
4535 + unsigned long long this = first;
4537 + const gchar *next_s = argv_idx(argv, 2);
4539 + while(next_s && this != current) {
4540 + this = strtoull(next_s, NULL, 10);
4541 + next_s = argv_idx(argv, ++i);
4545 + next = strtoull(next_s, NULL, 10);
4551 + set_var_value_ull_c(c, next);
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));
4561 +#ifndef USE_WEBKIT2
4562 +#if WEBKIT_CHECK_VERSION (1, 9, 6)
4564 +snapshot(WebKitWebView *page, GArray *argv, GString *result) {
4566 + cairo_surface_t* surface;
4568 + surface = webkit_web_view_get_snapshot(page);
4570 + cairo_surface_write_to_png(surface, argv_idx(argv, 0));
4572 + cairo_surface_destroy(surface);
4578 +#if WEBKIT_CHECK_VERSION (1, 9, 90)
4580 +load(WebKitWebView *page, GArray *argv, GString *result) {
4583 + guint sz = argv->len;
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;
4589 + webkit_web_view_load_alternate_html(page, content, content_uri, base_uri);
4593 +save(WebKitWebView *page, GArray *argv, GString *result) {
4595 + guint sz = argv->len;
4597 + const gchar *mode_str = sz > 0 ? argv_idx(argv, 0) : NULL;
4599 + WebKitSaveMode mode = WEBKIT_SAVE_MODE_MHTML;
4602 + mode = WEBKIT_SAVE_MODE_MHTML;
4603 + } else if (!strcmp("mhtml", mode_str)) {
4604 + mode = WEBKIT_SAVE_MODE_MHTML;
4608 + const gchar *path = argv_idx(argv, 1);
4609 + GFile *gfile = g_file_new_for_path(path);
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);
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);
4624 +remove_all_db(WebKitWebView *page, GArray *argv, GString *result) {
4625 + (void) page; (void) argv; (void) result;
4627 + webkit_remove_all_web_databases ();
4630 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
4632 +plugin_refresh(WebKitWebView *page, GArray *argv, GString *result) {
4633 + (void) page; (void) argv; (void) result;
4635 + WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
4636 + webkit_web_plugin_database_refresh (db);
4640 +plugin_toggle_one(WebKitWebPlugin *plugin, const gchar *name) {
4641 + const gchar *plugin_name = webkit_web_plugin_get_name (plugin);
4643 + if (!name || !g_strcmp0 (name, plugin_name)) {
4644 + gboolean enabled = webkit_web_plugin_get_enabled (plugin);
4646 + webkit_web_plugin_set_enabled (plugin, !enabled);
4651 +plugin_toggle(WebKitWebView *page, GArray *argv, GString *result) {
4652 + (void) page; (void) result;
4654 + WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
4655 + GSList *plugins = webkit_web_plugin_database_get_plugins (db);
4657 + if (argv->len == 0) {
4658 + g_slist_foreach (plugins, (GFunc)plugin_toggle_one, NULL);
4661 + for (i = 0; i < argv->len; ++i) {
4662 + const gchar *plugin_name = argv_idx (argv, i);
4664 + g_slist_foreach (plugins, (GFunc)plugin_toggle_one, &plugin_name);
4668 + webkit_web_plugin_database_plugins_list_free (plugins);
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) {
4679 +inspector(WebKitWebView *page, GArray *argv, GString *result) {
4680 + (void) page; (void) result;
4682 + if (argv->len < 1) {
4686 + const gchar* command = argv_idx (argv, 0);
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) {
4697 + gdouble x = strtod (argv_idx (argv, 1), NULL);
4698 + gdouble y = strtod (argv_idx (argv, 2), NULL);
4700 + /* Let's not tempt the dragons. */
4701 + if (errno == ERANGE) {
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 */
4713 +#if WEBKIT_CHECK_VERSION (1, 5, 1)
4715 +spell_checker(WebKitWebView *page, GArray *argv, GString *result) {
4718 + if (argv->len < 1) {
4722 + GObject *obj = webkit_get_text_checker ();
4727 + if (!WEBKIT_IS_SPELL_CHECKER (obj)) {
4731 + WebKitSpellChecker *checker = WEBKIT_SPELL_CHECKER (obj);
4733 + const gchar* command = argv_idx (argv, 0);
4735 + if (!g_strcmp0 (command, "ignore")) {
4736 + if (argv->len < 2) {
4741 + for (i = 1; i < argv->len; ++i) {
4742 + const gchar *word = argv_idx (argv, i);
4744 + webkit_spell_checker_ignore_word (checker, word);
4746 + } else if (!g_strcmp0 (command, "learn")) {
4747 + if (argv->len < 2) {
4752 + for (i = 1; i < argv->len; ++i) {
4753 + const gchar *word = argv_idx (argv, i);
4755 + webkit_spell_checker_learn_word (checker, word);
4757 + } else if (!g_strcmp0 (command, "autocorrect")) {
4758 + if (argv->len != 2) {
4762 + gchar *word = argv_idx (argv, 1);
4764 + char *new_word = webkit_spell_checker_get_autocorrect_suggestions_for_misspelled_word (checker, word);
4766 + g_string_assign (result, new_word);
4769 + } else if (!g_strcmp0 (command, "guesses")) {
4770 + /* TODO Implement */
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();
4785 +auth(WebKitWebView *page, GArray *argv, GString *result) {
4786 + (void) page; (void) result;
4787 + gchar *info, *username, *password;
4789 + if(argv->len != 3)
4792 + info = argv_idx (argv, 0);
4793 + username = argv_idx (argv, 1);
4794 + password = argv_idx (argv, 2);
4796 + authenticate (info, username, password);
4798 diff --git a/src/commands.h b/src/commands.h
4799 index 38bd5f2..6a14b9b 100644
4800 --- a/src/commands.h
4801 +++ b/src/commands.h
4803 #ifndef __COMMANDS__
4804 #define __COMMANDS__
4807 +#include <webkit2/webkit2.h>
4809 #include <webkit/webkit.h>
4812 typedef void (*Command)(WebKitWebView*, GArray *argv, GString *result);
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);
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);
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);
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);
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);
4851 diff --git a/src/cookie-jar.c b/src/cookie-jar.c
4852 index 2f6be83..83facd5 100644
4853 --- a/src/cookie-jar.c
4854 +++ b/src/cookie-jar.c
4855 @@ -41,21 +41,21 @@ changed(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie) {
4856 * propagated to other uzbl instances using add/delete_cookie. */
4857 if(!uzbl_jar->in_manual_add) {
4858 gchar *scheme = cookie->secure
4859 - ? cookie->http_only ? "httpsOnly" : "https"
4860 - : cookie->http_only ? "httpOnly" : "http";
4861 + ? cookie->http_only ? "httpsOnly" : "https"
4862 + : cookie->http_only ? "httpOnly" : "http";
4864 gchar *expires = NULL;
4866 expires = g_strdup_printf ("%ld", (long)soup_date_to_time_t (cookie->expires));
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,
4874 - TYPE_STR, expires ? expires : "",
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,
4882 + TYPE_STR, expires ? expires : "",
4887 diff --git a/src/events.c b/src/events.c
4888 index 081a942..8fc8573 100644
4891 @@ -21,7 +21,9 @@ const char *event_table[LAST_EVENT] = {
4895 + "REQUEST_QUEUED" ,
4896 "REQUEST_STARTING" ,
4897 + "REQUEST_FINISHED" ,
4901 @@ -32,6 +34,7 @@ const char *event_table[LAST_EVENT] = {
4902 "GEOMETRY_CHANGED" ,
4906 "SELECTION_CHANGED",
4909 @@ -48,7 +51,6 @@ const char *event_table[LAST_EVENT] = {
4916 "DOWNLOAD_STARTED" ,
4917 @@ -57,7 +59,8 @@ const char *event_table[LAST_EVENT] = {
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));
4932 + g_string_append_printf (event_message, "%llu", va_arg (vargs, unsigned long long));
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|");
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) {
4955 -guint button_to_modifier(guint buttonval) {
4956 - if(buttonval <= 5)
4957 - return 1 << (7 + buttonval);
4959 +guint button_to_modifier (guint buttonval) {
4960 + if(buttonval <= 5)
4961 + return 1 << (7 + buttonval);
4965 /* Transform gdk key events to our own events */
4966 diff --git a/src/events.h b/src/events.h
4967 index 73d0712..bd9df33 100644
4973 LOAD_START, LOAD_COMMIT, LOAD_FINISH, LOAD_ERROR,
4975 + REQUEST_QUEUED, REQUEST_STARTING, REQUEST_FINISHED,
4976 KEY_PRESS, KEY_RELEASE, MOD_PRESS, MOD_RELEASE,
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,
4993 /* must be last entry */
4995 diff --git a/src/inspector.c b/src/inspector.c
4996 index d0d86b9..d584c77 100644
4997 --- a/src/inspector.c
4998 +++ b/src/inspector.c
5000 #include "callbacks.h"
5005 hide_window_cb(GtkWidget *widget, gpointer data) {
5008 gtk_widget_hide(widget);
5012 +static WebKitWebView *
5013 create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){
5016 @@ -44,7 +44,7 @@ create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpo
5017 return WEBKIT_WEB_VIEW(new_web_view);
5022 inspector_show_window_cb (WebKitWebInspector* inspector){
5024 gtk_widget_show(uzbl.gui.inspector_window);
5025 @@ -54,32 +54,32 @@ inspector_show_window_cb (WebKitWebInspector* inspector){
5028 /* TODO: Add variables and code to make use of these functions */
5031 inspector_close_window_cb (WebKitWebInspector* inspector){
5033 send_event(WEBINSPECTOR, NULL, TYPE_NAME, "close", NULL);
5039 inspector_attach_window_cb (WebKitWebInspector* inspector){
5046 inspector_detach_window_cb (WebKitWebInspector* inspector){
5053 inspector_uri_changed_cb (WebKitWebInspector* inspector){
5060 inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){
5063 diff --git a/src/io.c b/src/io.c
5064 index ff418ef..ca3fcf6 100644
5067 @@ -47,7 +47,7 @@ control_fifo(GIOChannel *gio, GIOCondition condition) {
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) {
5082 attach_socket(gchar *path, struct sockaddr_un *local) {
5083 GIOChannel *chan = NULL;
5084 int sock = socket (AF_UNIX, SOCK_STREAM, 0);
5085 diff --git a/src/menu.c b/src/menu.c
5086 index 451b35a..1d9c5b4 100644
5091 #include "uzbl-core.h"
5095 add_to_menu(GArray *argv, guint context) {
5098 @@ -68,7 +68,7 @@ menu_add_edit(WebKitWebView *page, GArray *argv, GString *result) {
5104 add_separator_to_menu(GArray *argv, guint context) {
5107 @@ -127,7 +127,7 @@ menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result) {
5113 remove_from_menu(GArray *argv, guint context) {
5116 diff --git a/src/menu.h b/src/menu.h
5117 index 03055e5..c4ca047 100644
5125 +#include <webkit2/webkit2.h>
5127 #include <webkit/webkit.h>
5135 - WebKitHitTestResult* hittest;
5139 void menu_add(WebKitWebView *page, GArray *argv, GString *result);
5140 diff --git a/src/soup.c b/src/soup.c
5141 new file mode 100644
5142 index 0000000..8430018
5146 +#include "uzbl-core.h"
5148 +#include "events.h"
5151 +static void handle_authentication (SoupSession *session,
5154 + gboolean retrying,
5155 + gpointer user_data);
5157 +static void handle_request_queued (SoupSession *session,
5159 + gpointer user_data);
5161 +static void handle_request_started (SoupSession *session,
5163 + gpointer user_data);
5165 +static void handle_request_finished (SoupMessage *msg,
5166 + gpointer user_data);
5168 +struct _PendingAuth
5173 +typedef struct _PendingAuth PendingAuth;
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);
5181 +uzbl_soup_init (SoupSession *session)
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
5189 + soup_session_add_feature (
5191 + SOUP_SESSION_FEATURE (uzbl.net.soup_cookie_jar)
5194 + g_signal_connect (
5195 + session, "request-queued",
5196 + G_CALLBACK (handle_request_queued), NULL
5199 + g_signal_connect (
5200 + session, "request-started",
5201 + G_CALLBACK (handle_request_started), NULL
5204 + g_signal_connect (
5205 + session, "authenticate",
5206 + G_CALLBACK (handle_authentication), NULL
5210 +void authenticate (const char *authinfo,
5211 + const char *username,
5212 + const char *password)
5214 + PendingAuth *pending = g_hash_table_lookup (
5215 + uzbl.net.pending_auths,
5219 + if (pending == NULL) {
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)
5231 + g_hash_table_remove (uzbl.net.pending_auths, authinfo);
5235 +handle_request_queued (SoupSession *session,
5237 + gpointer user_data)
5239 + (void) session; (void) user_data;
5242 + REQUEST_QUEUED, NULL,
5243 + TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
5249 +handle_request_started (SoupSession *session,
5251 + gpointer user_data)
5253 + (void) session; (void) user_data;
5256 + REQUEST_STARTING, NULL,
5257 + TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
5261 + g_signal_connect (
5262 + G_OBJECT (msg), "finished",
5263 + G_CALLBACK (handle_request_finished), NULL
5268 +handle_request_finished (SoupMessage *msg, gpointer user_data)
5273 + REQUEST_FINISHED, NULL,
5274 + TYPE_STR, soup_uri_to_string (soup_message_get_uri (msg), FALSE),
5280 +handle_authentication (SoupSession *session,
5283 + gboolean retrying,
5284 + gpointer user_data)
5287 + PendingAuth *pending;
5288 + char *authinfo = soup_auth_get_info (auth);
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);
5296 + pending_auth_add_message (pending, msg);
5297 + soup_session_pause_message (session, msg);
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" : ""),
5309 +static PendingAuth *pending_auth_new (SoupAuth *auth)
5311 + PendingAuth *self = g_new (PendingAuth, 1);
5312 + self->auth = auth;
5313 + self->messages = NULL;
5314 + g_object_ref (auth);
5318 +static void pending_auth_free (PendingAuth *self)
5320 + g_object_unref (self->auth);
5321 + g_list_free_full (self->messages, g_object_unref);
5325 +static void pending_auth_add_message (PendingAuth *self, SoupMessage *message)
5327 + self->messages = g_list_append (self->messages, g_object_ref (message));
5329 diff --git a/src/soup.h b/src/soup.h
5330 new file mode 100644
5331 index 0000000..ce7d605
5336 + * Uzbl tweaks and extension for soup
5339 +#ifndef __UZBL_SOUP__
5340 +#define __UZBL_SOUP__
5342 +#include <libsoup/soup.h>
5345 + * Attach uzbl specific behaviour to the given SoupSession
5347 +void uzbl_soup_init (SoupSession *session);
5349 +void authenticate (const char *authinfo,
5350 + const char *username,
5351 + const char *password);
5353 diff --git a/src/status-bar.h b/src/status-bar.h
5354 index cae8905..1fd4ef2 100644
5355 --- a/src/status-bar.h
5356 +++ b/src/status-bar.h
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))
5361 -typedef struct _UzblStatusBar UzblStatusBar;
5362 +typedef struct _UzblStatusBar UzblStatusBar;
5363 typedef struct _UzblStatusBarClass UzblStatusBarClass;
5365 struct _UzblStatusBar {
5366 diff --git a/src/type.h b/src/type.h
5367 index eda02c1..826ca3b 100644
5370 @@ -9,6 +9,7 @@ enum ptr_type {
5376 // used by send_event
5378 @@ -24,6 +25,7 @@ enum ptr_type {
5379 typedef union uzbl_value_ptr_t {
5382 + unsigned long long *ull;
5386 diff --git a/src/util.c b/src/util.c
5387 index 1468d23..3f7f093 100644
5390 @@ -195,3 +195,9 @@ append_escaped (GString *dest, const gchar *src) {
5396 +sharg_append (GArray *a, const gchar *str) {
5397 + const gchar *s = (str ? str : "");
5398 + g_array_append_val(a, s);
5400 diff --git a/src/util.h b/src/util.h
5401 index cc29247..e0a8bbd 100644
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);
5410 * appends `src' to `dest' with backslash, single-quotes and newlines in
5411 - * `src' escaped */
5414 GString * append_escaped (GString *dest, const gchar *src);
5416 +void sharg_append (GArray *array, const gchar *str);
5417 diff --git a/src/uzbl-core.c b/src/uzbl-core.c
5418 index 1288f22..748202f 100644
5419 --- a/src/uzbl-core.c
5420 +++ b/src/uzbl-core.c
5423 #include "variables.h"
5429 @@ -279,22 +280,6 @@ clean_up(void) {
5434 -get_click_context() {
5435 - GUI *g = &uzbl.gui;
5436 - WebKitHitTestResult *ht;
5439 - if(!uzbl.state.last_button)
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);
5446 - return (gint)context;
5449 /* --- SIGNALS --- */
5451 setup_signal(int signr, sigfunc *shandler) {
5452 @@ -310,7 +295,7 @@ setup_signal(int signr, sigfunc *shandler) {
5458 empty_event_buffer(int s) {
5460 if(uzbl.state.event_buffer) {
5461 @@ -356,12 +341,12 @@ parse_cmd_line_cb(const char *line, void *user_data) {
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);
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);
5480 @@ -463,12 +448,6 @@ search_text (WebKitWebView *page, const gchar *key, const gboolean forward) {
5485 -sharg_append(GArray *a, const gchar *str) {
5486 - const gchar *s = (str ? str : "");
5487 - g_array_append_val(a, s);
5490 /* make sure that the args string you pass can properly be interpreted (eg
5491 * properly escaped against whitespace, quotes etc) */
5493 @@ -517,7 +496,7 @@ run_command (const gchar *command, const gchar **args, const gboolean sync,
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,
5523 "signal::populate-popup", (GCallback)populate_popup_cb, NULL,
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() {
5531 State* s = &uzbl.state;
5532 - Network* n = &uzbl.net;
5535 /* Load default config */
5536 @@ -841,70 +822,13 @@ settings_init () {
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");
5546 if (s->connect_socket_names)
5547 init_connect_socket();
5549 - g_signal_connect(n->soup_session, "authenticate", G_CALLBACK(handle_authentication), NULL);
5553 -void handle_authentication (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data) {
5556 - if (uzbl.behave.authentication_handler && *uzbl.behave.authentication_handler != 0) {
5557 - soup_session_pause_message(session, msg);
5559 - GString *result = g_string_new ("");
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));
5565 - GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
5566 - const CommandInfo *c = parse_command_parts(uzbl.behave.authentication_handler, a);
5568 - sharg_append(a, info);
5569 - sharg_append(a, host);
5570 - sharg_append(a, realm);
5571 - sharg_append(a, retrying ? "TRUE" : "FALSE");
5573 - run_parsed_command(c, a, result);
5575 - g_array_free(a, TRUE);
5577 - if (result->len > 0) {
5578 - char *username, *password;
5579 - int number_of_endls=0;
5581 - username = result->str;
5584 - for (p = result->str; *p; p++) {
5587 - if (++number_of_endls == 1)
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);
5598 - soup_session_unpause_message(session, msg);
5600 - g_string_free(result, TRUE);
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();
5613 + uzbl.info.webkit2 = 1;
5615 + uzbl.info.webkit2 = 0;
5617 uzbl.info.arch = ARCH;
5618 uzbl.info.commit = COMMIT;
5620 uzbl.state.last_result = NULL;
5622 + /* BUG There isn't a getter for this; need to maintain separately. */
5623 + uzbl.behave.maintain_history = TRUE;
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);
5632 - uzbl.net.soup_session = webkit_get_default_session();
5633 - uzbl.net.soup_cookie_jar = uzbl_cookie_jar_new();
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);
5641 @@ -1081,6 +1011,13 @@ main (int argc, char* argv[]) {
5643 gboolean verbose_override = uzbl.state.verbose;
5645 + /* Finally show the window */
5646 + if (uzbl.gui.main_window) {
5647 + gtk_widget_show_all (uzbl.gui.main_window);
5649 + gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug));
5652 /* Read configuration file */
5655 @@ -1099,13 +1036,6 @@ main (int argc, char* argv[]) {
5656 g_free(uri_override);
5659 - /* Finally show the window */
5660 - if (uzbl.gui.main_window) {
5661 - gtk_widget_show_all (uzbl.gui.main_window);
5663 - gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug));
5666 /* Verbose feedback */
5667 if (uzbl.state.verbose) {
5668 printf("Uzbl start location: %s\n", argv[0]);
5669 diff --git a/src/uzbl-core.h b/src/uzbl-core.h
5670 index 1f9613e..6f27818 100644
5671 --- a/src/uzbl-core.h
5672 +++ b/src/uzbl-core.h
5675 #include <sys/utsname.h>
5676 #include <sys/time.h>
5678 +#include <webkit2/webkit2.h>
5680 #include <webkit/webkit.h>
5682 #include <libsoup/soup.h>
5683 #include <JavaScriptCore/JavaScript.h>
5685 @@ -78,6 +82,7 @@ typedef struct {
5686 WebKitWebInspector* inspector;
5688 /* Custom context menu item */
5689 + gboolean custom_context_menu;
5690 GPtrArray* menu_items;
5693 @@ -114,6 +119,9 @@ typedef struct {
5694 gboolean handle_multi_button;
5695 GPtrArray* event_buffer;
5696 gchar** connect_socket_names;
5698 + /* Temporary web view used when a new window is opened */
5699 + WebKitWebView* _tmp_web_view;
5703 @@ -121,6 +129,7 @@ typedef struct {
5705 SoupSession* soup_session;
5706 UzblCookieJar* soup_cookie_jar;
5707 + GHashTable* pending_auths;
5708 SoupLogger* soup_logger;
5711 @@ -129,12 +138,6 @@ typedef struct {
5712 gint max_conns_host;
5718 - gchar *verify_cert;
5724 @@ -161,6 +164,7 @@ typedef struct {
5728 + gboolean maintain_history;
5730 gboolean print_version;
5732 @@ -176,6 +180,7 @@ typedef struct {
5740 @@ -189,7 +194,6 @@ typedef struct {
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);
5751 /* Network functions */
5752 -void handle_authentication (SoupSession *session,
5755 - gboolean retrying,
5756 - gpointer user_data);
5758 void init_connect_socket();
5759 gboolean remove_socket_from_array(GIOChannel *chan);
5762 void retrieve_geometry();
5763 void scroll(GtkAdjustment* bar, gchar *amount_str);
5764 -gint get_click_context();
5768 diff --git a/src/variables.c b/src/variables.c
5769 index e4763bc..749a176 100644
5770 --- a/src/variables.c
5771 +++ b/src/variables.c
5776 +#include <stdlib.h>
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),
5786 + send_event (VARIABLE_SET, NULL,
5789 + TYPE_ULL, get_var_value_ull_c(c),
5793 send_event (VARIABLE_SET, NULL,
5795 @@ -78,6 +87,14 @@ set_var_value_int_c(uzbl_cmdprop *c, int i) {
5799 +set_var_value_ull_c(uzbl_cmdprop *c, unsigned long long ull) {
5801 + ((void (*)(unsigned long long))c->setter)(ull);
5803 + *(c->ptr.ull) = ull;
5807 set_var_value_float_c(uzbl_cmdprop *c, float f) {
5809 ((void (*)(float))c->setter)(f);
5810 @@ -100,10 +117,16 @@ set_var_value(const gchar *name, gchar *val) {
5814 - int i = (int)strtoul(val, NULL, 10);
5815 + int i = (int)strtol(val, NULL, 10);
5816 set_var_value_int_c(c, i);
5821 + unsigned long long ull = strtoull(val, NULL, 10);
5822 + set_var_value_ull_c(c, ull);
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);
5833 +get_var_value_ull_c(const uzbl_cmdprop *c) {
5837 + return ((unsigned long long (*)())c->getter)();
5838 + } else if(c->ptr.ull)
5839 + return *(c->ptr.ull);
5845 +get_var_value_ull(const gchar *name) {
5846 + uzbl_cmdprop *c = get_var_c(name);
5847 + return get_var_value_ull_c(c);
5851 get_var_value_float_c(const uzbl_cmdprop *c) {
5853 @@ -202,7 +243,7 @@ get_var_value_float(const gchar *name) {
5854 return get_var_value_float_c(c);
5859 dump_var_hash(gpointer k, gpointer v, gpointer 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);
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));
5878 @@ -225,7 +269,7 @@ dump_config() {
5879 g_hash_table_foreach(uzbl.behave.proto_var, dump_var_hash, NULL);
5884 dump_var_hash_as_event(gpointer k, gpointer v, gpointer ud) {
5886 uzbl_cmdprop *c = v;
5887 @@ -240,18 +284,23 @@ dump_config_as_events() {
5890 /* is the given string made up entirely of decimal digits? */
5893 string_is_integer(const char *s) {
5894 return (strspn(s, "0123456789") == strlen(s));
5901 + return G_OBJECT(uzbl.net.soup_cookie_jar);
5906 return G_OBJECT(webkit_web_view_get_settings(uzbl.gui.web_view));
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);
5920 make_uri_from_user_input(const gchar *uri) {
5921 gchar *result = NULL;
5923 @@ -312,7 +361,7 @@ make_uri_from_user_input(const gchar *uri) {
5924 return g_strconcat("http://", uri, NULL);
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) {
5938 set_max_conns(int max_conns) {
5939 uzbl.net.max_conns = max_conns;
5941 @@ -348,7 +397,7 @@ set_max_conns(int max_conns) {
5942 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
5947 set_max_conns_host(int max_conns_host) {
5948 uzbl.net.max_conns_host = max_conns_host;
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);
5956 set_http_debug(int debug) {
5957 uzbl.behave.http_debug = debug;
5959 @@ -371,77 +420,258 @@ set_http_debug(int debug) {
5960 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
5965 set_ca_file(gchar *path) {
5966 g_object_set (uzbl.net.soup_session, "ssl-ca-file", path, NULL);
5973 g_object_get (uzbl.net.soup_session, "ssl-ca-file", &path, NULL);
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); \
5984 +static TYPE get_##SYM() { \
5986 + g_object_get(uzbl.gui.inspector, (PROPERTY), &val, NULL); \
5991 -get_verify_cert() {
5993 - g_object_get (uzbl.net.soup_session, "ssl-strict", &strict, NULL);
5995 +EXPOSE_WEB_INSPECTOR_SETTINGS(profile_js, "javascript-profiling-enabled", int)
5996 +EXPOSE_WEB_INSPECTOR_SETTINGS(profile_timeline, "timeline-profiling-enabled", gchar *)
5998 +#undef EXPOSE_WEB_INSPECTOR_SETTINGS
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); \
6004 +static TYPE get_##SYM() { \
6006 + g_object_get(uzbl.net.soup_session, (PROPERTY), &val, NULL); \
6010 +EXPOSE_SOUP_SESSION_SETTINGS(verify_cert, "ssl-strict", int)
6012 +#undef EXPOSE_SOUP_SESSION_SETTINGS
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); \
6018 +static TYPE get_##SYM() { \
6020 + g_object_get(cookie_jar(), (PROPERTY), &val, NULL); \
6024 +EXPOSE_SOUP_COOKIE_JAR_SETTINGS(cookie_policy, "accept-policy", int)
6026 +#undef EXPOSE_SOUP_COOKIE_JAR_SETTINGS
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); \
6032 +static TYPE get_##SYM() { \
6034 + g_object_get(uzbl.gui.web_view, (PROPERTY), &val, NULL); \
6038 +EXPOSE_WEBKIT_VIEW_VIEW_SETTINGS(editable, "editable", int)
6039 +EXPOSE_WEBKIT_VIEW_VIEW_SETTINGS(transparent, "transparent", int)
6041 +#undef EXPOSE_WEBKIT_VIEW_SETTINGS
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); \
6048 -TYPE get_##SYM() { \
6049 +static TYPE get_##SYM() { \
6051 g_object_get(view_settings(), (PROPERTY), &val, NULL); \
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 *)
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)
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)
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)
6084 +#if WEBKIT_CHECK_VERSION (1, 7, 5)
6085 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_webaudio, "enable-webaudio", int)
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)
6090 +#if WEBKIT_CHECK_VERSION (1, 11, 1)
6091 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_css_shaders, "enable-css-shaders", int)
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 *)
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)
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)
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)
6129 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
6130 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_fullscreen, "enable-fullscreen", int)
6133 +#if WEBKIT_CHECK_VERSION (1, 7, 91)
6134 +EXPOSE_WEBKIT_VIEW_SETTINGS(zoom_text_only, "zoom-text-only", int)
6137 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
6138 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_smooth_scrolling, "enable-smooth-scrolling", int)
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)
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)
6158 +#if WEBKIT_CHECK_VERSION (1, 11, 1)
6159 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_media_stream, "enable-media-stream", int)
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)
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)
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)
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)
6189 -EXPOSE_WEBKIT_VIEW_SETTINGS(default_encoding, "default-encoding", gchar *)
6191 +EXPOSE_WEBKIT_VIEW_SETTINGS(enable_site_workarounds, "enable-site-specific-quirks", int)
6194 +/* Printing settings */
6195 +EXPOSE_WEBKIT_VIEW_SETTINGS(print_bg, "print-backgrounds", int)
6197 +#undef EXPOSE_WEBKIT_VIEW_SETTINGS
6200 +set_maintain_history (int maintain) {
6201 + uzbl.behave.maintain_history = maintain;
6203 + webkit_web_view_set_maintains_back_forward_list (uzbl.gui.web_view, maintain);
6207 +get_maintain_history () {
6208 + return uzbl.behave.maintain_history;
6212 +set_spellcheck_languages(const gchar *languages) {
6213 + GObject *obj = webkit_get_text_checker ();
6218 + if (!WEBKIT_IS_SPELL_CHECKER (obj)) {
6222 + WebKitSpellChecker *checker = WEBKIT_SPELL_CHECKER (obj);
6224 + webkit_spell_checker_update_spell_checking_languages (checker, languages);
6225 + g_object_set(view_settings(), "spell-checking-languages", languages, NULL);
6229 +get_spellcheck_languages() {
6231 + g_object_get(view_settings(), "spell-checking-languages", &val, NULL);
6236 +set_enable_private (int private) {
6237 + const char *priv_envvar = "UZBL_PRIVATE";
6240 + setenv (priv_envvar, "true", 1);
6242 + unsetenv (priv_envvar);
6244 + set_enable_private_webkit (private);
6248 +get_enable_private () {
6249 + return get_enable_private_webkit ();
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);
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);
6267 - g_free(uzbl.behave.authentication_handler);
6268 - uzbl.behave.authentication_handler = g_strdup(handler);
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);
6275 + * Check if the webkit auth dialog is enabled for the soup session
6278 +get_enable_builtin_auth () {
6279 + SoupSessionFeature *auth = soup_session_get_feature (
6280 + uzbl.net.soup_session,
6281 + (GType) WEBKIT_TYPE_SOUP_AUTH_DIALOG
6284 + return auth != NULL;
6288 + * Enable/Disable the webkit auth dialog for the soup session
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
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
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);
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) {
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) {
6335 set_window_role(const gchar *role) {
6336 if (!uzbl.gui.main_window)
6338 @@ -521,7 +768,7 @@ set_window_role(const gchar *role) {
6339 gtk_window_set_role(GTK_WINDOW (uzbl.gui.main_window), role);
6345 if (!uzbl.gui.main_window)
6347 @@ -585,7 +832,7 @@ get_show_status() {
6348 return gtk_widget_get_visible(uzbl.gui.status_bar);
6353 set_status_top(int status_top) {
6354 if (!uzbl.gui.scrolled_win && !uzbl.gui.status_bar)
6356 @@ -612,21 +859,27 @@ set_status_top(int status_top) {
6357 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
6361 -set_current_encoding(const gchar *encoding) {
6363 +set_custom_encoding(const gchar *encoding) {
6364 if(strlen(encoding) == 0)
6367 webkit_web_view_set_custom_encoding(uzbl.gui.web_view, encoding);
6371 -get_current_encoding() {
6373 +get_custom_encoding() {
6374 const gchar *encoding = webkit_web_view_get_custom_encoding(uzbl.gui.web_view);
6375 return g_strdup(encoding);
6380 +get_current_encoding() {
6381 + const gchar *encoding = webkit_web_view_get_encoding (uzbl.gui.web_view);
6382 + return g_strdup(encoding);
6386 set_fifo_dir(const gchar *fifo_dir) {
6387 g_free(uzbl.behave.fifo_dir);
6389 @@ -636,7 +889,7 @@ set_fifo_dir(const gchar *fifo_dir) {
6390 uzbl.behave.fifo_dir = NULL;
6395 set_socket_dir(const gchar *socket_dir) {
6396 g_free(uzbl.behave.socket_dir);
6398 @@ -646,26 +899,38 @@ set_socket_dir(const gchar *socket_dir) {
6399 uzbl.behave.socket_dir = NULL;
6405 +set_inject_text(const gchar *text) {
6406 + webkit_web_view_load_plain_text (uzbl.gui.web_view, html, NULL);
6411 set_inject_html(const gchar *html) {
6413 + webkit_web_view_load_html (uzbl.gui.web_view, html, NULL);
6415 webkit_web_view_load_html_string (uzbl.gui.web_view, html, NULL);
6421 set_useragent(const gchar *useragent) {
6422 g_free(uzbl.net.useragent);
6424 - if (*useragent == ' ') {
6425 + if (!useragent || !*useragent) {
6426 uzbl.net.useragent = NULL;
6428 uzbl.net.useragent = g_strdup(useragent);
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);
6438 set_accept_languages(const gchar *accept_languages) {
6439 g_free(uzbl.net.accept_languages);
6441 @@ -679,8 +944,7 @@ set_accept_languages(const gchar *accept_languages) {
6445 -/* requires webkit >=1.1.14 */
6448 set_view_source(int view_source) {
6449 uzbl.behave.view_source = view_source;
6451 @@ -688,6 +952,7 @@ set_view_source(int view_source) {
6452 (gboolean) uzbl.behave.view_source);
6455 +#ifndef USE_WEBKIT2
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
6461 return webkit_web_view_get_full_content_zoom (uzbl.gui.web_view);
6467 set_zoom_level(float zoom_level) {
6468 webkit_web_view_set_zoom_level (uzbl.gui.web_view, zoom_level);
6474 return webkit_web_view_get_zoom_level (uzbl.gui.web_view);
6478 +get_cache_model() {
6479 + WebKitCacheModel model = webkit_get_cache_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");
6489 + return g_strdup("unknown");
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);
6507 +get_web_database_directory() {
6508 + return g_strdup (webkit_get_web_database_directory_path ());
6511 +static unsigned long long
6512 +get_web_database_quota () {
6513 + return webkit_get_default_web_database_quota ();
6517 +set_web_database_quota (unsigned long long quota) {
6518 + webkit_set_default_web_database_quota (quota);
6522 +set_web_database_directory(const gchar *path) {
6523 + webkit_set_web_database_directory_path (path);
6526 +#if WEBKIT_CHECK_VERSION (1, 3, 4)
6529 + WebKitWebViewViewMode mode = webkit_web_view_get_view_mode (uzbl.gui.web_view);
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");
6543 + return g_strdup("unknown");
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);
6563 +#if WEBKIT_CHECK_VERSION (1, 3, 17)
6565 +get_inspected_uri() {
6566 + return g_strdup (webkit_web_inspector_get_inspected_uri (uzbl.gui.inspector));
6570 +#if WEBKIT_CHECK_VERSION (1, 3, 13)
6572 +get_app_cache_directory() {
6573 + return g_strdup (webkit_application_cache_get_database_directory_path ());
6576 +static unsigned long long
6577 +get_app_cache_size() {
6578 + return webkit_application_cache_get_maximum_size ();
6582 +set_app_cache_size(unsigned long long size) {
6583 + webkit_application_cache_set_maximum_size (size);
6587 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
6589 +mimetype_list_append(WebKitWebPluginMIMEType *mimetype, GString *list) {
6590 + if (*list->str != '[') {
6591 + g_string_append_c (list, ',');
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 */
6600 + mimetype->description);
6602 + char **extension = mimetype->extensions;
6603 + gboolean first = TRUE;
6605 + while (extension) {
6609 + g_string_append_c (list, ',');
6611 + g_string_append (list, *extension);
6616 + g_string_append_c (list, '}');
6620 +plugin_list_append(WebKitWebPlugin *plugin, GString *list) {
6621 + if (*list->str != '[') {
6622 + g_string_append_c (list, ',');
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);
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 */
6640 + enabled ? "true" : "false",
6643 + g_slist_foreach (mimetypes, (GFunc)mimetype_list_append, list);
6645 + /* Close the array and the object */
6646 + g_string_append (list, "]}");
6650 +get_plugin_list() {
6651 + WebKitWebPluginDatabase *db = webkit_get_web_plugin_database ();
6652 + GSList *plugins = webkit_web_plugin_database_get_plugins (db);
6654 + GString *list = g_string_new ("[");
6656 + g_slist_foreach (plugins, (GFunc)plugin_list_append, list);
6658 + g_string_append_c (list, ']');
6660 + webkit_web_plugin_database_plugins_list_free (plugins);
6662 + return g_string_free (list, FALSE);
6666 /* abbreviations to help keep the table's width humane */
6669 @@ -717,6 +1172,7 @@ get_zoom_level() {
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 }
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 }
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 }
6686 const struct var_name_to_ptr_t {
6689 @@ -751,7 +1212,6 @@ const struct var_name_to_ptr_t {
6691 { "forward_keys", PTR_V_INT(uzbl.behave.forward_keys, 1, NULL)},
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)},
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)},
6710 + { "web_database_directory", PTR_V_STR_GETSET(web_database_directory)},
6711 + { "web_database_quota", PTR_V_ULL_GETSET(web_database_quota)},
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)},
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)},
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)},
6742 +#if WEBKIT_CHECK_VERSION (1, 7, 5)
6743 + { "enable_webaudio", PTR_V_INT_GETSET(enable_webaudio)},
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)},
6748 +#if WEBKIT_CHECK_VERSION (1, 11, 1)
6749 + { "enable_css_shaders", PTR_V_INT_GETSET(enable_css_shaders)},
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)},
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)},
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)},
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)},
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)},
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)},
6788 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
6789 + { "enable_fullscreen", PTR_V_INT_GETSET(enable_fullscreen)},
6792 +#if WEBKIT_CHECK_VERSION (1, 7, 91)
6793 + { "zoom_text_only", PTR_V_INT_GETSET(zoom_text_only)},
6796 +#if WEBKIT_CHECK_VERSION (1, 9, 0)
6797 + { "enable_smooth_scrolling",PTR_V_INT_GETSET(enable_smooth_scrolling)},
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)},
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)},
6811 +#if WEBKIT_CHECK_VERSION (1, 11, 1)
6812 + { "enable_media_stream", PTR_V_INT_GETSET(enable_media_stream)},
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)},
6838 + { "default_context_menu", PTR_V_INT_GETSET(default_context_menu)},
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)},
6849 + { "inject_text", { .type = TYPE_STR, .dump = 0, .writeable = 1, .getter = NULL, .setter = (uzbl_fp) set_inject_text }},
6851 { "inject_html", { .type = TYPE_STR, .dump = 0, .writeable = 1, .getter = NULL, .setter = (uzbl_fp) set_inject_html }},
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)},
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)},
6870 +#if WEBKIT_CHECK_VERSION (1, 3, 13)
6871 + { "app_cache_directory", PTR_C_STR_F(get_app_cache_directory)},
6873 +#if WEBKIT_CHECK_VERSION (1, 3, 8)
6874 + { "plugin_list", PTR_C_STR_F(get_plugin_list)},
6877 /* and we terminate the whole thing with the closest thing we have to NULL.
6878 * it's important that dump = 0. */
6879 { NULL, {.ptr = { .i = NULL }, .type = TYPE_INT, .dump = 0, .writeable = 0}}
6880 diff --git a/src/variables.h b/src/variables.h
6881 index dade652..0b935eb 100644
6882 --- a/src/variables.h
6883 +++ b/src/variables.h
6885 #define __VARIABLES__
6889 +#include <webkit2/webkit2.h>
6891 #include <webkit/webkit.h>
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);
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);
6911 void send_set_var_event(const char *name, const uzbl_cmdprop *c);
6912 @@ -34,8 +41,6 @@ void dump_config_as_events();
6914 void uri_change_cb (WebKitWebView *web_view, GParamSpec param_spec);
6916 -void set_show_status(int);
6918 void set_zoom_type(int);
6919 int get_zoom_type();
6921 diff --git a/tests/event-manager/emtest.py b/tests/event-manager/emtest.py
6922 new file mode 100644
6923 index 0000000..27ce21b
6925 +++ b/tests/event-manager/emtest.py
6927 +from mock import Mock
6929 +from uzbl.event_manager import Uzbl
6932 +class EventManagerMock(object):
6933 + def __init__(self,
6934 + global_plugins=(), instance_plugins=(),
6935 + global_mock_plugins=(), instance_mock_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)
6947 + u = Mock(spec=Uzbl)
6949 + u.logger = logging.getLogger('debug')
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
6957 diff --git a/tests/event-manager/testarguments.py b/tests/event-manager/testarguments.py
6958 new file mode 100755
6959 index 0000000..edb102d
6961 +++ b/tests/event-manager/testarguments.py
6963 +#!/usr/bin/env python
6966 +from uzbl.arguments import Arguments
6969 +class ArgumentsTest(unittest.TestCase):
6970 + def test_empty(self):
6972 + self.assertEqual(len(a), 0)
6973 + self.assertEqual(a.raw(), '')
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')
6982 + def test_tab(self):
6983 + a = Arguments('\tfoo\t\tbar')
6984 + self.assertEqual(len(a), 2)
6985 + self.assertEqual(a.raw(), 'foo\t\tbar')
6986 + self.assertEqual(a.raw(0, 0), 'foo')
6987 + self.assertEqual(a.raw(1, 1), 'bar')
6988 diff --git a/tests/event-manager/testbind.py b/tests/event-manager/testbind.py
6989 new file mode 100644
6990 index 0000000..a2e8d70
6992 +++ b/tests/event-manager/testbind.py
6994 +#!/usr/bin/env python
6998 +if '' not in sys.path:
6999 + sys.path.insert(0, '')
7003 +from emtest import EventManagerMock
7004 +from uzbl.plugins.bind import Bind, BindPlugin
7005 +from uzbl.plugins.config import Config
7008 +def justafunction():
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)
7019 +class BindPluginTest(unittest.TestCase):
7021 + self.event_manager = EventManagerMock((), (Config, BindPlugin))
7022 + self.uzbl = self.event_manager.add()
7024 + def test_add_bind(self):
7025 + b = BindPlugin[self.uzbl]
7028 + handler = justafunction
7029 + b.mode_bind(modes, glob, handler)
7031 + binds = b.bindlet.get_binds()
7032 + self.assertEqual(len(binds), 1)
7033 + self.assertIs(binds[0].function, justafunction)
7035 + def test_parse_bind(self):
7036 + b = BindPlugin[self.uzbl]
7039 + handler = 'handler'
7041 + b.parse_mode_bind('%s %s = %s' % (modes, glob, handler))
7042 + binds = b.bindlet.get_binds()
7043 + self.assertEqual(len(binds), 1)
7044 + self.assertEqual(binds[0].commands, [handler])
7045 diff --git a/tests/event-manager/testcompletion.py b/tests/event-manager/testcompletion.py
7046 new file mode 100644
7047 index 0000000..8ae32a2
7049 +++ b/tests/event-manager/testcompletion.py
7051 +#!/usr/bin/env python
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
7063 +class DummyFormatter(object):
7064 + def format(self, partial, completions):
7065 + return '[%s] %s' % (partial, ', '.join(completions))
7067 +class TestAdd(unittest.TestCase):
7069 + self.event_manager = EventManagerMock(
7070 + (), (CompletionPlugin,),
7071 + (), ((Config, dict), (KeyCmd, None))
7073 + self.uzbl = self.event_manager.add()
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)
7081 + def test_config(self):
7082 + c = CompletionPlugin[self.uzbl]
7083 + c.add_config_key('spam', 'SPAM')
7084 + self.assertIn('@spam', c.completion)
7087 +class TestCompletion(unittest.TestCase):
7089 + self.event_manager = EventManagerMock(
7090 + (), (KeyCmd, CompletionPlugin),
7091 + (), ((Config, dict),)
7093 + self.uzbl = self.event_manager.add()
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')
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)
7106 + r = c.get_incomplete_keyword()
7107 + self.assertEqual(r, ('sp', False))
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)
7114 + r = c.get_incomplete_keyword()
7115 + self.assertEqual(r, ('@sp', False))
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)
7122 + r = c.get_incomplete_keyword()
7123 + self.assertEqual(r, ('@Some', True))
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'
7130 + c.stop_completion()
7131 + self.assertNotIn('completion_list', config)
7132 + self.assertEqual(c.completion.level, 0)
7134 + def test_completion(self):
7135 + k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
7136 + config = Config[self.uzbl]
7141 + ('set @sp', 'set @spam '),
7145 + k.keylet.keycmd = i
7146 + k.keylet.cursor = len(k.keylet.keycmd)
7148 + c.start_completion()
7149 + self.assertEqual(k.keylet.keycmd, o)
7150 + c.start_completion()
7151 + self.assertNotIn('completion_list', config)
7153 + def test_completion_list(self):
7154 + k, c = KeyCmd[self.uzbl], CompletionPlugin[self.uzbl]
7155 + config = Config[self.uzbl]
7158 + ('b', 'ba', '[ba] bar, baz'),
7161 + for i, o, l in comp:
7162 + k.keylet.keycmd = i
7163 + k.keylet.cursor = len(k.keylet.keycmd)
7165 + c.start_completion()
7166 + self.assertEqual(k.keylet.keycmd, o)
7167 + c.start_completion()
7168 + self.assertEqual(config['completion_list'], l)
7169 diff --git a/tests/event-manager/testconfig.py b/tests/event-manager/testconfig.py
7170 new file mode 100644
7171 index 0000000..d2a891e
7173 +++ b/tests/event-manager/testconfig.py
7178 +from emtest import EventManagerMock
7180 +from uzbl.plugins.config import Config
7183 +class ConfigTest(unittest.TestCase):
7185 + self.event_manager = EventManagerMock((), (Config,))
7186 + self.uzbl = self.event_manager.add()
7188 + def test_set(self):
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()
7202 + def test_set_invalid(self):
7204 + ("foo\nbar", AssertionError), # Better Exception type
7205 + ("bad'key", AssertionError)
7207 + c = Config[self.uzbl]
7208 + for input, exception in cases:
7209 + self.assertRaises(exception, c.set, input)
7211 + def test_parse(self):
7213 + ('foo str value', 'foo', 'value'),
7214 + ('foo str "ba ba"', 'foo', 'ba ba'),
7215 + ('foo float 5', 'foo', 5.0)
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()
7226 + def test_parse_null(self):
7228 + ('foo str', 'foo'),
7229 + ('foo str ""', 'foo'),
7230 + #('foo int', 'foo') # Not sure if this input is valid
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()
7241 + def test_parse_invalid(self):
7243 + ('foo bar', AssertionError), # TypeError?
7244 + ('foo bad^key', AssertionError),
7246 + ('foo int z', ValueError)
7248 + c = Config[self.uzbl]
7249 + for input, exception in cases:
7250 + self.assertRaises(exception, c.parse_set_event, input)
7251 + self.assertEqual(len(list(c.keys())), 0)
7252 diff --git a/tests/event-manager/testcookie.py b/tests/event-manager/testcookie.py
7253 new file mode 100755
7254 index 0000000..c33ba3b
7256 +++ b/tests/event-manager/testcookie.py
7258 +#!/usr/bin/env python
7262 +if '' not in sys.path:
7263 + sys.path.insert(0, '')
7266 +from emtest import EventManagerMock
7268 +from uzbl.plugins.cookies import Cookies
7271 + r'".nyan.cat" "/" "__utmb" "183192761.1.10.1313990640" "http" "1313992440"',
7272 + r'".twitter.com" "/" "guest_id" "v1%3A131399064036991891" "http" "1377104460"'
7276 +class CookieFilterTest(unittest.TestCase):
7278 + self.event_manager = EventManagerMock((), (Cookies,))
7279 + self.uzbl = self.event_manager.add()
7280 + self.other = self.event_manager.add()
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])
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])
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])
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])
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])
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])
7323 +if __name__ == '__main__':
7325 diff --git a/tests/event-manager/testcore.py b/tests/event-manager/testcore.py
7326 new file mode 100644
7327 index 0000000..1a8b4fa
7329 +++ b/tests/event-manager/testcore.py
7331 +#!/usr/bin/env python
7337 +from mock import Mock
7338 +from uzbl.core import Uzbl
7341 +class TestUzbl(unittest.TestCase):
7344 + options.print_events = False
7346 + self.proto = Mock()
7347 + self.uzbl = Uzbl(self.em, self.proto, options)
7349 + def test_repr(self):
7350 + r = '%r' % self.uzbl
7351 + self.assertRegex(r, r'<uzbl\(.*\)>')
7353 + def test_event_handler(self):
7355 + event, arg = 'FOO', 'test'
7356 + self.uzbl.connect(event, handler)
7357 + self.uzbl.event(event, arg)
7358 + handler.assert_called_once_with(arg)
7360 + def test_parse_sets_name(self):
7362 + self.uzbl.parse_msg(' '.join(['EVENT', name, 'FOO', 'BAR']))
7363 + self.assertEqual(self.uzbl.name, name)
7365 + def test_parse_sends_event(self):
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)
7372 + def test_malformed_message(self):
7373 + # Should not crash
7374 + self.uzbl.parse_msg('asdaf')
7375 + self.assertTrue(True)
7377 + def test_send(self):
7378 + self.uzbl.send('hello ')
7379 + self.proto.push.assert_called_once_with('hello\n'.encode('utf-8'))
7381 + def test_close_calls_remove_instance(self):
7383 + self.em.remove_instance.assert_called_once_with(self.proto.socket)
7385 + def test_close_cleans_plugins(self):
7386 + p1, p2 = Mock(), Mock()
7387 + self.uzbl._plugin_instances = (p1, p2)
7388 + self.uzbl.plugins = {}
7390 + p1.cleanup.assert_called_once_with()
7391 + p2.cleanup.assert_called_once_with()
7393 + def test_close_connection_closes_protocol(self):
7394 + self.uzbl.close_connection(Mock())
7395 + self.proto.close.assert_called_once_with()
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)
7401 + def test_instance_start(self):
7403 + self.em.plugind.per_instance_plugins = []
7404 + self.uzbl.parse_msg(
7405 + ' '.join(['EVENT', 'spam', 'INSTANCE_START', str(pid)])
7407 + self.assertEqual(self.uzbl.pid, pid)
7409 + def test_init_plugins(self):
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]
7417 + self.assertEqual(len(u.plugins), 2)
7418 + for t in (FooPlugin, BarPlugin):
7419 + self.assertIn(t, u.plugins)
7420 + self.assertTrue(isinstance(u.plugins[t], t))
7421 diff --git a/tests/event-manager/testdoc.py b/tests/event-manager/testdoc.py
7422 new file mode 100755
7423 index 0000000..0f3b8eb
7425 +++ b/tests/event-manager/testdoc.py
7427 +#!/usr/bin/env python
7431 +if '' not in sys.path:
7432 + sys.path.insert(0, '')
7435 +from doctest import DocTestSuite
7436 +keycmd_tests = DocTestSuite('uzbl.plugins.keycmd')
7437 +arguments_tests = DocTestSuite('uzbl.arguments')
7440 +def load_tests(loader, standard, pattern):
7441 + tests = unittest.TestSuite()
7442 + tests.addTest(keycmd_tests)
7443 + tests.addTest(arguments_tests)
7446 +if __name__ == '__main__':
7447 + runner = unittest.TextTestRunner()
7449 diff --git a/tests/event-manager/testdownload.py b/tests/event-manager/testdownload.py
7450 new file mode 100644
7451 index 0000000..49ed726
7453 +++ b/tests/event-manager/testdownload.py
7460 +from emtest import EventManagerMock
7462 +from uzbl.plugins.config import Config
7463 +from uzbl.plugins.downloads import Downloads
7466 +class DownloadsTest(unittest.TestCase):
7468 + self.event_manager = EventManagerMock((), (Downloads, Config))
7469 + self.uzbl = self.event_manager.add()
7471 + def test_start(self):
7473 + ('foo', 'foo', 'foo (0%)'),
7474 + ('"b@r"', 'b@r', 'b@r (0%'),
7476 + d = Downloads[self.uzbl]
7477 + for input, key, section in cases:
7478 + d.download_started(input)
7479 + self.assertIn(key, d.active_downloads)
7480 + self.assertEqual(d.active_downloads[key], 0)
7481 + self.uzbl.send.assert_called_once()
7482 + self.assertIn(section, self.uzbl.send.call_args[0][0])
7483 + self.uzbl.reset_mock()
7484 diff --git a/tests/event-manager/testhistory.py b/tests/event-manager/testhistory.py
7485 new file mode 100644
7486 index 0000000..0817d9f
7488 +++ b/tests/event-manager/testhistory.py
7490 +#!/usr/bin/env python
7496 +if '' not in sys.path:
7497 + sys.path.insert(0, '')
7500 +from emtest import EventManagerMock
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
7508 +class SharedHistoryTest(unittest.TestCase):
7510 + self.event_manager = EventManagerMock((SharedHistory,), ())
7511 + self.uzbl = self.event_manager.add()
7512 + self.other = self.event_manager.add()
7514 + def test_instance(self):
7515 + a = SharedHistory[self.uzbl]
7516 + b = SharedHistory[self.other]
7517 + self.assertIs(a, b)
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')
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)
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)
7543 +class HistoryTest(unittest.TestCase):
7545 + self.event_manager = EventManagerMock(
7547 + (OnSetPlugin, KeyCmd, Config, History)
7549 + self.uzbl = self.event_manager.add()
7550 + self.other = self.event_manager.add()
7551 + s = SharedHistory[self.uzbl]
7561 + for prompt, input in data:
7562 + s.addline(prompt, input)
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))
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))
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())
7597 + def test_exec(self):
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')
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())
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())
7621 + def test_search(self):
7622 + h = History[self.uzbl]
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())
7633 + def test_temp(self):
7634 + kl = KeyCmd[self.uzbl].keylet
7635 + kl.set_keycmd('uzbl')
7636 + h = History[self.uzbl]
7637 + h.change_prompt('foo')
7638 + # Why is the preserve current logic in this method?
7639 + h.history_prev(None)
7640 + self.assertTrue(len(h.prev()) > 0)
7641 + self.assertEqual('foo', next(h))
7642 + self.assertEqual('uzbl', next(h))
7643 + self.assertEqual('', next(h)) # this clears the keycmd
7644 diff --git a/tests/event-manager/testkeycmd.py b/tests/event-manager/testkeycmd.py
7645 new file mode 100644
7646 index 0000000..bc82c6d
7648 +++ b/tests/event-manager/testkeycmd.py
7650 +#!/usr/bin/env python
7655 +from emtest import EventManagerMock
7656 +from uzbl.plugins.keycmd import KeyCmd
7657 +from uzbl.plugins.config import Config
7661 + return re.match(r'@\[([^\]]*)\]@', s).group(1)
7663 +class KeyCmdTest(unittest.TestCase):
7665 + self.event_manager = EventManagerMock(
7667 + (), ((Config, dict),)
7669 + self.uzbl = self.event_manager.add()
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')
7678 + def test_press_keys(self):
7679 + c, k = Config[self.uzbl], KeyCmd[self.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)
7687 + def test_press_unicode_keys(self):
7688 + c, k = Config[self.uzbl], KeyCmd[self.uzbl]
7689 + string = '\u5927\u962a\u5e02'
7690 + for char in string:
7691 + k.key_press(('', char))
7692 + self.assertEqual(c.get('modcmd', ''), '')
7693 + keycmd = getkeycmd(c['keycmd'])
7694 + self.assertEqual(keycmd, string)
7695 diff --git a/tests/event-manager/testmode.py b/tests/event-manager/testmode.py
7696 new file mode 100644
7697 index 0000000..d198c32
7699 +++ b/tests/event-manager/testmode.py
7701 +#!/usr/bin/env python
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
7712 +class ModeParseTest(unittest.TestCase):
7714 + self.event_manager = EventManagerMock(
7715 + (), (OnSetPlugin, ModePlugin),
7716 + (), ((Config, dict),)
7718 + self.uzbl = self.event_manager.add()
7720 + def test_parse_config(self):
7722 + m = ModePlugin[uzbl]
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)
7731 +class ModeTest(unittest.TestCase):
7733 + self.event_manager = EventManagerMock(
7734 + (), (OnSetPlugin, ModePlugin),
7735 + (), ((Config, dict),)
7737 + self.uzbl = self.event_manager.add()
7739 + mode = ModePlugin[self.uzbl]
7740 + config = Config[self.uzbl]
7742 + mode.parse_mode_config(('mode0', 'foo', '=', 'default'))
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"')
7748 + mode.parse_mode_config(('mode2', 'foo', '=', 'XXX'))
7749 + mode.parse_mode_config(('mode2', 'spam', '=', 'spam'))
7751 + config['default_mode'] = 'mode0'
7752 + mode.default_mode_updated(None, 'mode0')
7754 + def test_mode_sets_vars(self):
7755 + mode, config = ModePlugin[self.uzbl], Config[self.uzbl]
7756 + mode.mode_updated(None, 'mode1')
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"')
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')
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')
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]
7785 + config['foo'] = 'nthth'
7786 + config['mode'] = ''
7787 + mode.mode_updated(None, '')
7788 + self.assertEqual(config['mode'], 'mode0')
7789 + mode.mode_updated(None, config['mode'])
7790 + self.assertEqual(config['mode'], 'mode0')
7791 + self.assertEqual(config['foo'], 'default')
7792 diff --git a/tests/event-manager/testonevent.py b/tests/event-manager/testonevent.py
7793 new file mode 100644
7794 index 0000000..7d80dbd
7796 +++ b/tests/event-manager/testonevent.py
7798 +#!/usr/bin/env python
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
7810 +class OnEventTest(unittest.TestCase):
7812 + self.event_manager = EventManagerMock(
7813 + (), (OnEventPlugin,),
7815 + self.uzbl = self.event_manager.add()
7817 + def test_command(self):
7818 + oe = OnEventPlugin[self.uzbl]
7819 + event, command = 'FOO', 'test test'
7821 + oe.on_event(event, [], command)
7822 + oe.event_handler('', on_event=event)
7823 + self.uzbl.send.assert_called_once_with(command)
7825 + def test_matching_pattern(self):
7826 + oe = OnEventPlugin[self.uzbl]
7827 + event, pattern, command = 'FOO', ['BAR'], 'test test'
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)
7833 + def test_non_matching_pattern(self):
7834 + oe = OnEventPlugin[self.uzbl]
7835 + event, pattern, command = 'FOO', ['BAR'], 'test test'
7837 + oe.on_event(event, pattern, command)
7838 + oe.event_handler('FOO else', on_event=event)
7839 + self.assertFalse(self.uzbl.send.called)
7841 + def test_parse(self):
7842 + oe = OnEventPlugin[self.uzbl]
7843 + event, command = 'FOO', 'test test'
7845 + oe.parse_on_event((event, command))
7846 + self.assertIn(event, oe.events)
7848 + def test_parse_pattern(self):
7849 + oe = OnEventPlugin[self.uzbl]
7850 + event, pattern, command = 'FOO', 'BAR', 'test test'
7852 + oe.parse_on_event((event, '[', pattern, ']', command))
7853 + self.assertIn(event, oe.events)
7854 + commands = oe.events[event]
7855 + self.assertIn(command, commands)
7856 + self.assertEqual(commands[command], [pattern])
7857 diff --git a/tests/event-manager/testprogressbar.py b/tests/event-manager/testprogressbar.py
7858 new file mode 100644
7859 index 0000000..93ebaa8
7861 +++ b/tests/event-manager/testprogressbar.py
7863 +#!/usr/bin/env python
7869 +if '' not in sys.path:
7870 + sys.path.insert(0, '')
7873 +from emtest import EventManagerMock
7874 +from uzbl.plugins.config import Config
7875 +from uzbl.plugins.progress_bar import ProgressBar
7878 +class ProgressBarTest(unittest.TestCase):
7880 + self.event_manager = EventManagerMock(
7881 + (), (ProgressBar,),
7882 + (), ((Config, dict),)
7884 + self.uzbl = self.event_manager.add()
7886 + def test_percent_done(self):
7888 + p, c = ProgressBar[uzbl], Config[uzbl]
7889 + c['progress.format'] = '%c'
7891 + p.update_progress()
7896 + #(101, '100%') # TODO
7899 + for i, o in inout:
7900 + p.update_progress(i)
7901 + self.assertEqual(c['progress.output'], o)
7903 + def test_done_char(self):
7905 + p, c = ProgressBar[uzbl], Config[uzbl]
7906 + c['progress.format'] = '%d'
7908 + p.update_progress()
7913 + (100, '========'),
7917 + for i, o in inout:
7918 + p.update_progress(i)
7919 + self.assertEqual(c['progress.output'], o)
7921 + def test_pending_char(self):
7923 + p, c = ProgressBar[uzbl], Config[uzbl]
7924 + c['progress.format'] = '%p'
7925 + c['progress.pending'] = '-'
7927 + p.update_progress()
7936 + for i, o in inout:
7937 + p.update_progress(i)
7938 + self.assertEqual(c['progress.output'], o)
7940 + def test_percent_pending(self):
7942 + p, c = ProgressBar[uzbl], Config[uzbl]
7943 + c['progress.format'] = '%t'
7945 + p.update_progress()
7951 + #(101, '0%') # TODO
7954 + for i, o in inout:
7955 + p.update_progress(i)
7956 + self.assertEqual(c['progress.output'], o)
7957 diff --git a/uzbl/__init__.py b/uzbl/__init__.py
7958 new file mode 100644
7959 index 0000000..58f0d85
7961 +++ b/uzbl/__init__.py
7964 +Event manager for uzbl
7966 +A event manager for uzbl that supports plugins and multiple simultaneus
7967 +connections from uzbl-core(s)
7969 diff --git a/uzbl/arguments.py b/uzbl/arguments.py
7970 new file mode 100644
7971 index 0000000..7f71b74
7973 +++ b/uzbl/arguments.py
7978 +provides argument parsing for event handlers
7985 +class Arguments(tuple):
7987 + Given a argument line gives access to the split parts
7988 + honoring common quotation and escaping rules
7990 + >>> Arguments(r"simple 'quoted string'")
7991 + ('simple', 'quoted string')
7994 + _splitquoted = re.compile("(\s+|\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')")
7996 + def __new__(cls, s):
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')
8006 + if isinstance(s, tuple):
8007 + self = tuple.__new__(cls, s)
8008 + self._raw, self._ref = s, list(range(len(s)))
8010 + raw = cls._splitquoted.split(s)
8012 + self = tuple.__new__(cls, cls.parse(raw, ref))
8013 + self._raw, self._ref = raw, ref
8017 + def parse(cls, raw, ref):
8019 + Generator used to initialise the arguments tuple
8021 + Indexes to where in source list the arguments start will be put in 'ref'
8024 + for i, part in enumerate(raw):
8025 + if re.match('\s+', part):
8026 + # Whitespace ends the current argument, leading ws is ignored
8033 + # Mark the start of the argument in the raw input
8042 + def raw(self, frm=0, to=None):
8044 + Returs the portion of the raw input that yielded arguments
8045 + from 'frm' to 'to'
8047 + >>> args = Arguments(r"'spam, spam' egg sausage and 'spam'")
8049 + ('spam, spam', 'egg', 'sausage', 'and', 'spam')
8051 + "egg sausage and 'spam'"
8053 + if len(self._ref) < 1:
8055 + rfrm = self._ref[frm]
8056 + if to is None or len(self._ref) <= to + 1:
8057 + rto = len(self._raw)
8059 + rto = self._ref[to + 1] - 1
8060 + return ''.join(self._raw[rfrm:rto])
8062 +splitquoted = Arguments # or define a function?
8066 + return s and s[0] == s[-1] and s[0] in "'\""
8071 + Returns the input string without quotations and with
8072 + escape sequences interpreted
8076 + return ast.literal_eval(s)
8077 + return ast.literal_eval('"' + s + '"')
8078 diff --git a/uzbl/core.py b/uzbl/core.py
8079 new file mode 100644
8080 index 0000000..d0f9010
8086 +from collections import defaultdict
8089 +class Uzbl(object):
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()
8100 + # Flag if the instance has raised the INSTANCE_START event.
8101 + self.instance_start = False
8103 + # Use name "unknown" until name is discovered.
8104 + self.logger = logging.getLogger('uzbl-instance[]')
8106 + # Plugin instances
8107 + self._plugin_instances = []
8110 + # Track plugin event handlers
8111 + self.handlers = defaultdict(list)
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())])])
8124 + def init_plugins(self):
8125 + '''Creates instances of per-instance plugins'''
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
8132 + def send(self, msg):
8133 + '''Send a command to the uzbl instance via the child socket
8138 + if self.opts.print_events:
8139 + print(('%s<-- %s' % (' ' * self._depth, msg)))
8141 + self.proto.push((msg+'\n').encode('utf-8'))
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)`.'''
8147 + # Split by spaces (and fill missing with nulls)
8148 + elems = (line.split(' ', 3) + [''] * 3)[:4]
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))
8157 + # Check event string elements
8158 + (name, event, args) = elems[1:]
8159 + assert name and event, 'event string missing elements'
8162 + self.logger = logging.getLogger('uzbl-instance%s' % name)
8163 + self.logger.info('found instance name %r', name)
8165 + assert self.name == name, 'instance name mismatch'
8167 + # Handle the event with the event handlers through the event method
8168 + self.event(event, args)
8170 + def event(self, event, *args, **kargs):
8171 + '''Raise an event.'''
8173 + event = event.upper()
8175 + if not self.opts.daemon_mode and self.opts.print_events:
8178 + elems.append(str(args))
8180 + elems.append(str(kargs))
8181 + print(('%s--> %s' % (' ' * self._depth, ' '.join(elems))))
8183 + if event == "INSTANCE_START" and args:
8184 + assert not self.instance_start, 'instance already started'
8186 + self.pid = int(args[0])
8187 + self.logger.info('found instance pid %r', self.pid)
8189 + self.init_plugins()
8191 + elif event == "INSTANCE_EXIT":
8192 + self.logger.info('uzbl instance exit')
8195 + if event not in self.handlers:
8198 + for handler in self.handlers[event]:
8201 + handler(*args, **kargs)
8204 + self.logger.error('error in handler', exc_info=True)
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()
8214 + '''Close the client socket and call the plugin cleanup hooks.'''
8216 + self.logger.debug('called close method')
8218 + # Remove self from parent uzbls dict.
8219 + self.logger.debug('removing self from uzbls list')
8220 + self.parent.remove_instance(self.proto.socket)
8222 + for plugin in self._plugin_instances:
8224 + del self.plugins # to avoid cyclic links
8225 + del self._plugin_instances
8227 + self.logger.info('removed %r', self)
8229 + def connect(self, name, handler):
8230 + """Attach event handler
8232 + No extra arguments added. Use bound methods and partials to have
8235 + self.handlers[name].append(handler)
8237 diff --git a/uzbl/event_manager.py b/uzbl/event_manager.py
8238 new file mode 100755
8239 index 0000000..fcf9a47
8241 +++ b/uzbl/event_manager.py
8243 +#!/usr/bin/env python3
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>
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.
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.
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/>.
8265 +E V E N T _ M A N A G E R . P Y
8266 +===============================
8268 +Event manager for uzbl written in python.
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
8291 +from uzbl.net import Listener, Protocol
8292 +from uzbl.core import Uzbl
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.'''
8298 + xdgkey = "XDG_%s_HOME" % key
8299 + if xdgkey in list(os.environ.keys()) and os.environ[xdgkey]:
8300 + return os.environ[xdgkey]
8302 + return os.path.join(os.environ['HOME'], default)
8305 +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/')
8306 +CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/')
8308 +# Define some globals.
8309 +SCRIPTNAME = os.path.basename(sys.argv[0])
8311 +logger = logging.getLogger(SCRIPTNAME)
8315 + '''Format `format_exc` for logging.'''
8316 + return "\n%s" % format_exc().rstrip()
8319 +def expandpath(path):
8320 + '''Expand and realpath paths.'''
8321 + return os.path.realpath(os.path.expandvars(path))
8326 + '''Daemonize the process using the Stevens' double-fork magic.'''
8328 + logger.info('entering daemon mode')
8335 + logger.critical('failed to daemonize', exc_info=True)
8347 + logger.critical('failed to daemonize', exc_info=True)
8350 + if sys.stdout.isatty():
8351 + sys.stdout.flush()
8352 + sys.stderr.flush()
8354 + devnull = '/dev/null'
8355 + stdin = open(devnull, 'r')
8356 + stdout = open(devnull, 'a+')
8357 + stderr = open(devnull, 'a+')
8359 + os.dup2(stdin.fileno(), sys.stdin.fileno())
8360 + os.dup2(stdout.fileno(), sys.stdout.fileno())
8361 + os.dup2(stderr.fileno(), sys.stderr.fileno())
8363 + logger.info('entered daemon mode')
8366 +def make_dirs(path):
8367 + '''Make all basedirs recursively as required.'''
8370 + dirname = os.path.dirname(path)
8371 + if not os.path.isdir(dirname):
8372 + logger.debug('creating directories %r', dirname)
8373 + os.makedirs(dirname)
8376 + logger.error('failed to create directories', exc_info=True)
8379 +class PluginDirectory(object):
8380 + def __init__(self):
8381 + self.global_plugins = []
8382 + self.per_instance_plugins = []
8385 + ''' Import plugin files '''
8387 + import uzbl.plugins
8390 + path = uzbl.plugins.__path__
8391 + for impr, name, ispkg in pkgutil.iter_modules(path, 'uzbl.plugins.'):
8392 + __import__(name, globals(), locals())
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)
8399 +class UzblEventDaemon(object):
8400 + def __init__(self, listener, plugind):
8401 + listener.target = self
8403 + self.listener = listener
8404 + self.plugind = plugind
8405 + self._quit = False
8407 + # Hold uzbl instances
8408 + # {child socket: Uzbl instance, ..}
8413 + # Register that the event daemon server has started by creating the
8415 + make_pid_file(opts.pid_file)
8417 + # Register a function to clean up the socket and pid file on exit.
8418 + atexit.register(self.quit)
8420 + # Add signal handlers.
8421 + for sigint in [SIGTERM, SIGINT]:
8422 + signal(sigint, self.quit)
8424 + # Scan plugin directory for plugins
8425 + self.plugind.load()
8427 + # Initialise global plugins with instances in self.plugins
8428 + self.init_plugins()
8430 + def init_plugins(self):
8431 + '''Initialise event manager plugins.'''
8432 + self._plugin_instances = []
8435 + for plugin in self.plugind.global_plugins:
8436 + pinst = plugin(self)
8437 + self._plugin_instances.append(pinst)
8438 + self.plugins[plugin] = pinst
8441 + '''Main event daemon loop.'''
8443 + logger.debug('entering main loop')
8445 + if opts.daemon_mode:
8446 + # Daemonize the process
8449 + # Update the pid file
8450 + make_pid_file(opts.pid_file)
8454 + # Clean up and exit
8457 + logger.debug('exiting main loop')
8459 + def add_instance(self, sock):
8460 + proto = Protocol(sock)
8461 + uzbl = Uzbl(self, proto, opts)
8462 + self.uzbls[sock] = uzbl
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:
8470 + def close_server_socket(self):
8471 + '''Close and delete the server socket.'''
8474 + self.listener.close()
8477 + logger.error('failed to close server socket', exc_info=True)
8479 + def quit(self, sigint=None, *args):
8480 + '''Close all instance socket objects, server socket and delete the
8483 + if sigint == SIGTERM:
8484 + logger.critical('caught SIGTERM, exiting')
8486 + elif sigint == SIGINT:
8487 + logger.critical('caught SIGINT, exiting')
8489 + elif not self._quit:
8490 + logger.debug('shutting down event manager')
8492 + self.close_server_socket()
8494 + for uzbl in list(self.uzbls.values()):
8497 + if not self._quit:
8498 + for plugin in self._plugin_instances:
8500 + del self.plugins # to avoid cyclic links
8501 + del self._plugin_instances
8503 + del_pid_file(opts.pid_file)
8505 + if not self._quit:
8506 + logger.info('event manager shut down')
8508 + raise SystemExit()
8511 +def make_pid_file(pid_file):
8512 + '''Creates a pid file at `pid_file`, fails silently.'''
8515 + logger.debug('creating pid file %r', pid_file)
8516 + make_dirs(pid_file)
8518 + fileobj = open(pid_file, 'w')
8519 + fileobj.write('%d' % pid)
8521 + logger.info('created pid file %r with pid %d', pid_file, pid)
8524 + logger.error('failed to create pid file', exc_info=True)
8527 +def del_pid_file(pid_file):
8528 + '''Deletes a pid file at `pid_file`, fails silently.'''
8530 + if os.path.isfile(pid_file):
8532 + logger.debug('deleting pid file %r', pid_file)
8533 + os.remove(pid_file)
8534 + logger.info('deleted pid file %r', pid_file)
8537 + logger.error('failed to delete pid file', exc_info=True)
8540 +def get_pid(pid_file):
8541 + '''Reads a pid from pid file `pid_file`, fails None.'''
8544 + logger.debug('reading pid file %r', pid_file)
8545 + fileobj = open(pid_file, 'r')
8546 + pid = int(fileobj.read())
8548 + logger.info('read pid %d from pid file %r', pid, pid_file)
8551 + except (IOError, ValueError):
8552 + logger.error('failed to read pid', exc_info=True)
8556 +def pid_running(pid):
8557 + '''Checks if a process with a pid `pid` is running.'''
8567 +def term_process(pid):
8568 + '''Asks nicely then forces process with pid `pid` to exit.'''
8571 + logger.info('sending SIGTERM to process with pid %r', pid)
8572 + os.kill(pid, SIGTERM)
8575 + logger.error(get_exc())
8577 + logger.debug('waiting for process with pid %r to exit', pid)
8578 + start = time.time()
8580 + if not pid_running(pid):
8581 + logger.debug('process with pid %d exit', pid)
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)
8588 + os.kill(pid, SIGKILL)
8590 + logger.critical('failed to kill %d', pid, exc_info=True)
8593 + if (time.time() - start) > 10:
8594 + logger.critical('unable to kill process with pid %d', pid)
8601 + '''Stop the event manager daemon.'''
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',
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)
8615 + logger.debug('terminating process with pid %r', pid)
8617 + del_pid_file(pid_file)
8618 + logger.info('stopped event manager process with pid %d', pid)
8621 +def start_action():
8622 + '''Start the event manager daemon.'''
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)
8631 + logger.info('no process with pid %d', pid)
8632 + del_pid_file(pid_file)
8634 + listener = Listener(opts.server_socket)
8636 + plugind = PluginDirectory()
8637 + daemon = UzblEventDaemon(listener, plugind)
8641 +def restart_action():
8642 + '''Restart the event manager daemon.'''
8649 + '''List all the plugins that would be loaded in the current search
8652 + from types import ModuleType
8653 + import uzbl.plugins
8655 + for line in pkgutil.iter_modules(uzbl.plugins.__path__, 'uzbl.plugins.'):
8656 + imp, name, ispkg = line
8661 + parser = OptionParser('usage: %prog [options] {start|stop|restart|list}')
8662 + add = parser.add_option
8664 + add('-v', '--verbose',
8665 + dest='verbose', default=2, action='count',
8666 + help='increase verbosity')
8668 + socket_location = os.path.join(CACHE_DIR, 'event_daemon')
8670 + add('-s', '--server-socket',
8671 + dest='server_socket', metavar="SOCKET", default=socket_location,
8672 + help='server AF_UNIX socket location')
8674 + add('-p', '--pid-file',
8675 + metavar="FILE", dest='pid_file',
8676 + help='pid file location, defaults to server socket + .pid')
8678 + add('-n', '--no-daemon',
8679 + dest='daemon_mode', action='store_false', default=True,
8680 + help='do not daemonize the process')
8682 + add('-a', '--auto-close',
8683 + dest='auto_close', action='store_true', default=False,
8684 + help='auto close after all instances disconnect')
8686 + add('-o', '--log-file',
8687 + dest='log_file', metavar='FILE',
8688 + help='write logging output to a file, defaults to server socket +'
8691 + add('-q', '--quiet-events',
8692 + dest='print_events', action="store_false", default=True,
8693 + help="silence the printing of events to stdout")
8699 + log_level = logging.CRITICAL - opts.verbose * 10
8700 + logger = logging.getLogger()
8701 + logger.setLevel(max(log_level, 10))
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)
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)
8721 + parser = make_parser()
8723 + (opts, args) = parser.parse_args()
8725 + opts.server_socket = expandpath(opts.server_socket)
8727 + # Set default pid file location
8728 + if not opts.pid_file:
8729 + opts.pid_file = "%s.pid" % opts.server_socket
8732 + opts.pid_file = expandpath(opts.pid_file)
8734 + # Set default log file location
8735 + if not opts.log_file:
8736 + opts.log_file = "%s.log" % opts.server_socket
8739 + opts.log_file = expandpath(opts.log_file)
8743 + logger.info('logging to %r', opts.log_file)
8745 + if opts.auto_close:
8746 + logger.debug('will auto close')
8748 + logger.debug('will not auto close')
8750 + if opts.daemon_mode:
8751 + logger.debug('will daemonize')
8753 + logger.debug('will not daemonize')
8755 + # init like {start|stop|..} daemon actions
8756 + daemon_actions = {'start': start_action, 'stop': stop_action,
8757 + 'restart': restart_action, 'list': list_action}
8759 + if len(args) == 1:
8761 + if action not in daemon_actions:
8762 + parser.error('invalid action: %r' % action)
8766 + logger.warning('no daemon action given, assuming %r', action)
8769 + parser.error('invalid action argument: %r' % args)
8771 + logger.info('daemon action %r', action)
8773 + daemon_actions[action]()
8775 + logger.debug('process CPU time: %f', time.clock())
8778 +if __name__ == "__main__":
8783 diff --git a/uzbl/ext.py b/uzbl/ext.py
8784 new file mode 100644
8785 index 0000000..b2795ed
8789 +from .event_manager import Uzbl
8793 +per_instance_registry = []
8794 +global_registry = []
8797 +class PluginMeta(type):
8798 + """Registers plugin in registry so that it instantiates when needed"""
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
8806 + if issubclass(self, PerInstancePlugin):
8807 + per_instance_registry.append(self)
8808 + elif issubclass(self, GlobalPlugin):
8809 + global_registry.append(self)
8811 + def __getitem__(self, owner):
8812 + """This method returns instance of plugin corresponding to owner
8814 + :param owner: can be uzbl or event manager
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`
8821 + return self._get_instance(owner)
8824 +class BasePlugin(object, metaclass=PluginMeta):
8825 + """Base class for all uzbl plugins"""
8828 +class PerInstancePlugin(BasePlugin):
8829 + """Base class for plugins which instantiate once per uzbl instance"""
8831 + def __init__(self, uzbl):
8833 + self.logger = uzbl.logger # we can also append plugin name to logger
8835 + def cleanup(self):
8836 + """Cleanup state after instance is gone
8838 + Default function avoids cyclic refrences, so don't forget to call
8839 + super() if overriding
8844 + def _get_instance(cls, owner):
8845 + """Returns instance of the plugin
8847 + This method should be private to not violate TOOWTDI
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]
8856 +class GlobalPlugin(BasePlugin):
8857 + """Base class for plugins which instantiate once per daemon"""
8859 + def __init__(self, event_manager):
8860 + self.event_manager = event_manager
8861 + self.logger = logging.getLogger(self.__module__)
8864 + def _get_instance(cls, owner):
8865 + """Returns instance of the plugin
8867 + This method should be private to not violate TOOWTDI
8869 + if isinstance(owner, Uzbl):
8870 + owner = owner.parent
8871 + # TODO(tailhook) probably subclasses can be returned as well
8872 + return owner.plugins[cls]
8874 + def cleanup(self):
8875 + """Cleanup state after instance is gone
8877 + Default function avoids cyclic refrences, so don't forget to call
8878 + super() if overriding
8880 + del self.event_manager
8881 diff --git a/uzbl/net.py b/uzbl/net.py
8882 new file mode 100644
8883 index 0000000..2c34e7b
8887 +# Network communication classes
8895 +logger = logging.getLogger('uzbl.net')
8898 +class NoTargetSet(Exception):
8902 +class TargetAlreadySet(Exception):
8906 +class WithTarget(object):
8908 + Mixin that adds a property 'target' than can only be set once and
8909 + raises an exception if not set when accesed
8915 + return self._target
8916 + except AttributeError as e:
8917 + raise NoTargetSet("No target for %r" % self, e)
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
8925 + self._target = value
8928 +class Listener(asyncore.dispatcher, WithTarget):
8929 + ''' Waits for new connections and accept()s them '''
8931 + def __init__(self, addr, target=None):
8932 + asyncore.dispatcher.__init__(self)
8934 + self.target = target
8937 + self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
8939 + self.set_reuse_addr()
8940 + self.bind(self.addr)
8944 + '''Unlink existing socket if it's stale'''
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)
8950 + s.connect(self.addr)
8951 + except socket.error as e:
8952 + logger.info('unlinking %r', self.addr)
8953 + os.unlink(self.addr)
8955 + def writable(self):
8958 + def handle_accept(self):
8960 + sock, addr = self.accept()
8961 + except socket.error:
8964 + self.target.add_instance(sock)
8967 + super(Listener, self).close()
8968 + if os.path.exists(self.addr):
8969 + logger.info('unlinking %r', self.addr)
8970 + os.unlink(self.addr)
8972 + def handle_error(self):
8976 +class Protocol(asynchat.async_chat):
8977 + ''' A connection with a single client '''
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')
8986 + def collect_incoming_data(self, data):
8987 + self.buffer += data
8989 + def found_terminator(self):
8990 + val = self.buffer.decode('utf-8')
8991 + del self.buffer[:]
8992 + self.target.parse_msg(val)
8994 + def handle_error(self):
8996 diff --git a/uzbl/plugins/__init__.py b/uzbl/plugins/__init__.py
8997 new file mode 100644
8998 index 0000000..84cf2ec
9000 +++ b/uzbl/plugins/__init__.py
9005 +plugins for use with uzbl-event-manager
9010 +plugin_path = os.environ.get("UZBL_PLUGIN_PATH",
9011 + "~/.local/share/uzbl/plugins:/usr/share/uzbl/site-plugins",
9014 + __path__ = list(map(os.path.expanduser, plugin_path)) + __path__
9016 diff --git a/uzbl/plugins/bind.py b/uzbl/plugins/bind.py
9017 new file mode 100644
9018 index 0000000..f40da36
9020 +++ b/uzbl/plugins/bind.py
9022 +'''Plugin provides support for binds in uzbl.
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'")
9029 +And it is also possible to execute a function on activation:
9030 + bind('DD', myhandler)
9035 +from functools import partial
9036 +from itertools import count
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
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
9052 +# For accessing a bind glob stack.
9053 +ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = list(range(5))
9057 +class ArgumentError(Exception): pass
9060 +class Bindlet(object):
9061 + '''Per-instance bind status/state tracker.'''
9063 + def __init__(self, uzbl):
9064 + self.binds = {'global': {}}
9066 + self.uzbl_config = Config[uzbl]
9069 + self.last_mode = None
9070 + self.after_cmds = None
9071 + self.stack_binds = []
9073 + # A subset of the global mode binds containing non-stack and modkey
9074 + # activiated binds for use in the stack mode.
9078 + def __getitem__(self, key):
9079 + return self.get_binds(key)
9083 + '''Reset the tracker state and return to last mode.'''
9087 + self.after_cmds = None
9088 + self.stack_binds = []
9090 + if self.last_mode:
9091 + mode, self.last_mode = self.last_mode, None
9092 + self.uzbl_config['mode'] = mode
9094 + del self.uzbl_config['keycmd_prompt']
9097 + def stack(self, bind, args, depth):
9098 + '''Enter or add new bind in the next stack level.'''
9100 + if self.depth != depth:
9101 + if bind not in self.stack_binds:
9102 + self.stack_binds.append(bind)
9106 + mode = self.uzbl_config.get('mode', None)
9107 + if mode != 'stack':
9108 + self.last_mode = mode
9109 + self.uzbl_config['mode'] = 'stack'
9111 + self.stack_binds = [bind,]
9114 + self.after_cmds = bind.prompts[depth]
9118 + '''If a stack was triggered then set the prompt and default value.'''
9120 + if self.after_cmds is None:
9123 + (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None
9125 + KeyCmd[self.uzbl].clear_keycmd()
9127 + self.uzbl_config['keycmd_prompt'] = prompt
9129 + if set and is_cmd:
9130 + self.uzbl.send(set)
9132 + elif set and not is_cmd:
9133 + self.uzbl.send('event SET_KEYCMD %s' % set)
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.'''
9141 + mode = self.uzbl_config.get('mode', None)
9147 + return self.stack_binds + self.globals
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]
9153 + binds = dict(list(globals.items()) + list(self.binds[mode].items()))
9154 + return [_f for _f in list(binds.values()) if _f]
9157 + def add_bind(self, mode, glob, bind=None):
9158 + '''Insert (or override) a bind into the mode bind dict.'''
9160 + if mode not in self.binds:
9161 + self.binds[mode] = {glob: bind}
9164 + binds = self.binds[mode]
9165 + binds[glob] = bind
9167 + if mode == 'global':
9168 + # Regen the global-globals list.
9170 + for bind in list(binds.values()):
9171 + if bind is not None and bind.is_global:
9172 + self.globals.append(bind)
9175 +def ismodbind(glob):
9176 + '''Return True if the glob specifies a modbind.'''
9178 + return bool(MOD_START(glob))
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.'''
9187 + match = MOD_START(glob)
9191 + end = match.span()[1]
9192 + mods.add(glob[:end])
9195 + return (mods, glob)
9198 +class Bind(object):
9200 + # unique id generator
9201 + nextid = count().__next__
9203 + def __init__(self, glob, handler, *args, **kargs):
9204 + self.is_callable = isinstance(handler, collections.Callable)
9205 + self._repr_cache = None
9208 + raise ArgumentError('glob cannot be blank')
9210 + if self.is_callable:
9211 + self.function = handler
9213 + self.kargs = kargs
9216 + raise ArgumentError('cannot supply kargs for uzbl commands')
9218 + elif not isinstance(handler, str):
9219 + self.commands = handler
9222 + self.commands = [handler,] + list(args)
9226 + # Assign unique id.
9227 + self.bid = self.nextid()
9229 + self.split = split = FIND_PROMPTS(glob)
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
9237 + self.prompts.append((prompt, cmd, set))
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)
9245 + # Check that there is nothing like: fl<prompt1:><prompt2:>_
9246 + for glob in split[4::4]:
9248 + msg = 'found null segment after first prompt: %r' % split
9249 + raise SyntaxError(msg)
9252 + for (index, glob) in enumerate(reversed(split[::4])):
9253 + # Is the binding a MODCMD or KEYCMD:
9254 + mod_cmd = ismodbind(glob)
9256 + # Do we execute on UPDATES or EXEC events?
9257 + on_exec = True if glob[-1] in ['!', '_'] else False
9259 + # Does the command take arguments?
9260 + has_args = True if glob[-1] in ['*', '_'] else False
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))
9266 + self.stack = list(reversed(stack))
9267 + self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD])
9270 + def __getitem__(self, depth):
9271 + '''Get bind info at a depth.'''
9273 + if self.is_global:
9274 + return self.stack[0]
9276 + return self.stack[depth]
9279 + def __repr__(self):
9280 + if self._repr_cache:
9281 + return self._repr_cache
9283 + args = ['glob=%r' % self.glob, 'bid=%d' % self.bid]
9285 + if self.is_callable:
9286 + args.append('function=%r' % self.function)
9288 + args.append('args=%r' % self.args)
9291 + args.append('kargs=%r' % self.kargs)
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))
9298 + self._repr_cache = '<Bind(%s)>' % ', '.join(args)
9299 + return self._repr_cache
9302 +class BindPlugin(PerInstancePlugin):
9303 + def __init__(self, uzbl):
9304 + '''Export functions and connect handlers to events.'''
9305 + super(BindPlugin, self).__init__(uzbl)
9307 + self.bindlet = Bindlet(uzbl)
9309 + uzbl.connect('BIND', self.parse_bind)
9310 + uzbl.connect('MODE_BIND', self.parse_mode_bind)
9311 + uzbl.connect('MODE_CHANGED', self.mode_changed)
9313 + # Connect key related events to the key_event function.
9314 + events = [['KEYCMD_UPDATE', 'KEYCMD_EXEC'],
9315 + ['MODCMD_UPDATE', 'MODCMD_EXEC']]
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)
9325 + def exec_bind(self, bind, *args, **kargs):
9326 + '''Execute bind objects.'''
9328 + self.uzbl.event("EXEC_BIND", bind, args, kargs)
9330 + if bind.is_callable:
9332 + kargs = dict(list(bind.kargs.items())+list(kargs.items()))
9333 + bind.function(self.uzbl, *args, **kargs)
9337 + raise ArgumentError('cannot supply kargs for uzbl commands')
9340 + for cmd in bind.commands:
9341 + cmd = cmd_expand(cmd, args)
9342 + self.uzbl.send(cmd)
9344 + def mode_bind(self, modes, glob, handler=None, *args, **kargs):
9345 + '''Add a mode bind.'''
9347 + bindlet = self.bindlet
9349 + if isinstance(modes, str):
9350 + modes = modes.split(',')
9352 + # Sort and filter binds.
9353 + modes = [_f for _f in map(str.strip, modes) if _f]
9355 + if isinstance(handler, collections.Callable) or (handler is not None and handler.strip()):
9356 + bind = Bind(glob, handler, *args, **kargs)
9361 + for mode in modes:
9362 + if not VALID_MODE(mode):
9363 + raise NameError('invalid mode name: %r' % mode)
9365 + for mode in modes:
9366 + if mode[0] == '-':
9367 + mode, bind = mode[1:], None
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)
9373 + def bind(self, glob, handler, *args, **kargs):
9374 + '''Legacy bind function.'''
9376 + self.mode_bind('global', glob, handler, *args, **kargs)
9378 + def parse_mode_bind(self, args):
9379 + '''Parser for the MODE_BIND event.
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 ... = ...
9389 + args = splitquoted(args)
9391 + raise ArgumentError('missing mode or bind section: %r' % args.raw())
9393 + modes = args[0].split(',')
9394 + for i, g in enumerate(args[1:]):
9396 + glob = args.raw(1, i)
9397 + command = args.raw(i+2)
9400 + raise ArgumentError('missing delimiter in bind section: %r' % args.raw())
9402 + self.mode_bind(modes, glob, command)
9404 + def parse_bind(self, args):
9405 + '''Legacy parsing of the BIND event and conversion to the new format.
9408 + request BIND <bind> = <command>
9409 + request BIND o<location:>_ = uri %s
9410 + request BIND <BackSpace> = ...
9411 + request BIND ... = ...
9414 + self.parse_mode_bind("global %s" % args)
9416 + def mode_changed(self, mode):
9417 + '''Clear the stack on all non-stack mode changes.'''
9419 + if mode != 'stack':
9420 + self.bindlet.reset()
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
9426 + if mod_cmd and modstate != mod_cmd:
9430 + if not cmd.startswith(glob):
9433 + args = [cmd[len(glob):],]
9441 + if bind.is_global or (not more and depth == 0):
9442 + self.exec_bind(bind, *args)
9444 + KeyCmd[self.uzbl].clear_current()
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+['',]))
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']
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():
9470 + if (bool(t[MOD_CMD]) != mod_cmd) or (t[ON_EXEC] != on_exec):
9473 + if self.match_and_exec(bind, depth, modstate, keylet, bindlet):
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']
9485 diff --git a/uzbl/plugins/cmd_expand.py b/uzbl/plugins/cmd_expand.py
9486 new file mode 100644
9487 index 0000000..46e1e67
9489 +++ b/uzbl/plugins/cmd_expand.py
9492 + for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]:
9493 + str = str.replace(char, (level * '\\') + char)
9498 +def cmd_expand(cmd, args):
9499 + '''Exports a function that provides the following
9500 + expansions in any uzbl command string:
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])
9509 + # Ensure (1) all string representable and (2) correct string encoding.
9510 + args = list(map(str, args))
9512 + # Direct string replace.
9514 + cmd = cmd.replace('%s', ' '.join(args))
9516 + # Escaped and quoted string replace.
9518 + cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args)))
9520 + # Arg index string replace.
9521 + for (index, arg) in enumerate(args):
9523 + if '%%%d' % index in cmd:
9524 + cmd = cmd.replace('%%%d' % index, str(arg))
9527 diff --git a/uzbl/plugins/completion.py b/uzbl/plugins/completion.py
9528 new file mode 100644
9529 index 0000000..ef2f277
9531 +++ b/uzbl/plugins/completion.py
9533 +'''Keycmd completion.'''
9537 +from uzbl.arguments import splitquoted
9538 +from uzbl.ext import PerInstancePlugin
9539 +from .config import Config
9540 +from .keycmd import KeyCmd
9543 +NONE, ONCE, LIST, COMPLETE = list(range(4))
9545 +# The reverse keyword finding re.
9546 +FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall
9550 + return str.replace("@", "\@")
9553 +class Completions(set):
9554 + def __init__(self):
9555 + set.__init__(self)
9556 + self.locked = False
9560 + self.locked = True
9563 + self.locked = False
9565 + def add_var(self, var):
9566 + self.add('@' + var)
9569 +class CompletionListFormatter(object):
9570 + LIST_FORMAT = "<span> %s </span>"
9571 + ITEM_FORMAT = "<span @hint_style>%s</span>%s"
9573 + def format(self, partial, completions):
9575 + completions.sort()
9576 + return self.LIST_FORMAT % ' '.join(
9577 + [self.ITEM_FORMAT % (escape(h[:p]), h[p:]) for h in completions]
9581 +class CompletionPlugin(PerInstancePlugin):
9582 + def __init__(self, uzbl):
9583 + '''Export functions and connect handlers to events.'''
9584 + super(CompletionPlugin, self).__init__(uzbl)
9586 + self.completion = Completions()
9587 + self.listformatter = CompletionListFormatter()
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)
9597 + uzbl.send('dump_config_as_events')
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.'''
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)
9611 + return (partial, False)
9613 + def stop_completion(self, *args):
9614 + '''Stop command completion and return the level to NONE.'''
9616 + self.completion.level = NONE
9617 + if 'completion_list' in Config[self.uzbl]:
9618 + del Config[self.uzbl]['completion_list']
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.'''
9624 + if set_completion:
9625 + remainder = "%s = " % hint[len(partial):]
9628 + remainder = "%s " % hint[len(partial):]
9630 + KeyCmd[self.uzbl].inject_keycmd(remainder)
9631 + self.stop_completion()
9633 + def partial_completion(self, partial, hint):
9634 + '''Inject a common portion of the hints into the keycmd.'''
9636 + remainder = hint[len(partial):]
9637 + KeyCmd[self.uzbl].inject_keycmd(remainder)
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.'''
9643 + partial = self.get_incomplete_keyword()[0]
9645 + return self.stop_completion()
9647 + if self.completion.level < LIST:
9650 + config = Config[self.uzbl]
9652 + hints = [h for h in self.completion if h.startswith(partial)]
9654 + del config['completion_list']
9657 + config['completion_list'] = self.listformatter.format(partial, hints)
9659 + def start_completion(self, *args):
9660 + if self.completion.locked:
9663 + (partial, set_completion) = self.get_incomplete_keyword()
9665 + return self.stop_completion()
9667 + if self.completion.level < COMPLETE:
9668 + self.completion.level += 1
9670 + hints = [h for h in self.completion if h.startswith(partial)]
9674 + elif len(hints) == 1:
9675 + self.completion.lock()
9676 + self.complete_completion(partial, hints[0], set_completion)
9677 + self.completion.unlock()
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()
9686 + smalllen, smallest = sorted([(len(h), h) for h in hints])[0]
9688 + for i in range(len(partial), smalllen):
9689 + char, same = smallest[i], True
9690 + for hint in hints:
9691 + if hint[i] != char:
9701 + self.completion.lock()
9702 + self.partial_completion(partial, partial + common)
9703 + self.completion.unlock()
9705 + self.update_completion_list()
9707 + def add_builtins(self, builtins):
9708 + '''Pump the space delimited list of builtin commands into the
9711 + builtins = splitquoted(builtins)
9712 + self.completion.update(builtins)
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.'''
9718 + self.completion.add_var(key)
9719 diff --git a/uzbl/plugins/config.py b/uzbl/plugins/config.py
9720 new file mode 100644
9721 index 0000000..4f00315
9723 +++ b/uzbl/plugins/config.py
9725 +from re import compile
9727 +from uzbl.arguments import splitquoted
9728 +from uzbl.ext import PerInstancePlugin
9730 +types = {'int': int, 'float': float, 'str': str}
9732 +valid_key = compile('^[A-Za-z0-9_\.]+$').match
9734 +class Config(PerInstancePlugin):
9735 + """Configuration plugin, has dictionary interface for config access
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.
9742 + def __init__(self, uzbl):
9743 + super(Config, self).__init__(uzbl)
9746 + uzbl.connect('VARIABLE_SET', self.parse_set_event)
9747 + assert not 'a' in self.data
9749 + def __getitem__(self, key):
9750 + return self.data[key]
9752 + def __setitem__(self, key, value):
9753 + self.set(key, value)
9755 + def __delitem__(self, key):
9758 + def get(self, key, default=None):
9759 + return self.data.get(key, default)
9761 + def __contains__(self, key):
9762 + return key in self.data
9765 + return iter(self.data.keys())
9768 + return iter(self.data.items())
9770 + def update(self, other=None, **kwargs):
9774 + for (key, value) in list(dict(other).items()) + list(kwargs.items()):
9778 + def set(self, key, value='', force=False):
9779 + '''Generates a `set <key> = <value>` command string to send to the
9780 + current uzbl instance.
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.'''
9786 + assert valid_key(key)
9788 + if isinstance(value, bool):
9789 + value = int(value)
9792 + value = str(value)
9793 + assert '\n' not in value
9795 + if not force and key in self and self[key] == value:
9798 + self.uzbl.send('set %s = %s' % (key, value))
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.'''
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
9811 + raise Exception('Invalid number of arguments')
9813 + assert valid_key(key)
9814 + assert type in types
9816 + new_value = types[type](raw_value)
9817 + old_value = self.data.get(key, None)
9819 + # Update new value.
9820 + self.data[key] = new_value
9822 + if old_value != new_value:
9823 + self.uzbl.event('CONFIG_CHANGED', key, new_value)
9825 + # Cleanup null config values.
9826 + if type == 'str' and not new_value:
9827 + del self.data[key]
9829 + def cleanup(self):
9830 + # not sure it's needed, but safer for cyclic links
9832 + super(Config, self).cleanup()
9834 diff --git a/uzbl/plugins/cookies.py b/uzbl/plugins/cookies.py
9835 new file mode 100644
9836 index 0000000..8875e99
9838 +++ b/uzbl/plugins/cookies.py
9840 +""" Basic cookie manager
9841 + forwards cookies to all other instances connected to the event manager"""
9843 +from collections import defaultdict
9844 +import os, re, stat
9846 +from uzbl.arguments import splitquoted
9847 +from uzbl.ext import GlobalPlugin, PerInstancePlugin
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}
9852 +# allows for partial cookies
9853 +# ? allow wildcard in key
9854 +def match(key, cookie):
9855 + for k,c in zip(key,cookie):
9860 +def match_list(_list, cookie):
9861 + for matcher in _list:
9862 + for component, match in matcher:
9863 + if match(cookie[component]) is None:
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.
9877 + args = splitquoted(arg)
9879 + for (component, regexp) in zip(args[0::2], args[1::2]):
9881 + component = symbolic[component]
9883 + component = int(component)
9884 + assert component <= 5
9885 + mlist.append((component, re.compile(regexp).search))
9886 + _list.append(mlist)
9888 +class NullStore(object):
9889 + def add_cookie(self, rawcookie, cookie):
9892 + def delete_cookie(self, rkey, key):
9895 +class ListStore(list):
9896 + def add_cookie(self, rawcookie, cookie):
9897 + self.append(rawcookie)
9899 + def delete_cookie(self, rkey, key):
9900 + self[:] = [x for x in self if not match(key, splitquoted(x))]
9902 +class TextStore(object):
9903 + def __init__(self, filename):
9904 + self.filename = filename
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)
9914 + def as_event(self, cookie):
9915 + """Convert cookie.txt row to uzbls cookie event format"""
9921 + if cookie[0].startswith("#HttpOnly_"):
9923 + domain = cookie[0][len("#HttpOnly_"):]
9924 + elif cookie[0].startswith('#'):
9927 + domain = cookie[0]
9933 + scheme[cookie[3]] + extra,
9935 + except (KeyError,IndexError):
9936 + # Let malformed rows pass through like comments
9939 + def as_file(self, cookie):
9940 + """Convert cookie event to cookie.txt row"""
9944 + 'httpsOnly' : 'TRUE',
9945 + 'httpOnly' : 'FALSE'
9950 + 'httpsOnly' : '#HttpOnly_',
9951 + 'httpOnly' : '#HttpOnly_'
9953 + return (http_only[cookie[4]] + cookie[0],
9954 + 'TRUE' if cookie[0].startswith('.') else 'FALSE',
9956 + secure[cookie[4]],
9961 + def add_cookie(self, rawcookie, cookie):
9962 + assert len(cookie) == 6
9964 + # delete equal cookies (ignoring expire time, value and secure flag)
9965 + self.delete_cookie(None, cookie[:-3])
9967 + # restrict umask before creating the cookie jar
9968 + curmask=os.umask(0)
9969 + os.umask(curmask| stat.S_IRWXO | stat.S_IRWXG)
9971 + first = not os.path.exists(self.filename)
9972 + with open(self.filename, 'a') as f:
9974 + print("# HTTP Cookie File", file=f)
9975 + print('\t'.join(self.as_file(cookie)), file=f)
9977 + def delete_cookie(self, rkey, key):
9978 + if not os.path.exists(self.filename):
9981 + # restrict umask before creating the cookie jar
9982 + curmask=os.umask(0)
9983 + os.umask(curmask | stat.S_IRWXO | stat.S_IRWXG)
9985 + # read all cookies
9986 + with open(self.filename, 'r') as f:
9987 + cookies = f.readlines()
9989 + # write those that don't match the cookie to delete
9990 + with open(self.filename, 'w') as f:
9992 + c = self.as_event(l.split('\t'))
9993 + if c is None or not match(key, c):
9994 + print(l, end='', file=f)
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'))
10001 +class Cookies(PerInstancePlugin):
10002 + def __init__(self, uzbl):
10003 + super(Cookies, self).__init__(uzbl)
10005 + self.whitelist = []
10006 + self.blacklist = []
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)
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)
10022 + return not match_list(self.blacklist, cookie)
10024 + def expires_with_session(self, cookie):
10025 + return cookie[5] == ''
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]
10032 + def get_store(self, session=False):
10034 + return SessionStore
10035 + return DefaultStore
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())
10043 + self.get_store(self.expires_with_session(cookie)).add_cookie(cookie.raw(), cookie)
10045 + self.logger.debug('cookie %r is blacklisted', cookie)
10046 + self.uzbl.send('delete_cookie %s' % cookie.raw())
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())
10053 + if len(cookie) == 6:
10054 + self.get_store(self.expires_with_session(cookie)).delete_cookie(cookie.raw(), cookie)
10056 + for store in set([self.get_store(session) for session in (True, False)]):
10057 + store.delete_cookie(cookie.raw(), cookie)
10059 + def blacklist_cookie(self, arg):
10060 + add_cookie_matcher(self.blacklist, arg)
10062 + def whitelist_cookie(self, arg):
10063 + add_cookie_matcher(self.whitelist, arg)
10065 +# vi: set et ts=4:
10066 diff --git a/uzbl/plugins/downloads.py b/uzbl/plugins/downloads.py
10067 new file mode 100644
10068 index 0000000..208dbda
10070 +++ b/uzbl/plugins/downloads.py
10072 +# this plugin does a very simple display of download progress. to use it, add
10073 +# @downloads to your status_format.
10078 +from uzbl.arguments import splitquoted
10079 +from .config import Config
10080 +from uzbl.ext import PerInstancePlugin
10082 +class Downloads(PerInstancePlugin):
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 = {}
10091 + def update_download_section(self):
10092 + """after a download's status has changed this
10093 + is called to update the status bar
10096 + if self.active_downloads:
10097 + # add a newline before we list downloads
10098 + result = ' downloads:'
10099 + for path, progress in list(self.active_downloads.items()):
10100 + # add each download
10101 + fn = os.path.basename(path)
10103 + dl = " %s (%d%%)" % (fn, progress * 100)
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 above to make a new line)
10108 + dl = html.escape(dl)
10113 + # and the result gets saved to an uzbl variable that can be used in
10115 + config = Config[self.uzbl]
10116 + if config.get('downloads', '') != result:
10117 + config['downloads'] = result
10119 + def download_started(self, args):
10120 + # parse the arguments
10121 + args = splitquoted(args)
10122 + destination_path = args[0]
10124 + # add to the list of active downloads
10125 + self.active_downloads[destination_path] = 0.0
10127 + # update the progress
10128 + self.update_download_section()
10130 + def download_progress(self, args):
10131 + # parse the arguments
10132 + args = splitquoted(args)
10133 + destination_path = args[0]
10134 + progress = float(args[1])
10136 + # update the progress
10137 + self.active_downloads[destination_path] = progress
10139 + # update the status bar variable
10140 + self.update_download_section()
10142 + def download_complete(self, args):
10143 + # TODO(tailhook) be more userfriendly: show download for some time!
10145 + # parse the arguments
10146 + args = splitquoted(args)
10147 + destination_path = args[0]
10149 + # remove from the list of active downloads
10150 + del self.active_downloads[destination_path]
10152 + # update the status bar variable
10153 + self.update_download_section()
10155 diff --git a/uzbl/plugins/history.py b/uzbl/plugins/history.py
10156 new file mode 100644
10157 index 0000000..1a0e0fb
10159 +++ b/uzbl/plugins/history.py
10163 +from .on_set import OnSetPlugin
10164 +from .keycmd import KeyCmd
10165 +from uzbl.ext import GlobalPlugin, PerInstancePlugin
10167 +class SharedHistory(GlobalPlugin):
10169 + def __init__(self, event_manager):
10170 + super(SharedHistory, self).__init__(event_manager)
10171 + self.history = {} #TODO(tailhook) save and load from file
10173 + def get_line_number(self, prompt):
10175 + return len(self.history[prompt])
10179 + def addline(self, prompt, entry):
10180 + lst = self.history.get(prompt)
10182 + self.history[prompt] = [entry]
10184 + lst.append(entry)
10186 + def getline(self, prompt, index):
10188 + return self.history[prompt][index]
10190 + # not existent list is same as empty one
10191 + raise IndexError()
10194 +class History(PerInstancePlugin):
10196 + def __init__(self, uzbl):
10197 + super(History, self).__init__(uzbl)
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))
10210 + shared = SharedHistory[self.uzbl]
10211 + if self.cursor is None:
10212 + self.cursor = shared.get_line_number(self.prompt) - 1
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:
10223 + if self.cursor >= 0:
10224 + return shared.getline(self.prompt, self.cursor)
10227 + return random.choice(end_messages)
10229 + def __next__(self):
10230 + if self.cursor is None:
10232 + shared = SharedHistory[self.uzbl]
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:
10244 + if self.cursor >= num:
10245 + self.cursor = None
10246 + self.search_key = None
10248 + value = self._tail
10249 + self._tail = None
10252 + return shared.getline(self.prompt, self.cursor)
10254 + def change_prompt(self, prompt):
10255 + self.prompt = prompt
10256 + self._tail = None
10258 + def search(self, key):
10259 + self.search_key = key
10260 + self.cursor = None
10262 + def __str__(self):
10263 + return "(History %s, %s)" % (self.cursor, self.prompt)
10265 + def keycmd_exec(self, modstate, keylet):
10266 + cmd = keylet.get_keycmd()
10268 + SharedHistory[self.uzbl].addline(self.prompt, cmd)
10269 + self._tail = None
10270 + self.cursor = None
10271 + self.search_key = None
10273 + def history_prev(self, _x):
10274 + cmd = KeyCmd[self.uzbl].keylet.get_keycmd()
10275 + if self.cursor is None and cmd:
10277 + val = self.prev()
10278 + KeyCmd[self.uzbl].set_keycmd(val)
10280 + def history_next(self, _x):
10281 + KeyCmd[self.uzbl].set_keycmd(next(self))
10283 + def history_search(self, key):
10285 + self.uzbl.send('event HISTORY_PREV')
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.',
10308 +# vi: set et ts=4:
10309 diff --git a/uzbl/plugins/keycmd.py b/uzbl/plugins/keycmd.py
10310 new file mode 100644
10311 index 0000000..2020fda
10313 +++ b/uzbl/plugins/keycmd.py
10317 +from uzbl.arguments import splitquoted
10318 +from uzbl.ext import PerInstancePlugin
10319 +from .config import Config
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>"
10326 +# FIXME, add utility functions to shared module
10328 + for char in ['\\', '@']:
10329 + str = str.replace(char, '\\'+char)
10334 +def uzbl_escape(str):
10335 + return "@[%s]@" % escape(str) if str else ''
10338 +def inject_str(str, index, inj):
10339 + '''Inject a string into string at at given index.'''
10341 + return "%s%s%s" % (str[:index], inj, str[index:])
10344 +class Keylet(object):
10345 + '''Small per-instance object that tracks characters typed.
10348 + >>> k.set_keycmd('spam')
10350 + <keylet(keycmd='spam')>
10351 + >>> k.append_keycmd(' and egg')
10353 + <keylet(keycmd='spam and egg')>
10354 + >>> print(k.cursor)
10358 + def __init__(self):
10359 + # Modcmd tracking
10361 + self.is_modcmd = False
10363 + # Keycmd tracking
10367 + def get_keycmd(self):
10368 + ''' Get the keycmd-part of the keylet. '''
10370 + return self.keycmd
10372 + def clear_keycmd(self):
10373 + ''' Clears the keycmd part of the keylet '''
10378 + def get_modcmd(self):
10379 + ''' Get the modcmd-part of the keylet. '''
10381 + if not self.is_modcmd:
10384 + return self.modcmd
10386 + def clear_modcmd(self):
10388 + self.is_modcmd = False
10390 + def set_keycmd(self, keycmd):
10391 + self.keycmd = keycmd
10392 + self.cursor = len(keycmd)
10394 + def insert_keycmd(self, s):
10395 + ''' Inserts string at the current position
10398 + >>> k.set_keycmd('spam')
10400 + >>> k.insert_keycmd('egg')
10402 + <keylet(keycmd='seggpam')>
10403 + >>> print(k.cursor)
10407 + self.keycmd = inject_str(self.keycmd, self.cursor, s)
10408 + self.cursor += len(s)
10410 + def append_keycmd(self, s):
10411 + ''' Appends string to to end of keycmd and moves the cursor
10414 + >>> k.set_keycmd('spam')
10416 + >>> k.append_keycmd('egg')
10418 + <keylet(keycmd='spamegg')>
10419 + >>> print(k.cursor)
10424 + self.cursor = len(self.keycmd)
10426 + def backspace(self):
10427 + ''' Removes the character at the cursor position. '''
10428 + if not self.keycmd or not self.cursor:
10431 + self.keycmd = self.keycmd[:self.cursor-1] + self.keycmd[self.cursor:]
10435 + def delete(self):
10436 + ''' Removes the character after the cursor position. '''
10437 + if not self.keycmd:
10440 + self.keycmd = self.keycmd[:self.cursor] + self.keycmd[self.cursor+1:]
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
10448 + >>> k.set_keycmd('spam and egg')
10449 + >>> k.strip_word()
10452 + <keylet(keycmd='spam and ')>
10453 + >>> k.strip_word()
10456 + <keylet(keycmd='spam ')>
10458 + if not self.keycmd:
10461 + head, tail = self.keycmd[:self.cursor].rstrip(seps), self.keycmd[self.cursor:]
10464 + p = head.rfind(sep)
10465 + if p >= 0 and rfind < p + 1:
10467 + if rfind == len(head) and head[-1] in seps:
10469 + self.keycmd = head[:rfind] if rfind + 1 else '' + tail
10470 + self.cursor = len(head)
10471 + return head[rfind:]
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
10479 + >>> k.set_keycmd('spam and egg')
10480 + >>> k.set_cursor_pos(2)
10482 + >>> k.set_cursor_pos(-3)
10484 + >>> k.set_cursor_pos('+')
10489 + cursor = self.cursor - 1
10491 + elif index == '+':
10492 + cursor = self.cursor + 1
10495 + cursor = int(index)
10497 + cursor = len(self.keycmd) + cursor + 1
10502 + if cursor > len(self.keycmd):
10503 + cursor = len(self.keycmd)
10505 + self.cursor = cursor
10506 + return self.cursor
10508 + def markup(self):
10509 + ''' Returns the keycmd with the cursor in pango markup spliced in
10512 + >>> k.set_keycmd('spam and egg')
10513 + >>> k.set_cursor_pos(4)
10516 + '@[spam]@<span @cursor_style>@[ ]@</span>@[and egg]@'
10519 + if self.cursor < len(self.keycmd):
10520 + curchar = self.keycmd[self.cursor]
10523 + chunks = [self.keycmd[:self.cursor], curchar, self.keycmd[self.cursor+1:]]
10524 + return KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks))
10526 + def __repr__(self):
10527 + ''' Return a string representation of the keylet. '''
10530 + if self.is_modcmd:
10531 + l.append('modcmd=%r' % self.get_modcmd())
10534 + l.append('keycmd=%r' % self.get_keycmd())
10536 + return '<keylet(%s)>' % ', '.join(l)
10539 +class KeyCmd(PerInstancePlugin):
10540 + def __init__(self, uzbl):
10541 + '''Export functions and connect handlers to events.'''
10542 + super(KeyCmd, self).__init__(uzbl)
10544 + self.keylet = Keylet()
10545 + self.modmaps = {}
10546 + self.ignores = {}
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)
10564 + def modmap_key(self, key):
10565 + '''Make some obscure names for some keys friendlier.'''
10567 + if key in self.modmaps:
10568 + return self.modmaps[key]
10570 + elif key.endswith('_L') or key.endswith('_R'):
10571 + # Remove left-right discrimination and try again.
10572 + return self.modmap_key(key[:-2])
10578 + def key_ignored(self, key):
10579 + '''Check if the given key is ignored by any ignore rules.'''
10581 + for (glob, match) in list(self.ignores.items()):
10587 + def add_modmap(self, key, map):
10591 + set modmap = request MODMAP
10592 + @modmap <Control> <Ctrl>
10593 + @modmap <ISO_Left_Tab> <Shift-Tab>
10597 + @bind <Shift-Tab> = <command1>
10598 + @bind <Ctrl>x = <command2>
10604 + modmaps = self.modmaps
10606 + modmaps[key.strip('<>')] = map.strip('<>')
10607 + self.uzbl.event("NEW_MODMAP", key, map)
10609 + def modmap_parse(self, map):
10610 + '''Parse a modmap definiton.'''
10612 + split = splitquoted(map)
10614 + if not split or len(split) > 2:
10615 + raise Exception('Invalid modmap arugments: %r' % map)
10617 + self.add_modmap(*split)
10619 + def add_key_ignore(self, glob):
10620 + '''Add an ignore definition.
10623 + set ignore_key = request IGNORE_KEY
10624 + @ignore_key <Shift>
10625 + @ignore_key <ISO_*>
10629 + assert len(glob) > 1
10630 + ignores = self.ignores
10632 + glob = "<%s>" % glob.strip("<> ")
10633 + restr = glob.replace('*', '[^\s]*')
10634 + match = re.compile(restr).match
10636 + ignores[glob] = match
10637 + self.uzbl.event('NEW_KEY_IGNORE', glob)
10639 + def clear_keycmd(self, *args):
10640 + '''Clear the keycmd for this uzbl instance.'''
10642 + self.keylet.clear_keycmd()
10643 + config = Config[self.uzbl]
10644 + del config['keycmd']
10645 + self.uzbl.event('KEYCMD_CLEARED')
10647 + def clear_modcmd(self):
10648 + '''Clear the modcmd for this uzbl instance.'''
10650 + self.keylet.clear_modcmd()
10652 + config = Config[self.uzbl]
10653 + del config['modcmd']
10654 + self.uzbl.event('MODCMD_CLEARED')
10656 + def clear_current(self):
10657 + '''Clear the modcmd if is_modcmd else clear keycmd.'''
10659 + if self.keylet.is_modcmd:
10660 + self.clear_modcmd()
10663 + self.clear_keycmd()
10665 + def update_event(self, modstate, k, execute=True):
10666 + '''Raise keycmd & modcmd update events.'''
10668 + keycmd, modcmd = k.get_keycmd(), ''.join(modstate) + k.get_modcmd()
10671 + self.logger.debug('modcmd_update, %s', modcmd)
10672 + self.uzbl.event('MODCMD_UPDATE', modstate, k)
10675 + self.logger.debug('keycmd_update, %s', keycmd)
10676 + self.uzbl.event('KEYCMD_UPDATE', modstate, k)
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']
10685 + elif new_modcmd == modcmd:
10686 + config['modcmd'] = MODCMD_FORMAT % uzbl_escape(modcmd)
10688 + if config.get('keycmd_events', '1') != '1':
10691 + new_keycmd = k.get_keycmd()
10692 + if not new_keycmd:
10693 + del config['keycmd']
10695 + elif new_keycmd == keycmd:
10696 + # Generate the pango markup for the cursor in the keycmd.
10697 + config['keycmd'] = str(k.markup())
10699 + def parse_key_event(self, key):
10700 + ''' Build a set from the modstate part of the event, and pass all keys through modmap '''
10702 + modstate, key = splitquoted(key)
10703 + modstate = set(['<%s>' % self.modmap_key(k) for k in modstate.split('|') if k])
10705 + key = self.modmap_key(key)
10706 + return modstate, key
10708 + def key_press(self, key):
10709 + '''Handle KEY_PRESS events. Things done by this function include:
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.'''
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)
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(' ')
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
10731 + del config['keycmd']
10734 + k.insert_keycmd(key)
10736 + elif len(key) == 1:
10740 + if not self.key_ignored('<%s>' % key):
10741 + modstate.add('<%s>' % key)
10742 + k.is_modcmd = True
10744 + self.update_event(modstate, k)
10746 + def key_release(self, key):
10747 + '''Respond to KEY_RELEASE event. Things done by this function include:
10749 + 1. If in a mod-command then raise a MODCMD_EXEC.
10750 + 2. Update the keycmd uzbl variable if anything changed.'''
10752 + modstate, key = self.parse_key_event(key)
10756 + self.uzbl.event('MODCMD_EXEC', modstate, k)
10758 + self.clear_modcmd()
10760 + def set_keycmd(self, keycmd):
10761 + '''Allow setting of the keycmd externally.'''
10763 + self.keylet.set_keycmd(keycmd)
10764 + self.update_event(set(), self.keylet, False)
10766 + def inject_keycmd(self, keycmd):
10767 + '''Allow injecting of a string into the keycmd at the cursor position.'''
10769 + self.keylet.insert_keycmd(keycmd)
10770 + self.update_event(set(), self.keylet, False)
10772 + def append_keycmd(self, keycmd):
10773 + '''Allow appening of a string to the keycmd.'''
10775 + self.keylet.append_keycmd(keycmd)
10776 + self.update_event(set(), self.keylet, False)
10778 + def keycmd_strip_word(self, args):
10779 + ''' Removes the last word from the keycmd, similar to readline ^W '''
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)
10787 + def keycmd_backspace(self, *args):
10788 + '''Removes the character at the cursor position in the keycmd.'''
10790 + if self.keylet.backspace():
10791 + self.update_event(set(), self.keylet, False)
10793 + def keycmd_delete(self, *args):
10794 + '''Removes the character after the cursor position in the keycmd.'''
10796 + if self.keylet.delete():
10797 + self.update_event(set(), self.keylet, False)
10799 + def keycmd_exec_current(self, *args):
10800 + '''Raise a KEYCMD_EXEC with the current keylet and then clear the
10803 + self.uzbl.event('KEYCMD_EXEC', set(), self.keylet)
10804 + self.clear_keycmd()
10806 + def set_cursor_pos(self, args):
10807 + '''Allow setting of the cursor position externally. Supports negative
10808 + indexing and relative stepping with '+' and '-'.'''
10810 + args = splitquoted(args)
10811 + assert len(args) == 1
10813 + self.keylet.set_cursor_pos(args[0])
10814 + self.update_event(set(), self.keylet, False)
10816 +# vi: set et ts=4:
10818 diff --git a/uzbl/plugins/mode.py b/uzbl/plugins/mode.py
10819 new file mode 100644
10820 index 0000000..6eaf009
10822 +++ b/uzbl/plugins/mode.py
10824 +from collections import defaultdict
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
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)
10841 + def cleanup(self):
10842 + self.mode_config.clear()
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.'''
10848 + args = splitquoted(args)
10849 + assert len(args) >= 3, 'missing mode config args %r' % args
10852 + assert args[2] == '=', 'invalid mode config set syntax'
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)):
10859 + value = args.raw(3).strip()
10861 + self.logger.debug('value %r', value)
10863 + self.mode_config[mode][key] = value
10864 + config = Config[self.uzbl]
10865 + if config.get('mode', None) == mode:
10866 + config[key] = value
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
10874 + def mode_updated(self, var, mode):
10875 + config = Config[self.uzbl]
10877 + mode = config.get('default_mode', 'command')
10878 + self.logger.debug('setting mode to default %r' % mode)
10879 + config['mode'] = mode
10882 + # Load mode config
10883 + mode_config = self.mode_config.get(mode, None)
10885 + config.update(mode_config)
10887 + self.uzbl.send('event MODE_CONFIRM %s' % mode)
10889 + def confirm_change(self, mode):
10890 + config = Config[self.uzbl]
10891 + if mode and config.get('mode', None) == mode:
10892 + self.uzbl.event('MODE_CHANGED', mode)
10893 diff --git a/uzbl/plugins/on_event.py b/uzbl/plugins/on_event.py
10894 new file mode 100644
10895 index 0000000..cf33799
10897 +++ b/uzbl/plugins/on_event.py
10899 +'''Plugin provides arbitrary binding of uzbl events to uzbl commands.
10901 +Formatting options:
10902 + %s = space separated string of the arguments
10903 + %r = escaped and quoted version of %s
10909 + request ON_EVENT LINK_HOVER set selected_uri = $1
10910 + --> LINK_HOVER http://uzbl.org/
10911 + <-- set selected_uri = http://uzbl.org/
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/
10920 +from functools import partial
10922 +from uzbl.arguments import splitquoted
10923 +from .cmd_expand import cmd_expand
10924 +from uzbl.ext import PerInstancePlugin
10926 +def match_args(pattern, args):
10927 + if len(pattern) > len(args):
10929 + for p, a in zip(pattern, args):
10930 + if not fnmatch.fnmatch(a, p):
10935 +class OnEventPlugin(PerInstancePlugin):
10937 + def __init__(self, uzbl):
10938 + '''Export functions and connect handlers to events.'''
10939 + super(OnEventPlugin, self).__init__(uzbl)
10943 + uzbl.connect('ON_EVENT', self.parse_on_event)
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.'''
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])
10953 + event = kargs['on_event']
10954 + if event not in self.events:
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)
10963 + def on_event(self, event, pattern, cmd):
10964 + '''Add a new event to watch and respond to.'''
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] = {}
10973 + cmds = self.events[event]
10974 + if cmd not in cmds:
10975 + cmds[cmd] = pattern
10977 + def parse_on_event(self, args):
10978 + '''Parse ON_EVENT events and pass them to the on_event function.
10980 + Syntax: "event ON_EVENT <EVENT_NAME> commands".'''
10982 + args = splitquoted(args)
10983 + assert args, 'missing on event arguments'
10985 + # split arguments into event name, optional argument pattern and command
10988 + if args[1] == '[':
10989 + for i, arg in enumerate(args[2:]):
10992 + pattern.append(arg)
10993 + command = args.raw(3+i)
10995 + command = args.raw(1)
10997 + assert event and command, 'missing on event command'
10998 + self.on_event(event, pattern, command)
11000 + def cleanup(self):
11001 + self.events.clear()
11002 + super(OnEventPlugin, self).cleanup()
11004 +# vi: set et ts=4:
11005 diff --git a/uzbl/plugins/on_set.py b/uzbl/plugins/on_set.py
11006 new file mode 100644
11007 index 0000000..f6b9229
11009 +++ b/uzbl/plugins/on_set.py
11011 +from re import compile
11012 +from functools import partial
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
11020 +valid_glob = compile('^[A-Za-z0-9_\*\.]+$').match
11022 +def make_matcher(glob):
11023 + '''Make matcher function from simple glob.'''
11025 + pattern = "^%s$" % glob.replace('*', '[^\s]*')
11026 + return compile(pattern).match
11029 +class OnSetPlugin(PerInstancePlugin):
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)
11037 + def _exec_handlers(self, handlers, key, arg):
11038 + '''Execute the on_set handlers that matched the key.'''
11040 + for handler in handlers:
11041 + if isinstance(handler, collections.Callable):
11042 + handler(key, arg)
11044 + self.uzbl.send(cmd_expand(handler, [key, arg]))
11046 + def check_for_handlers(self, key, arg):
11047 + '''Check for handlers for the current key.'''
11049 + for (matcher, handlers) in list(self.on_sets.values()):
11051 + self._exec_handlers(handlers, key, arg)
11053 + def on_set(self, glob, handler, prepend=True):
11054 + '''Add a new handler for a config key change.
11056 + Structure of the `self.on_sets` dict:
11057 + { glob : ( glob matcher function, handlers list ), .. }
11060 + assert valid_glob(glob)
11062 + while '**' in glob:
11063 + glob = glob.replace('**', '*')
11065 + if isinstance(handler, collections.Callable):
11066 + orig_handler = handler
11068 + handler = partial(handler, self.uzbl)
11071 + orig_handler = handler = str(handler)
11073 + if glob in self.on_sets:
11074 + (matcher, handlers) = self.on_sets[glob]
11075 + handlers.append(handler)
11078 + matcher = make_matcher(glob)
11079 + self.on_sets[glob] = (matcher, [handler,])
11081 + self.logger.info('on set %r call %r' % (glob, orig_handler))
11084 + def parse_on_set(self, args):
11085 + '''Parse `ON_SET <glob> <command>` event then pass arguments to the
11086 + `on_set(..)` function.'''
11088 + args = splitquoted(args)
11089 + assert len(args) >= 2
11091 + command = args.raw(1)
11093 + assert glob and command and valid_glob(glob)
11094 + self.on_set(glob, command)
11096 diff --git a/uzbl/plugins/progress_bar.py b/uzbl/plugins/progress_bar.py
11097 new file mode 100644
11098 index 0000000..5eb10d3
11100 +++ b/uzbl/plugins/progress_bar.py
11103 +from .config import Config
11105 +from uzbl.ext import PerInstancePlugin
11107 +class ProgressBar(PerInstancePlugin):
11108 + splitfrmt = re.compile(r'(%[A-Z][^%]|%[^%])').split
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)
11116 + def update_progress(self, progress=None):
11117 + '''Updates the progress.output variable on LOAD_PROGRESS update.
11119 + The current substitution options are:
11120 + %d = done char * done
11121 + %p = pending char * remaining
11122 + %c = percent done
11124 + %s = -\|/ spinner
11125 + %t = percent pending
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
11138 + if progress is None:
11143 + self.updates += 1
11144 + progress = int(progress)
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 ' '
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]
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])
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
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
11174 + done = int(((progress/100.0)*width)+0.5)
11175 + pending = width - done
11177 + # values to replace with
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),
11189 + frmt = ''.join([str(values[k[1:]]) if k.startswith('%') else k for
11190 + k in self.splitfrmt(frmt)])
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
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)
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