From 9dd1370d0b7cd876f004f7a822b0357039252184 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 20:11:15 +0100 Subject: remove the 'uzbl' subdirectory in examples/*/, since the sandbox they are no longer needed + update paths everywhere + remove examples/config/enchant (also not needed since sandbox) + bugfix: set /home/dieter in sandbox --- .gitignore | 1 - Makefile | 18 +- examples/config/config | 376 ++++++ examples/config/cookies | 22 + examples/config/uzbl/config | 376 ------ examples/config/uzbl/cookies | 22 - examples/data/bookmarks | 4 + examples/data/forms/bbs.archlinux.org | 5 + examples/data/plugins/bind.py | 521 +++++++ examples/data/plugins/cmd_expand.py | 42 + examples/data/plugins/completion.py | 206 +++ examples/data/plugins/config.py | 97 ++ examples/data/plugins/keycmd.py | 571 ++++++++ examples/data/plugins/mode.py | 176 +++ examples/data/plugins/on_event.py | 107 ++ examples/data/plugins/plugin_template.py | 76 ++ examples/data/plugins/progress_bar.py | 159 +++ examples/data/scripts/cookies.sh | 154 +++ examples/data/scripts/download.sh | 22 + examples/data/scripts/extedit.js | 102 ++ examples/data/scripts/follow_Numbers.js | 228 ++++ examples/data/scripts/follow_Numbers_Strings.js | 212 +++ examples/data/scripts/formfiller.pl | 99 ++ examples/data/scripts/formfiller.sh | 62 + examples/data/scripts/hint.js | 26 + examples/data/scripts/history.sh | 5 + examples/data/scripts/insert_bookmark.sh | 16 + examples/data/scripts/instance-select-wmii.sh | 54 + examples/data/scripts/linkfollow.js | 269 ++++ examples/data/scripts/load_url_from_bookmarks.sh | 20 + examples/data/scripts/load_url_from_history.sh | 24 + examples/data/scripts/scheme.py | 24 + examples/data/scripts/scroll-percentage.js | 68 + examples/data/scripts/session.sh | 62 + examples/data/scripts/uzbl-cookie-daemon | 664 +++++++++ examples/data/scripts/uzbl-event-manager | 833 ++++++++++++ examples/data/scripts/uzbl-tabbed | 1417 ++++++++++++++++++++ examples/data/scripts/uzblcat | 12 + examples/data/style.css | 25 + examples/data/uzbl.png | Bin 0 -> 2185 bytes examples/data/uzbl/bookmarks | 4 - examples/data/uzbl/forms/bbs.archlinux.org | 5 - examples/data/uzbl/plugins/bind.py | 521 ------- examples/data/uzbl/plugins/cmd_expand.py | 42 - examples/data/uzbl/plugins/completion.py | 206 --- examples/data/uzbl/plugins/config.py | 97 -- examples/data/uzbl/plugins/keycmd.py | 571 -------- examples/data/uzbl/plugins/mode.py | 176 --- examples/data/uzbl/plugins/on_event.py | 107 -- examples/data/uzbl/plugins/plugin_template.py | 76 -- examples/data/uzbl/plugins/progress_bar.py | 159 --- examples/data/uzbl/scripts/cookies.sh | 154 --- examples/data/uzbl/scripts/download.sh | 22 - examples/data/uzbl/scripts/extedit.js | 102 -- examples/data/uzbl/scripts/follow_Numbers.js | 228 ---- .../data/uzbl/scripts/follow_Numbers_Strings.js | 212 --- examples/data/uzbl/scripts/formfiller.pl | 99 -- examples/data/uzbl/scripts/formfiller.sh | 62 - examples/data/uzbl/scripts/hint.js | 26 - examples/data/uzbl/scripts/history.sh | 5 - examples/data/uzbl/scripts/insert_bookmark.sh | 16 - examples/data/uzbl/scripts/instance-select-wmii.sh | 54 - examples/data/uzbl/scripts/linkfollow.js | 269 ---- .../data/uzbl/scripts/load_url_from_bookmarks.sh | 20 - .../data/uzbl/scripts/load_url_from_history.sh | 24 - examples/data/uzbl/scripts/scheme.py | 24 - examples/data/uzbl/scripts/scroll-percentage.js | 68 - examples/data/uzbl/scripts/session.sh | 62 - examples/data/uzbl/scripts/uzbl-cookie-daemon | 664 --------- examples/data/uzbl/scripts/uzbl-event-manager | 833 ------------ examples/data/uzbl/scripts/uzbl-tabbed | 1417 -------------------- examples/data/uzbl/scripts/uzblcat | 12 - examples/data/uzbl/style.css | 25 - examples/data/uzbl/uzbl.png | Bin 2185 -> 0 bytes sandbox/env.sh | 9 +- src/uzbl-browser | 2 +- 76 files changed, 6780 insertions(+), 6770 deletions(-) create mode 100644 examples/config/config create mode 100644 examples/config/cookies delete mode 100644 examples/config/uzbl/config delete mode 100644 examples/config/uzbl/cookies create mode 100644 examples/data/bookmarks create mode 100644 examples/data/forms/bbs.archlinux.org create mode 100644 examples/data/plugins/bind.py create mode 100644 examples/data/plugins/cmd_expand.py create mode 100644 examples/data/plugins/completion.py create mode 100644 examples/data/plugins/config.py create mode 100644 examples/data/plugins/keycmd.py create mode 100644 examples/data/plugins/mode.py create mode 100644 examples/data/plugins/on_event.py create mode 100644 examples/data/plugins/plugin_template.py create mode 100644 examples/data/plugins/progress_bar.py create mode 100755 examples/data/scripts/cookies.sh create mode 100755 examples/data/scripts/download.sh create mode 100644 examples/data/scripts/extedit.js create mode 100644 examples/data/scripts/follow_Numbers.js create mode 100644 examples/data/scripts/follow_Numbers_Strings.js create mode 100755 examples/data/scripts/formfiller.pl create mode 100755 examples/data/scripts/formfiller.sh create mode 100644 examples/data/scripts/hint.js create mode 100755 examples/data/scripts/history.sh create mode 100755 examples/data/scripts/insert_bookmark.sh create mode 100755 examples/data/scripts/instance-select-wmii.sh create mode 100644 examples/data/scripts/linkfollow.js create mode 100755 examples/data/scripts/load_url_from_bookmarks.sh create mode 100755 examples/data/scripts/load_url_from_history.sh create mode 100755 examples/data/scripts/scheme.py create mode 100644 examples/data/scripts/scroll-percentage.js create mode 100755 examples/data/scripts/session.sh create mode 100755 examples/data/scripts/uzbl-cookie-daemon create mode 100755 examples/data/scripts/uzbl-event-manager create mode 100755 examples/data/scripts/uzbl-tabbed create mode 100755 examples/data/scripts/uzblcat create mode 100644 examples/data/style.css create mode 100644 examples/data/uzbl.png delete mode 100644 examples/data/uzbl/bookmarks delete mode 100644 examples/data/uzbl/forms/bbs.archlinux.org delete mode 100644 examples/data/uzbl/plugins/bind.py delete mode 100644 examples/data/uzbl/plugins/cmd_expand.py delete mode 100644 examples/data/uzbl/plugins/completion.py delete mode 100644 examples/data/uzbl/plugins/config.py delete mode 100644 examples/data/uzbl/plugins/keycmd.py delete mode 100644 examples/data/uzbl/plugins/mode.py delete mode 100644 examples/data/uzbl/plugins/on_event.py delete mode 100644 examples/data/uzbl/plugins/plugin_template.py delete mode 100644 examples/data/uzbl/plugins/progress_bar.py delete mode 100755 examples/data/uzbl/scripts/cookies.sh delete mode 100755 examples/data/uzbl/scripts/download.sh delete mode 100644 examples/data/uzbl/scripts/extedit.js delete mode 100644 examples/data/uzbl/scripts/follow_Numbers.js delete mode 100644 examples/data/uzbl/scripts/follow_Numbers_Strings.js delete mode 100755 examples/data/uzbl/scripts/formfiller.pl delete mode 100755 examples/data/uzbl/scripts/formfiller.sh delete mode 100644 examples/data/uzbl/scripts/hint.js delete mode 100755 examples/data/uzbl/scripts/history.sh delete mode 100755 examples/data/uzbl/scripts/insert_bookmark.sh delete mode 100755 examples/data/uzbl/scripts/instance-select-wmii.sh delete mode 100644 examples/data/uzbl/scripts/linkfollow.js delete mode 100755 examples/data/uzbl/scripts/load_url_from_bookmarks.sh delete mode 100755 examples/data/uzbl/scripts/load_url_from_history.sh delete mode 100755 examples/data/uzbl/scripts/scheme.py delete mode 100644 examples/data/uzbl/scripts/scroll-percentage.js delete mode 100755 examples/data/uzbl/scripts/session.sh delete mode 100755 examples/data/uzbl/scripts/uzbl-cookie-daemon delete mode 100755 examples/data/uzbl/scripts/uzbl-event-manager delete mode 100755 examples/data/uzbl/scripts/uzbl-tabbed delete mode 100755 examples/data/uzbl/scripts/uzblcat delete mode 100644 examples/data/uzbl/style.css delete mode 100644 examples/data/uzbl/uzbl.png diff --git a/.gitignore b/.gitignore index 803a154..078164f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ uzbl-core *.pyc *~ tags -examples/config/enchant diff --git a/Makefile b/Makefile index f48d1f0..626d21e 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,7 @@ test-uzbl-browser: uzbl-browser test-uzbl-core-sandbox: uzbl-core make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core + make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data source ./sandbox/env.sh && uzbl-core --uri http://www.uzbl.org --verbose make DESTDIR=./sandbox uninstall rm -rf ./sandbox/usr @@ -72,6 +73,7 @@ test-uzbl-core-sandbox: uzbl-core test-uzbl-browser-sandbox: uzbl-browser make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-browser + make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data source ./sandbox/env.sh && uzbl-cookie-daemon restart -nv & source ./sandbox/env.sh && uzbl-event-manager restart -nav & source ./sandbox/env.sh && uzbl-browser --uri http://www.uzbl.org --verbose @@ -102,19 +104,27 @@ install-uzbl-core: all install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core install -m644 AUTHORS $(INSTALLDIR)/share/uzbl/docs install -m644 README $(INSTALLDIR)/share/uzbl/docs - sed -i 's#^set prefix.*=.*#set prefix = $(RUN_PREFIX)#' $(INSTALLDIR)/share/uzbl/examples/config/uzbl/config + sed -i 's#^set prefix.*=.*#set prefix = $(RUN_PREFIX)#' $(INSTALLDIR)/share/uzbl/examples/config/config install-uzbl-browser: install -d $(INSTALLDIR)/bin install -m755 src/uzbl-browser $(INSTALLDIR)/bin/uzbl-browser - install -m755 examples/data/uzbl/scripts/uzbl-cookie-daemon $(INSTALLDIR)/bin/uzbl-cookie-daemon - install -m755 examples/data/uzbl/scripts/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager + install -m755 examples/data/scripts/uzbl-cookie-daemon $(INSTALLDIR)/bin/uzbl-cookie-daemon + install -m755 examples/data/scripts/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager sed -i 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' $(INSTALLDIR)/bin/uzbl-browser sed -i "s#^PREFIX = .*#PREFIX = '$(RUN_PREFIX)'#" $(INSTALLDIR)/bin/uzbl-event-manager install-uzbl-tabbed: install -d $(INSTALLDIR)/bin - install -m755 examples/data/uzbl/scripts/uzbl-tabbed $(INSTALLDIR)/bin/uzbl-tabbed + install -m755 examples/data/scripts/uzbl-tabbed $(INSTALLDIR)/bin/uzbl-tabbed + +# you probably only want to do this manually when testing and/or to the sandbox. not meant for distributors +install-example-data: + install -d $(INSTALLDIR)/home/.config/uzbl + install -d $(INSTALLDIR)/home/.cache/uzbl + install -d $(INSTALLDIR)/home/.local/share/uzbl + cp -rp examples/config/* $(INSTALLDIR)/home/.config/uzbl/ + cp -rp examples/data/* $(INSTALLDIR)/home/.local/share/uzbl/ uninstall: rm -rf $(INSTALLDIR)/bin/uzbl-* diff --git a/examples/config/config b/examples/config/config new file mode 100644 index 0000000..eb17813 --- /dev/null +++ b/examples/config/config @@ -0,0 +1,376 @@ +# example uzbl config. +# all settings are optional. you can use uzbl without any config at all (but it won't do much) + +set prefix = /usr/local + +# === Shortcuts / Aliases =================================================== + +# Config related events (use the request function): +# request BIND = +set bind = request BIND +# request MODE_BIND = +set mode_bind = request MODE_BIND +# request MODE_CONFIG = +set mode_config = request MODE_CONFIG +# request ON_EVENT +set on_event = request ON_EVENT +# request PROGRESS_CONFIG = +set progress = request PROGRESS_CONFIG +# request MODMAP +set modmap = request MODMAP +# request IGNORE_KEY +set ignore_key = request IGNORE_KEY +# request MODKEY_ADDITION +set modkey_addition = request MODKEY_ADDITION + +# Action related events (use the event function): +# event TOGGLE_MODES ... +set toggle_modes = event TOGGLE_MODES + +set set_mode = set mode = +set set_status = set status_message = +set shell_cmd = sh -c + +# Spawn path shortcuts. In spawn the first dir+path match is used in "dir1:dir2:dir3:executable" +set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data:scripts + +# Javascipt helpers. +set jsh = js var run=Uzbl.run; function get(k){return run("print \\\@"+k)}; function set(k, v) {run("set "+k+" = "+v)}; + +# === Handlers =============================================================== + +# --- Hardcoded event handlers ----------------------------------------------- + +# These handlers can't be moved to the new event system yet as we don't +# support events that can wait for a response from a script. +set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket +set scheme_handler = sync_spawn @scripts_dir/scheme.py + +# Open in the same window. +#set new_window = sh 'echo uri "$8" > $4' +# Open a link in a new window. equivalent to default behavior +set new_window = sh 'uzbl-browser -u $8' + +# --- Optional dynamic event handlers ---------------------------------------- + +# Download handler +@on_event DOWNLOAD_REQUEST spawn @scripts_dir/download.sh %s \@proxy_url + +# Load start handler +@on_event LOAD_START @set_status wait + +# Load commit handlers +@on_event LOAD_COMMIT @set_status recv +@on_event LOAD_COMMIT script @scripts_dir/scroll-percentage.js +# Reset the keycmd on navigation +@on_event LOAD_COMMIT @set_mode + +# Load finish handlers +@on_event LOAD_FINISH @set_status done +@on_event LOAD_FINISH spawn @scripts_dir/history.sh + +# Generate a FORM_ACTIVE event if an editable +# element on the loaded site has initial focus +@on_event LOAD_FINISH js if(document.activeElement.type == 'text') {Uzbl.run("event FORM_ACTIVE");} + +# Switch to insert mode if a (editable) html form is clicked +@on_event FORM_ACTIVE @set_mode insert +# Switch to command mode if anything else is clicked +@on_event ROOT_ACTIVE @set_mode command + +# Example CONFIG_CHANGED event handler +#@on_event CONFIG_CHANGED print Config changed: %1 = %2 + +# === Behaviour and appearance =============================================== + +set show_status = 1 +set status_top = 0 +set status_background = #303030 + +set modcmd_style = weight="bold" foreground="red" +set keycmd_style = weight="light" foreground="red" +set prompt_style = foreground="grey" +set cursor_style = underline="single" +set completion_style = foreground="green" +set hint_style = weight="bold" + +set mode_section = [\@[\@mode_indicator]\@] +set keycmd_section = [\@[\@keycmd_prompt]\@\@modcmd\@keycmd\@completion_list] +set progress_section = \@[\@progress_format]\@ +set scroll_section = \@[\@scroll_message]\@ +set uri_section = \@[\@uri]\@ +set name_section = \@[\@NAME]\@ +set status_section = \@status_message +set selected_section = \@[\@SELECTED_URI]\@ + +set status_format = @mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section + +set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI + +# Progress bar config +@progress width = 8 +# %d = done, %p = pending %c = percent done, %i = int done, %s = spinner, +# %t = percent pending, %o = int pending, %r = sprite scroll +@progress format = [%d>%p]%c +@progress done = = +@progress pending = + +# Or ride those spinnas' +#@progress format = [%d%s%p] +#@progress spinner = -\\|/ +#@progress done = - +#@progress pending = + + +# === Core settings ========================================================== + +set useragent = Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO) (@(+uname -o)@ @(+uname -m)@ [@ARCH_UZBL]) (Commit @COMMIT) +set fifo_dir = /tmp +set socket_dir = /tmp + + +# === Key modmapping and ignoring ============================================ + +#modmap +@modmap +@modmap +@modmap + +#modkey_addition +@modkey_addition +@modkey_addition + +#ignore_key +@ignore_key +@ignore_key + + +# === Mode bind aliases ====================================================== + +# Global binding alias (this is done automatically inside the bind plugin). +#set bind = @mode_bind global + +# Insert mode binding alias +set ibind = @mode_bind insert + +# Command mode binding alias +set cbind = @mode_bind command + +# Non-insert mode bindings alias (ebind for edit-bind). +set ebind = @mode_bind global,-insert + + +# === Global & keycmd editing binds ========================================== + +# Resets keycmd and returns to default mode. +@bind = @set_mode + +# Commands for editing and traversing the keycmd. +@ebind = event KEYCMD_EXEC_CURRENT +@ebind = event SET_CURSOR_POS +@ebind = event SET_CURSOR_POS -1 +@ebind = event SET_CURSOR_POS - +@ebind = event SET_CURSOR_POS + +@ebind = event KEYCMD_BACKSPACE +@ebind = event KEYCMD_DELETE +@ebind = event START_COMPLETION +# Readline-ish bindings. +@ebind w = event KEYCMD_STRIP_WORD +@ebind u = event SET_KEYCMD +@ebind a = event SET_CURSOR_POS 0 +@ebind e = event SET_CURSOR_POS -1 + +# Keycmd injection/append examples. +#@ebind su = event INJECT_KEYCMD \@uri +#@ebind st = event INJECT_KEYCMD \@title +#@ebind du = event APPEND_KEYCMD \@uri +#@ebind dt = event APPEND_KEYCMD \@title + + +# === Mouse bindings ========================================================= + +# Middle click +# if clicked on a link open the link in a new uzbl window +# otherwise open the selection in the current window +set load_from_xclip = sh 'echo "uri $(xclip -o)" > $4' +set open_new_window = sh 'uzbl-browser -u \@SELECTED_URI' +@bind = @jsh if(get("SELECTED_URI")) { run("\@open_new_window"); } else { run("\\\@load_from_xclip"); } + + +# === Keyboard bindings ====================================================== + +# With this command you can enter in any command at runtime when prefixed with +# a colon. +@cbind :_ = %s + +# --- Page movement binds --- +@cbind j = scroll vertical 20 +@cbind k = scroll vertical -20 +@cbind h = scroll horizontal -20 +@cbind l = scroll horizontal 20 +@cbind = scroll vertical -100% +@cbind = scroll vertical 100% +@cbind << = scroll vertical begin +@cbind >> = scroll vertical end +@cbind ^ = scroll horizontal begin +@cbind $ = scroll horizontal end +@cbind = scroll vertical end + +# --- Navigation binds --- +@cbind b = back +@cbind m = forward +@cbind S = stop +@cbind r = reload +@cbind R = reload_ign_cache + +# --- Zoom binds --- +@cbind + = zoom_in +@cbind - = zoom_out +@cbind T = toggle_zoom_type +@cbind 1 = set zoom_level 1.0 +@cbind 2 = set zoom_level 2.0 + +# --- Appearance binds --- +@cbind t = toggle_status + +# --- Page searching binds --- +@cbind /* = search %s +@cbind ?* = search_reverse %s +# Jump to next and previous items +@cbind n = search +@cbind N = search_reverse + +# --- Web searching binds --- +@cbind gg_ = uri http://www.google.com/search?q=\@\@ +@cbind \\awiki_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=\@\@&go=Go +@cbind \\wiki_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=\@\@&go=Go + +# --- Handy binds --- +# Set function shortcut +@cbind s__ = set %1 = %2 +# Exit binding +@cbind ZZ = exit +# Dump config to stdout +@cbind !dump = sh "echo dump_config > $4" +# Reload config +@cbind !reload = sh "sed '/^# === Post-load misc commands/,$d' $1 > $4" +# Uzbl Terminal. TODO explain why this is useful +@cbind t = sh 'xterm -e "socat unix-connect:$5 -"' +#@cbind t = sh 'urxvt -e socat unix-connect:$5 -' + +# --- Uri opening prompts --- +@cbind o_ = uri %s +# Or have it load the current uri into the keycmd for editing +@cbind O_ = uri %s + +# --- Mode setting binds --- +# Changing mode via set. +@cbind I = @set_mode insert +# Or toggle between modes by raising the toggle event. +set toggle_cmd_ins = @toggle_modes command insert +@cbind i = @toggle_cmd_ins +# And the global toggle bind. +@bind i = @toggle_cmd_ins + +# --- Hard-bound bookmarks --- +@cbind gh = uri http://www.uzbl.org + +# --- Yanking & pasting binds --- +@cbind y* = @jsh if('%s' == 'u') { run("sh 'echo -n $6 | xclip'"); } else if('%s' == 't') { run("sh 'echo -n $7 | xclip'"); }; run('event SET_KEYCMD'); + +# Go the page from primary selection +@cbind p = sh 'echo "uri `xclip -selection primary -o`" > $4' +# Go to the page in clipboard +@cbind P = sh 'echo "uri `xclip -selection clipboard -o`" > $4' +# Start a new uzbl instance from the page in primary selection +@cbind 'p = sh 'exec uzbl-browser --uri $(xclip -o)' + +# --- Bookmark inserting binds --- +@cbind b_ = sh 'echo -e "$6 %s" >> $XDG_DATA_HOME/uzbl/bookmarks' +# Or use a script to insert a bookmark. +@cbind B = spawn @scripts_dir/insert_bookmark.sh + +# --- Bookmark/history loading --- +@cbind U = spawn @scripts_dir/load_url_from_history.sh +@cbind u = spawn @scripts_dir/load_url_from_bookmarks.sh + +# --- Link following (similar to vimperator and konqueror) --- +@cbind fl* = script @scripts_dir/follow_Numbers.js %s +# Or number with strings instead of numbers: +@cbind fL* = script @scripts_dir/follow_Numbers_Strings.js %s + +# --- Form filler binds --- +# this script allows you to configure (per domain) values to fill in form +# fields (eg login information) and to fill in these values automatically +set formfiller = spawn @scripts_dir/formfiller +@cbind za = @{formfiller}.sh +@cbind ze = @{formfiller}.sh edit +@cbind zn = @{formfiller}.sh new +@cbind zl = @{formfiller}.sh load +# Or the more advanced implementation using perl: (could not get this to run - Dieter) +@cbind LL = @{formfiller}.pl load +@cbind LN = @{formfiller}.pl new +@cbind LE = @{formfiller}.pl edit + +# --- External edit script configuration & binds --- +# Edit form input fields in an external editor (gvim, emacs, urxvt -e vim, ..) +set external_editor = gvim +#set external_editor = xterm -e vim +@cbind E = script @scripts_dir/extedit.js +# And add menu option. +menu_editable_add Open in @external_editor = script @scripts_dir/extedit.js + +# --- Examples --- +# Example showing how to use uzbl's fifo to execute a command. +#@bind X1 = sh 'echo "set zoom_level = 1.0" > "$4"' +#@bind X2 = sh 'echo "js alert (\\"This is sent by the shell via a fifo\\")" > "$4"' + +# Working with the javascript helper variable jsh. +#@bind X3 = @jsh alert(get('zoom_level')); +#@bind X4 = @jsh if(get('mode') == "insert") { alert("You are in insert mode") } else { alert(get('mode')+" is a silly mode.") }; + + +# === Context menu items ===================================================== + +# Default context menu +menu_add Google = set uri = http://google.com +menu_add Go Home = set uri = http://uzbl.org +menu_separator separator_1 +menu_add Quit uzbl = exit + +# Link context menu +menu_link_add Print Link = print \@SELECTED_URI + + +# === Mode configuration ===================================================== + +# Define some mode specific uzbl configurations. +set command = @mode_config command +set insert = @mode_config insert +set stack = @mode_config stack + +# Command mode config. +@command keycmd_style = foreground="red" +@command status_background = #202020 +@command mode_indicator = Cmd + +# Insert mode config. +@insert status_background = #303030 +@insert mode_indicator = Ins + +# Multi-stage-binding mode config. +@stack keycmd_events = 1 +@stack modcmd_updates = 1 +@stack forward_keys = 0 +@stack keycmd_style = foreground="red" +@stack prompt_style = foreground="#888" weight="light" +@stack status_background = #202020 +@stack mode_indicator = Bnd + +set default_mode = command + + +# === Post-load misc commands =============================================== + +# Set the "home" page. +set uri = uzbl.org/doesitwork/@COMMIT diff --git a/examples/config/cookies b/examples/config/cookies new file mode 100644 index 0000000..9b7374a --- /dev/null +++ b/examples/config/cookies @@ -0,0 +1,22 @@ +# This file demonstrates how one *could* manage his cookies. this file is used by the example cookie handler script. +# stick to this format. +# trusted -> always store what we get, send what we have (TODO: by default, or when requested?) +# deny -> deny storing + sending + +# if you don't like to edit this file manually, you could even write a script that adds/removes entries using sed, and call the script from uzbl with a keybind... + + +TRUSTED +bbs.archlinux.org +archlinux.org +linux.com + + + + +DENY +www.icanhascheezburger.com + + + +# rest -> ask \ No newline at end of file diff --git a/examples/config/uzbl/config b/examples/config/uzbl/config deleted file mode 100644 index 3edb36c..0000000 --- a/examples/config/uzbl/config +++ /dev/null @@ -1,376 +0,0 @@ -# example uzbl config. -# all settings are optional. you can use uzbl without any config at all (but it won't do much) - -set prefix = /usr/local - -# === Shortcuts / Aliases =================================================== - -# Config related events (use the request function): -# request BIND = -set bind = request BIND -# request MODE_BIND = -set mode_bind = request MODE_BIND -# request MODE_CONFIG = -set mode_config = request MODE_CONFIG -# request ON_EVENT -set on_event = request ON_EVENT -# request PROGRESS_CONFIG = -set progress = request PROGRESS_CONFIG -# request MODMAP -set modmap = request MODMAP -# request IGNORE_KEY -set ignore_key = request IGNORE_KEY -# request MODKEY_ADDITION -set modkey_addition = request MODKEY_ADDITION - -# Action related events (use the event function): -# event TOGGLE_MODES ... -set toggle_modes = event TOGGLE_MODES - -set set_mode = set mode = -set set_status = set status_message = -set shell_cmd = sh -c - -# Spawn path shortcuts. In spawn the first dir+path match is used in "dir1:dir2:dir3:executable" -set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data/uzbl:scripts - -# Javascipt helpers. -set jsh = js var run=Uzbl.run; function get(k){return run("print \\\@"+k)}; function set(k, v) {run("set "+k+" = "+v)}; - -# === Handlers =============================================================== - -# --- Hardcoded event handlers ----------------------------------------------- - -# These handlers can't be moved to the new event system yet as we don't -# support events that can wait for a response from a script. -set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket -set scheme_handler = sync_spawn @scripts_dir/scheme.py - -# Open in the same window. -#set new_window = sh 'echo uri "$8" > $4' -# Open a link in a new window. equivalent to default behavior -set new_window = sh 'uzbl-browser -u $8' - -# --- Optional dynamic event handlers ---------------------------------------- - -# Download handler -@on_event DOWNLOAD_REQUEST spawn @scripts_dir/download.sh %s \@proxy_url - -# Load start handler -@on_event LOAD_START @set_status wait - -# Load commit handlers -@on_event LOAD_COMMIT @set_status recv -@on_event LOAD_COMMIT script @scripts_dir/scroll-percentage.js -# Reset the keycmd on navigation -@on_event LOAD_COMMIT @set_mode - -# Load finish handlers -@on_event LOAD_FINISH @set_status done -@on_event LOAD_FINISH spawn @scripts_dir/history.sh - -# Generate a FORM_ACTIVE event if an editable -# element on the loaded site has initial focus -@on_event LOAD_FINISH js if(document.activeElement.type == 'text') {Uzbl.run("event FORM_ACTIVE");} - -# Switch to insert mode if a (editable) html form is clicked -@on_event FORM_ACTIVE @set_mode insert -# Switch to command mode if anything else is clicked -@on_event ROOT_ACTIVE @set_mode command - -# Example CONFIG_CHANGED event handler -#@on_event CONFIG_CHANGED print Config changed: %1 = %2 - -# === Behaviour and appearance =============================================== - -set show_status = 1 -set status_top = 0 -set status_background = #303030 - -set modcmd_style = weight="bold" foreground="red" -set keycmd_style = weight="light" foreground="red" -set prompt_style = foreground="grey" -set cursor_style = underline="single" -set completion_style = foreground="green" -set hint_style = weight="bold" - -set mode_section = [\@[\@mode_indicator]\@] -set keycmd_section = [\@[\@keycmd_prompt]\@\@modcmd\@keycmd\@completion_list] -set progress_section = \@[\@progress_format]\@ -set scroll_section = \@[\@scroll_message]\@ -set uri_section = \@[\@uri]\@ -set name_section = \@[\@NAME]\@ -set status_section = \@status_message -set selected_section = \@[\@SELECTED_URI]\@ - -set status_format = @mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section - -set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI - -# Progress bar config -@progress width = 8 -# %d = done, %p = pending %c = percent done, %i = int done, %s = spinner, -# %t = percent pending, %o = int pending, %r = sprite scroll -@progress format = [%d>%p]%c -@progress done = = -@progress pending = - -# Or ride those spinnas' -#@progress format = [%d%s%p] -#@progress spinner = -\\|/ -#@progress done = - -#@progress pending = - - -# === Core settings ========================================================== - -set useragent = Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO) (@(+uname -o)@ @(+uname -m)@ [@ARCH_UZBL]) (Commit @COMMIT) -set fifo_dir = /tmp -set socket_dir = /tmp - - -# === Key modmapping and ignoring ============================================ - -#modmap -@modmap -@modmap -@modmap - -#modkey_addition -@modkey_addition -@modkey_addition - -#ignore_key -@ignore_key -@ignore_key - - -# === Mode bind aliases ====================================================== - -# Global binding alias (this is done automatically inside the bind plugin). -#set bind = @mode_bind global - -# Insert mode binding alias -set ibind = @mode_bind insert - -# Command mode binding alias -set cbind = @mode_bind command - -# Non-insert mode bindings alias (ebind for edit-bind). -set ebind = @mode_bind global,-insert - - -# === Global & keycmd editing binds ========================================== - -# Resets keycmd and returns to default mode. -@bind = @set_mode - -# Commands for editing and traversing the keycmd. -@ebind = event KEYCMD_EXEC_CURRENT -@ebind = event SET_CURSOR_POS -@ebind = event SET_CURSOR_POS -1 -@ebind = event SET_CURSOR_POS - -@ebind = event SET_CURSOR_POS + -@ebind = event KEYCMD_BACKSPACE -@ebind = event KEYCMD_DELETE -@ebind = event START_COMPLETION -# Readline-ish bindings. -@ebind w = event KEYCMD_STRIP_WORD -@ebind u = event SET_KEYCMD -@ebind a = event SET_CURSOR_POS 0 -@ebind e = event SET_CURSOR_POS -1 - -# Keycmd injection/append examples. -#@ebind su = event INJECT_KEYCMD \@uri -#@ebind st = event INJECT_KEYCMD \@title -#@ebind du = event APPEND_KEYCMD \@uri -#@ebind dt = event APPEND_KEYCMD \@title - - -# === Mouse bindings ========================================================= - -# Middle click -# if clicked on a link open the link in a new uzbl window -# otherwise open the selection in the current window -set load_from_xclip = sh 'echo "uri $(xclip -o)" > $4' -set open_new_window = sh 'uzbl-browser -u \@SELECTED_URI' -@bind = @jsh if(get("SELECTED_URI")) { run("\@open_new_window"); } else { run("\\\@load_from_xclip"); } - - -# === Keyboard bindings ====================================================== - -# With this command you can enter in any command at runtime when prefixed with -# a colon. -@cbind :_ = %s - -# --- Page movement binds --- -@cbind j = scroll vertical 20 -@cbind k = scroll vertical -20 -@cbind h = scroll horizontal -20 -@cbind l = scroll horizontal 20 -@cbind = scroll vertical -100% -@cbind = scroll vertical 100% -@cbind << = scroll vertical begin -@cbind >> = scroll vertical end -@cbind ^ = scroll horizontal begin -@cbind $ = scroll horizontal end -@cbind = scroll vertical end - -# --- Navigation binds --- -@cbind b = back -@cbind m = forward -@cbind S = stop -@cbind r = reload -@cbind R = reload_ign_cache - -# --- Zoom binds --- -@cbind + = zoom_in -@cbind - = zoom_out -@cbind T = toggle_zoom_type -@cbind 1 = set zoom_level 1.0 -@cbind 2 = set zoom_level 2.0 - -# --- Appearance binds --- -@cbind t = toggle_status - -# --- Page searching binds --- -@cbind /* = search %s -@cbind ?* = search_reverse %s -# Jump to next and previous items -@cbind n = search -@cbind N = search_reverse - -# --- Web searching binds --- -@cbind gg_ = uri http://www.google.com/search?q=\@\@ -@cbind \\awiki_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=\@\@&go=Go -@cbind \\wiki_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=\@\@&go=Go - -# --- Handy binds --- -# Set function shortcut -@cbind s__ = set %1 = %2 -# Exit binding -@cbind ZZ = exit -# Dump config to stdout -@cbind !dump = sh "echo dump_config > $4" -# Reload config -@cbind !reload = sh "sed '/^# === Post-load misc commands/,$d' $1 > $4" -# Uzbl Terminal. TODO explain why this is useful -@cbind t = sh 'xterm -e "socat unix-connect:$5 -"' -#@cbind t = sh 'urxvt -e socat unix-connect:$5 -' - -# --- Uri opening prompts --- -@cbind o_ = uri %s -# Or have it load the current uri into the keycmd for editing -@cbind O_ = uri %s - -# --- Mode setting binds --- -# Changing mode via set. -@cbind I = @set_mode insert -# Or toggle between modes by raising the toggle event. -set toggle_cmd_ins = @toggle_modes command insert -@cbind i = @toggle_cmd_ins -# And the global toggle bind. -@bind i = @toggle_cmd_ins - -# --- Hard-bound bookmarks --- -@cbind gh = uri http://www.uzbl.org - -# --- Yanking & pasting binds --- -@cbind y* = @jsh if('%s' == 'u') { run("sh 'echo -n $6 | xclip'"); } else if('%s' == 't') { run("sh 'echo -n $7 | xclip'"); }; run('event SET_KEYCMD'); - -# Go the page from primary selection -@cbind p = sh 'echo "uri `xclip -selection primary -o`" > $4' -# Go to the page in clipboard -@cbind P = sh 'echo "uri `xclip -selection clipboard -o`" > $4' -# Start a new uzbl instance from the page in primary selection -@cbind 'p = sh 'exec uzbl-browser --uri $(xclip -o)' - -# --- Bookmark inserting binds --- -@cbind b_ = sh 'echo -e "$6 %s" >> $XDG_DATA_HOME/uzbl/bookmarks' -# Or use a script to insert a bookmark. -@cbind B = spawn @scripts_dir/insert_bookmark.sh - -# --- Bookmark/history loading --- -@cbind U = spawn @scripts_dir/load_url_from_history.sh -@cbind u = spawn @scripts_dir/load_url_from_bookmarks.sh - -# --- Link following (similar to vimperator and konqueror) --- -@cbind fl* = script @scripts_dir/follow_Numbers.js %s -# Or number with strings instead of numbers: -@cbind fL* = script @scripts_dir/follow_Numbers_Strings.js %s - -# --- Form filler binds --- -# this script allows you to configure (per domain) values to fill in form -# fields (eg login information) and to fill in these values automatically -set formfiller = spawn @scripts_dir/formfiller -@cbind za = @{formfiller}.sh -@cbind ze = @{formfiller}.sh edit -@cbind zn = @{formfiller}.sh new -@cbind zl = @{formfiller}.sh load -# Or the more advanced implementation using perl: (could not get this to run - Dieter) -@cbind LL = @{formfiller}.pl load -@cbind LN = @{formfiller}.pl new -@cbind LE = @{formfiller}.pl edit - -# --- External edit script configuration & binds --- -# Edit form input fields in an external editor (gvim, emacs, urxvt -e vim, ..) -set external_editor = gvim -#set external_editor = xterm -e vim -@cbind E = script @scripts_dir/extedit.js -# And add menu option. -menu_editable_add Open in @external_editor = script @scripts_dir/extedit.js - -# --- Examples --- -# Example showing how to use uzbl's fifo to execute a command. -#@bind X1 = sh 'echo "set zoom_level = 1.0" > "$4"' -#@bind X2 = sh 'echo "js alert (\\"This is sent by the shell via a fifo\\")" > "$4"' - -# Working with the javascript helper variable jsh. -#@bind X3 = @jsh alert(get('zoom_level')); -#@bind X4 = @jsh if(get('mode') == "insert") { alert("You are in insert mode") } else { alert(get('mode')+" is a silly mode.") }; - - -# === Context menu items ===================================================== - -# Default context menu -menu_add Google = set uri = http://google.com -menu_add Go Home = set uri = http://uzbl.org -menu_separator separator_1 -menu_add Quit uzbl = exit - -# Link context menu -menu_link_add Print Link = print \@SELECTED_URI - - -# === Mode configuration ===================================================== - -# Define some mode specific uzbl configurations. -set command = @mode_config command -set insert = @mode_config insert -set stack = @mode_config stack - -# Command mode config. -@command keycmd_style = foreground="red" -@command status_background = #202020 -@command mode_indicator = Cmd - -# Insert mode config. -@insert status_background = #303030 -@insert mode_indicator = Ins - -# Multi-stage-binding mode config. -@stack keycmd_events = 1 -@stack modcmd_updates = 1 -@stack forward_keys = 0 -@stack keycmd_style = foreground="red" -@stack prompt_style = foreground="#888" weight="light" -@stack status_background = #202020 -@stack mode_indicator = Bnd - -set default_mode = command - - -# === Post-load misc commands =============================================== - -# Set the "home" page. -set uri = uzbl.org/doesitwork/@COMMIT diff --git a/examples/config/uzbl/cookies b/examples/config/uzbl/cookies deleted file mode 100644 index 9b7374a..0000000 --- a/examples/config/uzbl/cookies +++ /dev/null @@ -1,22 +0,0 @@ -# This file demonstrates how one *could* manage his cookies. this file is used by the example cookie handler script. -# stick to this format. -# trusted -> always store what we get, send what we have (TODO: by default, or when requested?) -# deny -> deny storing + sending - -# if you don't like to edit this file manually, you could even write a script that adds/removes entries using sed, and call the script from uzbl with a keybind... - - -TRUSTED -bbs.archlinux.org -archlinux.org -linux.com - - - - -DENY -www.icanhascheezburger.com - - - -# rest -> ask \ No newline at end of file diff --git a/examples/data/bookmarks b/examples/data/bookmarks new file mode 100644 index 0000000..13fcd48 --- /dev/null +++ b/examples/data/bookmarks @@ -0,0 +1,4 @@ +http://www.archlinux.org linux arch +http://www.uzbl.org uzbl browser +http://dieter.plaetinck.be uzbl +http://www.icanhascheezburger.com lolcats fun diff --git a/examples/data/forms/bbs.archlinux.org b/examples/data/forms/bbs.archlinux.org new file mode 100644 index 0000000..73c1539 --- /dev/null +++ b/examples/data/forms/bbs.archlinux.org @@ -0,0 +1,5 @@ +form_sent: +redirect_url: +req_username: +req_password: +login: diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py new file mode 100644 index 0000000..9e09337 --- /dev/null +++ b/examples/data/plugins/bind.py @@ -0,0 +1,521 @@ +'''Plugin provides support for binds in uzbl. + +For example: + event BIND ZZ = exit -> bind('ZZ', 'exit') + event BIND o _ = uri %s -> bind('o _', 'uri %s') + event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'") + +And it is also possible to execute a function on activation: + bind('DD', myhandler) +''' + +import sys +import re +import pprint + +# Hold the bind dicts for each uzbl instance. +UZBLS = {} + +# Commonly used regular expressions. +MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match +# Matches , <'x':y>, <:'y'>, , <'x'!y>, ... +PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>' +FIND_PROMPTS = re.compile(PROMPTS).split +VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match + +# For accessing a bind glob stack. +ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) + + +# Custom errors. +class ArgumentError(Exception): pass + + +class Bindlet(object): + '''Per-instance bind status/state tracker.''' + + def __init__(self, uzbl): + self.binds = {'global': {}} + self.uzbl = uzbl + self.depth = 0 + self.args = [] + self.last_mode = None + self.after_cmds = None + self.stack_binds = [] + + # A subset of the global mode binds containing non-stack and modkey + # activiated binds for use in the stack mode. + self.globals = [] + + + def __getitem__(self, key): + return self.get_binds(key) + + + def reset(self): + '''Reset the tracker state and return to last mode.''' + + self.depth = 0 + self.args = [] + self.after_cmds = None + self.stack_binds = [] + + if self.last_mode: + mode, self.last_mode = self.last_mode, None + self.uzbl.set_mode(mode) + + self.uzbl.set('keycmd_prompt') + + + def stack(self, bind, args, depth): + '''Enter or add new bind in the next stack level.''' + + if self.depth != depth: + if bind not in self.stack_binds: + self.stack_binds.append(bind) + + return + + current_mode = self.uzbl.get_mode() + if current_mode != 'stack': + self.last_mode = current_mode + self.uzbl.set_mode('stack') + + self.stack_binds = [bind,] + self.args += args + self.depth += 1 + self.after_cmds = bind.prompts[depth] + + + def after(self): + '''If a stack was triggered then set the prompt and default value.''' + + if self.after_cmds is None: + return + + (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None + + self.uzbl.clear_keycmd() + if prompt: + self.uzbl.set('keycmd_prompt', prompt) + + if set and is_cmd: + self.uzbl.send(set) + + elif set and not is_cmd: + self.uzbl.send('event SET_KEYCMD %s' % set) + + + def get_binds(self, mode=None): + '''Return the mode binds + globals. If we are stacked then return + the filtered stack list and modkey & non-stack globals.''' + + if mode is None: + mode = self.uzbl.get_mode() + + if not mode: + mode = 'global' + + if self.depth: + return self.stack_binds + self.globals + + globals = self.binds['global'] + if mode not in self.binds or mode == 'global': + return filter(None, globals.values()) + + binds = dict(globals.items() + self.binds[mode].items()) + return filter(None, binds.values()) + + + def add_bind(self, mode, glob, bind=None): + '''Insert (or override) a bind into the mode bind dict.''' + + if mode not in self.binds: + self.binds[mode] = {glob: bind} + return + + binds = self.binds[mode] + binds[glob] = bind + + if mode == 'global': + # Regen the global-globals list. + self.globals = [] + for bind in binds.values(): + if bind is not None and bind.is_global: + self.globals.append(bind) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = Bindlet(uzbl) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_bindlet(uzbl): + '''Return the bind tracklet for the given uzbl instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def ismodbind(glob): + '''Return True if the glob specifies a modbind.''' + + return bool(MOD_START(glob)) + + +def split_glob(glob): + '''Take a string of the form "cmd _" and return a list of the + modkeys in the glob and the command.''' + + mods = set() + while True: + match = MOD_START(glob) + if not match: + break + + end = match.span()[1] + mods.add(glob[:end]) + glob = glob[end:] + + return (mods, glob) + + +def unquote(str): + '''Remove quotation marks around string.''' + + if str and str[0] == str[-1] and str[0] in ['"', "'"]: + str = str[1:-1] + + return str + + +class Bind(object): + + # Class attribute to hold the number of Bind classes created. + counter = [0,] + + def __init__(self, glob, handler, *args, **kargs): + self.is_callable = callable(handler) + self._repr_cache = None + + if not glob: + raise ArgumentError('glob cannot be blank') + + if self.is_callable: + self.function = handler + self.args = args + self.kargs = kargs + + elif kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + elif hasattr(handler, '__iter__'): + self.commands = handler + + else: + self.commands = [handler,] + list(args) + + self.glob = glob + + # Assign unique id. + self.counter[0] += 1 + self.bid = self.counter[0] + + self.split = split = FIND_PROMPTS(glob) + self.prompts = [] + for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]): + prompt, set = map(unquote, [prompt, set]) + cmd = True if cmd == '!' else False + if prompt and prompt[-1] != ":": + prompt = "%s:" % prompt + + self.prompts.append((prompt, cmd, set)) + + # Check that there is nothing like: fl** + for glob in split[:-1:4]: + if glob.endswith('*'): + msg = "token '*' not at the end of a prompt bind: %r" % split + raise SyntaxError(msg) + + # Check that there is nothing like: fl_ + for glob in split[4::4]: + if not glob: + msg = 'found null segment after first prompt: %r' % split + raise SyntaxError(msg) + + stack = [] + for (index, glob) in enumerate(reversed(split[::4])): + # Is the binding a MODCMD or KEYCMD: + mod_cmd = ismodbind(glob) + + # Do we execute on UPDATES or EXEC events? + on_exec = True if glob[-1] in ['!', '_'] else False + + # Does the command take arguments? + has_args = True if glob[-1] in ['*', '_'] else False + + glob = glob[:-1] if has_args or on_exec else glob + mods, glob = split_glob(glob) + stack.append((on_exec, has_args, mods, glob, index)) + + self.stack = list(reversed(stack)) + self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD]) + + + def __getitem__(self, depth): + '''Get bind info at a depth.''' + + if self.is_global: + return self.stack[0] + + return self.stack[depth] + + + def __repr__(self): + if self._repr_cache: + return self._repr_cache + + args = ['glob=%r' % self.glob, 'bid=%d' % self.bid] + + if self.is_callable: + args.append('function=%r' % self.function) + if self.args: + args.append('args=%r' % self.args) + + if self.kargs: + args.append('kargs=%r' % self.kargs) + + else: + cmdlen = len(self.commands) + cmds = self.commands[0] if cmdlen == 1 else self.commands + args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) + + self._repr_cache = '' % ', '.join(args) + return self._repr_cache + + +def exec_bind(uzbl, bind, *args, **kargs): + '''Execute bind objects.''' + + uzbl.event("EXEC_BIND", bind, args, kargs) + + if bind.is_callable: + args += bind.args + kargs = dict(bind.kargs.items()+kargs.items()) + bind.function(uzbl, *args, **kargs) + return + + if kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + commands = [] + cmd_expand = uzbl.cmd_expand + for cmd in bind.commands: + cmd = cmd_expand(cmd, args) + uzbl.send(cmd) + + +def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs): + '''Add a mode bind.''' + + bindlet = get_bindlet(uzbl) + + if not hasattr(modes, '__iter__'): + modes = unicode(modes).split(',') + + # Sort and filter binds. + modes = filter(None, map(unicode.strip, modes)) + + if callable(handler) or (handler is not None and handler.strip()): + bind = Bind(glob, handler, *args, **kargs) + + else: + bind = None + + for mode in modes: + if not VALID_MODE(mode): + raise NameError('invalid mode name: %r' % mode) + + for mode in modes: + if mode[0] == '-': + mode, bind = mode[1:], None + + bindlet.add_bind(mode, glob, bind) + uzbl.event('ADDED_MODE_BIND', mode, glob, bind) + + +def bind(uzbl, glob, handler, *args, **kargs): + '''Legacy bind function.''' + + mode_bind(uzbl, 'global', glob, handler, *args, **kargs) + + +def parse_mode_bind(uzbl, args): + '''Parser for the MODE_BIND event. + + Example events: + MODE_BIND = + MODE_BIND command o_ = uri %s + MODE_BIND insert,command = ... + MODE_BIND global ... = ... + MODE_BIND global,-insert ... = ... + ''' + + if not args: + raise ArgumentError('missing bind arguments') + + split = map(unicode.strip, args.split(' ', 1)) + if len(split) != 2: + raise ArgumentError('missing mode or bind section: %r' % args) + + modes, args = split[0].split(','), split[1] + split = map(unicode.strip, args.split('=', 1)) + if len(split) != 2: + raise ArgumentError('missing delimiter in bind section: %r' % args) + + glob, command = split + mode_bind(uzbl, modes, glob, command) + + +def parse_bind(uzbl, args): + '''Legacy parsing of the BIND event and conversion to the new format. + + Example events: + request BIND = + request BIND o_ = uri %s + request BIND = ... + request BIND ... = ... + ''' + + parse_mode_bind(uzbl, "global %s" % args) + + +def mode_changed(uzbl, mode): + '''Clear the stack on all non-stack mode changes.''' + + if mode != 'stack': + get_bindlet(uzbl).reset() + + +def match_and_exec(uzbl, bind, depth, keylet, bindlet): + + (on_exec, has_args, mod_cmd, glob, more) = bind[depth] + cmd = keylet.modcmd if mod_cmd else keylet.keycmd + + if mod_cmd and keylet.held != mod_cmd: + return False + + if has_args: + if not cmd.startswith(glob): + return False + + args = [cmd[len(glob):],] + + elif cmd != glob: + return False + + else: + args = [] + + if bind.is_global or (not more and depth == 0): + exec_bind(uzbl, bind, *args) + if not has_args: + uzbl.clear_current() + + return True + + elif more: + bindlet.stack(bind, args, depth) + return False + + args = bindlet.args + args + exec_bind(uzbl, bind, *args) + uzbl.set_mode() + if not has_args: + bindlet.reset() + uzbl.clear_current() + + return True + + +def keycmd_update(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if t[MOD_CMD] or t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return + + bindlet.after() + + +def keycmd_exec(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if t[MOD_CMD] or not t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return uzbl.clear_keycmd() + + bindlet.after() + + +def modcmd_update(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if not t[MOD_CMD] or t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return + + bindlet.after() + + +def modcmd_exec(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if not t[MOD_CMD] or not t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return uzbl.clear_modcmd() + + bindlet.after() + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'BIND': parse_bind, + 'KEYCMD_EXEC': keycmd_exec, + 'KEYCMD_UPDATE': keycmd_update, + 'MODCMD_EXEC': modcmd_exec, + 'MODCMD_UPDATE': modcmd_update, + 'MODE_BIND': parse_mode_bind, + 'MODE_CHANGED': mode_changed, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'bind': bind, + 'mode_bind': mode_bind, + 'get_bindlet': get_bindlet, + }) diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py new file mode 100644 index 0000000..3f6ae2b --- /dev/null +++ b/examples/data/plugins/cmd_expand.py @@ -0,0 +1,42 @@ +def escape(str): + for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]: + str = str.replace(char, (level * '\\') + char) + + return str + + +def cmd_expand(uzbl, cmd, args): + '''Exports a function that provides the following + expansions in any uzbl command string: + + %s = replace('%s', ' '.join(args)) + %r = replace('%r', "'%s'" % escaped(' '.join(args))) + %1 = replace('%1', arg[0]) + %2 = replace('%2', arg[1]) + %n = replace('%n', arg[n-1]) + ''' + + # Ensure (1) all string representable and (2) correct string encoding. + args = map(unicode, args) + + # Direct string replace. + if '%s' in cmd: + cmd = cmd.replace('%s', ' '.join(args)) + + # Escaped and quoted string replace. + if '%r' in cmd: + cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args))) + + # Arg index string replace. + for (index, arg) in enumerate(args): + index += 1 + if '%%%d' % index in cmd: + cmd = cmd.replace('%%%d' % index, unicode(arg)) + + return cmd + + +def init(uzbl): + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export('cmd_expand', cmd_expand) diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py new file mode 100644 index 0000000..8cea203 --- /dev/null +++ b/examples/data/plugins/completion.py @@ -0,0 +1,206 @@ +'''Keycmd completion.''' + +# A list of functions this plugin exports to be used via uzbl object. +__export__ = ['start_completion', 'get_completion_dict'] + +import re + +# Holds the per-instance completion dicts. +UZBLS = {} + +# Completion level +NONE, ONCE, LIST, COMPLETE = range(4) + +# Default instance dict. +DEFAULTS = {'completions': [], 'level': NONE, 'lock': False} + +# The reverse keyword finding re. +FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall + +# Formats +LIST_FORMAT = " %s " +ITEM_FORMAT = "%s%s" + + +def escape(str): + return str.replace("@", "\@") + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + # Make sure the config keys for all possible completions are known. + uzbl.send('dump_config_as_events') + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_completion_dict(uzbl): + '''Get data stored for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_incomplete_keyword(uzbl): + '''Gets the segment of the keycmd leading up to the cursor position and + uses a regular expression to search backwards finding parially completed + keywords or @variables. Returns a null string if the correct completion + conditions aren't met.''' + + keylet = uzbl.get_keylet() + left_segment = keylet.keycmd[:keylet.cursor] + partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip() + if partial.startswith('set '): + return ('@%s' % partial[4:].lstrip(), True) + + return (partial, False) + + +def stop_completion(uzbl, *args): + '''Stop command completion and return the level to NONE.''' + + d = get_completion_dict(uzbl) + d['level'] = NONE + uzbl.set('completion_list') + + +def complete_completion(uzbl, partial, hint, set_completion=False): + '''Inject the remaining porition of the keyword into the keycmd then stop + the completioning.''' + + if set_completion: + remainder = "%s = " % hint[len(partial):] + + else: + remainder = "%s " % hint[len(partial):] + + uzbl.inject_keycmd(remainder) + stop_completion(uzbl) + + +def partial_completion(uzbl, partial, hint): + '''Inject a common portion of the hints into the keycmd.''' + + remainder = hint[len(partial):] + uzbl.inject_keycmd(remainder) + + +def update_completion_list(uzbl, *args): + '''Checks if the user still has a partially completed keyword under his + cursor then update the completion hints list.''' + + partial = get_incomplete_keyword(uzbl)[0] + if not partial: + return stop_completion(uzbl) + + d = get_completion_dict(uzbl) + if d['level'] < LIST: + return + + hints = [h for h in d['completions'] if h.startswith(partial)] + if not hints: + return uzbl.set('completion_list') + + j = len(partial) + l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)] + uzbl.set('completion_list', LIST_FORMAT % ' '.join(l)) + + +def start_completion(uzbl, *args): + + d = get_completion_dict(uzbl) + if d['lock']: + return + + (partial, set_completion) = get_incomplete_keyword(uzbl) + if not partial: + return stop_completion(uzbl) + + if d['level'] < COMPLETE: + d['level'] += 1 + + hints = [h for h in d['completions'] if h.startswith(partial)] + if not hints: + return + + elif len(hints) == 1: + d['lock'] = True + complete_completion(uzbl, partial, hints[0], set_completion) + d['lock'] = False + return + + elif partial in hints and d['level'] == COMPLETE: + d['lock'] = True + complete_completion(uzbl, partial, partial, set_completion) + d['lock'] = False + return + + smalllen, smallest = sorted([(len(h), h) for h in hints])[0] + common = '' + for i in range(len(partial), smalllen): + char, same = smallest[i], True + for hint in hints: + if hint[i] != char: + same = False + break + + if not same: + break + + common += char + + if common: + d['lock'] = True + partial_completion(uzbl, partial, partial+common) + d['lock'] = False + + update_completion_list(uzbl) + + +def add_builtins(uzbl, args): + '''Pump the space delimited list of builtin commands into the + builtin list.''' + + completions = get_completion_dict(uzbl)['completions'] + builtins = filter(None, map(unicode.strip, args.split(" "))) + for builtin in builtins: + if builtin not in completions: + completions.append(builtin) + + +def add_config_key(uzbl, key, value): + '''Listen on the CONFIG_CHANGED event and add config keys to the variable + list for @var like expansion support.''' + + completions = get_completion_dict(uzbl)['completions'] + key = "@%s" % key + if key not in completions: + completions.append(key) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'BUILTINS': add_builtins, + 'CONFIG_CHANGED': add_config_key, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'KEYCMD_CLEARED': stop_completion, + 'KEYCMD_EXEC': stop_completion, + 'KEYCMD_UPDATE': update_completion_list, + 'START_COMPLETION': start_completion, + 'STOP_COMPLETION': stop_completion, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_completion_dict': get_completion_dict, + 'start_completion': start_completion, + }) diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py new file mode 100644 index 0000000..4a848a3 --- /dev/null +++ b/examples/data/plugins/config.py @@ -0,0 +1,97 @@ +import re +import types + +__export__ = ['set', 'get_config'] + +VALIDKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match +TYPECONVERT = {'int': int, 'float': float, 'str': unicode} + +UZBLS = {} + + +def escape(value): + '''A real escaping function may be required.''' + + return unicode(value) + + +def set(uzbl, key, value='', config=None, force=False): + '''Sends a: "set key = value" command to the uzbl instance. If force is + False then only send a set command if the values aren't equal.''' + + if type(value) == types.BooleanType: + value = int(value) + + else: + value = unicode(value) + + if not VALIDKEY(key): + raise KeyError("%r" % key) + + value = escape(value) + if '\n' in value: + value = value.replace("\n", "\\n") + + if not force: + if config is None: + config = get_config(uzbl) + + if key in config and config[key] == value: + return + + uzbl.send('set %s = %s' % (key, value)) + + +class ConfigDict(dict): + def __init__(self, uzbl): + self._uzbl = uzbl + + def __setitem__(self, key, value): + '''Makes "config[key] = value" a wrapper for the set function.''' + + set(self._uzbl, key, value, config=self) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = ConfigDict(uzbl) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del uzbl + + +def get_config(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def variable_set(uzbl, args): + config = get_config(uzbl) + + key, type, value = list(args.split(' ', 2) + ['',])[:3] + old = config[key] if key in config else None + value = TYPECONVERT[type](value) + + dict.__setitem__(config, key, value) + + if old != value: + uzbl.event("CONFIG_CHANGED", key, value) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'VARIABLE_SET': variable_set, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_config': get_config, + 'set': set, + }) diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py new file mode 100644 index 0000000..c119077 --- /dev/null +++ b/examples/data/plugins/keycmd.py @@ -0,0 +1,571 @@ +import re + +# Hold the keylets. +UZBLS = {} + +# Keycmd format which includes the markup for the cursor. +KEYCMD_FORMAT = "%s%s%s" +MODCMD_FORMAT = " %s " + + +def escape(str): + for char in ['\\', '@']: + str = str.replace(char, '\\'+char) + + return str + + +def uzbl_escape(str): + return "@[%s]@" % escape(str) if str else '' + + +class Keylet(object): + '''Small per-instance object that tracks all the keys held and characters + typed.''' + + def __init__(self): + # Modcmd tracking + self.held = set() + self.ignored = set() + self.modcmd = '' + self.is_modcmd = False + + # Keycmd tracking + self.keycmd = '' + self.cursor = 0 + + self.modmaps = {} + self.ignores = {} + self.additions = {} + + # Keylet string repr cache. + self._repr_cache = None + + + def get_keycmd(self): + '''Get the keycmd-part of the keylet.''' + + return self.keycmd + + + def get_modcmd(self): + '''Get the modcmd-part of the keylet.''' + + if not self.is_modcmd: + return '' + + return ''.join(self.held) + self.modcmd + + + def modmap_key(self, key): + '''Make some obscure names for some keys friendlier.''' + + if key in self.modmaps: + return self.modmaps[key] + + elif key.endswith('_L') or key.endswith('_R'): + # Remove left-right discrimination and try again. + return self.modmap_key(key[:-2]) + + else: + return key + + + def find_addition(self, modkey): + '''Key has just been pressed, check if this key + the held list + results in a modkey addition. Return that addition and remove all + modkeys that created it.''' + + # Intersection of (held list + modkey) and additions. + added = (self.held | set([modkey])) & set(self.additions.keys()) + for key in added: + if key == modkey or modkey in self.additions[key]: + self.held -= self.additions[key] + return key + + # Held list + ignored list + modkey. + modkeys = self.held | self.ignored | set([modkey]) + for (key, value) in self.additions.items(): + if modkeys.issuperset(value): + self.held -= value + return key + + return modkey + + + def key_ignored(self, key): + '''Check if the given key is ignored by any ignore rules.''' + + for (glob, match) in self.ignores.items(): + if match(key): + return True + + return False + + + def __repr__(self): + '''Return a string representation of the keylet.''' + + if self._repr_cache: + return self._repr_cache + + l = [] + if self.is_modcmd: + l.append('modcmd=%r' % self.get_modcmd()) + + elif self.held: + l.append('held=%r' % ''.join(sorted(self.held))) + + if self.keycmd: + l.append('keycmd=%r' % self.get_keycmd()) + + self._repr_cache = '' % ', '.join(l) + return self._repr_cache + + +def add_modmap(uzbl, key, map): + '''Add modmaps. + + Examples: + set modmap = request MODMAP + @modmap + @modmap + ... + + Then: + @bind = + @bind x = + ... + + ''' + + assert len(key) + modmaps = get_keylet(uzbl).modmaps + + if key[0] == "<" and key[-1] == ">": + key = key[1:-1] + + modmaps[key] = map + uzbl.event("NEW_MODMAP", key, map) + + +def modmap_parse(uzbl, map): + '''Parse a modmap definiton.''' + + split = [s.strip() for s in map.split(' ') if s.split()] + + if not split or len(split) > 2: + raise Exception('Invalid modmap arugments: %r' % map) + + add_modmap(uzbl, *split) + + +def add_key_ignore(uzbl, glob): + '''Add an ignore definition. + + Examples: + set ignore_key = request IGNORE_KEY + @ignore_key + @ignore_key + ... + ''' + + assert len(glob) > 1 + ignores = get_keylet(uzbl).ignores + + glob = "<%s>" % glob.strip("<> ") + restr = glob.replace('*', '[^\s]*') + match = re.compile(restr).match + + ignores[glob] = match + uzbl.event('NEW_KEY_IGNORE', glob) + + +def add_modkey_addition(uzbl, modkeys, result): + '''Add a modkey addition definition. + + Examples: + set mod_addition = request MODKEY_ADDITION + @mod_addition + @mod_addition + @mod_addition + ... + + Then: + @bind = + @bind o = + ... + ''' + + additions = get_keylet(uzbl).additions + modkeys = set(modkeys) + + assert len(modkeys) and result and result not in modkeys + + for (existing_result, existing_modkeys) in additions.items(): + if existing_result != result: + assert modkeys != existing_modkeys + + additions[result] = modkeys + uzbl.event('NEW_MODKEY_ADDITION', modkeys, result) + + +def modkey_addition_parse(uzbl, modkeys): + '''Parse modkey addition definition.''' + + keys = filter(None, map(unicode.strip, modkeys.split(" "))) + keys = ['<%s>' % key.strip("<>") for key in keys if key.strip("<>")] + + assert len(keys) > 1 + add_modkey_addition(uzbl, keys[:-1], keys[-1]) + + +def add_instance(uzbl, *args): + '''Create the Keylet object for this uzbl instance.''' + + UZBLS[uzbl] = Keylet() + + +def del_instance(uzbl, *args): + '''Delete the Keylet object for this uzbl instance.''' + + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_keylet(uzbl): + '''Return the corresponding keylet for this uzbl instance.''' + + # Startup events are not correctly captured and sent over the uzbl socket + # yet so this line is needed because the INSTANCE_START event is lost. + if uzbl not in UZBLS: + add_instance(uzbl) + + keylet = UZBLS[uzbl] + keylet._repr_cache = False + return keylet + + +def clear_keycmd(uzbl): + '''Clear the keycmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.keycmd = '' + k.cursor = 0 + k._repr_cache = False + uzbl.set('keycmd') + uzbl.set('raw_keycmd') + uzbl.event('KEYCMD_CLEARED') + + +def clear_modcmd(uzbl, clear_held=False): + '''Clear the modcmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.modcmd = '' + k.is_modcmd = False + k._repr_cache = False + if clear_held: + k.ignored = set() + k.held = set() + + uzbl.set('modcmd') + uzbl.set('raw_modcmd') + uzbl.event('MODCMD_CLEARED') + + +def clear_current(uzbl): + '''Clear the modcmd if is_modcmd else clear keycmd.''' + + k = get_keylet(uzbl) + if k.is_modcmd: + clear_modcmd(uzbl) + + else: + clear_keycmd(uzbl) + + +def focus_changed(uzbl, *args): + '''Focus to the uzbl instance has now been lost which means all currently + held keys in the held list will not get a KEY_RELEASE event so clear the + entire held list.''' + + clear_modcmd(uzbl, clear_held=True) + + +def update_event(uzbl, k, execute=True): + '''Raise keycmd & modcmd update events.''' + + config = uzbl.get_config() + keycmd, modcmd = k.get_keycmd(), k.get_modcmd() + + if k.is_modcmd: + uzbl.event('MODCMD_UPDATE', k) + + else: + uzbl.event('KEYCMD_UPDATE', k) + + if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': + new_modcmd = k.get_modcmd() + if not new_modcmd: + uzbl.set('modcmd', config=config) + uzbl.set('raw_modcmd', config=config) + + elif new_modcmd == modcmd: + uzbl.set('raw_modcmd', escape(modcmd), config=config) + uzbl.set('modcmd', MODCMD_FORMAT % uzbl_escape(modcmd), + config=config) + + if 'keycmd_events' in config and config['keycmd_events'] != '1': + return + + new_keycmd = k.get_keycmd() + if not new_keycmd: + uzbl.set('keycmd', config=config) + uzbl.set('raw_keycmd', config=config) + + elif new_keycmd == keycmd: + # Generate the pango markup for the cursor in the keycmd. + curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' ' + chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]] + value = KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks)) + uzbl.set('keycmd', value, config=config) + uzbl.set('raw_keycmd', escape(keycmd), config=config) + + +def inject_str(str, index, inj): + '''Inject a string into string at at given index.''' + + return "%s%s%s" % (str[:index], inj, str[index:]) + + +def get_keylet_and_key(uzbl, key, add=True): + '''Return the keylet and apply any transformations to the key as defined + by the modmapping or modkey addition rules. Return None if the key is + ignored.''' + + keylet = get_keylet(uzbl) + key = keylet.modmap_key(key) + if len(key) == 1: + return (keylet, key) + + modkey = "<%s>" % key.strip("<>") + + if keylet.key_ignored(modkey): + if add: + keylet.ignored.add(modkey) + + elif modkey in keylet.ignored: + keylet.ignored.remove(modkey) + + modkey = keylet.find_addition(modkey) + + if keylet.key_ignored(modkey): + return (keylet, None) + + return (keylet, modkey) + + +def key_press(uzbl, key): + '''Handle KEY_PRESS events. Things done by this function include: + + 1. Ignore all shift key presses (shift can be detected by capital chars) + 3. In non-modcmd mode: + a. append char to keycmd + 4. If not in modcmd mode and a modkey was pressed set modcmd mode. + 5. If in modcmd mode the pressed key is added to the held keys list. + 6. Keycmd is updated and events raised if anything is changed.''' + + (k, key) = get_keylet_and_key(uzbl, key.strip()) + if not key: + return + + if key.lower() == '' and not k.held and k.keycmd: + k.keycmd = inject_str(k.keycmd, k.cursor, ' ') + k.cursor += 1 + + elif not k.held and len(key) == 1: + config = uzbl.get_config() + if 'keycmd_events' in config and config['keycmd_events'] != '1': + k.keycmd = '' + k.cursor = 0 + uzbl.set('keycmd', config=config) + uzbl.set('raw_keycmd', config=config) + return + + k.keycmd = inject_str(k.keycmd, k.cursor, key) + k.cursor += 1 + + elif len(key) > 1: + k.is_modcmd = True + if key not in k.held: + k.held.add(key) + + else: + k.is_modcmd = True + k.modcmd += key + + update_event(uzbl, k) + + +def key_release(uzbl, key): + '''Respond to KEY_RELEASE event. Things done by this function include: + + 1. Remove the key from the keylet held list. + 2. If in a mod-command then raise a MODCMD_EXEC. + 3. Check if any modkey is held, if so set modcmd mode. + 4. Update the keycmd uzbl variable if anything changed.''' + + (k, key) = get_keylet_and_key(uzbl, key.strip(), add=False) + + if key in k.held: + if k.is_modcmd: + uzbl.event('MODCMD_EXEC', k) + + k.held.remove(key) + clear_modcmd(uzbl) + + +def set_keycmd(uzbl, keycmd): + '''Allow setting of the keycmd externally.''' + + k = get_keylet(uzbl) + k.keycmd = keycmd + k._repr_cache = None + k.cursor = len(keycmd) + update_event(uzbl, k, False) + + +def inject_keycmd(uzbl, keycmd): + '''Allow injecting of a string into the keycmd at the cursor position.''' + + k = get_keylet(uzbl) + k.keycmd = inject_str(k.keycmd, k.cursor, keycmd) + k._repr_cache = None + k.cursor += len(keycmd) + update_event(uzbl, k, False) + + +def append_keycmd(uzbl, keycmd): + '''Allow appening of a string to the keycmd.''' + + k = get_keylet(uzbl) + k.keycmd += keycmd + k._repr_cache = None + k.cursor = len(k.keycmd) + update_event(uzbl, k, False) + + +def keycmd_strip_word(uzbl, sep): + ''' Removes the last word from the keycmd, similar to readline ^W ''' + + sep = sep or ' ' + k = get_keylet(uzbl) + if not k.keycmd: + return + + head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] + rfind = head.rfind(sep) + head = head[:rfind] if rfind + 1 else '' + k.keycmd = head + tail + k.cursor = len(head) + update_event(uzbl, k, False) + + +def keycmd_backspace(uzbl, *args): + '''Removes the character at the cursor position in the keycmd.''' + + k = get_keylet(uzbl) + if not k.keycmd: + return + + k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] + k.cursor -= 1 + update_event(uzbl, k, False) + + +def keycmd_delete(uzbl, *args): + '''Removes the character after the cursor position in the keycmd.''' + + k = get_keylet(uzbl) + if not k.keycmd: + return + + k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] + update_event(uzbl, k, False) + + +def keycmd_exec_current(uzbl, *args): + '''Raise a KEYCMD_EXEC with the current keylet and then clear the + keycmd.''' + + k = get_keylet(uzbl) + uzbl.event('KEYCMD_EXEC', k) + clear_keycmd(uzbl) + + +def set_cursor_pos(uzbl, index): + '''Allow setting of the cursor position externally. Supports negative + indexing and relative stepping with '+' and '-'.''' + + k = get_keylet(uzbl) + if index == '-': + cursor = k.cursor - 1 + + elif index == '+': + cursor = k.cursor + 1 + + else: + cursor = int(index.strip()) + if cursor < 0: + cursor = len(k.keycmd) + cursor + 1 + + if cursor < 0: + cursor = 0 + + if cursor > len(k.keycmd): + cursor = len(k.keycmd) + + k.cursor = cursor + update_event(uzbl, k, False) + + +def init(uzbl): + '''Connect handlers to uzbl events.''' + + # Event handling hooks. + uzbl.connect_dict({ + 'APPEND_KEYCMD': append_keycmd, + 'FOCUS_GAINED': focus_changed, + 'FOCUS_LOST': focus_changed, + 'IGNORE_KEY': add_key_ignore, + 'INJECT_KEYCMD': inject_keycmd, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'KEYCMD_BACKSPACE': keycmd_backspace, + 'KEYCMD_DELETE': keycmd_delete, + 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, + 'KEYCMD_STRIP_WORD': keycmd_strip_word, + 'KEY_PRESS': key_press, + 'KEY_RELEASE': key_release, + 'MODKEY_ADDITION': modkey_addition_parse, + 'MODMAP': modmap_parse, + 'SET_CURSOR_POS': set_cursor_pos, + 'SET_KEYCMD': set_keycmd, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'add_key_ignore': add_key_ignore, + 'add_modkey_addition': add_modkey_addition, + 'add_modmap': add_modmap, + 'append_keycmd': append_keycmd, + 'clear_current': clear_current, + 'clear_keycmd': clear_keycmd, + 'clear_modcmd': clear_modcmd, + 'get_keylet': get_keylet, + 'inject_keycmd': inject_keycmd, + 'set_cursor_pos': set_cursor_pos, + 'set_keycmd': set_keycmd, + }) diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py new file mode 100644 index 0000000..54d865a --- /dev/null +++ b/examples/data/plugins/mode.py @@ -0,0 +1,176 @@ +import sys +import re + +__export__ = ['set_mode', 'get_mode', 'set_mode_config', 'get_mode_config'] + +UZBLS = {} + +DEFAULTS = { + 'mode': '', + 'modes': { + 'insert': { + 'forward_keys': True, + 'keycmd_events': False, + 'modcmd_updates': False, + 'mode_indicator': 'I'}, + 'command': { + 'forward_keys': False, + 'keycmd_events': True, + 'modcmd_updates': True, + 'mode_indicator': 'C'}}} + +FINDSPACES = re.compile("\s+") +VALID_KEY = re.compile("^[\w_]+$").match + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_mode_dict(uzbl): + '''Return the mode dict for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_mode_config(uzbl, mode): + '''Return the mode config for a given mode.''' + + modes = get_mode_dict(uzbl)['modes'] + if mode not in modes: + modes[mode] = {} + + return modes[mode] + + +def get_mode(uzbl): + return get_mode_dict(uzbl)['mode'] + + +def mode_changed(uzbl, mode): + '''The mode has just been changed, now set the per-mode config.''' + + if get_mode(uzbl) != mode: + return + + config = uzbl.get_config() + mode_config = get_mode_config(uzbl, mode) + for (key, value) in mode_config.items(): + uzbl.set(key, value, config=config) + + if 'mode_indicator' not in mode_config: + config['mode_indicator'] = mode + + uzbl.clear_keycmd() + uzbl.clear_modcmd() + + +def set_mode(uzbl, mode=None): + '''Set the mode and raise the MODE_CHANGED event if the mode has changed. + Fallback on the default mode if no mode argument was given and the default + mode is not null.''' + + config = uzbl.get_config() + mode_dict = get_mode_dict(uzbl) + if mode is None: + mode_dict['mode'] = '' + if 'default_mode' in config: + mode = config['default_mode'] + + else: + mode = 'command' + + if not VALID_KEY(mode): + raise KeyError("invalid mode name: %r" % mode) + + if 'mode' not in config or config['mode'] != mode: + config['mode'] = mode + + elif mode_dict['mode'] != mode: + mode_dict['mode'] = mode + uzbl.event("MODE_CHANGED", mode) + + +def config_changed(uzbl, key, value): + '''Check for mode related config changes.''' + + value = None if not value else value + if key == 'default_mode': + if not get_mode(uzbl): + set_mode(uzbl, value) + + elif key == 'mode': + set_mode(uzbl, value) + + +def set_mode_config(uzbl, mode, key, value): + '''Set mode specific configs. If the mode being modified is the current + mode then apply the changes on the go.''' + + assert VALID_KEY(mode) and VALID_KEY(key) + + mode_config = get_mode_config(uzbl, mode) + mode_config[key] = value + + if get_mode(uzbl) == mode: + uzbl.set(key, value) + + +def mode_config(uzbl, args): + '''Parse mode config events.''' + + split = map(unicode.strip, FINDSPACES.split(args.lstrip(), 1)) + if len(split) != 2: + raise SyntaxError('invalid mode config syntax: %r' % args) + + mode, set = split + split = map(unicode.strip, set.split('=', 1)) + if len(split) != 2: + raise SyntaxError('invalid set syntax: %r' % args) + + key, value = split + set_mode_config(uzbl, mode, key, value) + + +def toggle_modes(uzbl, modes): + '''Toggle or cycle between or through a list of modes.''' + + assert len(modes.strip()) + + modelist = filter(None, map(unicode.strip, modes.split(' '))) + mode = get_mode(uzbl) + + index = 0 + if mode in modelist: + index = (modelist.index(mode)+1) % len(modelist) + + set_mode(uzbl, modelist[index]) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'CONFIG_CHANGED': config_changed, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'MODE_CHANGED': mode_changed, + 'MODE_CONFIG': mode_config, + 'TOGGLE_MODES': toggle_modes, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_mode': get_mode, + 'get_mode_config': get_mode_config, + 'set_mode': set_mode, + 'set_mode_config': set_mode_config, + }) diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py new file mode 100644 index 0000000..b9c504a --- /dev/null +++ b/examples/data/plugins/on_event.py @@ -0,0 +1,107 @@ +'''Plugin provides arbitrary binding of uzbl events to uzbl commands. + +Formatting options: + %s = space separated string of the arguments + %r = escaped and quoted version of %s + %1 = argument 1 + %2 = argument 2 + %n = argument n + +Usage: + request ON_EVENT LINK_HOVER set selected_uri = $1 + --> LINK_HOVER http://uzbl.org/ + <-- set selected_uri = http://uzbl.org/ + + request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2 + --> CONFIG_CHANGED selected_uri http://uzbl.org/ + <-- print Config changed: selected_uri = http://uzbl.org/ +''' + +import sys +import re + +__export__ = ['get_on_events', 'on_event'] + +UZBLS = {} + + +def error(msg): + sys.stderr.write('on_event plugin: error: %s\n' % msg) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = {} + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_on_events(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def event_handler(uzbl, *args, **kargs): + '''This function handles all the events being watched by various + on_event definitions and responds accordingly.''' + + events = get_on_events(uzbl) + event = kargs['on_event'] + if event not in events: + return + + commands = events[event] + cmd_expand = uzbl.cmd_expand + for cmd in commands: + cmd = cmd_expand(cmd, args) + uzbl.send(cmd) + + +def on_event(uzbl, event, cmd): + '''Add a new event to watch and respond to.''' + + event = event.upper() + events = get_on_events(uzbl) + if event not in events: + uzbl.connect(event, event_handler, on_event=event) + events[event] = [] + + cmds = events[event] + if cmd not in cmds: + cmds.append(cmd) + + +def parse_on_event(uzbl, args): + '''Parse ON_EVENT events and pass them to the on_event function. + + Syntax: "event ON_EVENT commands".''' + + if not args: + return error("missing on_event arguments") + + split = args.split(' ', 1) + if len(split) != 2: + return error("invalid ON_EVENT syntax: %r" % args) + + event, cmd = split + on_event(uzbl, event, cmd) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'ON_EVENT': parse_on_event, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_on_events': get_on_events, + 'on_event': on_event, + }) diff --git a/examples/data/plugins/plugin_template.py b/examples/data/plugins/plugin_template.py new file mode 100644 index 0000000..565a999 --- /dev/null +++ b/examples/data/plugins/plugin_template.py @@ -0,0 +1,76 @@ +'''Plugin template.''' + +# Holds the per-instance data dict. +UZBLS = {} + +# The default instance dict. +DEFAULTS = {} + + +def add_instance(uzbl, *args): + '''Add a new instance with default config options.''' + + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + '''Delete data stored for an instance.''' + + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_myplugin_dict(uzbl): + '''Get data stored for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def myplugin_function(uzbl, *args, **kargs): + '''Custom plugin function which is exported by the __export__ list at the + top of the file for use by other functions/callbacks.''' + + print "My plugin function arguments:", args, kargs + + # Get the per-instance data object. + data = get_myplugin_dict(uzbl) + + # Function logic goes here. + + +def myplugin_event_parser(uzbl, args): + '''Parses MYPLUGIN_EVENT raised by uzbl or another plugin.''' + + print "Got MYPLUGIN_EVENT with arguments: %r" % args + + # Parsing logic goes here. + + +def init(uzbl): + '''The main function of the plugin which is used to attach all the event + hooks that are going to be used throughout the plugins life. This function + is called each time a UzblInstance() object is created in the event + manager.''' + + # Make a dictionary comprising of {"EVENT_NAME": handler, ..} to the event + # handler stack: + uzbl.connect_dict({ + # event name function + 'INSTANCE_START': add_instance, + 'INSTANCE_EXIT': del_instance, + 'MYPLUGIN_EVENT': myplugin_event_parser, + }) + + # Or connect a handler to an event manually and supply additional optional + # arguments: + #uzbl.connect("MYOTHER_EVENT", myother_event_parser, True, limit=20) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.connect_dict({ + # external name function + 'myplugin_function': myplugin_function, + }) diff --git a/examples/data/plugins/progress_bar.py b/examples/data/plugins/progress_bar.py new file mode 100644 index 0000000..89ba175 --- /dev/null +++ b/examples/data/plugins/progress_bar.py @@ -0,0 +1,159 @@ +import sys + +UZBLS = {} + +DEFAULTS = {'width': 8, + 'done': '=', + 'pending': '.', + 'format': '[%d%a%p]%c', + 'spinner': '-\\|/', + 'sprites': 'loading', + 'updates': 0, + 'progress': 100} + + +def error(msg): + sys.stderr.write("progress_bar plugin: error: %s\n" % msg) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_progress_config(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def update_progress(uzbl, prog=None): + '''Updates the progress_format variable on LOAD_PROGRESS update. + + The current substitution options are: + %d = done char * done + %p = pending char * remaining + %c = percent done + %i = int done + %s = -\|/ spinner + %t = percent pending + %o = int pending + %r = sprites + ''' + + prog_config = get_progress_config(uzbl) + config = uzbl.get_config() + + if prog is None: + prog = prog_config['progress'] + + else: + prog = int(prog) + prog_config['progress'] = prog + + prog_config['updates'] += 1 + format = prog_config['format'] + width = prog_config['width'] + + # Inflate the done and pending bars to stop the progress bar + # jumping around. + if '%c' in format or '%i' in format: + count = format.count('%c') + format.count('%i') + width += (3-len(str(prog))) * count + + if '%t' in format or '%o' in format: + count = format.count('%t') + format.count('%o') + width += (3-len(str(100-prog))) * count + + done = int(((prog/100.0)*width)+0.5) + pending = width - done + + if '%d' in format: + format = format.replace('%d', prog_config['done']*done) + + if '%p' in format: + format = format.replace('%p', prog_config['pending']*pending) + + if '%c' in format: + format = format.replace('%c', '%d%%' % prog) + + if '%i' in format: + format = format.replace('%i', '%d' % prog) + + if '%t' in format: + format = format.replace('%t', '%d%%' % (100-prog)) + + if '%o' in format: + format = format.replace('%o', '%d' % (100-prog)) + + if '%s' in format: + spinner = prog_config['spinner'] + spin = '-' if not spinner else spinner + index = 0 if prog == 100 else prog_config['updates'] % len(spin) + char = '\\\\' if spin[index] == '\\' else spin[index] + format = format.replace('%s', char) + + if '%r' in format: + sprites = prog_config['sprites'] + sprites = '-' if not sprites else sprites + index = int(((prog/100.0)*len(sprites))+0.5)-1 + sprite = '\\\\' if sprites[index] == '\\' else sprites[index] + format = format.replace('%r', sprite) + + if 'progress_format' not in config or config['progress_format'] != format: + config['progress_format'] = format + + +def progress_config(uzbl, args): + '''Parse PROGRESS_CONFIG events from the uzbl instance. + + Syntax: event PROGRESS_CONFIG = + ''' + + split = args.split('=', 1) + if len(split) != 2: + return error("invalid syntax: %r" % args) + + key, value = map(unicode.strip, split) + prog_config = get_progress_config(uzbl) + + if key not in prog_config: + return error("key error: %r" % args) + + if type(prog_config[key]) == type(1): + try: + value = int(value) + + except: + return error("invalid type: %r" % args) + + elif not value: + value = ' ' + + prog_config[key] = value + update_progress(uzbl) + + +def reset_progress(uzbl, args): + '''Reset the spinner counter, reset the progress int and re-draw the + progress bar on LOAD_COMMIT.''' + + prog_dict = get_progress_config(uzbl) + prog_dict['updates'] = prog_dict['progress'] = 0 + update_progress(uzbl) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'LOAD_COMMIT': reset_progress, + 'LOAD_PROGRESS': update_progress, + 'PROGRESS_CONFIG': progress_config, + }) diff --git a/examples/data/scripts/cookies.sh b/examples/data/scripts/cookies.sh new file mode 100755 index 0000000..ee2ce51 --- /dev/null +++ b/examples/data/scripts/cookies.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +set -n; + +# THIS IS EXPERIMENTAL AND COULD BE INSECURE !!!!!! + +# this is an example bash script of how you could manage your cookies. it is very raw and basic and not as good as uzbl-cookie-daemon +# we use the cookies.txt format (See http://kb.mozillazine.org/Cookies.txt) +# This is one textfile with entries like this: +# kb.mozillazine.org FALSE / FALSE 1146030396 wikiUserID 16993 +# domain alow-read-other-subdomains path http-required expiration name value +# you probably want your cookies config file in your $XDG_CONFIG_HOME ( eg $HOME/.config/uzbl/cookies) +# Note. in uzbl there is no strict definition on what a session is. it's YOUR job to clear cookies marked as end_session if you want to keep cookies only valid during a "session" +# MAYBE TODO: allow user to edit cookie before saving. this cannot be done with zenity :( +# TODO: different cookie paths per config (eg per group of uzbl instances) + +# TODO: correct implementation. +# see http://curl.haxx.se/rfc/cookie_spec.html +# http://en.wikipedia.org/wiki/HTTP_cookie + +# TODO : check expires= before sending. +# write sample script that cleans up cookies dir based on expires attribute. +# TODO: check uri against domain attribute. and path also. +# implement secure attribute. +# support blocking or not for 3rd parties +# http://kb.mozillazine.org/Cookies.txt +# don't always append cookies, sometimes we need to overwrite + +cookie_config=${XDG_CONFIG_HOME:-${HOME}/.config}/uzbl/cookies +[ "x$cookie_config" = x ] && exit 1 +[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/" ] &&\ +cookie_data=${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/cookies.txt || exit 1 + +notifier= +#notifier=notify-send +#notify_wrapper () { +# echo "$@" >> $HOME/cookielog +#} +#notifier=notifier_wrapper + +# if this variable is set, we will use it to inform you when and which cookies we store, and when/which we send. +# it's primarily used for debugging +notifier= +which zenity &>/dev/null || exit 2 + +# Example cookie: +# test_cookie=CheckForPermission; expires=Thu, 07-May-2009 19:17:55 GMT; path=/; domain=.doubleclick.net + +# uri=$6 +# uri=${uri/http:\/\/} # strip 'http://' part +# host=${uri/\/*/} +action=$8 # GET/PUT +shift +host=$9 +shift +path=$9 +shift +cookie=$9 + +field_domain=$host +field_path=$path +field_name= +field_value= +field_exp='end_session' + +notify() { + [ -n "$notifier" ] && $notifier "$@" +} + + +# FOR NOW LETS KEEP IT SIMPLE AND JUST ALWAYS PUT AND ALWAYS GET +parse_cookie() { + IFS=$';' + first_pair=1 + for pair in $cookie + do + if [ "x$first_pair" = x1 ] + then + field_name=${pair%%=*} + field_value=${pair#*=} + first_pair=0 + else + echo "$pair" | read -r pair #strip leading/trailing wite space + key=${pair%%=*} + val=${pair#*=} + [ "$key" == expires ] && field_exp=`date -u -d "$val" +'%s'` + # TODO: domain + [ "$key" == path ] && field_path=$val + fi + done + unset IFS +} + +# match cookies in cookies.txt against hostname and path +get_cookie() { + path_esc=${path//\//\\/} + search="^[^\t]*$host\t[^\t]*\t$path_esc" + cookie=`awk "/$search/" $cookie_data 2>/dev/null | tail -n 1` + if [ -z "$cookie" ] + then + notify "Get_cookie: search: $search in $cookie_data -> no result" + false + else + notify "Get_cookie: search: $search in $cookie_data -> result: $cookie" + echo "$cookie" | \ + read domain alow_read_other_subdomains path http_required expiration name \ + value; + cookie="$name=$value" + true + fi +} + +save_cookie() { + if parse_cookie + then + data="$field_domain\tFALSE\t$field_path\tFALSE\t$field_exp\t$field_name\t$field_value" + notify "save_cookie: adding $data to $cookie_data" + echo -e "$data" >> $cookie_data + else + notify "not saving a cookie. since we don't have policies yet, parse_cookie must have returned false. this is a bug" + fi +} + +[ "x$action" = xPUT ] && save_cookie +[ "x$action" = xGET ] && get_cookie && echo "$cookie" + +exit + + +# TODO: implement this later. +# $1 = section (TRUSTED or DENY) +# $2 =url +match() { + sed -n "/$1/,/^\$/p" $cookie_config 2>/dev/null | grep -q "^$host" +} + +fetch_cookie() { + cookie=`cat $cookie_data` +} + +store_cookie() { + echo $cookie > $cookie_data +} + +if match TRUSTED $host +then + [ "x$action" = xPUT ] && store_cookie $host + [ "x$action" = xGET ] && fetch_cookie && echo "$cookie" +elif ! match DENY $host +then + [ "x$action" = xPUT ] && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Accept this cookie from $host ?" --entry-text="$cookie"` && store_cookie $host + [ "x$action" = xGET ] && fetch_cookie && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Submit this cookie to $host ?" --entry-text="$cookie"` && echo $cookie +fi +exit 0 diff --git a/examples/data/scripts/download.sh b/examples/data/scripts/download.sh new file mode 100755 index 0000000..1c7d039 --- /dev/null +++ b/examples/data/scripts/download.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# just an example of how you could handle your downloads +# try some pattern matching on the uri to determine what we should do + +# Some sites block the default wget --user-agent.. +GET="wget --user-agent=Firefox" + +dest="$HOME" +url="$8" + +http_proxy="$9" +export http_proxy + +test "x$url" = "x" && { echo "you must supply a url! ($url)"; exit 1; } + +# only changes the dir for the $get sub process +if echo "$url" | grep -E '.*\.torrent' >/dev/null; +then + ( cd "$dest"; $GET "$url") +else + ( cd "$dest"; $GET "$url") +fi diff --git a/examples/data/scripts/extedit.js b/examples/data/scripts/extedit.js new file mode 100644 index 0000000..8ed346d --- /dev/null +++ b/examples/data/scripts/extedit.js @@ -0,0 +1,102 @@ +/* + * Edit forms in external editor + * + * (c) 2009, Robert Manea + * utf8 functions are (c) by Webtoolkit.info (http://www.webtoolkit.info/) + * + * + * Installation: + * - Copy this script to $HOME/.local/share/uzbl/scripts + * - Add the following to $HOME/.config/uzbl/config: + * @bind E = script @scripts_dir/extedit.js + * - Set your preferred editor + * set editor = gvim + * - non-GUI editors + * set editor = xterm -e vim + * + * Usage: + * Select (click) an editable form, go to command mode and hit E + * +*/ + + +function utf8_decode ( str_data ) { + var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0; + + str_data += ''; + + while ( i < str_data.length ) { + c1 = str_data.charCodeAt(i); + if (c1 < 128) { + tmp_arr[ac++] = String.fromCharCode(c1); + i++; + } else if ((c1 > 191) && (c1 < 224)) { + c2 = str_data.charCodeAt(i+1); + tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = str_data.charCodeAt(i+1); + c3 = str_data.charCodeAt(i+2); + tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + + return tmp_arr.join(''); +} + + +function utf8_encode ( argString ) { + var string = (argString+''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + + var utftext = ""; + var start, end; + var stringl = 0; + + start = end = 0; + stringl = string.length; + for (var n = 0; n < stringl; n++) { + var c1 = string.charCodeAt(n); + var enc = null; + + if (c1 < 128) { + end++; + } else if (c1 > 127 && c1 < 2048) { + enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128); + } else { + enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128); + } + if (enc !== null) { + if (end > start) { + utftext += string.substring(start, end); + } + utftext += enc; + start = end = n+1; + } + } + + if (end > start) { + utftext += string.substring(start, string.length); + } + + return utftext; +} + + +(function() { + var actelem = document.activeElement; + + if(actelem.type == 'text' || actelem.type == 'textarea') { + var editor = Uzbl.run("print @external_editor") || "gvim"; + var filename = Uzbl.run("print @(mktemp /tmp/uzbl_edit.XXXXXX)@"); + + if(actelem.value) + Uzbl.run("sh 'echo " + window.btoa(utf8_encode(actelem.value)) + " | base64 -d > " + filename + "'"); + + Uzbl.run("sync_sh '" + editor + " " + filename + "'"); + actelem.value = utf8_decode(window.atob(Uzbl.run("print @(base64 -w 0 " + filename + ")@"))); + + Uzbl.run("sh 'rm -f " + filename + "'"); + } + + })(); diff --git a/examples/data/scripts/follow_Numbers.js b/examples/data/scripts/follow_Numbers.js new file mode 100644 index 0000000..00b279e --- /dev/null +++ b/examples/data/scripts/follow_Numbers.js @@ -0,0 +1,228 @@ +/* This is the basic linkfollowing script. + * Its pretty stable, only using numbers to navigate. + * + * TODO: Some pages mess around a lot with the zIndex which + * lets some hints in the background. + * TODO: Some positions are not calculated correctly (mostly + * because of uber-fancy-designed-webpages. Basic HTML and CSS + * works good + * TODO: Still some links can't be followed/unexpected things + * happen. Blame some freaky webdesigners ;) + */ + +//Just some shortcuts and globals +var uzblid = 'uzbl_link_hint'; +var uzbldivid = uzblid + '_div_container'; +var doc = document; +var win = window; +var links = document.links; +var forms = document.forms; + +//Reset keycmd, modcmd and return to default mode. +function clearKeycmd() { Uzbl.run('set mode ='); } + +//Make onlick-links "clickable" +try { + HTMLElement.prototype.click = function() { + if (typeof this.onclick == 'function') { + this.onclick({ + type: 'click' + }); + } + }; +} catch(e) {} +//Catch the ESC keypress to stop linkfollowing +function keyPressHandler(e) { + var kC = window.event ? event.keyCode: e.keyCode; + var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; + if (kC == Esc) { + removeAllHints(); + } +} +//Calculate element position to draw the hint +//Pretty accurate but on fails in some very fancy cases +function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; + var height = el.offsetHeight; + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return [up, left, width, height]; +} +//Calculate if an element is visible +function isVisible(el) { + if (el == doc) { + return true; + } + if (!el) { + return false; + } + if (!el.parentNode) { + return false; + } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); +} +//Calculate if an element is on the viewport. +function elementInViewport(el) { + offset = elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; + var height = offset[3]; + return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; +} +//Removes all hints/leftovers that might be generated +//by this script. +function removeAllHints() { + var elements = doc.getElementById(uzbldivid); + if (elements) { + elements.parentNode.removeChild(elements); + } +} +//Generate a hint for an element with the given label +//Here you can play around with the style of the hints! +function generateHint(el, label) { + var pos = elementPosition(el); + var hint = doc.createElement('div'); + hint.setAttribute('name', uzblid); + hint.innerText = label; + hint.style.display = 'inline'; + hint.style.backgroundColor = '#B9FF00'; + hint.style.border = '2px solid #4A6600'; + hint.style.color = 'black'; + hint.style.fontSize = '9px'; + hint.style.fontWeight = 'bold'; + hint.style.lineHeight = '9px'; + hint.style.margin = '0px'; + hint.style.padding = '1px'; + hint.style.position = 'absolute'; + hint.style.zIndex = '1000'; + hint.style.left = pos[1] + 'px'; + hint.style.top = pos[0] + 'px'; + var img = el.getElementsByTagName('img'); + if (img.length > 0) { + hint.style.left = pos[1] + img[0].width / 2 + 'px'; + } + hint.style.textDecoration = 'none'; + hint.style.webkitBorderRadius = '6px'; + // Play around with this, pretty funny things to do :) + hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; + return hint; +} +//Here we choose what to do with an element if we +//want to "follow" it. On form elements we "select" +//or pass the focus, on links we try to perform a click, +//but at least set the href of the link. (needs some improvements) +function clickElem(item) { + removeAllHints(); + clearKeycmd(); + if (item) { + var name = item.tagName; + if (name == 'A') { + item.click(); + window.location = item.href; + } else if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { + item.focus(); + item.select(); + } else { + item.click(); + } + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + } else { + item.click(); + window.location = item.href; + } + } +} +//Returns a list of all links (in this version +//just the elements itself, but in other versions, we +//add the label here. +function addLinks() { + res = [[], []]; + for (var l = 0; l < links.length; l++) { + var li = links[l]; + if (isVisible(li) && elementInViewport(li)) { + res[0].push(li); + } + } + return res; +} +//Same as above, just for the form elements +function addFormElems() { + res = [[], []]; + for (var f = 0; f < forms.length; f++) { + for (var e = 0; e < forms[f].elements.length; e++) { + var el = forms[f].elements[e]; + if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { + res[0].push(el); + } + } + } + return res; +} +//Draw all hints for all elements passed. "len" is for +//the number of chars we should use to avoid collisions +function reDrawHints(elems, chars) { + removeAllHints(); + var hintdiv = doc.createElement('div'); + hintdiv.setAttribute('id', uzbldivid); + for (var i = 0; i < elems[0].length; i++) { + if (elems[0][i]) { + var label = elems[1][i].substring(chars); + var h = generateHint(elems[0][i], label); + hintdiv.appendChild(h); + } + } + if (document.body) { + document.body.appendChild(hintdiv); + } +} +//Put it all together +function followLinks(follow) { + var s = follow.split(''); + var linknr = parseInt(follow, 10); + if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); + var linkelems = addLinks(); + var formelems = addFormElems(); + var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; + var len = (elems[0].length + '').length; + var oldDiv = doc.getElementById(uzbldivid); + var leftover = [[], []]; + if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { + clickElem(elems[0][linknr]); + } else { + for (var j = 0; j < elems[0].length; j++) { + var b = true; + var label = j + ''; + var n = label.length; + for (n; n < len; n++) { + label = '0' + label; + } + for (var k = 0; k < s.length; k++) { + b = b && label.charAt(k) == s[k]; + } + if (b) { + leftover[0].push(elems[0][j]); + leftover[1].push(label); + } + } + reDrawHints(leftover, s.length); + } +} +followLinks('%s'); diff --git a/examples/data/scripts/follow_Numbers_Strings.js b/examples/data/scripts/follow_Numbers_Strings.js new file mode 100644 index 0000000..e50da5d --- /dev/null +++ b/examples/data/scripts/follow_Numbers_Strings.js @@ -0,0 +1,212 @@ +var uzblid = 'uzbl_link_hint'; +var uzbldivid = uzblid + '_div_container'; +var doc = document; +var win = window; +var links = document.links; +var forms = document.forms; + +//Reset keycmd, modcmd and return to default mode. +function clearKeycmd() { Uzbl.run('set mode ='); } + +try { + HTMLElement.prototype.click = function() { + if (typeof this.onclick == 'function') { + this.onclick({ + type: 'click' + }); + } + }; +} catch(e) {} +function keyPressHandler(e) { + var kC = window.event ? event.keyCode: e.keyCode; + var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; + if (kC == Esc) { + removeAllHints(); + } +} +function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; + var height = el.offsetHeight; + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return [up, left, width, height]; +} +function isVisible(el) { + if (el == doc) { + return true; + } + if (!el) { + return false; + } + if (!el.parentNode) { + return false; + } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); +} +function elementInViewport(el) { + offset = elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; + var height = offset[3]; + return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; +} +function removeAllHints() { + var elements = doc.getElementById(uzbldivid); + if (elements) { + elements.parentNode.removeChild(elements); + } +} +function generateHint(el, label) { + var pos = elementPosition(el); + var hint = doc.createElement('div'); + hint.setAttribute('name', uzblid); + hint.innerText = label; + hint.style.display = 'inline'; + hint.style.backgroundColor = '#B9FF00'; + hint.style.border = '2px solid #4A6600'; + hint.style.color = 'black'; + hint.style.zIndex = '1000'; + hint.style.fontSize = '9px'; + hint.style.fontWeight = 'bold'; + hint.style.lineHeight = '9px'; + hint.style.margin = '0px'; + hint.style.padding = '1px'; + hint.style.position = 'absolute'; + hint.style.left = pos[1] + 'px'; + hint.style.top = pos[0] + 'px'; + var img = el.getElementsByTagName('img'); + if (img.length > 0) { + hint.style.left = pos[1] + img[0].width / 2 + 'px'; + } + hint.style.textDecoration = 'none'; + hint.style.webkitBorderRadius = '6px'; + hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; + return hint; +} + +function clickElem(item) { + removeAllHints(); + clearKeycmd(); + if (item) { + var name = item.tagName; + if (name == 'A') { + item.click(); + window.location = item.href; + } else if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { + item.focus(); + item.select(); + } else { + item.click(); + } + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + } else { + item.click(); + window.location = item.href; + } + } +} + +function addLinks() { + res = [[], []]; + for (var l = 0; l < links.length; l++) { + var li = links[l]; + if (isVisible(li) && elementInViewport(li)) { + res[0].push(li); + res[1].push(li.innerText.toLowerCase()); + } + } + return res; +} +function addFormElems() { + res = [[], []]; + for (var f = 0; f < forms.length; f++) { + for (var e = 0; e < forms[f].elements.length; e++) { + var el = forms[f].elements[e]; + if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { + res[0].push(el); + if (el.getAttribute('value')) { + res[1].push(el.getAttribute('value').toLowerCase()); + } else { + res[1].push(el.getAttribute('name').toLowerCase()); + } + } + } + } + return res; +} +function reDrawHints(elems, len) { + var hintdiv = doc.createElement('div'); + hintdiv.setAttribute('id', uzbldivid); + hintdiv.style.opacity = '0.0'; + for (var i = 0; i < elems[0].length; i++) { + var label = i + ''; + var n = label.length; + for (n; n < len; n++) { + label = '0' + label; + } + if (elems[0][i]) { + var h = generateHint(elems[0][i], label); + hintdiv.appendChild(h); + } + } + if (document.body) { + document.body.appendChild(hintdiv); + hintdiv.style.opacity = '0.7' + } +} +function followLinks(follow) { + var s = follow.split(''); + var linknr = parseInt(follow, 10); + if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); + var linkelems = addLinks(); + var formelems = addFormElems(); + var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; + var len = (elems[0].length + '').length; + var oldDiv = doc.getElementById(uzbldivid); + var leftover = [[], []]; + if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { + clickElem(elems[0][linknr]); + } else { + for (var j = 0; j < elems[0].length; j++) { + var b = true; + for (var k = 0; k < s.length; k++) { + b = b && elems[1][j].charAt(k) == s[k]; + } + if (!b) { + elems[0][j] = null; + elems[1][j] = null; + } else { + leftover[0].push(elems[0][j]); + leftover[1].push(elems[1][j]); + } + } + if (leftover[0].length == 1) { + clickElem(leftover[0][0]); + } else if (!oldDiv) { + if (linknr + 1 || s.length == 0) { + reDrawHints(elems, len); + } else { + reDrawHints(leftover, len); + } + } + } +} +followLinks('%s'); diff --git a/examples/data/scripts/formfiller.pl b/examples/data/scripts/formfiller.pl new file mode 100755 index 0000000..74dcc80 --- /dev/null +++ b/examples/data/scripts/formfiller.pl @@ -0,0 +1,99 @@ +#!/usr/bin/perl + +# a slightly more advanced form filler +# +# uses settings file like: $keydir/ +#TODO: fallback to $HOME/.local/share +# user arg 1: +# edit: force editing of the file (fetches if file is missing) +# load: fill forms from file (fetches if file is missing) +# new: fetch new file + +# usage example: +# bind LL = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl load +# bind LN = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl new +# bind LE = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl edit + +use strict; +use warnings; + +my $keydir = $ENV{XDG_CONFIG_HOME} . "/uzbl/forms"; +my ($config,$pid,$xid,$fifoname,$socket,$url,$title,$cmd) = @ARGV; +if (!defined $fifoname || $fifoname eq "") { die "No fifo"; } + +sub domain { + my ($url) = @_; + $url =~ s#http(s)?://([A-Za-z0-9\.-]+)(/.*)?#$2#; + return $url; +}; + +my $editor = "xterm -e vim"; +#my $editor = "gvim"; + +# ideally, there would be some way to ask uzbl for the html content instead of having to redownload it with +# Also, you may need to fake the user-agent on some sites (like facebook) + my $downloader = "curl -A 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.10) Gecko/2009042810 GranParadiso/3.0.10' "; +#my $downloader = "curl -s"; + +my @fields = ("type","name","value"); + +my %command; + +$command{load} = sub { + my ($domain) = @_; + my $filename = "$keydir/$domain"; + if (-e $filename){ + open(my $file, $filename) or die "Failed to open $filename: $!"; + my (@lines) = <$file>; + close($file); + $|++; + open(my $fifo, ">>", $fifoname) or die "Failed to open $fifoname: $!"; + foreach my $line (@lines) { + next if ($line =~ m/^#/); + my ($type,$name,$value) = ($line =~ /^\s*(\w+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*$/); + if ($type eq "checkbox") + { + printf $fifo 'js document.getElementsByName("%s")[0].checked = %s;', $name, $value; + } elsif ($type eq "submit") + { + printf $fifo 'js function fs (n) {try{n.submit()} catch (e){fs(n.parentNode)}}; fs(document.getElementsByName("%s")[0]);', $name; + } elsif ($type ne "") + { + printf $fifo 'js document.getElementsByName("%s")[0].value = "%s";', $name, $value; + } + print $fifo "\n"; + } + $|--; + } else { + $command{new}->($domain); + $command{edit}->($domain); + } +}; +$command{edit} = sub { + my ($domain) = @_; + my $file = "$keydir/$domain"; + if(-e $file){ + system ($editor, $file); + } else { + $command{new}->($domain); + } +}; +$command{new} = sub { + my ($domain) = @_; + my $filename = "$keydir/$domain"; + open (my $file,">>", $filename) or die "Failed to open $filename: $!"; + $|++; + print $file "# Make sure that there are no extra submits, since it may trigger the wrong one.\n"; + printf $file "#%-10s | %-10s | %s\n", @fields; + print $file "#------------------------------\n"; + my @data = `$downloader $url`; + foreach my $line (@data){ + if($line =~ m/].*?)>/i){ + $line =~ s/.*(].*?)>).*/$1/; + printf $file " %-10s | %-10s | %s\n", map { my ($r) = $line =~ /.*$_=["'](.*?)["']/;$r } @fields; + }; + }; + $|--; +}; + +$command{$cmd}->(domain($url)); diff --git a/examples/data/scripts/formfiller.sh b/examples/data/scripts/formfiller.sh new file mode 100755 index 0000000..10afaba --- /dev/null +++ b/examples/data/scripts/formfiller.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# simple html form (eg for logins) filler (and manager) for uzbl. +# uses settings files like: $keydir/ +# files contain lines like: : + + +# user arg 1: +# edit: force editing the file (falls back to new if not found) +# new: start with a new file. +# load: try to load from file into form + +# something else (or empty): if file not available: new, otherwise load. + +keydir=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/forms +[ -d "`dirname $keydir`" ] || exit 1 +[ -d "$keydir" ] || mkdir "$keydir" + +editor=${VISUAL} +if [[ -z ${editor} ]]; then + #editor='gvim' + editor='urxvt -e vim' +fi + +config=$1; shift +pid=$1; shift +xid=$1; shift +fifo=$1; shift +socket=$1; shift +url=$1; shift +title=$1; shift +action=$1 + +[ -d $keydir ] || mkdir $keydir || exit 1 + +if [ "$action" != 'edit' -a "$action" != 'new' -a "$action" != 'load' ] +then + action=new + [[ -e $keydir/$domain ]] && action=load +elif [ "$action" == 'edit' ] && [[ ! -e $keydir/$domain ]] +then + action=new +fi +domain=$(echo $url | sed -re 's|(http\|https)+://([A-Za-z0-9\.]+)/.*|\2|') + + +#regex='s|.*.*|\1: |p' # sscj's first version, does not work on http://wiki.archlinux.org/index.php?title=Special:UserLogin&returnto=Main_Page + regex='s|.*> $fifo +else + if [ "$action" == 'new' ] + then + curl "$url" | grep ' $keydir/$domain + fi + [[ -e $keydir/$domain ]] || exit 3 #this should never happen, but you never know. + $editor $keydir/$domain #TODO: if user aborts save in editor, the file is already overwritten +fi diff --git a/examples/data/scripts/hint.js b/examples/data/scripts/hint.js new file mode 100644 index 0000000..ec7f1e2 --- /dev/null +++ b/examples/data/scripts/hint.js @@ -0,0 +1,26 @@ +for (var i=0; i < document.links.length; i++) { + var uzblid = 'uzbl_link_hint_'; + var li = document.links[i]; + var pre = document.getElementById(uzblid+i); + + if (pre) { + li.removeChild(pre); + } else { + var hint = document.createElement('div'); + hint.setAttribute('id',uzblid+i); + hint.innerHTML = i; + hint.style.display='inline'; + hint.style.lineHeight='90%'; + hint.style.backgroundColor='red'; + hint.style.color='white'; + hint.style.fontSize='small-xx'; + hint.style.fontWeight='light'; + hint.style.margin='0px'; + hint.style.padding='2px'; + hint.style.position='absolute'; + hint.style.textDecoration='none'; + hint.style.left=li.style.left; + hint.style.top=li.style.top; + li.insertAdjacentElement('afterBegin',hint); + } +} diff --git a/examples/data/scripts/history.sh b/examples/data/scripts/history.sh new file mode 100755 index 0000000..7c83aa6 --- /dev/null +++ b/examples/data/scripts/history.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history +[ -d `dirname $file` ] || exit 1 +echo `date +'%Y-%m-%d %H:%M:%S'`" $6 $7" >> $file diff --git a/examples/data/scripts/insert_bookmark.sh b/examples/data/scripts/insert_bookmark.sh new file mode 100755 index 0000000..c34e7db --- /dev/null +++ b/examples/data/scripts/insert_bookmark.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +[ -d "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl" ] || exit 1 +file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/bookmarks + +which zenity &>/dev/null || exit 2 + +entry=`zenity --entry --text="Add bookmark. add tags after the '\t', separated by spaces" --entry-text="$6 $7\t"` +exitstatus=$? +if [ $exitstatus -ne 0 ]; then exit $exitstatus; fi +url=`echo $entry | awk '{print $1}'` + +# TODO: check if already exists, if so, and tags are different: ask if you want to replace tags +echo "$entry" >/dev/null #for some reason we need this.. don't ask me why +echo -e "$entry" >> $file +true diff --git a/examples/data/scripts/instance-select-wmii.sh b/examples/data/scripts/instance-select-wmii.sh new file mode 100755 index 0000000..2bf13ba --- /dev/null +++ b/examples/data/scripts/instance-select-wmii.sh @@ -0,0 +1,54 @@ +#!/bin/sh + + +# This script allows you to focus another uzbl window +# It considers all uzbl windows in the current tag +# you can select one from a list, or go to the next/previous one +# It does not change the layout (stacked/tiled/floating) nor does it +# changes the size or viewing mode of a uzbl window +# When your current uzbl window is maximized, the one you change to +# will be maximized as well. +# See http://www.uzbl.org/wiki/wmii for more info +# $1 must be one of 'list', 'next', 'prev' + +COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" + +if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch +else + DMENU="dmenu -i" +fi + +if [ "$1" == 'list' ] +then + list= + # get window id's of uzbl clients. we could also get the label in one shot but it's pretty tricky + for i in $(wmiir read /tag/sel/index | grep uzbl |cut -d ' ' -f2) + do + label=$(wmiir read /client/$i/label) + list="$list$i : $label\n" + done + window=$(echo -e "$list" | $DMENU $COLORS | cut -d ' ' -f1) + wmiir xwrite /tag/sel/ctl "select client $window" +elif [ "$1" == 'next' ] +then + current=$(wmiir read /client/sel/ctl | head -n 1) + # find the next uzbl window and focus it + next=$(wmiir read /tag/sel/index | grep -A 10000 " $current " | grep -m 1 uzbl | cut -d ' ' -f2) + if [ x"$next" != "x" ] + then + wmiir xwrite /tag/sel/ctl "select client $next" + fi +elif [ "$1" == 'prev' ] +then + current=$(wmiir read /client/sel/ctl | head -n 1) + prev=$(wmiir read /tag/sel/index | grep -B 10000 " $current " | tac | grep -m 1 uzbl | cut -d ' ' -f2) + if [ x"$prev" != "x" ] + then + wmiir xwrite /tag/sel/ctl "select client $prev" + fi +else + echo "\$1 not valid" >&2 + exit 2 +fi diff --git a/examples/data/scripts/linkfollow.js b/examples/data/scripts/linkfollow.js new file mode 100644 index 0000000..3109cda --- /dev/null +++ b/examples/data/scripts/linkfollow.js @@ -0,0 +1,269 @@ +// link follower for uzbl +// requires http://github.com/DuClare/uzbl/commit/6c11777067bdb8aac09bba78d54caea04f85e059 +// +// first, it needs to be loaded before every time it is used. +// One way would be to use the load_commit_handler: +// set load_commit_handler = sh 'echo "script /usr/share/uzbl/examples/data/scripts/linkfollow.js" > "$4"' +// +// when script is loaded, it can be invoked with +// bind f* = js hints.set("%s", hints.open) +// bind f_ = js hints.follow("%s",hints.open) +// +// At the moment, it may be useful to have way of forcing uzbl to load the script +// bind :lf = script /usr/share/uzbl/examples/data/scripts/linkfollow.js +// +// The default style for the hints are pretty ugly, so it is recommended to add the following +// to config file +// set stylesheet_uri = /usr/share/uzbl/examples/data/style.css +// +// based on follow_Numbers.js +// +// TODO: fix styling for the first element +// TODO: emulate mouseover events when visiting some elements +// TODO: rewrite the element->action handling + + +function Hints(){ + + // Settings + //////////////////////////////////////////////////////////////////////////// + + // if set to true, you must explicitly call hints.follow(), otherwise it will + // follow the link if there is only one matching result + var requireReturn = true; + + // Case sensitivity flag + var matchCase = "i"; + + // For case sensitive matching, uncomment: + // var matchCase = ""; + + + var uzblid = 'uzbl_hint'; + var uzblclass = 'uzbl_highlight'; + var uzblclassfirst = 'uzbl_h_first'; + var doc = document; + var visible = []; + var hintdiv; + + this.set = hint; + this.follow = follow; + this.keyPressHandler = keyPressHandler; + + function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; var width = el.offsetWidth; + var height = el.offsetHeight; + + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return {up: up, left: left, width: width, height: height}; + } + + function elementInViewport(p) { + return (p.up < window.pageYOffset + window.innerHeight && + p.left < window.pageXOffset + window.innerWidth && + (p.up + p.height) > window.pageYOffset && + (p.left + p.width) > window.pageXOffset); + } + + function isVisible(el) { + if (el == doc) { return true; } + if (!el) { return false; } + if (!el.parentNode) { return false; } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); + } + + // the vimperator defaults minus the xhtml elements, since it gave DOM errors + var hintable = " //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select"; + + function Matcher(str){ + var numbers = str.replace(/[^\d]/g,""); + var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,matchCase)}); + this.test = test; + this.toString = toString; + this.numbers = numbers; + function matchAgainst(element){ + if(element.node.nodeName == "INPUT"){ + return element.node.value; + } else { + return element.node.textContent; + } + } + function test(element) { + // test all the regexp + var item = matchAgainst(element); + return words.every(function (regex) { return item.match(regex)}); + } + } + + function HintElement(node,pos){ + + this.node = node; + this.isHinted = false; + this.position = pos; + this.num = 0; + + this.addHint = function (labelNum) { + // TODO: fix uzblclassfirst + if(!this.isHinted){ + this.node.className += " " + uzblclass; + } + this.isHinted = true; + + // create hint + var hintNode = doc.createElement('div'); + hintNode.name = uzblid; + hintNode.innerText = labelNum; + hintNode.style.left = this.position.left + 'px'; + hintNode.style.top = this.position.up + 'px'; + hintNode.style.position = "absolute"; + doc.body.firstChild.appendChild(hintNode); + + } + this.removeHint = function(){ + if(this.isHinted){ + var s = (this.num)?uzblclassfirst:uzblclass; + this.node.className = this.node.className.replace(new RegExp(" "+s,"g"),""); + this.isHinted = false; + } + } + } + + function createHintDiv(){ + var hintdiv = doc.getElementById(uzblid); + if(hintdiv){ + hintdiv.parentNode.removeChild(hintdiv); + } + hintdiv = doc.createElement("div"); + hintdiv.setAttribute('id',uzblid); + doc.body.insertBefore(hintdiv,doc.body.firstChild); + return hintdiv; + } + + function init(){ + // WHAT? + doc.body.setAttribute("onkeyup","hints.keyPressHandler(event)"); + hintdiv = createHintDiv(); + visible = []; + + var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null); + for (var i = 0;i&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch + # show tags as well + goto=`$DMENU $COLORS < $file | awk '{print $1}'` +else + DMENU="dmenu -i" + # because they are all after each other, just show the url, not their tags. + goto=`awk '{print $1}' $file | $DMENU $COLORS` +fi + +#[ -n "$goto" ] && echo "uri $goto" > $4 +[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/scripts/load_url_from_history.sh b/examples/data/scripts/load_url_from_history.sh new file mode 100755 index 0000000..62e02ac --- /dev/null +++ b/examples/data/scripts/load_url_from_history.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +history_file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history +[ -r "$history_file" ] || exit 1 + +# choose from all entries, sorted and uniqued +# goto=`awk '{print $3}' $history_file | sort -u | dmenu -i` +COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" +if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]'; +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch + # choose an item in reverse order, showing also the date and page titles + # pick the last field from the first 3 fields. this way you can pick a url (prefixed with date & time) or type just a new url. + goto=`tac $history_file | $DMENU $COLORS | cut -d ' ' -f -3 | awk '{print $NF}'` +else + DMENU="dmenu -i" + # choose from all entries (no date or title), the first one being current url, and after that all others, sorted and uniqued, in ascending order + current=`tail -n 1 $history_file | awk '{print $3}'`; + goto=`(echo $current; awk '{print $3}' $history_file | grep -v "^$current\$" \ + | sort -u) | $DMENU $COLORS` +fi + +[ -n "$goto" ] && echo "uri $goto" > $4 +#[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/scripts/scheme.py b/examples/data/scripts/scheme.py new file mode 100755 index 0000000..0916466 --- /dev/null +++ b/examples/data/scripts/scheme.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import os, subprocess, sys, urlparse + +def detach_open(cmd): + # Thanks to the vast knowledge of Laurence Withers (lwithers) and this message: + # http://mail.python.org/pipermail/python-list/2006-November/587523.html + if not os.fork(): + null = os.open(os.devnull,os.O_WRONLY) + for i in range(3): os.dup2(null,i) + os.close(null) + subprocess.Popen(cmd) + print 'USED' + +if __name__ == '__main__': + uri = sys.argv[8] + u = urlparse.urlparse(uri) + if u.scheme == 'mailto': + detach_open(['xterm', '-e', 'mail', u.path]) + elif u.scheme == 'xmpp': + # Someone check for safe arguments to gajim-remote + detach_open(['gajim-remote', 'open_chat', uri]) + elif u.scheme == 'git': + detach_open(['git', 'clone', '--', uri], cwd=os.path.expanduser('~/src')) diff --git a/examples/data/scripts/scroll-percentage.js b/examples/data/scripts/scroll-percentage.js new file mode 100644 index 0000000..c9a51aa --- /dev/null +++ b/examples/data/scripts/scroll-percentage.js @@ -0,0 +1,68 @@ +// VIM ruler style scroll message +(function() { + var run = Uzbl.run; + var update_message = function() { + var innerHeight = window.innerHeight; + var scrollY = window.scrollY; + var height = document.height; + var message; + + if (UzblZoom.type === "full") { + var zoom_level = UzblZoom.level; + innerHeight = Math.ceil(innerHeight * zoom_level); + scrollY = Math.ceil(scrollY * zoom_level); + height -= 1; + } + + if (! height) { + message = ""; + } + else if (height <= innerHeight) { + message = run("print @scroll_all_indicator") || "All"; + } + else if (scrollY === 0) { + message = run("print @scroll_top_indicator") || "Top"; + } + else if (scrollY + innerHeight >= height) { + message = run("print @scroll_bottom_indicator") || "Bot"; + } + else { + var percentage = Math.round(scrollY / (height - innerHeight) * 100); + message = percentage + "%"; + } + run("set scroll_message=" + message); + }; + + self.UzblZoom = { + get level() { + return Number(run("print @zoom_level")) || 1; + }, + set level(level) { + if (typeof level === "number" && level > 0) { + run("set zoom_level = " + level); + update_message(); + } + }, + get type() { + return run("print @zoom_type") || "text"; + }, + set type(type) { + if ((type === "text" || type === "full") && this.type != type) { + run("toggle_zoom_type"); + run("set zoom_type = " + type); + update_message(); + } + }, + toggle_type: function() { + this.type = (this.type === "text" ? "full" : "text"); + } + }; + + window.addEventListener("DOMContentLoaded", update_message, false); + window.addEventListener("load", update_message, false); + window.addEventListener("resize", update_message, false); + window.addEventListener("scroll", update_message, false); + update_message(); +})(); + +// vim: set noet ff=unix diff --git a/examples/data/scripts/session.sh b/examples/data/scripts/session.sh new file mode 100755 index 0000000..1059b5e --- /dev/null +++ b/examples/data/scripts/session.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Very simple session manager for uzbl-browser. When called with "endsession" as the +# argument, it'll backup $sessionfile, look for fifos in $fifodir and +# instruct each of them to store their current url in $sessionfile and +# terminate themselves. Run with "launch" as the argument and an instance of +# uzbl-browser will be launched for each stored url. "endinstance" is used internally +# and doesn't need to be called manually at any point. +# Add a line like 'bind quit = /path/to/session.sh endsession' to your config + +[ -d ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl ] || exit 1 +scriptfile=$0 # this script +sessionfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/browser-session # the file in which the "session" (i.e. urls) are stored +configfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/config # uzbl configuration file +UZBL="uzbl-browser -c $configfile" # add custom flags and whatever here. + +fifodir=/tmp # remember to change this if you instructed uzbl to put its fifos elsewhere +thisfifo="$4" +act="$8" +url="$6" + +if [ "$act." = "." ]; then + act="$1" +fi + + +case $act in + "launch" ) + urls=`cat $sessionfile` + if [ "$urls." = "." ]; then + $UZBL + else + for url in $urls; do + $UZBL --uri "$url" & + done + fi + exit 0 + ;; + + "endinstance" ) + if [ "$url" != "(null)" ]; then + echo "$url" >> $sessionfile; + fi + echo "exit" > "$thisfifo" + ;; + + "endsession" ) + mv "$sessionfile" "$sessionfile~" + for fifo in $fifodir/uzbl_fifo_*; do + if [ "$fifo" != "$thisfifo" ]; then + echo "spawn $scriptfile endinstance" > "$fifo" + fi + done + echo "spawn $scriptfile endinstance" > "$thisfifo" + ;; + + * ) echo "session manager: bad action" + echo "Usage: $scriptfile [COMMAND] where commands are:" + echo " launch - Restore a saved session or start a new one" + echo " endsession - Quit the running session. Must be called from uzbl" + ;; +esac diff --git a/examples/data/scripts/uzbl-cookie-daemon b/examples/data/scripts/uzbl-cookie-daemon new file mode 100755 index 0000000..fde8b8e --- /dev/null +++ b/examples/data/scripts/uzbl-cookie-daemon @@ -0,0 +1,664 @@ +#!/usr/bin/env python + +# The Python Cookie Daemon for Uzbl. +# Copyright (c) 2009, Tom Adams +# Copyright (c) 2009, Dieter Plaetinck +# Copyright (c) 2009, Mason Larobina +# Copyright (c) 2009, Michael Fiano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +''' +The Python Cookie Daemon +======================== + +This daemon is a re-write of the original cookies.py script found in uzbl's +master branch. This script provides more functionality than the original +cookies.py by adding numerous command line options to specify different cookie +jar locations, socket locations, verbose output, etc. This functionality is +very useful as it allows you to run multiple daemons at once serving cookies +to different groups of uzbl instances as required. + +Keeping up to date +================== + +Check the cookie daemon uzbl-wiki page for more information on where to +find the latest version of the cookie_daemon.py + + http://www.uzbl.org/wiki/cookie_daemon.py + +Command line options +==================== + +Use the following command to get a full list of the cookie_daemon.py command +line options: + + ./cookie_daemon.py --help + +Talking with uzbl +================= + +In order to get uzbl to talk to a running cookie daemon you add the following +to your uzbl config: + + set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket + +Or if you prefer using the $HOME variable: + + set cookie_handler = talk_to_socket $HOME/.cache/uzbl/cookie_daemon_socket + +Todo list +========= + + - Use a pid file to make force killing a running daemon possible. + +Reporting bugs / getting help +============================= + +The best way to report bugs and or get help with the cookie daemon is to +contact the maintainers it the #uzbl irc channel found on the Freenode IRC +network (irc.freenode.org). +''' + +import cookielib +import os +import sys +import urllib2 +import select +import socket +import time +import atexit +from traceback import print_exc +from signal import signal, SIGTERM +from optparse import OptionParser +from os.path import join + +try: + import cStringIO as StringIO + +except ImportError: + import StringIO + + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return join(os.environ['HOME'], default) + +# Setup xdg paths. +CACHE_DIR = join(xdghome('CACHE', '.cache/'), 'uzbl/') +DATA_DIR = join(xdghome('DATA', '.local/share/'), 'uzbl/') +CONFIG_DIR = join(xdghome('CONFIG', '.config/'), 'uzbl/') + +# Ensure data paths exist. +for path in [CACHE_DIR, DATA_DIR, CONFIG_DIR]: + if not os.path.exists(path): + os.makedirs(path) + +# Default config +config = { + + # Default cookie jar, whitelist, and daemon socket locations. + 'cookie_jar': join(DATA_DIR, 'cookies.txt'), + 'cookie_whitelist': join(CONFIG_DIR, 'cookie_whitelist'), + 'cookie_socket': join(CACHE_DIR, 'cookie_daemon_socket'), + + # Don't use a cookie whitelist policy by default. + 'use_whitelist': False, + + # Time out after x seconds of inactivity (set to 0 for never time out). + # WARNING: Do not use this option if you are manually launching the daemon. + 'daemon_timeout': 0, + + # Daemonise by default. + 'daemon_mode': True, + + # Optionally print helpful debugging messages to the terminal. + 'verbose': False, + +} # End of config dictionary. + + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + + +_SCRIPTNAME = os.path.basename(sys.argv[0]) +def echo(msg): + '''Prints only if the verbose flag has been set.''' + + if config['verbose']: + sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) + + +def error(msg): + '''Prints error message and exits.''' + + sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) + sys.exit(1) + + +def mkbasedir(filepath): + '''Create the base directories of the file in the file-path if the dirs + don't exist.''' + + dirname = os.path.dirname(filepath) + if not os.path.exists(dirname): + echo("creating dirs: %r" % dirname) + os.makedirs(dirname) + + +def daemon_running(cookie_socket): + '''Check if another process (hopefully a cookie_daemon.py) is listening + on the cookie daemon socket. If another process is found to be + listening on the socket exit the daemon immediately and leave the + socket alone. If the connect fails assume the socket has been abandoned + and delete it (to be re-created in the create socket function).''' + + if not os.path.exists(cookie_socket): + return False + + if os.path.isfile(cookie_socket): + raise Exception("regular file at %r is not a socket" % cookie_socket) + + + if os.path.isdir(cookie_socket): + raise Exception("directory at %r is not a socket" % cookie_socket) + + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + sock.connect(cookie_socket) + sock.close() + echo("detected daemon listening on %r" % cookie_socket) + return True + + except socket.error: + # Failed to connect to cookie_socket so assume it has been + # abandoned by another cookie daemon process. + if os.path.exists(cookie_socket): + echo("deleting abandoned socket at %r" % cookie_socket) + os.remove(cookie_socket) + + return False + + +def send_command(cookie_socket, cmd): + '''Send a command to a running cookie daemon.''' + + if not daemon_running(cookie_socket): + return False + + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + sock.connect(cookie_socket) + sock.send(cmd) + sock.close() + echo("sent command %r to %r" % (cmd, cookie_socket)) + return True + + except socket.error: + print_exc() + error("failed to send message %r to %r" % (cmd, cookie_socket)) + return False + + +def kill_daemon(cookie_socket): + '''Send the "EXIT" command to running cookie_daemon.''' + + if send_command(cookie_socket, "EXIT"): + # Now ensure the cookie_socket is cleaned up. + start = time.time() + while os.path.exists(cookie_socket): + time.sleep(0.1) + if (time.time() - start) > 5: + error("force deleting socket %r" % cookie_socket) + os.remove(cookie_socket) + return + + echo("stopped daemon listening on %r"% cookie_socket) + + else: + if os.path.exists(cookie_socket): + os.remove(cookie_socket) + echo("removed abandoned/broken socket %r" % cookie_socket) + + +def daemonize(): + '''Daemonize the process using the Stevens' double-fork magic.''' + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #1 failed") + sys.exit(1) + + os.chdir('/') + os.setsid() + os.umask(0) + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #2 failed") + sys.exit(1) + + sys.stdout.flush() + sys.stderr.flush() + + devnull = '/dev/null' + stdin = file(devnull, 'r') + stdout = file(devnull, 'a+') + stderr = file(devnull, 'a+', 0) + + os.dup2(stdin.fileno(), sys.stdin.fileno()) + os.dup2(stdout.fileno(), sys.stdout.fileno()) + os.dup2(stderr.fileno(), sys.stderr.fileno()) + + +class CookieMonster: + '''The uzbl cookie daemon class.''' + + def __init__(self): + '''Initialise class variables.''' + + self.server_socket = None + self.jar = None + self.last_request = time.time() + self._running = False + + + def run(self): + '''Start the daemon.''' + + # The check healthy function will exit if another daemon is detected + # listening on the cookie socket and remove the abandoned socket if + # there isnt. + if os.path.exists(config['cookie_socket']): + if daemon_running(config['cookie_socket']): + sys.exit(1) + + # Create cookie daemon socket. + self.create_socket() + + # Daemonize process. + if config['daemon_mode']: + echo("entering daemon mode") + daemonize() + + # Register a function to cleanup on exit. + atexit.register(self.quit) + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) + + # Create cookie jar object from file. + self.open_cookie_jar() + + # Create a way to exit nested loops by setting a running flag. + self._running = True + + while self._running: + try: + # Enter main listen loop. + self.listen() + + except KeyboardInterrupt: + self._running = False + print + + except socket.error: + print_exc() + + except: + # Clean up + self.del_socket() + + # Raise exception + raise + + # Always delete the socket before calling create again. + self.del_socket() + # Create cookie daemon socket. + self.create_socket() + + + def load_whitelist(self): + '''Load the cookie jar whitelist policy.''' + + cookie_whitelist = config['cookie_whitelist'] + + if cookie_whitelist: + mkbasedir(cookie_whitelist) + + # Create cookie whitelist file if it does not exist. + if not os.path.exists(cookie_whitelist): + open(cookie_whitelist, 'w').close() + + # Read cookie whitelist file into list. + file = open(cookie_whitelist,'r') + domain_list = [line.rstrip('\n') for line in file] + file.close() + + # Define policy of allowed domains + policy = cookielib.DefaultCookiePolicy(allowed_domains=domain_list) + self.jar.set_policy(policy) + + # Save the last modified time of the whitelist. + self._whitelistmtime = os.stat(cookie_whitelist).st_mtime + + + def open_cookie_jar(self): + '''Open the cookie jar.''' + + cookie_jar = config['cookie_jar'] + cookie_whitelist = config['cookie_whitelist'] + + if cookie_jar: + mkbasedir(cookie_jar) + + # Create cookie jar object from file. + self.jar = cookielib.MozillaCookieJar(cookie_jar) + + # Load cookie whitelist policy. + if config['use_whitelist']: + self.load_whitelist() + + if cookie_jar: + try: + # Attempt to load cookies from the cookie jar. + self.jar.load(ignore_discard=True) + + # Ensure restrictive permissions are set on the cookie jar + # to prevent other users on the system from hi-jacking your + # authenticated sessions simply by copying your cookie jar. + os.chmod(cookie_jar, 0600) + + except: + pass + + + def reload_whitelist(self): + '''Reload the cookie whitelist.''' + + cookie_whitelist = config['cookie_whitelist'] + if os.path.exists(cookie_whitelist): + echo("reloading whitelist %r" % cookie_whitelist) + self.open_cookie_jar() + + + def create_socket(self): + '''Create AF_UNIX socket for communication with uzbl instances.''' + + cookie_socket = config['cookie_socket'] + mkbasedir(cookie_socket) + + self.server_socket = socket.socket(socket.AF_UNIX, + socket.SOCK_SEQPACKET) + + self.server_socket.bind(cookie_socket) + + # Set restrictive permissions on the cookie socket to prevent other + # users on the system from data-mining your cookies. + os.chmod(cookie_socket, 0600) + + + def listen(self): + '''Listen for incoming cookie PUT and GET requests.''' + + daemon_timeout = config['daemon_timeout'] + echo("listening on %r" % config['cookie_socket']) + + while self._running: + # This line tells the socket how many pending incoming connections + # to enqueue at once. Raising this number may or may not increase + # performance. + self.server_socket.listen(1) + + if bool(select.select([self.server_socket], [], [], 1)[0]): + client_socket, _ = self.server_socket.accept() + self.handle_request(client_socket) + self.last_request = time.time() + client_socket.close() + continue + + if daemon_timeout: + # Checks if the daemon has been idling for too long. + idle = time.time() - self.last_request + if idle > daemon_timeout: + self._running = False + + + def handle_request(self, client_socket): + '''Connection made, now to serve a cookie PUT or GET request.''' + + # Receive cookie request from client. + data = client_socket.recv(8192) + if not data: + return + + # Cookie argument list in packet is null separated. + argv = data.split("\0") + action = argv[0].upper().strip() + + # Catch the EXIT command sent to kill running daemons. + if action == "EXIT": + self._running = False + return + + # Catch whitelist RELOAD command. + elif action == "RELOAD": + self.reload_whitelist() + return + + # Return if command unknown. + elif action not in ['GET', 'PUT']: + error("unknown command %r." % argv) + return + + # Determine whether or not to print cookie data to terminal. + print_cookie = (config['verbose'] and not config['daemon_mode']) + if print_cookie: + print ' '.join(argv[:4]) + + uri = urllib2.urlparse.ParseResult( + scheme=argv[1], + netloc=argv[2], + path=argv[3], + params='', + query='', + fragment='').geturl() + + req = urllib2.Request(uri) + + if action == "GET": + self.jar.add_cookie_header(req) + if req.has_header('Cookie'): + cookie = req.get_header('Cookie') + client_socket.send(cookie) + if print_cookie: + print cookie + + else: + client_socket.send("\0") + + elif action == "PUT": + cookie = argv[4] if len(argv) > 3 else None + if print_cookie: + print cookie + + self.put_cookie(req, cookie) + + if print_cookie: + print + + + def put_cookie(self, req, cookie=None): + '''Put a cookie in the cookie jar.''' + + hdr = urllib2.httplib.HTTPMessage(\ + StringIO.StringIO('Set-Cookie: %s' % cookie)) + res = urllib2.addinfourl(StringIO.StringIO(), hdr, + req.get_full_url()) + self.jar.extract_cookies(res, req) + if config['cookie_jar']: + self.jar.save(ignore_discard=True) + + + def del_socket(self): + '''Remove the cookie_socket file on exit. In a way the cookie_socket + is the daemons pid file equivalent.''' + + if self.server_socket: + try: + self.server_socket.close() + + except: + pass + + self.server_socket = None + + cookie_socket = config['cookie_socket'] + if os.path.exists(cookie_socket): + echo("deleting socket %r" % cookie_socket) + os.remove(cookie_socket) + + + def quit(self): + '''Called on exit to make sure all loose ends are tied up.''' + + self.del_socket() + sys.exit(0) + + +def main(): + '''Main function.''' + + # Define command line parameters. + usage = "usage: %prog [options] {start|stop|restart|reload}" + parser = OptionParser(usage=usage) + parser.add_option('-n', '--no-daemon', dest='no_daemon', + action='store_true', help="don't daemonise the process.") + + parser.add_option('-v', '--verbose', dest="verbose", + action='store_true', help="print verbose output.") + + parser.add_option('-t', '--daemon-timeout', dest='daemon_timeout', + action="store", metavar="SECONDS", help="shutdown the daemon after x "\ + "seconds inactivity. WARNING: Do not use this when launching the "\ + "cookie daemon manually.") + + parser.add_option('-s', '--cookie-socket', dest="cookie_socket", + metavar="SOCKET", help="manually specify the socket location.") + + parser.add_option('-j', '--cookie-jar', dest='cookie_jar', + metavar="FILE", help="manually specify the cookie jar location.") + + parser.add_option('-m', '--memory', dest='memory', action='store_true', + help="store cookies in memory only - do not write to disk") + + parser.add_option('-u', '--use-whitelist', dest='usewhitelist', + action='store_true', help="use cookie whitelist policy") + + parser.add_option('-w', '--cookie-whitelist', dest='whitelist', + action='store', help="manually specify whitelist location", + metavar='FILE') + + # Parse the command line arguments. + (options, args) = parser.parse_args() + + expand = lambda p: os.path.realpath(os.path.expandvars(p)) + + initcommands = ['start', 'stop', 'restart', 'reload'] + for arg in args: + if arg not in initcommands: + error("unknown argument %r" % args[0]) + sys.exit(1) + + if len(args) > 1: + error("the daemon only accepts one {%s} action at a time." + % '|'.join(initcommands)) + sys.exit(1) + + if len(args): + action = args[0] + + else: + action = "start" + + if options.no_daemon: + config['daemon_mode'] = False + + if options.cookie_socket: + config['cookie_socket'] = expand(options.cookie_socket) + + if options.cookie_jar: + config['cookie_jar'] = expand(options.cookie_jar) + + if options.memory: + config['cookie_jar'] = None + + if options.whitelist: + config['cookie_whitelist'] = expand(options.whitelist) + + if options.whitelist or options.usewhitelist: + config['use_whitelist'] = True + + if options.daemon_timeout: + try: + config['daemon_timeout'] = int(options.daemon_timeout) + + except ValueError: + error("expected int argument for -t, --daemon-timeout") + + # Expand $VAR's in config keys that relate to paths. + for key in ['cookie_socket', 'cookie_jar', 'cookie_whitelist']: + if config[key]: + config[key] = os.path.expandvars(config[key]) + + if options.verbose: + config['verbose'] = True + import pprint + sys.stderr.write("%s\n" % pprint.pformat(config)) + + # It would be better if we didn't need to start this python process just + # to send a command to the socket, but unfortunately socat doesn't seem + # to support SEQPACKET. + if action == "reload": + send_command(config['cookie_socket'], "RELOAD") + + if action in ['stop', 'restart']: + kill_daemon(config['cookie_socket']) + + if action in ['start', 'restart']: + CookieMonster().run() + + +if __name__ == "__main__": + main() diff --git a/examples/data/scripts/uzbl-event-manager b/examples/data/scripts/uzbl-event-manager new file mode 100755 index 0000000..99b215a --- /dev/null +++ b/examples/data/scripts/uzbl-event-manager @@ -0,0 +1,833 @@ +#!/usr/bin/env python + +# Event Manager for Uzbl +# Copyright (c) 2009, Mason Larobina +# Copyright (c) 2009, Dieter Plaetinck +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +''' + +E V E N T _ M A N A G E R . P Y +=============================== + +Event manager for uzbl written in python. + +''' + +import imp +import os +import sys +import re +import socket +import pprint +import time +import atexit +from select import select +from signal import signal, SIGTERM +from optparse import OptionParser +from traceback import print_exc +from functools import partial + + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return os.path.join(os.environ['HOME'], default) + + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +# `make install` will put the correct value here for your system +PREFIX = '/usr/local/' + +# Setup xdg paths. +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') +CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') + +# Event manager config dictionary. This is not to be confused with the config +# dict that tracks variables in the uzbl instance. +CONFIG = { + 'verbose': False, + 'daemon_mode': True, + 'auto_close': False, + + 'plugins_load': [], + 'plugins_ignore': [], + + 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'), + os.path.join(PREFIX, 'share/uzbl/examples/data/plugins/')], + + 'server_socket': os.path.join(CACHE_DIR, 'event_daemon'), + 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'), +} + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + + +# Define some globals. +SCRIPTNAME = os.path.basename(sys.argv[0]) +FINDSPACES = re.compile("\s+") + + +class ArgumentError(Exception): + pass + + +def echo(msg): + '''Prints only if the verbose flag has been set.''' + + if CONFIG['verbose']: + sys.stdout.write("%s: %s\n" % (SCRIPTNAME, msg)) + + +def error(msg): + '''Prints error messages to stderr.''' + + sys.stderr.write("%s: error: %s\n" % (SCRIPTNAME, msg)) + + +def counter(): + '''Generate unique object id's.''' + + i = 0 + while True: + i += 1 + yield i + + +def find_plugins(plugin_dirs): + '''Find all event manager plugins in the plugin dirs and return a + dictionary of {'plugin-name.py': '/full/path/to/plugin-name.py', ...}''' + + plugins = {} + + for plugin_dir in plugin_dirs: + plugin_dir = os.path.realpath(os.path.expandvars(plugin_dir)) + if not os.path.isdir(plugin_dir): + continue + + for filename in os.listdir(plugin_dir): + if not filename.lower().endswith('.py'): + continue + + path = os.path.join(plugin_dir, filename) + if not os.path.isfile(path): + continue + + if filename not in plugins: + plugins[filename] = plugin_dir + + return plugins + + +def load_plugins(plugin_dirs, load=None, ignore=None): + '''Load event manager plugins found in the plugin_dirs.''' + + load = [] if load is None else load + ignore = [] if ignore is None else ignore + + # Find the plugins in the plugin_dirs. + found = find_plugins(plugin_dirs) + + if load: + # Ignore anything not in the load list. + for plugin in found.keys(): + if plugin not in load: + del found[plugin] + + if ignore: + # Ignore anything in the ignore list. + for plugin in found.keys(): + if plugin in ignore: + del found[plugin] + + # Print plugin list to be loaded. + pprint.pprint(found) + + loaded = {} + # Load all found plugins into the loaded dict. + for (filename, plugin_dir) in found.items(): + name = filename[:-3] + info = imp.find_module(name, [plugin_dir]) + plugin = imp.load_module(name, *info) + loaded[(plugin_dir, filename)] = plugin + + return loaded + + +def daemonize(): + '''Daemonize the process using the Stevens' double-fork magic.''' + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #1 failed") + sys.exit(1) + + os.chdir('/') + os.setsid() + os.umask(0) + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #2 failed") + sys.exit(1) + + sys.stdout.flush() + sys.stderr.flush() + + devnull = '/dev/null' + stdin = file(devnull, 'r') + stdout = file(devnull, 'a+') + stderr = file(devnull, 'a+', 0) + + os.dup2(stdin.fileno(), sys.stdin.fileno()) + os.dup2(stdout.fileno(), sys.stdout.fileno()) + os.dup2(stderr.fileno(), sys.stderr.fileno()) + + +def make_dirs(path): + '''Make all basedirs recursively as required.''' + + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + +def make_pid_file(pid_file): + '''Make pid file at given pid_file location.''' + + make_dirs(pid_file) + fileobj = open(pid_file, 'w') + fileobj.write('%d' % os.getpid()) + fileobj.close() + + +def del_pid_file(pid_file): + '''Delete pid file at given pid_file location.''' + + if os.path.isfile(pid_file): + os.remove(pid_file) + + +def get_pid(pid_file): + '''Read pid from pid_file.''' + + try: + fileobj = open(pid_file, 'r') + pid = int(fileobj.read()) + fileobj.close() + return pid + + except IOError, ValueError: + print_exc() + return None + + +def pid_running(pid): + '''Returns True if a process with the given pid is running.''' + + try: + os.kill(pid, 0) + + except OSError: + return False + + else: + return True + + +def term_process(pid): + '''Send a SIGTERM signal to the process with the given pid.''' + + if not pid_running(pid): + return False + + os.kill(pid, SIGTERM) + + start = time.time() + while True: + if not pid_running(pid): + return True + + if time.time() - start > 5: + raise OSError('failed to stop process with pid: %d' % pid) + + time.sleep(0.25) + + +def parse_msg(uzbl, msg): + '''Parse an incoming msg from a uzbl instance. All non-event messages + will be printed here and not be passed to the uzbl instance event + handler function.''' + + if not msg: + return + + cmd = FINDSPACES.split(msg, 3) + if not cmd or cmd[0] != 'EVENT': + # Not an event message. + print '---', msg + return + + while len(cmd) < 4: + cmd.append('') + + event, args = cmd[2], cmd[3] + if not event: + return + + try: + uzbl.event(event, args) + + except: + print_exc() + + +class EventHandler(object): + + nexthid = counter().next + + def __init__(self, event, handler, *args, **kargs): + if not callable(handler): + raise ArgumentError("EventHandler object requires a callable " + "object function for the handler argument not: %r" % handler) + + self.function = handler + self.args = args + self.kargs = kargs + self.event = event + self.hid = self.nexthid() + + + def __repr__(self): + args = ["event=%s" % self.event, "hid=%d" % self.hid, + "function=%r" % self.function] + + if self.args: + args.append("args=%r" % self.args) + + if self.kargs: + args.append("kargs=%r" % self.kargs) + + return "" % ', '.join(args) + + +class UzblInstance(object): + + # Give all plugins access to the main config dict. + config = CONFIG + + def __init__(self, parent, client_socket): + + # Internal variables. + self.exports = {} + self.handlers = {} + self.parent = parent + self.client_socket = client_socket + + self.depth = 0 + self.buffer = '' + self.pid = None + + # Call the init function in every plugin. The init function in each + # plugin is where that plugin connects functions to events and exports + # functions to the uzbl object. + for plugin in self.parent['plugins'].values(): + try: + plugin.init(self) + + except: + raise + + + def send(self, msg): + '''Send a command to the uzbl instance via the socket file.''' + + msg = msg.strip() + if self.client_socket: + print '%s<-- %s' % (' ' * self.depth, msg) + self.client_socket.send(("%s\n" % msg).encode('utf-8')) + + else: + print '%s!-- %s' % (' ' * self.depth, msg) + + + def export(self, name, function): + '''Export `function(uzbl, *args, ..)` inside a plugin to the uzbl + object like so `uzbl.function(*args, ..)`. This will allow other + plugins to call functions inside the current plugin (which is currently + calling this function) via the uzbl object.''' + + self.__dict__.__setitem__(name, partial(function, self)) + + + def export_dict(self, export_dict): + '''Export multiple (name, function)'s at once inside a dict of the + form `{name1: function1, name2: function2, ...}`.''' + + for (name, function) in export_dict.items(): + self.export(name, function) + + + def connect(self, event, handler, *args, **kargs): + '''Connect a uzbl event with a handler. Handlers can either be a + function or a uzbl command string.''' + + event = event.upper().strip() + assert event and ' ' not in event + + if event not in self.handlers.keys(): + self.handlers[event] = [] + + handlerobj = EventHandler(event, handler, *args, **kargs) + self.handlers[event].append(handlerobj) + print handlerobj + + + def connect_dict(self, connect_dict): + '''Connect a dictionary comprising of {"EVENT_NAME": handler, ..} to + the event handler stack. + + If you need to supply args or kargs to an event use the normal connect + function.''' + + for (event, handler) in connect_dict.items(): + self.connect(event, handler) + + + def remove_by_id(self, hid): + '''Remove connected event handler by unique handler id.''' + + for (event, handlers) in self.handlers.items(): + for handler in list(handlers): + if hid != handler.hid: + continue + + echo("removed %r" % handler) + handlers.remove(handler) + return + + echo('unable to find & remove handler with id: %d' % hid) + + + def remove(self, handler): + '''Remove connected event handler.''' + + for (event, handlers) in self.handlers.items(): + if handler in handlers: + echo("removed %r" % handler) + handlers.remove(handler) + return + + echo('unable to find & remove handler: %r' % handler) + + + def exec_handler(self, handler, *args, **kargs): + '''Execute event handler function.''' + + args += handler.args + kargs = dict(handler.kargs.items()+kargs.items()) + handler.function(self, *args, **kargs) + + + def event(self, event, *args, **kargs): + '''Raise an event.''' + + event = event.upper() + elems = [event,] + if args: elems.append(unicode(args)) + if kargs: elems.append(unicode(kargs)) + print "%s--> %s" % (' ' * self.depth, ' '.join(elems)) + + if event == "INSTANCE_START" and args: + self.pid = int(args[0]) + + if event not in self.handlers: + return + + for handler in self.handlers[event]: + self.depth += 1 + try: + self.exec_handler(handler, *args, **kargs) + + except: + print_exc() + + self.depth -= 1 + + + def close(self): + '''Close the client socket and clean up.''' + + try: + self.client_socket.close() + + except: + pass + + for (name, plugin) in self.parent['plugins'].items(): + if hasattr(plugin, 'cleanup'): + plugin.cleanup(self) + + +class UzblEventDaemon(dict): + def __init__(self): + + # Init variables and dict keys. + dict.__init__(self, {'uzbls': {}}) + self.running = None + self.server_socket = None + self.socket_location = None + + # Register that the event daemon server has started by creating the + # pid file. + make_pid_file(CONFIG['pid_file']) + + # Register a function to clean up the socket and pid file on exit. + atexit.register(self.quit) + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) + + # Load plugins, first-build of the plugins may be a costly operation. + self['plugins'] = load_plugins(CONFIG['plugin_dirs'], + CONFIG['plugins_load'], CONFIG['plugins_ignore']) + + + def _create_server_socket(self): + '''Create the event manager daemon socket for uzbl instance duplex + communication.''' + + server_socket = CONFIG['server_socket'] + server_socket = os.path.realpath(os.path.expandvars(server_socket)) + self.socket_location = server_socket + + # Delete socket if it exists. + if os.path.exists(server_socket): + os.remove(server_socket) + + self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.server_socket.bind(server_socket) + self.server_socket.listen(5) + + + def _close_server_socket(self): + '''Close and delete the server socket.''' + + try: + self.server_socket.close() + self.server_socket = None + + if os.path.exists(self.socket_location): + os.remove(self.socket_location) + + except: + pass + + + def run(self): + '''Main event daemon loop.''' + + # Create event daemon socket. + self._create_server_socket() + echo('listening on: %s' % self.socket_location) + + if CONFIG['daemon_mode']: + echo('entering daemon mode.') + daemonize() + # The pid has changed so update the pid file. + make_pid_file(CONFIG['pid_file']) + + # Now listen for incoming connections and or data. + self.listen() + + # Clean up. + self.quit() + + + def listen(self): + '''Accept incoming connections and constantly poll instance sockets + for incoming data.''' + + self.running = True + while self.running: + + sockets = [self.server_socket] + self['uzbls'].keys() + + reads, _, errors = select(sockets, [], sockets, 1) + + if self.server_socket in reads: + self.accept_connection() + reads.remove(self.server_socket) + + for client in reads: + self.read_socket(client) + + for client in errors: + error('Unknown error on socket: %r' % client) + self.close_connection(client) + + + def read_socket(self, client): + '''Read data from an instance socket and pass to the uzbl objects + event handler function.''' + + uzbl = self['uzbls'][client] + try: + raw = unicode(client.recv(8192), 'utf-8', 'ignore') + + except: + print_exc() + raw = None + + if not raw: + # Read null byte, close socket. + return self.close_connection(client) + + uzbl.buffer += raw + msgs = uzbl.buffer.split('\n') + uzbl.buffer = msgs.pop() + + for msg in msgs: + try: + parse_msg(uzbl, msg.strip()) + + except: + print_exc() + + + def accept_connection(self): + '''Accept incoming connection to the server socket.''' + + client_socket = self.server_socket.accept()[0] + + uzbl = UzblInstance(self, client_socket) + self['uzbls'][client_socket] = uzbl + + + def close_connection(self, client): + '''Clean up after instance close.''' + + try: + if client in self['uzbls']: + uzbl = self['uzbls'][client] + uzbl.close() + del self['uzbls'][client] + + except: + print_exc() + + if not len(self['uzbls']) and CONFIG['auto_close']: + echo('auto closing event manager.') + self.running = False + + + def quit(self): + '''Close all instance socket objects, server socket and delete the + pid file.''' + + echo('shutting down event manager.') + + for client in self['uzbls'].keys(): + self.close_connection(client) + + echo('unlinking: %r' % self.socket_location) + self._close_server_socket() + + echo('deleting pid file: %r' % CONFIG['pid_file']) + del_pid_file(CONFIG['pid_file']) + + +def stop_action(): + '''Stop the event manager daemon.''' + + pid_file = CONFIG['pid_file'] + if not os.path.isfile(pid_file): + return echo('no running daemon found.') + + echo('found pid file: %r' % pid_file) + pid = get_pid(pid_file) + if not pid_running(pid): + echo('no process with pid: %d' % pid) + return os.remove(pid_file) + + echo("terminating process with pid: %d" % pid) + term_process(pid) + if os.path.isfile(pid_file): + os.remove(pid_file) + + echo('stopped event daemon.') + + +def start_action(): + '''Start the event manager daemon.''' + + pid_file = CONFIG['pid_file'] + if os.path.isfile(pid_file): + echo('found pid file: %r' % pid_file) + pid = get_pid(pid_file) + if pid_running(pid): + return echo('event daemon already started with pid: %d' % pid) + + echo('no process with pid: %d' % pid) + os.remove(pid_file) + + echo('starting event manager.') + UzblEventDaemon().run() + + +def restart_action(): + '''Restart the event manager daemon.''' + + echo('restarting event manager daemon.') + stop_action() + start_action() + + +def list_action(): + '''List all the plugins being loaded by the event daemon.''' + + plugins = find_plugins(CONFIG['plugin_dirs']) + dirs = {} + + for (plugin, plugin_dir) in plugins.items(): + if plugin_dir not in dirs: + dirs[plugin_dir] = [] + + dirs[plugin_dir].append(plugin) + + for (index, (plugin_dir, plugin_list)) in enumerate(sorted(dirs.items())): + if index: + print + + print "%s:" % plugin_dir + for plugin in sorted(plugin_list): + print " %s" % plugin + + +if __name__ == "__main__": + USAGE = "usage: %prog [options] {start|stop|restart|list}" + PARSER = OptionParser(usage=USAGE) + PARSER.add_option('-v', '--verbose', dest='verbose', action="store_true", + help="print verbose output.") + + PARSER.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store", + metavar="DIRS", help="Specify plugin directories in the form of "\ + "'dir1:dir2:dir3'.") + + PARSER.add_option('-l', '--load-plugins', dest="load", action="store", + metavar="PLUGINS", help="comma separated list of plugins to load") + + PARSER.add_option('-i', '--ignore-plugins', dest="ignore", action="store", + metavar="PLUGINS", help="comma separated list of plugins to ignore") + + PARSER.add_option('-p', '--pid-file', dest='pid', action='store', + metavar='FILE', help="specify pid file location") + + PARSER.add_option('-s', '--server-socket', dest='socket', action='store', + metavar='SOCKET', help="specify the daemon socket location") + + PARSER.add_option('-n', '--no-daemon', dest="daemon", + action="store_true", help="don't enter daemon mode.") + + PARSER.add_option('-a', '--auto-close', dest='autoclose', + action='store_true', help='auto close after all instances disconnect.') + + (OPTIONS, ARGS) = PARSER.parse_args() + + # init like {start|stop|..} daemon actions dict. + DAEMON_ACTIONS = {'start': start_action, 'stop': stop_action, + 'restart': restart_action, 'list': list_action} + + if not ARGS: + ACTION = 'start' + + elif len(ARGS) == 1: + ACTION = ARGS[0] + if ACTION not in DAEMON_ACTIONS: + raise ArgumentError("unknown argument: %r" % ACTION) + + else: + raise ArgumentError("too many arguments: %r" % ARGS) + + # parse other flags & options. + if OPTIONS.verbose: + CONFIG['verbose'] = True + + if OPTIONS.plugin_dirs: + PLUGIN_DIRS = [] + for DIR in OPTIONS.plugin_dirs.split(':'): + if not DIR: + continue + + PLUGIN_DIRS.append(os.path.realpath(DIR)) + + CONFIG['plugin_dirs'] = PLUGIN_DIRS + echo("plugin search dirs: %r" % PLUGIN_DIRS) + + if OPTIONS.load and OPTIONS.ignore: + error("you can't load and ignore at the same time.") + sys.exit(1) + + elif OPTIONS.load: + LOAD = CONFIG['plugins_load'] + for PLUGIN in OPTIONS.load.split(','): + if PLUGIN.strip(): + LOAD.append(PLUGIN.strip()) + + echo('only loading plugin(s): %s' % ', '.join(LOAD)) + + elif OPTIONS.ignore: + IGNORE = CONFIG['plugins_ignore'] + for PLUGIN in OPTIONS.ignore.split(','): + if PLUGIN.strip(): + IGNORE.append(PLUGIN.strip()) + + echo('ignoring plugin(s): %s' % ', '.join(IGNORE)) + + if OPTIONS.autoclose: + CONFIG['auto_close'] = True + echo('will auto close.') + + if OPTIONS.pid: + CONFIG['pid_file'] = os.path.realpath(OPTIONS.pid) + echo("pid file location: %r" % CONFIG['pid_file']) + + if OPTIONS.socket: + CONFIG['server_socket'] = os.path.realpath(OPTIONS.socket) + echo("daemon socket location: %s" % CONFIG['server_socket']) + + if OPTIONS.daemon: + CONFIG['daemon_mode'] = False + + # Now {start|stop|...} + DAEMON_ACTIONS[ACTION]() diff --git a/examples/data/scripts/uzbl-tabbed b/examples/data/scripts/uzbl-tabbed new file mode 100755 index 0000000..7bd90d5 --- /dev/null +++ b/examples/data/scripts/uzbl-tabbed @@ -0,0 +1,1417 @@ +#!/usr/bin/env python + +# Uzbl tabbing wrapper using a fifo socket interface +# Copyright (c) 2009, Tom Adams +# Copyright (c) 2009, Chris van Dijk +# Copyright (c) 2009, Mason Larobina +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# Author(s): +# Tom Adams +# Wrote the original uzbl_tabbed.py as a proof of concept. +# +# Chris van Dijk (quigybo) +# Made signifigant headway on the old uzbl_tabbing.py script on the +# uzbl wiki +# +# Mason Larobina +# Rewrite of the uzbl_tabbing.py script to use a fifo socket interface +# and inherit configuration options from the user's uzbl config. +# +# Contributor(s): +# mxey +# uzbl_config path now honors XDG_CONFIG_HOME if it exists. +# +# Romain Bignon +# Fix for session restoration code. +# +# Jake Probst +# Wrote a patch that overflows tabs in the tablist on to new lines when +# running of room. +# +# Devon Jones +# Fifo command bring_to_front which brings the gtk window to focus. +# +# Simon Lipp (sloonz) +# Various + + +# Dependencies: +# pygtk - python bindings for gtk. +# pango - python bindings needed for text rendering & layout in gtk widgets. +# pygobject - GLib's GObject bindings for python. +# +# Optional dependencies: +# simplejson - save uzbl_tabbed.py sessions & presets in json. +# +# Note: I haven't included version numbers with this dependency list because +# I've only ever tested uzbl_tabbed.py on the latest stable versions of these +# packages in Gentoo's portage. Package names may vary on different systems. + + +# Configuration: +# Because this version of uzbl_tabbed is able to inherit options from your main +# uzbl configuration file you may wish to configure uzbl tabbed from there. +# Here is a list of configuration options that can be customised and some +# example values for each: +# +# General tabbing options: +# show_tablist = 1 +# show_gtk_tabs = 0 +# tablist_top = 1 +# gtk_tab_pos = (top|left|bottom|right) +# gtk_refresh = 1000 +# switch_to_new_tabs = 1 +# capture_new_windows = 1 +# multiline_tabs = 1 +# +# Tab title options: +# tab_titles = 1 +# tab_indexes = 1 +# new_tab_title = Loading +# max_title_len = 50 +# show_ellipsis = 1 +# +# Session options: +# save_session = 1 +# json_session = 0 +# session_file = $HOME/.local/share/uzbl/session +# +# Inherited uzbl options: +# fifo_dir = /tmp +# socket_dir = /tmp +# icon_path = $HOME/.local/share/uzbl/uzbl.png +# status_background = #303030 +# +# Misc options: +# window_size = 800,800 +# verbose = 0 +# +# And uzbl_tabbed.py takes care of the actual binding of the commands via each +# instances fifo socket. +# +# Custom tab styling: +# tab_colours = foreground = "#888" background = "#303030" +# tab_text_colours = foreground = "#bbb" +# selected_tab = foreground = "#fff" +# selected_tab_text = foreground = "green" +# tab_indicate_https = 1 +# https_colours = foreground = "#888" +# https_text_colours = foreground = "#9c8e2d" +# selected_https = foreground = "#fff" +# selected_https_text = foreground = "gold" +# +# How these styling values are used are soley defined by the syling policy +# handler below (the function in the config section). So you can for example +# turn the tab text colour Firetruck-Red in the event "error" appears in the +# tab title or some other arbitrary event. You may wish to make a trusted +# hosts file and turn tab titles of tabs visiting trusted hosts purple. + + +# Issues: +# - new windows are not caught and opened in a new tab. +# - when uzbl_tabbed.py crashes it takes all the children with it. +# - when a new tab is opened when using gtk tabs the tab button itself +# grabs focus from its child for a few seconds. +# - when switch_to_new_tabs is not selected the notebook page is +# maintained but the new window grabs focus (try as I might to stop it). + + +# Todo: +# - add command line options to use a different session file, not use a +# session file and or open a uri on starup. +# - ellipsize individual tab titles when the tab-list becomes over-crowded +# - add "<" & ">" arrows to tablist to indicate that only a subset of the +# currently open tabs are being displayed on the tablist. +# - add the small tab-list display when both gtk tabs and text vim-like +# tablist are hidden (I.e. [ 1 2 3 4 5 ]) +# - check spelling. +# - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into +# the collective. Resistance is futile! + + +import pygtk +import gtk +import subprocess +import os +import re +import time +import getopt +import pango +import select +import sys +import gobject +import socket +import random +import hashlib +import atexit +import types + +from gobject import io_add_watch, source_remove, timeout_add, IO_IN, IO_HUP +from signal import signal, SIGTERM, SIGINT +from optparse import OptionParser, OptionGroup + + +pygtk.require('2.0') + +_SCRIPTNAME = os.path.basename(sys.argv[0]) +def error(msg): + sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return os.path.join(os.environ['HOME'], default) + +# Setup xdg paths. +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') + +# Ensure uzbl xdg paths exist +if not os.path.exists(DATA_DIR): + os.makedirs(DATA_DIR) + +# All of these settings can be inherited from your uzbl config file. +config = { + # Tab options + 'show_tablist': True, # Show text uzbl like statusbar tab-list + 'show_gtk_tabs': False, # Show gtk notebook tabs + 'tablist_top': True, # Display tab-list at top of window + 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) + 'gtk_refresh': 1000, # Tablist refresh millisecond interval + 'switch_to_new_tabs': True, # Upon opening a new tab switch to it + 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows + 'multiline_tabs': True, # Tabs overflow onto new tablist lines. + + # Tab title options + 'tab_titles': True, # Display tab titles (else only tab-nums) + 'tab_indexes': True, # Display tab nums (else only tab titles) + 'new_tab_title': 'Loading', # New tab title + 'max_title_len': 50, # Truncate title at n characters + 'show_ellipsis': True, # Show ellipsis when truncating titles + + # Session options + 'save_session': True, # Save session in file when quit + 'json_session': False, # Use json to save session. + 'saved_sessions_dir': os.path.join(DATA_DIR, 'sessions/'), + 'session_file': os.path.join(DATA_DIR, 'session'), + + # Inherited uzbl options + 'fifo_dir': '/tmp', # Path to look for uzbl fifo. + 'socket_dir': '/tmp', # Path to look for uzbl socket. + 'icon_path': os.path.join(DATA_DIR, 'uzbl.png'), + 'status_background': "#303030", # Default background for all panels. + + # Misc options + 'window_size': "800,800", # width,height in pixels. + 'verbose': False, # Print verbose output. + + # Add custom tab style definitions to be used by the tab colour policy + # handler here. Because these are added to the config dictionary like + # any other uzbl_tabbed configuration option remember that they can + # be superseeded from your main uzbl config file. + 'tab_colours': 'foreground = "#888" background = "#303030"', + 'tab_text_colours': 'foreground = "#bbb"', + 'selected_tab': 'foreground = "#fff"', + 'selected_tab_text': 'foreground = "green"', + 'tab_indicate_https': True, + 'https_colours': 'foreground = "#888"', + 'https_text_colours': 'foreground = "#9c8e2d"', + 'selected_https': 'foreground = "#fff"', + 'selected_https_text': 'foreground = "gold"', + +} # End of config dict. + +UZBL_TABBED_VARS = config.keys() + +# This is the tab style policy handler. Every time the tablist is updated +# this function is called to determine how to colourise that specific tab +# according the simple/complex rules as defined here. You may even wish to +# move this function into another python script and import it using: +# from mycustomtabbingconfig import colour_selector +# Remember to rename, delete or comment out this function if you do that. + +def colour_selector(tabindex, currentpage, uzbl): + '''Tablist styling policy handler. This function must return a tuple of + the form (tab style, text style).''' + + # Just as an example: + # if 'error' in uzbl.title: + # if tabindex == currentpage: + # return ('foreground="#fff"', 'foreground="red"') + # return ('foreground="#888"', 'foreground="red"') + + # Style tabs to indicate connected via https. + if config['tab_indicate_https'] and uzbl.uri.startswith("https://"): + if tabindex == currentpage: + return (config['selected_https'], config['selected_https_text']) + return (config['https_colours'], config['https_text_colours']) + + # Style to indicate selected. + if tabindex == currentpage: + return (config['selected_tab'], config['selected_tab_text']) + + # Default tab style. + return (config['tab_colours'], config['tab_text_colours']) + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def echo(msg): + if config['verbose']: + sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) + + +def counter(): + '''To infinity and beyond!''' + + i = 0 + while True: + i += 1 + yield i + + +def escape(s): + '''Replaces html markup in tab titles that screw around with pango.''' + + for (split, glue) in [('&','&'), ('<', '<'), ('>', '>')]: + s = s.replace(split, glue) + return s + + +class SocketClient: + '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance''' + + # List of UzblInstance objects not already linked with a SocketClient + instances_queue = {} + + def __init__(self, socket): + self._buffer = "" + self._socket = socket + self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\ + io_add_watch(socket, IO_HUP, self._socket_closed)] + self.uzbl = None + + + def _socket_recv(self, fd, condition): + '''Data available on socket, process it''' + + self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ? + return True + + + def _socket_closed(self, fd, condition): + '''Remote client exited''' + self.uzbl.close() + return False + + + def _feed(self, data): + '''An Uzbl instance sent some data, parse it''' + + self._buffer += data + if self.uzbl: + if "\n" in self._buffer: + cmds = self._buffer.split("\n") + + if cmds[-1]: # Last command has been received incomplete, don't process it + self._buffer, cmds = cmds[-1], cmds[:-1] + else: + self._buffer = "" + + for cmd in cmds: + if cmd: + self.uzbl.parse_command(cmd) + else: + name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M) + uzbl = self.instances_queue.get(name[0]) + if uzbl: + del self.instances_queue[name[0]] + self.uzbl = uzbl + self.uzbl.got_socket(self) + self._feed("") + + def send(self, data): + '''Child socket send function.''' + + self._socket.send(data + "\n") + + def close(self): + '''Close the connection''' + + if self._socket: + self._socket.close() + self._socket = None + map(source_remove, self._watchers) + self._watchers = [] + + +class UzblInstance: + '''Uzbl instance meta-data/meta-action object.''' + + def __init__(self, parent, tab, name, uri, title, switch): + + self.parent = parent + self.tab = tab + self.name = name + self.title = title + self.tabtitle = "" + self.uri = uri + self._client = None + self._switch = switch # Switch to tab after loading ? + self.title_changed() + + + def got_socket(self, client): + '''Uzbl instance is now connected''' + + self._client = client + self.parent.config_uzbl(self) + if self._switch: + tabid = self.parent.notebook.page_num(self.tab) + self.parent.goto_tab(tabid) + + + def title_changed(self, gtk_only = True): # GTK-only is for indexes + '''self.title has changed, update the tabs list''' + + tab_titles = config['tab_titles'] + tab_indexes = config['tab_indexes'] + show_ellipsis = config['show_ellipsis'] + max_title_len = config['max_title_len'] + + # Unicode heavy strings do not like being truncated/sliced so by + # re-encoding the string sliced of limbs are removed. + self.tabtitle = self.title[:max_title_len + int(show_ellipsis)] + if type(self.tabtitle) != types.UnicodeType: + self.tabtitle = unicode(self.tabtitle, 'utf-8', 'ignore') + + self.tabtitle = self.tabtitle.encode('utf-8', 'ignore').strip() + + if show_ellipsis and len(self.tabtitle) != len(self.title): + self.tabtitle += "\xe2\x80\xa6" + + gtk_tab_format = "%d %s" + index = self.parent.notebook.page_num(self.tab) + if tab_titles and tab_indexes: + self.parent.notebook.set_tab_label_text(self.tab, + gtk_tab_format % (index, self.tabtitle)) + elif tab_titles: + self.parent.notebook.set_tab_label_text(self.tab, self.tabtitle) + else: + self.parent.notebook.set_tab_label_text(self.tab, str(index)) + + # If instance is current tab, update window title + if index == self.parent.notebook.get_current_page(): + title_format = "%s - Uzbl Browser" + self.parent.window.set_title(title_format % self.title) + + # Non-GTK tabs + if not gtk_only: + self.parent.update_tablist() + + + def set(self, key, val): + ''' Send the SET command to Uzbl ''' + + if self._client: + self._client.send('set %s = %s') #TODO: escape chars ? + + + def exit(self): + ''' Ask the Uzbl instance to close ''' + + if self._client: + self._client.send('exit') + + + def parse_command(self, cmd): + ''' Parse event givent by the Uzbl instance ''' + + type, _, args = cmd.split(" ", 2) + if type == "EVENT": + type, args = args.split(" ", 1) + if type == "TITLE_CHANGED": + self.title = args + self.title_changed() + elif type == "VARIABLE_SET": + var, _, val = args.split(" ", 2) + try: + val = int(val) + except: + pass + + if var in UZBL_TABBED_VARS: + if config[var] != val: + config[var] = val + if var == "show_gtk_tabs": + self.parent.notebook.set_show_tabs(bool(val)) + elif var == "show_tablist" or var == "tablist_top": + self.parent.update_tablist_display() + elif var == "gtk_tab_pos": + self.parent.update_gtk_tab_pos() + elif var == "status_background": + col = gtk.gdk.color_parse(config['status_background']) + self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) + elif var == "tab_titles" or var == "tab_indexes": + for tab in self.parent.notebook: + self.parent.tabs[tab].title_changed(True) + + self.parent.update_tablist() + else: + config[var] = val + + if var == "uri": + self.uri = var + self.parent.update_tablist() + elif type == "NEW_TAB": + self.parent.new_tab(args) + elif type == "NEXT_TAB": + if args: + self.parent.next_tab(int(args)) + else: + self.parent.next_tab() + elif type == "PREV_TAB": + if args: + self.parent.prev_tab(int(args)) + else: + self.parent.prev_tab() + elif type == "GOTO_TAB": + self.parent.goto_tab(int(args)) + elif type == "FIRST_TAB": + self.parent.goto_tab(0) + elif type == "LAST_TAB": + self.parent.goto_tab(-1) + elif type == "PRESET_TABS": + self.parent.parse_command(["preset"] + args.split()) + elif type == "BRING_TO_FRONT": + self.parent.window.present() + elif type == "CLEAN_TABS": + self.parent.clean_slate() + elif type == "EXIT_ALL_TABS": + self.parent.quitrequest() + + + def close(self): + '''The remote instance exited''' + + if self._client: + self._client.close() + self._client = None + + +class UzblTabbed: + '''A tabbed version of uzbl using gtk.Notebook''' + + def __init__(self): + '''Create tablist, window and notebook.''' + + self._timers = {} + self._buffer = "" + self._killed = False + + # A list of the recently closed tabs + self._closed = [] + + # Holds metadata on the uzbl childen open. + self.tabs = {} + + # Uzbl sockets (socket => SocketClient) + self.clients = {} + + # Generates a unique id for uzbl socket filenames. + self.next_pid = counter().next + + # Create main window + self.window = gtk.Window() + try: + window_size = map(int, config['window_size'].split(',')) + self.window.set_default_size(*window_size) + + except: + error("Invalid value for default_size in config file.") + + self.window.set_title("Uzbl Browser") + self.window.set_border_width(0) + + # Set main window icon + icon_path = config['icon_path'] + if os.path.exists(icon_path): + self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) + + else: + icon_path = '/usr/share/uzbl/examples/data/uzbl.png' + if os.path.exists(icon_path): + self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) + + # Attach main window event handlers + self.window.connect("delete-event", self.quitrequest) + + # Create tab list + vbox = gtk.VBox() + self.vbox = vbox + self.window.add(vbox) + ebox = gtk.EventBox() + self.ebox = ebox + self.tablist = gtk.Label() + + self.tablist.set_use_markup(True) + self.tablist.set_justify(gtk.JUSTIFY_LEFT) + self.tablist.set_line_wrap(False) + self.tablist.set_selectable(False) + self.tablist.set_padding(2,2) + self.tablist.set_alignment(0,0) + self.tablist.set_ellipsize(pango.ELLIPSIZE_END) + self.tablist.set_text(" ") + self.tablist.show() + ebox.add(self.tablist) + ebox.show() + bgcolor = gtk.gdk.color_parse(config['status_background']) + ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) + + # Create notebook + self.notebook = gtk.Notebook() + self.notebook.set_show_tabs(config['show_gtk_tabs']) + + # Set tab position + self.update_gtk_tab_pos() + + self.notebook.set_show_border(False) + self.notebook.set_scrollable(True) + self.notebook.set_border_width(0) + + self.notebook.connect("page-removed", self.tab_closed) + self.notebook.connect("switch-page", self.tab_changed) + self.notebook.connect("page-added", self.tab_opened) + + self.notebook.show() + vbox.pack_start(self.notebook, True, True, 0) + vbox.reorder_child(self.notebook, 1) + self.update_tablist_display() + + self.vbox.show() + self.window.show() + self.wid = self.notebook.window.xid + + # Store information about the applications fifo and socket. + fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid() + socket_filename = 'uzbltabbed_%d.socket' % os.getpid() + self._fifo = None + self._socket = None + self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename) + self.socket_path = os.path.join(config['socket_dir'], socket_filename) + + # Now initialise the fifo and the socket + self.init_fifo() + self.init_socket() + + # If we are using sessions then load the last one if it exists. + if config['save_session']: + self.load_session() + + + def run(self): + '''UzblTabbed main function that calls the gtk loop.''' + + if not self.clients and not SocketClient.instances_queue and not self.tabs: + self.new_tab() + + gtk_refresh = int(config['gtk_refresh']) + if gtk_refresh < 100: + gtk_refresh = 100 + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) + + # Catch keyboard interrupts + signal(SIGINT, lambda signum, stack_frame: self.terminate(SIGINT)) + + try: + gtk.main() + + except: + error("encounted error %r" % sys.exc_info()[1]) + + # Unlink fifo socket + self.unlink_fifo() + self.close_socket() + + # Attempt to close all uzbl instances nicely. + self.quitrequest() + + # Allow time for all the uzbl instances to quit. + time.sleep(1) + + raise + + + def terminate(self, termsig=None): + '''Handle termination signals and exit safely and cleanly.''' + + # Not required but at least it lets the user know what killed his + # browsing session. + if termsig == SIGTERM: + error("caught SIGTERM signal") + + elif termsig == SIGINT: + error("caught keyboard interrupt") + + else: + error("caught unknown signal") + + error("commencing infanticide!") + + # Sends the exit signal to all uzbl instances. + self.quitrequest() + + + def init_socket(self): + '''Create interprocess communication socket.''' + + def accept(sock, condition): + '''A new uzbl instance was created''' + + client, _ = sock.accept() + self.clients[client] = SocketClient(client) + + return True + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(self.socket_path) + sock.listen(1) + + # Add event handler for IO_IN event. + self._socket = (sock, io_add_watch(sock, IO_IN, accept)) + + echo("[socket] listening at %r" % self.socket_path) + + # Add atexit register to destroy the socket on program termination. + atexit.register(self.close_socket) + + + def close_socket(self): + '''Close the socket when closing the application''' + + if self._socket: + (fd, watcher) = self._socket + source_remove(watcher) + fd.close() + os.unlink(self.socket_path) + self._socket = None + + + def init_fifo(self): + '''Create interprocess communication fifo.''' + + if os.path.exists(self.fifo_path): + if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK): + os.mkfifo(self.fifo_path) + + else: + basedir = os.path.dirname(self.fifo_path) + if not os.path.exists(basedir): + os.makedirs(basedir) + + os.mkfifo(self.fifo_path) + + # Add event handlers for IO_IN & IO_HUP events. + self.setup_fifo_watchers() + + echo("[fifo] listening at %r" % self.fifo_path) + + # Add atexit register to destroy the fifo on program termination. + atexit.register(self.unlink_fifo) + + + def unlink_fifo(self): + '''Unlink the fifo socket. Note: This function is called automatically + on exit by an atexit register.''' + + # Make sure the fifo fd is closed. + self.close_fifo() + + # And unlink if the real fifo exists. + if os.path.exists(self.fifo_path): + os.unlink(self.fifo_path) + echo("unlinked %r" % self.fifo_path) + + + def close_fifo(self): + '''Remove all event handlers watching the fifo and close the fd.''' + + # Already closed + if self._fifo is None: return + + (fd, watchers) = self._fifo + os.close(fd) + + # Stop all gobject io watchers watching the fifo. + for gid in watchers: + source_remove(gid) + + self._fifo = None + + + def setup_fifo_watchers(self): + '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event + handlers.''' + + # Close currently open fifo fd and kill all watchers + self.close_fifo() + + fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK) + + # Add gobject io event handlers to the fifo socket. + watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\ + io_add_watch(fd, IO_HUP, self.main_fifo_hangup)] + + self._fifo = (fd, watchers) + + + def main_fifo_hangup(self, fd, cb_condition): + '''Handle main fifo socket hangups.''' + + # Close old fd, open new fifo socket and add io event handlers. + self.setup_fifo_watchers() + + # Kill the gobject event handler calling this handler function. + return False + + + def main_fifo_read(self, fd, cb_condition): + '''Read from main fifo socket.''' + + self._buffer = os.read(fd, 1024) + temp = self._buffer.split("\n") + self._buffer = temp.pop() + cmds = [s.strip().split() for s in temp if len(s.strip())] + + for cmd in cmds: + try: + #print cmd + self.parse_command(cmd) + + except: + error("parse_command: invalid command %s" % ' '.join(cmd)) + raise + + return True + + + def parse_command(self, cmd): + '''Parse instructions from uzbl child processes.''' + + # Commands ( [] = optional, {} = required ) + # new [uri] + # open new tab and head to optional uri. + # close [tab-num] + # close current tab or close via tab id. + # next [n-tabs] + # open next tab or n tabs down. Supports negative indexing. + # prev [n-tabs] + # open prev tab or n tabs down. Supports negative indexing. + # goto {tab-n} + # goto tab n. + # first + # goto first tab. + # last + # goto last tab. + # title {pid} {document-title} + # updates tablist title. + # uri {pid} {document-location} + # updates tablist uri + # bring_to_front + # brings the gtk window to focus. + # exit + # exits uzbl_tabbed.py + + if cmd[0] == "new": + if len(cmd) == 2: + self.new_tab(cmd[1]) + + else: + self.new_tab() + + elif cmd[0] == "newfromclip": + uri = subprocess.Popen(['xclip','-selection','clipboard','-o'],\ + stdout=subprocess.PIPE).communicate()[0] + if uri: + self.new_tab(uri) + + elif cmd[0] == "close": + if len(cmd) == 2: + self.close_tab(int(cmd[1])) + + else: + self.close_tab() + + elif cmd[0] == "next": + if len(cmd) == 2: + self.next_tab(int(cmd[1])) + + else: + self.next_tab() + + elif cmd[0] == "prev": + if len(cmd) == 2: + self.prev_tab(int(cmd[1])) + + else: + self.prev_tab() + + elif cmd[0] == "goto": + self.goto_tab(int(cmd[1])) + + elif cmd[0] == "first": + self.goto_tab(0) + + elif cmd[0] == "last": + self.goto_tab(-1) + + elif cmd[0] in ["title", "uri"]: + if len(cmd) > 2: + uzbl = self.get_tab_by_name(int(cmd[1])) + if uzbl: + old = getattr(uzbl, cmd[0]) + new = ' '.join(cmd[2:]) + setattr(uzbl, cmd[0], new) + if old != new: + self.update_tablist() + + else: + error("parse_command: no uzbl with name %r" % int(cmd[1])) + + elif cmd[0] == "preset": + if len(cmd) < 3: + error("parse_command: invalid preset command") + + elif cmd[1] == "save": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + self.save_session(path) + + elif cmd[1] == "load": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + self.load_session(path) + + elif cmd[1] == "del": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + if os.path.isfile(path): + os.remove(path) + + else: + error("parse_command: preset %r does not exist." % path) + + elif cmd[1] == "list": + uzbl = self.get_tab_by_name(int(cmd[2])) + if uzbl: + if not os.path.isdir(config['saved_sessions_dir']): + js = "js alert('No saved presets.');" + uzbl._client.send(js) + + else: + listdir = os.listdir(config['saved_sessions_dir']) + listdir = "\\n".join(listdir) + js = "js alert('Session presets:\\n\\n%s');" % listdir + uzbl._client.send(js) + + else: + error("parse_command: unknown tab name.") + + else: + error("parse_command: unknown parse command %r"\ + % ' '.join(cmd)) + + elif cmd[0] == "bring_to_front": + self.window.present() + + elif cmd[0] == "clean": + self.clean_slate() + + elif cmd[0] == "exit": + self.quitrequest() + + else: + error("parse_command: unknown command %r" % ' '.join(cmd)) + + + def get_tab_by_name(self, name): + '''Return uzbl instance by name.''' + + for (tab, uzbl) in self.tabs.items(): + if uzbl.name == name: + return uzbl + + return False + + + def new_tab(self, uri='', title='', switch=None): + '''Add a new tab to the notebook and start a new instance of uzbl. + Use the switch option to negate config['switch_to_new_tabs'] option + when you need to load multiple tabs at a time (I.e. like when + restoring a session from a file).''' + + tab = gtk.Socket() + tab.show() + self.notebook.append_page(tab) + sid = tab.get_id() + uri = uri.strip() + name = "%d-%d" % (os.getpid(), self.next_pid()) + + if switch is None: + switch = config['switch_to_new_tabs'] + + if not title: + title = config['new_tab_title'] + + cmd = ['uzbl-browser', '-n', name, '-s', str(sid), + '--connect-socket', self.socket_path, '--uri', uri] + subprocess.Popen(cmd) # TODO: do i need close_fds=True ? + + uzbl = UzblInstance(self, tab, name, uri, title, switch) + SocketClient.instances_queue[name] = uzbl + self.tabs[tab] = uzbl + + + def clean_slate(self): + '''Close all open tabs and open a fresh brand new one.''' + + self.new_tab() + tabs = self.tabs.keys() + for tab in list(self.notebook)[:-1]: + if tab not in tabs: continue + uzbl = self.tabs[tab] + uzbl.exit() + + + def config_uzbl(self, uzbl): + '''Send bind commands for tab new/close/next/prev to a uzbl + instance.''' + + # Set definitions here + # set(key, command back to fifo) + if config['capture_new_windows']: + uzbl.set("new_window", r'new $8') + + + def goto_tab(self, index): + '''Goto tab n (supports negative indexing).''' + + title_format = "%s - Uzbl Browser" + + tabs = list(self.notebook) + if 0 <= index < len(tabs): + self.notebook.set_current_page(index) + uzbl = self.tabs[self.notebook.get_nth_page(index)] + self.window.set_title(title_format % uzbl.title) + self.update_tablist() + return None + + try: + tab = tabs[index] + # Update index because index might have previously been a + # negative index. + index = tabs.index(tab) + self.notebook.set_current_page(index) + uzbl = self.tabs[self.notebook.get_nth_page(index)] + self.window.set_title(title_format % uzbl.title) + self.update_tablist() + + except IndexError: + pass + + + def next_tab(self, step=1): + '''Switch to next tab or n tabs right.''' + + if step < 1: + error("next_tab: invalid step %r" % step) + return None + + ntabs = self.notebook.get_n_pages() + tabn = (self.notebook.get_current_page() + step) % ntabs + self.goto_tab(tabn) + + + def prev_tab(self, step=1): + '''Switch to prev tab or n tabs left.''' + + if step < 1: + error("prev_tab: invalid step %r" % step) + return None + + ntabs = self.notebook.get_n_pages() + tabn = self.notebook.get_current_page() - step + while tabn < 0: tabn += ntabs + self.goto_tab(tabn) + + + def close_tab(self, tabn=None): + '''Closes current tab. Supports negative indexing.''' + + if tabn is None: + tabn = self.notebook.get_current_page() + + else: + try: + tab = list(self.notebook)[tabn] + + except IndexError: + error("close_tab: invalid index %r" % tabn) + return None + + self.notebook.remove_page(tabn) + + + def tab_opened(self, notebook, tab, index): + '''Called upon tab creation. Called by page-added signal.''' + + if config['switch_to_new_tabs']: + self.notebook.set_focus_child(tab) + + else: + oldindex = self.notebook.get_current_page() + oldtab = self.notebook.get_nth_page(oldindex) + self.notebook.set_focus_child(oldtab) + + + def tab_closed(self, notebook, tab, index): + '''Close the window if no tabs are left. Called by page-removed + signal.''' + + if tab in self.tabs.keys(): + uzbl = self.tabs[tab] + uzbl.close() + + self._closed.append((uzbl.uri, uzbl.title)) + self._closed = self._closed[-10:] + del self.tabs[tab] + + if self.notebook.get_n_pages() == 0: + if not self._killed and config['save_session']: + if os.path.exists(config['session_file']): + os.remove(config['session_file']) + + self.quit() + + for tab in self.notebook: + self.tabs[tab].title_changed(True) + self.update_tablist() + + return True + + + def tab_changed(self, notebook, page, index): + '''Refresh tab list. Called by switch-page signal.''' + + tab = self.notebook.get_nth_page(index) + self.notebook.set_focus_child(tab) + self.update_tablist(index) + return True + + + def update_tablist_display(self): + '''Called when show_tablist or tablist_top has changed''' + + if self.ebox in self.vbox.get_children(): + self.vbox.remove(self.ebox) + + if config['show_tablist']: + self.vbox.pack_start(self.ebox, False, False, 0) + if config['tablist_top']: + self.vbox.reorder_child(self.ebox, 0) + else: + self.vbox.reorder_child(self.ebox, 2) + + def update_gtk_tab_pos(self): + ''' Called when gtk_tab_pos has changed ''' + + allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, + 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} + if config['gtk_tab_pos'] in allposes.keys(): + self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) + + + def update_tablist(self, curpage=None): + '''Upate tablist status bar.''' + + if not config['show_tablist']: + return True + + tab_titles = config['tab_titles'] + tab_indexes = config['tab_indexes'] + multiline_tabs = config['multiline_tabs'] + + if multiline_tabs: + multiline = [] + + tabs = self.tabs.keys() + if curpage is None: + curpage = self.notebook.get_current_page() + + pango = "" + normal = (config['tab_colours'], config['tab_text_colours']) + selected = (config['selected_tab'], config['selected_tab_text']) + + if tab_titles and tab_indexes: + tab_format = " [ %(index)d %(title)s ] " + elif tab_titles: + tab_format = " [ %(title)s ] " + else: + tab_format = " [ %(index)d ] " + + for index, tab in enumerate(self.notebook): + if tab not in tabs: continue + uzbl = self.tabs[tab] + title = escape(uzbl.tabtitle) + + style = colour_selector(index, curpage, uzbl) + (tabc, textc) = style + + if multiline_tabs: + opango = pango + + pango += tab_format % locals() + + self.tablist.set_markup(pango) + listwidth = self.tablist.get_layout().get_pixel_size()[0] + winwidth = self.window.get_size()[0] + + if listwidth > (winwidth - 20): + multiline.append(opango) + pango = tab_format % locals() + else: + pango += tab_format % locals() + + if multiline_tabs: + multiline.append(pango) + self.tablist.set_markup(' '.join(multiline)) + + else: + self.tablist.set_markup(pango) + + return True + + + def save_session(self, session_file=None): + '''Save the current session to file for restoration on next load.''' + + strip = str.strip + + if session_file is None: + session_file = config['session_file'] + + tabs = self.tabs.keys() + state = [] + for tab in list(self.notebook): + if tab not in tabs: continue + uzbl = self.tabs[tab] + if not uzbl.uri: continue + state += [(uzbl.uri, uzbl.title),] + + session = {'curtab': self.notebook.get_current_page(), + 'tabs': state} + + if config['json_session']: + raw = json.dumps(session) + + else: + lines = ["curtab = %d" % session['curtab'],] + for (uri, title) in session['tabs']: + lines += ["%s\t%s" % (strip(uri), strip(title)),] + + raw = "\n".join(lines) + + if not os.path.isfile(session_file): + dirname = os.path.dirname(session_file) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + h = open(session_file, 'w') + h.write(raw) + h.close() + + + def load_session(self, session_file=None): + '''Load a saved session from file.''' + + default_path = False + strip = str.strip + json_session = config['json_session'] + delete_loaded = False + + if session_file is None: + default_path = True + delete_loaded = True + session_file = config['session_file'] + + if not os.path.isfile(session_file): + return False + + h = open(session_file, 'r') + raw = h.read() + h.close() + if json_session: + if sum([1 for s in raw.split("\n") if strip(s)]) != 1: + error("Warning: The session file %r does not look json. "\ + "Trying to load it as a non-json session file."\ + % session_file) + json_session = False + + if json_session: + try: + session = json.loads(raw) + curtab, tabs = session['curtab'], session['tabs'] + + except: + error("Failed to load jsonifed session from %r"\ + % session_file) + return None + + else: + tabs = [] + strip = str.strip + curtab, tabs = 0, [] + lines = [s for s in raw.split("\n") if strip(s)] + if len(lines) < 2: + error("Warning: The non-json session file %r looks invalid."\ + % session_file) + return None + + try: + for line in lines: + if line.startswith("curtab"): + curtab = int(line.split()[-1]) + + else: + uri, title = line.split("\t",1) + tabs += [(strip(uri), strip(title)),] + + except: + error("Warning: failed to load session file %r" % session_file) + return None + + session = {'curtab': curtab, 'tabs': tabs} + + # Now populate notebook with the loaded session. + for (index, (uri, title)) in enumerate(tabs): + self.new_tab(uri=uri, title=title, switch=(curtab==index)) + + # A saved session has been loaded now delete it. + if delete_loaded and os.path.exists(session_file): + os.remove(session_file) + + # There may be other state information in the session dict of use to + # other functions. Of course however the non-json session object is + # just a dummy object of no use to no one. + return session + + + def quitrequest(self, *args): + '''Attempt to close all uzbl instances nicely and exit.''' + + self._killed = True + + if config['save_session']: + if len(list(self.notebook)) > 1: + self.save_session() + + else: + # Notebook has one page open so delete the session file. + if os.path.isfile(config['session_file']): + os.remove(config['session_file']) + + for (tab, uzbl) in self.tabs.items(): + uzbl.exit() + + # Add a gobject timer to make sure the application force-quits after a + # reasonable period. Calling quit when all the tabs haven't had time to + # close should be a last resort. + timer = "force-quit" + timerid = timeout_add(5000, self.quit, timer) + self._timers[timer] = timerid + + + def quit(self, *args): + '''Cleanup and quit. Called by delete-event signal.''' + + # Close the fifo socket, remove any gobject io event handlers and + # delete socket. + self.unlink_fifo() + self.close_socket() + + # Remove all gobject timers that are still ticking. + for (timerid, gid) in self._timers.items(): + source_remove(gid) + del self._timers[timerid] + + try: + gtk.main_quit() + + except: + pass + + +if __name__ == "__main__": + + # Build command line parser + usage = "usage: %prog [OPTIONS] {URIS}..." + parser = OptionParser(usage=usage) + parser.add_option('-n', '--no-session', dest='nosession', + action='store_true', help="ignore session saving a loading.") + parser.add_option('-v', '--verbose', dest='verbose', + action='store_true', help='print verbose output.') + + # Parse command line options + (options, uris) = parser.parse_args() + + if options.nosession: + config['save_session'] = False + + if options.verbose: + config['verbose'] = True + + if config['json_session']: + try: + import simplejson as json + + except: + error("Warning: json_session set but cannot import the python "\ + "module simplejson. Fix: \"set json_session = 0\" or "\ + "install the simplejson python module to remove this warning.") + config['json_session'] = False + + if config['verbose']: + import pprint + sys.stderr.write("%s\n" % pprint.pformat(config)) + + uzbl = UzblTabbed() + + # All extra arguments given to uzbl_tabbed.py are interpreted as + # web-locations to opened in new tabs. + lasturi = len(uris)-1 + for (index,uri) in enumerate(uris): + uzbl.new_tab(uri, switch=(index==lasturi)) + + uzbl.run() diff --git a/examples/data/scripts/uzblcat b/examples/data/scripts/uzblcat new file mode 100755 index 0000000..e955608 --- /dev/null +++ b/examples/data/scripts/uzblcat @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# uzblcat - safely push html to uzbl +# See http://www.uzbl.org/wiki/html-mode + +from sys import stdin, stdout + +stdout.write("uri data:text/html,") +for line in stdin: + stdout.write(line[0:-1]) + +# vim: set noet ff=unix + diff --git a/examples/data/style.css b/examples/data/style.css new file mode 100644 index 0000000..f9b111e --- /dev/null +++ b/examples/data/style.css @@ -0,0 +1,25 @@ +.uzbl_highlight { background-color: yellow;} +.uzbl_h_first { background-color: lightgreen;} + +.uzbl_follow { border-style: dotted; + border-width: thin; +} + +#uzbl_hint > div { + display: inline; + border: 2px solid #4a6600; + background-color: #b9ff00; + color: black; + font-size: 9px; + font-weight: bold; + line-height: 9px; + margin: 0px; + padding: 0px; + position: absolute; + z-index: 1000; + -webkit-border-radius: 6px; + text-decoration: none; + -wekit-transform: scale(1) rotate(0deg) translate(-6px,-5px); +} + +/* vim:set et ts=4: */ diff --git a/examples/data/uzbl.png b/examples/data/uzbl.png new file mode 100644 index 0000000..773ea84 Binary files /dev/null and b/examples/data/uzbl.png differ diff --git a/examples/data/uzbl/bookmarks b/examples/data/uzbl/bookmarks deleted file mode 100644 index 13fcd48..0000000 --- a/examples/data/uzbl/bookmarks +++ /dev/null @@ -1,4 +0,0 @@ -http://www.archlinux.org linux arch -http://www.uzbl.org uzbl browser -http://dieter.plaetinck.be uzbl -http://www.icanhascheezburger.com lolcats fun diff --git a/examples/data/uzbl/forms/bbs.archlinux.org b/examples/data/uzbl/forms/bbs.archlinux.org deleted file mode 100644 index 73c1539..0000000 --- a/examples/data/uzbl/forms/bbs.archlinux.org +++ /dev/null @@ -1,5 +0,0 @@ -form_sent: -redirect_url: -req_username: -req_password: -login: diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py deleted file mode 100644 index 9e09337..0000000 --- a/examples/data/uzbl/plugins/bind.py +++ /dev/null @@ -1,521 +0,0 @@ -'''Plugin provides support for binds in uzbl. - -For example: - event BIND ZZ = exit -> bind('ZZ', 'exit') - event BIND o _ = uri %s -> bind('o _', 'uri %s') - event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'") - -And it is also possible to execute a function on activation: - bind('DD', myhandler) -''' - -import sys -import re -import pprint - -# Hold the bind dicts for each uzbl instance. -UZBLS = {} - -# Commonly used regular expressions. -MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match -# Matches , <'x':y>, <:'y'>, , <'x'!y>, ... -PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>' -FIND_PROMPTS = re.compile(PROMPTS).split -VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match - -# For accessing a bind glob stack. -ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) - - -# Custom errors. -class ArgumentError(Exception): pass - - -class Bindlet(object): - '''Per-instance bind status/state tracker.''' - - def __init__(self, uzbl): - self.binds = {'global': {}} - self.uzbl = uzbl - self.depth = 0 - self.args = [] - self.last_mode = None - self.after_cmds = None - self.stack_binds = [] - - # A subset of the global mode binds containing non-stack and modkey - # activiated binds for use in the stack mode. - self.globals = [] - - - def __getitem__(self, key): - return self.get_binds(key) - - - def reset(self): - '''Reset the tracker state and return to last mode.''' - - self.depth = 0 - self.args = [] - self.after_cmds = None - self.stack_binds = [] - - if self.last_mode: - mode, self.last_mode = self.last_mode, None - self.uzbl.set_mode(mode) - - self.uzbl.set('keycmd_prompt') - - - def stack(self, bind, args, depth): - '''Enter or add new bind in the next stack level.''' - - if self.depth != depth: - if bind not in self.stack_binds: - self.stack_binds.append(bind) - - return - - current_mode = self.uzbl.get_mode() - if current_mode != 'stack': - self.last_mode = current_mode - self.uzbl.set_mode('stack') - - self.stack_binds = [bind,] - self.args += args - self.depth += 1 - self.after_cmds = bind.prompts[depth] - - - def after(self): - '''If a stack was triggered then set the prompt and default value.''' - - if self.after_cmds is None: - return - - (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None - - self.uzbl.clear_keycmd() - if prompt: - self.uzbl.set('keycmd_prompt', prompt) - - if set and is_cmd: - self.uzbl.send(set) - - elif set and not is_cmd: - self.uzbl.send('event SET_KEYCMD %s' % set) - - - def get_binds(self, mode=None): - '''Return the mode binds + globals. If we are stacked then return - the filtered stack list and modkey & non-stack globals.''' - - if mode is None: - mode = self.uzbl.get_mode() - - if not mode: - mode = 'global' - - if self.depth: - return self.stack_binds + self.globals - - globals = self.binds['global'] - if mode not in self.binds or mode == 'global': - return filter(None, globals.values()) - - binds = dict(globals.items() + self.binds[mode].items()) - return filter(None, binds.values()) - - - def add_bind(self, mode, glob, bind=None): - '''Insert (or override) a bind into the mode bind dict.''' - - if mode not in self.binds: - self.binds[mode] = {glob: bind} - return - - binds = self.binds[mode] - binds[glob] = bind - - if mode == 'global': - # Regen the global-globals list. - self.globals = [] - for bind in binds.values(): - if bind is not None and bind.is_global: - self.globals.append(bind) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = Bindlet(uzbl) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_bindlet(uzbl): - '''Return the bind tracklet for the given uzbl instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def ismodbind(glob): - '''Return True if the glob specifies a modbind.''' - - return bool(MOD_START(glob)) - - -def split_glob(glob): - '''Take a string of the form "cmd _" and return a list of the - modkeys in the glob and the command.''' - - mods = set() - while True: - match = MOD_START(glob) - if not match: - break - - end = match.span()[1] - mods.add(glob[:end]) - glob = glob[end:] - - return (mods, glob) - - -def unquote(str): - '''Remove quotation marks around string.''' - - if str and str[0] == str[-1] and str[0] in ['"', "'"]: - str = str[1:-1] - - return str - - -class Bind(object): - - # Class attribute to hold the number of Bind classes created. - counter = [0,] - - def __init__(self, glob, handler, *args, **kargs): - self.is_callable = callable(handler) - self._repr_cache = None - - if not glob: - raise ArgumentError('glob cannot be blank') - - if self.is_callable: - self.function = handler - self.args = args - self.kargs = kargs - - elif kargs: - raise ArgumentError('cannot supply kargs for uzbl commands') - - elif hasattr(handler, '__iter__'): - self.commands = handler - - else: - self.commands = [handler,] + list(args) - - self.glob = glob - - # Assign unique id. - self.counter[0] += 1 - self.bid = self.counter[0] - - self.split = split = FIND_PROMPTS(glob) - self.prompts = [] - for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]): - prompt, set = map(unquote, [prompt, set]) - cmd = True if cmd == '!' else False - if prompt and prompt[-1] != ":": - prompt = "%s:" % prompt - - self.prompts.append((prompt, cmd, set)) - - # Check that there is nothing like: fl** - for glob in split[:-1:4]: - if glob.endswith('*'): - msg = "token '*' not at the end of a prompt bind: %r" % split - raise SyntaxError(msg) - - # Check that there is nothing like: fl_ - for glob in split[4::4]: - if not glob: - msg = 'found null segment after first prompt: %r' % split - raise SyntaxError(msg) - - stack = [] - for (index, glob) in enumerate(reversed(split[::4])): - # Is the binding a MODCMD or KEYCMD: - mod_cmd = ismodbind(glob) - - # Do we execute on UPDATES or EXEC events? - on_exec = True if glob[-1] in ['!', '_'] else False - - # Does the command take arguments? - has_args = True if glob[-1] in ['*', '_'] else False - - glob = glob[:-1] if has_args or on_exec else glob - mods, glob = split_glob(glob) - stack.append((on_exec, has_args, mods, glob, index)) - - self.stack = list(reversed(stack)) - self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD]) - - - def __getitem__(self, depth): - '''Get bind info at a depth.''' - - if self.is_global: - return self.stack[0] - - return self.stack[depth] - - - def __repr__(self): - if self._repr_cache: - return self._repr_cache - - args = ['glob=%r' % self.glob, 'bid=%d' % self.bid] - - if self.is_callable: - args.append('function=%r' % self.function) - if self.args: - args.append('args=%r' % self.args) - - if self.kargs: - args.append('kargs=%r' % self.kargs) - - else: - cmdlen = len(self.commands) - cmds = self.commands[0] if cmdlen == 1 else self.commands - args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) - - self._repr_cache = '' % ', '.join(args) - return self._repr_cache - - -def exec_bind(uzbl, bind, *args, **kargs): - '''Execute bind objects.''' - - uzbl.event("EXEC_BIND", bind, args, kargs) - - if bind.is_callable: - args += bind.args - kargs = dict(bind.kargs.items()+kargs.items()) - bind.function(uzbl, *args, **kargs) - return - - if kargs: - raise ArgumentError('cannot supply kargs for uzbl commands') - - commands = [] - cmd_expand = uzbl.cmd_expand - for cmd in bind.commands: - cmd = cmd_expand(cmd, args) - uzbl.send(cmd) - - -def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs): - '''Add a mode bind.''' - - bindlet = get_bindlet(uzbl) - - if not hasattr(modes, '__iter__'): - modes = unicode(modes).split(',') - - # Sort and filter binds. - modes = filter(None, map(unicode.strip, modes)) - - if callable(handler) or (handler is not None and handler.strip()): - bind = Bind(glob, handler, *args, **kargs) - - else: - bind = None - - for mode in modes: - if not VALID_MODE(mode): - raise NameError('invalid mode name: %r' % mode) - - for mode in modes: - if mode[0] == '-': - mode, bind = mode[1:], None - - bindlet.add_bind(mode, glob, bind) - uzbl.event('ADDED_MODE_BIND', mode, glob, bind) - - -def bind(uzbl, glob, handler, *args, **kargs): - '''Legacy bind function.''' - - mode_bind(uzbl, 'global', glob, handler, *args, **kargs) - - -def parse_mode_bind(uzbl, args): - '''Parser for the MODE_BIND event. - - Example events: - MODE_BIND = - MODE_BIND command o_ = uri %s - MODE_BIND insert,command = ... - MODE_BIND global ... = ... - MODE_BIND global,-insert ... = ... - ''' - - if not args: - raise ArgumentError('missing bind arguments') - - split = map(unicode.strip, args.split(' ', 1)) - if len(split) != 2: - raise ArgumentError('missing mode or bind section: %r' % args) - - modes, args = split[0].split(','), split[1] - split = map(unicode.strip, args.split('=', 1)) - if len(split) != 2: - raise ArgumentError('missing delimiter in bind section: %r' % args) - - glob, command = split - mode_bind(uzbl, modes, glob, command) - - -def parse_bind(uzbl, args): - '''Legacy parsing of the BIND event and conversion to the new format. - - Example events: - request BIND = - request BIND o_ = uri %s - request BIND = ... - request BIND ... = ... - ''' - - parse_mode_bind(uzbl, "global %s" % args) - - -def mode_changed(uzbl, mode): - '''Clear the stack on all non-stack mode changes.''' - - if mode != 'stack': - get_bindlet(uzbl).reset() - - -def match_and_exec(uzbl, bind, depth, keylet, bindlet): - - (on_exec, has_args, mod_cmd, glob, more) = bind[depth] - cmd = keylet.modcmd if mod_cmd else keylet.keycmd - - if mod_cmd and keylet.held != mod_cmd: - return False - - if has_args: - if not cmd.startswith(glob): - return False - - args = [cmd[len(glob):],] - - elif cmd != glob: - return False - - else: - args = [] - - if bind.is_global or (not more and depth == 0): - exec_bind(uzbl, bind, *args) - if not has_args: - uzbl.clear_current() - - return True - - elif more: - bindlet.stack(bind, args, depth) - return False - - args = bindlet.args + args - exec_bind(uzbl, bind, *args) - uzbl.set_mode() - if not has_args: - bindlet.reset() - uzbl.clear_current() - - return True - - -def keycmd_update(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if t[MOD_CMD] or t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return - - bindlet.after() - - -def keycmd_exec(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if t[MOD_CMD] or not t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return uzbl.clear_keycmd() - - bindlet.after() - - -def modcmd_update(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if not t[MOD_CMD] or t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return - - bindlet.after() - - -def modcmd_exec(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if not t[MOD_CMD] or not t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return uzbl.clear_modcmd() - - bindlet.after() - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'BIND': parse_bind, - 'KEYCMD_EXEC': keycmd_exec, - 'KEYCMD_UPDATE': keycmd_update, - 'MODCMD_EXEC': modcmd_exec, - 'MODCMD_UPDATE': modcmd_update, - 'MODE_BIND': parse_mode_bind, - 'MODE_CHANGED': mode_changed, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'bind': bind, - 'mode_bind': mode_bind, - 'get_bindlet': get_bindlet, - }) diff --git a/examples/data/uzbl/plugins/cmd_expand.py b/examples/data/uzbl/plugins/cmd_expand.py deleted file mode 100644 index 3f6ae2b..0000000 --- a/examples/data/uzbl/plugins/cmd_expand.py +++ /dev/null @@ -1,42 +0,0 @@ -def escape(str): - for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]: - str = str.replace(char, (level * '\\') + char) - - return str - - -def cmd_expand(uzbl, cmd, args): - '''Exports a function that provides the following - expansions in any uzbl command string: - - %s = replace('%s', ' '.join(args)) - %r = replace('%r', "'%s'" % escaped(' '.join(args))) - %1 = replace('%1', arg[0]) - %2 = replace('%2', arg[1]) - %n = replace('%n', arg[n-1]) - ''' - - # Ensure (1) all string representable and (2) correct string encoding. - args = map(unicode, args) - - # Direct string replace. - if '%s' in cmd: - cmd = cmd.replace('%s', ' '.join(args)) - - # Escaped and quoted string replace. - if '%r' in cmd: - cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args))) - - # Arg index string replace. - for (index, arg) in enumerate(args): - index += 1 - if '%%%d' % index in cmd: - cmd = cmd.replace('%%%d' % index, unicode(arg)) - - return cmd - - -def init(uzbl): - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export('cmd_expand', cmd_expand) diff --git a/examples/data/uzbl/plugins/completion.py b/examples/data/uzbl/plugins/completion.py deleted file mode 100644 index 8cea203..0000000 --- a/examples/data/uzbl/plugins/completion.py +++ /dev/null @@ -1,206 +0,0 @@ -'''Keycmd completion.''' - -# A list of functions this plugin exports to be used via uzbl object. -__export__ = ['start_completion', 'get_completion_dict'] - -import re - -# Holds the per-instance completion dicts. -UZBLS = {} - -# Completion level -NONE, ONCE, LIST, COMPLETE = range(4) - -# Default instance dict. -DEFAULTS = {'completions': [], 'level': NONE, 'lock': False} - -# The reverse keyword finding re. -FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall - -# Formats -LIST_FORMAT = " %s " -ITEM_FORMAT = "%s%s" - - -def escape(str): - return str.replace("@", "\@") - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - # Make sure the config keys for all possible completions are known. - uzbl.send('dump_config_as_events') - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_completion_dict(uzbl): - '''Get data stored for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def get_incomplete_keyword(uzbl): - '''Gets the segment of the keycmd leading up to the cursor position and - uses a regular expression to search backwards finding parially completed - keywords or @variables. Returns a null string if the correct completion - conditions aren't met.''' - - keylet = uzbl.get_keylet() - left_segment = keylet.keycmd[:keylet.cursor] - partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip() - if partial.startswith('set '): - return ('@%s' % partial[4:].lstrip(), True) - - return (partial, False) - - -def stop_completion(uzbl, *args): - '''Stop command completion and return the level to NONE.''' - - d = get_completion_dict(uzbl) - d['level'] = NONE - uzbl.set('completion_list') - - -def complete_completion(uzbl, partial, hint, set_completion=False): - '''Inject the remaining porition of the keyword into the keycmd then stop - the completioning.''' - - if set_completion: - remainder = "%s = " % hint[len(partial):] - - else: - remainder = "%s " % hint[len(partial):] - - uzbl.inject_keycmd(remainder) - stop_completion(uzbl) - - -def partial_completion(uzbl, partial, hint): - '''Inject a common portion of the hints into the keycmd.''' - - remainder = hint[len(partial):] - uzbl.inject_keycmd(remainder) - - -def update_completion_list(uzbl, *args): - '''Checks if the user still has a partially completed keyword under his - cursor then update the completion hints list.''' - - partial = get_incomplete_keyword(uzbl)[0] - if not partial: - return stop_completion(uzbl) - - d = get_completion_dict(uzbl) - if d['level'] < LIST: - return - - hints = [h for h in d['completions'] if h.startswith(partial)] - if not hints: - return uzbl.set('completion_list') - - j = len(partial) - l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)] - uzbl.set('completion_list', LIST_FORMAT % ' '.join(l)) - - -def start_completion(uzbl, *args): - - d = get_completion_dict(uzbl) - if d['lock']: - return - - (partial, set_completion) = get_incomplete_keyword(uzbl) - if not partial: - return stop_completion(uzbl) - - if d['level'] < COMPLETE: - d['level'] += 1 - - hints = [h for h in d['completions'] if h.startswith(partial)] - if not hints: - return - - elif len(hints) == 1: - d['lock'] = True - complete_completion(uzbl, partial, hints[0], set_completion) - d['lock'] = False - return - - elif partial in hints and d['level'] == COMPLETE: - d['lock'] = True - complete_completion(uzbl, partial, partial, set_completion) - d['lock'] = False - return - - smalllen, smallest = sorted([(len(h), h) for h in hints])[0] - common = '' - for i in range(len(partial), smalllen): - char, same = smallest[i], True - for hint in hints: - if hint[i] != char: - same = False - break - - if not same: - break - - common += char - - if common: - d['lock'] = True - partial_completion(uzbl, partial, partial+common) - d['lock'] = False - - update_completion_list(uzbl) - - -def add_builtins(uzbl, args): - '''Pump the space delimited list of builtin commands into the - builtin list.''' - - completions = get_completion_dict(uzbl)['completions'] - builtins = filter(None, map(unicode.strip, args.split(" "))) - for builtin in builtins: - if builtin not in completions: - completions.append(builtin) - - -def add_config_key(uzbl, key, value): - '''Listen on the CONFIG_CHANGED event and add config keys to the variable - list for @var like expansion support.''' - - completions = get_completion_dict(uzbl)['completions'] - key = "@%s" % key - if key not in completions: - completions.append(key) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'BUILTINS': add_builtins, - 'CONFIG_CHANGED': add_config_key, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'KEYCMD_CLEARED': stop_completion, - 'KEYCMD_EXEC': stop_completion, - 'KEYCMD_UPDATE': update_completion_list, - 'START_COMPLETION': start_completion, - 'STOP_COMPLETION': stop_completion, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_completion_dict': get_completion_dict, - 'start_completion': start_completion, - }) diff --git a/examples/data/uzbl/plugins/config.py b/examples/data/uzbl/plugins/config.py deleted file mode 100644 index 4a848a3..0000000 --- a/examples/data/uzbl/plugins/config.py +++ /dev/null @@ -1,97 +0,0 @@ -import re -import types - -__export__ = ['set', 'get_config'] - -VALIDKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match -TYPECONVERT = {'int': int, 'float': float, 'str': unicode} - -UZBLS = {} - - -def escape(value): - '''A real escaping function may be required.''' - - return unicode(value) - - -def set(uzbl, key, value='', config=None, force=False): - '''Sends a: "set key = value" command to the uzbl instance. If force is - False then only send a set command if the values aren't equal.''' - - if type(value) == types.BooleanType: - value = int(value) - - else: - value = unicode(value) - - if not VALIDKEY(key): - raise KeyError("%r" % key) - - value = escape(value) - if '\n' in value: - value = value.replace("\n", "\\n") - - if not force: - if config is None: - config = get_config(uzbl) - - if key in config and config[key] == value: - return - - uzbl.send('set %s = %s' % (key, value)) - - -class ConfigDict(dict): - def __init__(self, uzbl): - self._uzbl = uzbl - - def __setitem__(self, key, value): - '''Makes "config[key] = value" a wrapper for the set function.''' - - set(self._uzbl, key, value, config=self) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = ConfigDict(uzbl) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del uzbl - - -def get_config(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def variable_set(uzbl, args): - config = get_config(uzbl) - - key, type, value = list(args.split(' ', 2) + ['',])[:3] - old = config[key] if key in config else None - value = TYPECONVERT[type](value) - - dict.__setitem__(config, key, value) - - if old != value: - uzbl.event("CONFIG_CHANGED", key, value) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'VARIABLE_SET': variable_set, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_config': get_config, - 'set': set, - }) diff --git a/examples/data/uzbl/plugins/keycmd.py b/examples/data/uzbl/plugins/keycmd.py deleted file mode 100644 index c119077..0000000 --- a/examples/data/uzbl/plugins/keycmd.py +++ /dev/null @@ -1,571 +0,0 @@ -import re - -# Hold the keylets. -UZBLS = {} - -# Keycmd format which includes the markup for the cursor. -KEYCMD_FORMAT = "%s%s%s" -MODCMD_FORMAT = " %s " - - -def escape(str): - for char in ['\\', '@']: - str = str.replace(char, '\\'+char) - - return str - - -def uzbl_escape(str): - return "@[%s]@" % escape(str) if str else '' - - -class Keylet(object): - '''Small per-instance object that tracks all the keys held and characters - typed.''' - - def __init__(self): - # Modcmd tracking - self.held = set() - self.ignored = set() - self.modcmd = '' - self.is_modcmd = False - - # Keycmd tracking - self.keycmd = '' - self.cursor = 0 - - self.modmaps = {} - self.ignores = {} - self.additions = {} - - # Keylet string repr cache. - self._repr_cache = None - - - def get_keycmd(self): - '''Get the keycmd-part of the keylet.''' - - return self.keycmd - - - def get_modcmd(self): - '''Get the modcmd-part of the keylet.''' - - if not self.is_modcmd: - return '' - - return ''.join(self.held) + self.modcmd - - - def modmap_key(self, key): - '''Make some obscure names for some keys friendlier.''' - - if key in self.modmaps: - return self.modmaps[key] - - elif key.endswith('_L') or key.endswith('_R'): - # Remove left-right discrimination and try again. - return self.modmap_key(key[:-2]) - - else: - return key - - - def find_addition(self, modkey): - '''Key has just been pressed, check if this key + the held list - results in a modkey addition. Return that addition and remove all - modkeys that created it.''' - - # Intersection of (held list + modkey) and additions. - added = (self.held | set([modkey])) & set(self.additions.keys()) - for key in added: - if key == modkey or modkey in self.additions[key]: - self.held -= self.additions[key] - return key - - # Held list + ignored list + modkey. - modkeys = self.held | self.ignored | set([modkey]) - for (key, value) in self.additions.items(): - if modkeys.issuperset(value): - self.held -= value - return key - - return modkey - - - def key_ignored(self, key): - '''Check if the given key is ignored by any ignore rules.''' - - for (glob, match) in self.ignores.items(): - if match(key): - return True - - return False - - - def __repr__(self): - '''Return a string representation of the keylet.''' - - if self._repr_cache: - return self._repr_cache - - l = [] - if self.is_modcmd: - l.append('modcmd=%r' % self.get_modcmd()) - - elif self.held: - l.append('held=%r' % ''.join(sorted(self.held))) - - if self.keycmd: - l.append('keycmd=%r' % self.get_keycmd()) - - self._repr_cache = '' % ', '.join(l) - return self._repr_cache - - -def add_modmap(uzbl, key, map): - '''Add modmaps. - - Examples: - set modmap = request MODMAP - @modmap - @modmap - ... - - Then: - @bind = - @bind x = - ... - - ''' - - assert len(key) - modmaps = get_keylet(uzbl).modmaps - - if key[0] == "<" and key[-1] == ">": - key = key[1:-1] - - modmaps[key] = map - uzbl.event("NEW_MODMAP", key, map) - - -def modmap_parse(uzbl, map): - '''Parse a modmap definiton.''' - - split = [s.strip() for s in map.split(' ') if s.split()] - - if not split or len(split) > 2: - raise Exception('Invalid modmap arugments: %r' % map) - - add_modmap(uzbl, *split) - - -def add_key_ignore(uzbl, glob): - '''Add an ignore definition. - - Examples: - set ignore_key = request IGNORE_KEY - @ignore_key - @ignore_key - ... - ''' - - assert len(glob) > 1 - ignores = get_keylet(uzbl).ignores - - glob = "<%s>" % glob.strip("<> ") - restr = glob.replace('*', '[^\s]*') - match = re.compile(restr).match - - ignores[glob] = match - uzbl.event('NEW_KEY_IGNORE', glob) - - -def add_modkey_addition(uzbl, modkeys, result): - '''Add a modkey addition definition. - - Examples: - set mod_addition = request MODKEY_ADDITION - @mod_addition - @mod_addition - @mod_addition - ... - - Then: - @bind = - @bind o = - ... - ''' - - additions = get_keylet(uzbl).additions - modkeys = set(modkeys) - - assert len(modkeys) and result and result not in modkeys - - for (existing_result, existing_modkeys) in additions.items(): - if existing_result != result: - assert modkeys != existing_modkeys - - additions[result] = modkeys - uzbl.event('NEW_MODKEY_ADDITION', modkeys, result) - - -def modkey_addition_parse(uzbl, modkeys): - '''Parse modkey addition definition.''' - - keys = filter(None, map(unicode.strip, modkeys.split(" "))) - keys = ['<%s>' % key.strip("<>") for key in keys if key.strip("<>")] - - assert len(keys) > 1 - add_modkey_addition(uzbl, keys[:-1], keys[-1]) - - -def add_instance(uzbl, *args): - '''Create the Keylet object for this uzbl instance.''' - - UZBLS[uzbl] = Keylet() - - -def del_instance(uzbl, *args): - '''Delete the Keylet object for this uzbl instance.''' - - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_keylet(uzbl): - '''Return the corresponding keylet for this uzbl instance.''' - - # Startup events are not correctly captured and sent over the uzbl socket - # yet so this line is needed because the INSTANCE_START event is lost. - if uzbl not in UZBLS: - add_instance(uzbl) - - keylet = UZBLS[uzbl] - keylet._repr_cache = False - return keylet - - -def clear_keycmd(uzbl): - '''Clear the keycmd for this uzbl instance.''' - - k = get_keylet(uzbl) - k.keycmd = '' - k.cursor = 0 - k._repr_cache = False - uzbl.set('keycmd') - uzbl.set('raw_keycmd') - uzbl.event('KEYCMD_CLEARED') - - -def clear_modcmd(uzbl, clear_held=False): - '''Clear the modcmd for this uzbl instance.''' - - k = get_keylet(uzbl) - k.modcmd = '' - k.is_modcmd = False - k._repr_cache = False - if clear_held: - k.ignored = set() - k.held = set() - - uzbl.set('modcmd') - uzbl.set('raw_modcmd') - uzbl.event('MODCMD_CLEARED') - - -def clear_current(uzbl): - '''Clear the modcmd if is_modcmd else clear keycmd.''' - - k = get_keylet(uzbl) - if k.is_modcmd: - clear_modcmd(uzbl) - - else: - clear_keycmd(uzbl) - - -def focus_changed(uzbl, *args): - '''Focus to the uzbl instance has now been lost which means all currently - held keys in the held list will not get a KEY_RELEASE event so clear the - entire held list.''' - - clear_modcmd(uzbl, clear_held=True) - - -def update_event(uzbl, k, execute=True): - '''Raise keycmd & modcmd update events.''' - - config = uzbl.get_config() - keycmd, modcmd = k.get_keycmd(), k.get_modcmd() - - if k.is_modcmd: - uzbl.event('MODCMD_UPDATE', k) - - else: - uzbl.event('KEYCMD_UPDATE', k) - - if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': - new_modcmd = k.get_modcmd() - if not new_modcmd: - uzbl.set('modcmd', config=config) - uzbl.set('raw_modcmd', config=config) - - elif new_modcmd == modcmd: - uzbl.set('raw_modcmd', escape(modcmd), config=config) - uzbl.set('modcmd', MODCMD_FORMAT % uzbl_escape(modcmd), - config=config) - - if 'keycmd_events' in config and config['keycmd_events'] != '1': - return - - new_keycmd = k.get_keycmd() - if not new_keycmd: - uzbl.set('keycmd', config=config) - uzbl.set('raw_keycmd', config=config) - - elif new_keycmd == keycmd: - # Generate the pango markup for the cursor in the keycmd. - curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' ' - chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]] - value = KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks)) - uzbl.set('keycmd', value, config=config) - uzbl.set('raw_keycmd', escape(keycmd), config=config) - - -def inject_str(str, index, inj): - '''Inject a string into string at at given index.''' - - return "%s%s%s" % (str[:index], inj, str[index:]) - - -def get_keylet_and_key(uzbl, key, add=True): - '''Return the keylet and apply any transformations to the key as defined - by the modmapping or modkey addition rules. Return None if the key is - ignored.''' - - keylet = get_keylet(uzbl) - key = keylet.modmap_key(key) - if len(key) == 1: - return (keylet, key) - - modkey = "<%s>" % key.strip("<>") - - if keylet.key_ignored(modkey): - if add: - keylet.ignored.add(modkey) - - elif modkey in keylet.ignored: - keylet.ignored.remove(modkey) - - modkey = keylet.find_addition(modkey) - - if keylet.key_ignored(modkey): - return (keylet, None) - - return (keylet, modkey) - - -def key_press(uzbl, key): - '''Handle KEY_PRESS events. Things done by this function include: - - 1. Ignore all shift key presses (shift can be detected by capital chars) - 3. In non-modcmd mode: - a. append char to keycmd - 4. If not in modcmd mode and a modkey was pressed set modcmd mode. - 5. If in modcmd mode the pressed key is added to the held keys list. - 6. Keycmd is updated and events raised if anything is changed.''' - - (k, key) = get_keylet_and_key(uzbl, key.strip()) - if not key: - return - - if key.lower() == '' and not k.held and k.keycmd: - k.keycmd = inject_str(k.keycmd, k.cursor, ' ') - k.cursor += 1 - - elif not k.held and len(key) == 1: - config = uzbl.get_config() - if 'keycmd_events' in config and config['keycmd_events'] != '1': - k.keycmd = '' - k.cursor = 0 - uzbl.set('keycmd', config=config) - uzbl.set('raw_keycmd', config=config) - return - - k.keycmd = inject_str(k.keycmd, k.cursor, key) - k.cursor += 1 - - elif len(key) > 1: - k.is_modcmd = True - if key not in k.held: - k.held.add(key) - - else: - k.is_modcmd = True - k.modcmd += key - - update_event(uzbl, k) - - -def key_release(uzbl, key): - '''Respond to KEY_RELEASE event. Things done by this function include: - - 1. Remove the key from the keylet held list. - 2. If in a mod-command then raise a MODCMD_EXEC. - 3. Check if any modkey is held, if so set modcmd mode. - 4. Update the keycmd uzbl variable if anything changed.''' - - (k, key) = get_keylet_and_key(uzbl, key.strip(), add=False) - - if key in k.held: - if k.is_modcmd: - uzbl.event('MODCMD_EXEC', k) - - k.held.remove(key) - clear_modcmd(uzbl) - - -def set_keycmd(uzbl, keycmd): - '''Allow setting of the keycmd externally.''' - - k = get_keylet(uzbl) - k.keycmd = keycmd - k._repr_cache = None - k.cursor = len(keycmd) - update_event(uzbl, k, False) - - -def inject_keycmd(uzbl, keycmd): - '''Allow injecting of a string into the keycmd at the cursor position.''' - - k = get_keylet(uzbl) - k.keycmd = inject_str(k.keycmd, k.cursor, keycmd) - k._repr_cache = None - k.cursor += len(keycmd) - update_event(uzbl, k, False) - - -def append_keycmd(uzbl, keycmd): - '''Allow appening of a string to the keycmd.''' - - k = get_keylet(uzbl) - k.keycmd += keycmd - k._repr_cache = None - k.cursor = len(k.keycmd) - update_event(uzbl, k, False) - - -def keycmd_strip_word(uzbl, sep): - ''' Removes the last word from the keycmd, similar to readline ^W ''' - - sep = sep or ' ' - k = get_keylet(uzbl) - if not k.keycmd: - return - - head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] - rfind = head.rfind(sep) - head = head[:rfind] if rfind + 1 else '' - k.keycmd = head + tail - k.cursor = len(head) - update_event(uzbl, k, False) - - -def keycmd_backspace(uzbl, *args): - '''Removes the character at the cursor position in the keycmd.''' - - k = get_keylet(uzbl) - if not k.keycmd: - return - - k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] - k.cursor -= 1 - update_event(uzbl, k, False) - - -def keycmd_delete(uzbl, *args): - '''Removes the character after the cursor position in the keycmd.''' - - k = get_keylet(uzbl) - if not k.keycmd: - return - - k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] - update_event(uzbl, k, False) - - -def keycmd_exec_current(uzbl, *args): - '''Raise a KEYCMD_EXEC with the current keylet and then clear the - keycmd.''' - - k = get_keylet(uzbl) - uzbl.event('KEYCMD_EXEC', k) - clear_keycmd(uzbl) - - -def set_cursor_pos(uzbl, index): - '''Allow setting of the cursor position externally. Supports negative - indexing and relative stepping with '+' and '-'.''' - - k = get_keylet(uzbl) - if index == '-': - cursor = k.cursor - 1 - - elif index == '+': - cursor = k.cursor + 1 - - else: - cursor = int(index.strip()) - if cursor < 0: - cursor = len(k.keycmd) + cursor + 1 - - if cursor < 0: - cursor = 0 - - if cursor > len(k.keycmd): - cursor = len(k.keycmd) - - k.cursor = cursor - update_event(uzbl, k, False) - - -def init(uzbl): - '''Connect handlers to uzbl events.''' - - # Event handling hooks. - uzbl.connect_dict({ - 'APPEND_KEYCMD': append_keycmd, - 'FOCUS_GAINED': focus_changed, - 'FOCUS_LOST': focus_changed, - 'IGNORE_KEY': add_key_ignore, - 'INJECT_KEYCMD': inject_keycmd, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'KEYCMD_BACKSPACE': keycmd_backspace, - 'KEYCMD_DELETE': keycmd_delete, - 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, - 'KEYCMD_STRIP_WORD': keycmd_strip_word, - 'KEY_PRESS': key_press, - 'KEY_RELEASE': key_release, - 'MODKEY_ADDITION': modkey_addition_parse, - 'MODMAP': modmap_parse, - 'SET_CURSOR_POS': set_cursor_pos, - 'SET_KEYCMD': set_keycmd, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'add_key_ignore': add_key_ignore, - 'add_modkey_addition': add_modkey_addition, - 'add_modmap': add_modmap, - 'append_keycmd': append_keycmd, - 'clear_current': clear_current, - 'clear_keycmd': clear_keycmd, - 'clear_modcmd': clear_modcmd, - 'get_keylet': get_keylet, - 'inject_keycmd': inject_keycmd, - 'set_cursor_pos': set_cursor_pos, - 'set_keycmd': set_keycmd, - }) diff --git a/examples/data/uzbl/plugins/mode.py b/examples/data/uzbl/plugins/mode.py deleted file mode 100644 index 54d865a..0000000 --- a/examples/data/uzbl/plugins/mode.py +++ /dev/null @@ -1,176 +0,0 @@ -import sys -import re - -__export__ = ['set_mode', 'get_mode', 'set_mode_config', 'get_mode_config'] - -UZBLS = {} - -DEFAULTS = { - 'mode': '', - 'modes': { - 'insert': { - 'forward_keys': True, - 'keycmd_events': False, - 'modcmd_updates': False, - 'mode_indicator': 'I'}, - 'command': { - 'forward_keys': False, - 'keycmd_events': True, - 'modcmd_updates': True, - 'mode_indicator': 'C'}}} - -FINDSPACES = re.compile("\s+") -VALID_KEY = re.compile("^[\w_]+$").match - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_mode_dict(uzbl): - '''Return the mode dict for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def get_mode_config(uzbl, mode): - '''Return the mode config for a given mode.''' - - modes = get_mode_dict(uzbl)['modes'] - if mode not in modes: - modes[mode] = {} - - return modes[mode] - - -def get_mode(uzbl): - return get_mode_dict(uzbl)['mode'] - - -def mode_changed(uzbl, mode): - '''The mode has just been changed, now set the per-mode config.''' - - if get_mode(uzbl) != mode: - return - - config = uzbl.get_config() - mode_config = get_mode_config(uzbl, mode) - for (key, value) in mode_config.items(): - uzbl.set(key, value, config=config) - - if 'mode_indicator' not in mode_config: - config['mode_indicator'] = mode - - uzbl.clear_keycmd() - uzbl.clear_modcmd() - - -def set_mode(uzbl, mode=None): - '''Set the mode and raise the MODE_CHANGED event if the mode has changed. - Fallback on the default mode if no mode argument was given and the default - mode is not null.''' - - config = uzbl.get_config() - mode_dict = get_mode_dict(uzbl) - if mode is None: - mode_dict['mode'] = '' - if 'default_mode' in config: - mode = config['default_mode'] - - else: - mode = 'command' - - if not VALID_KEY(mode): - raise KeyError("invalid mode name: %r" % mode) - - if 'mode' not in config or config['mode'] != mode: - config['mode'] = mode - - elif mode_dict['mode'] != mode: - mode_dict['mode'] = mode - uzbl.event("MODE_CHANGED", mode) - - -def config_changed(uzbl, key, value): - '''Check for mode related config changes.''' - - value = None if not value else value - if key == 'default_mode': - if not get_mode(uzbl): - set_mode(uzbl, value) - - elif key == 'mode': - set_mode(uzbl, value) - - -def set_mode_config(uzbl, mode, key, value): - '''Set mode specific configs. If the mode being modified is the current - mode then apply the changes on the go.''' - - assert VALID_KEY(mode) and VALID_KEY(key) - - mode_config = get_mode_config(uzbl, mode) - mode_config[key] = value - - if get_mode(uzbl) == mode: - uzbl.set(key, value) - - -def mode_config(uzbl, args): - '''Parse mode config events.''' - - split = map(unicode.strip, FINDSPACES.split(args.lstrip(), 1)) - if len(split) != 2: - raise SyntaxError('invalid mode config syntax: %r' % args) - - mode, set = split - split = map(unicode.strip, set.split('=', 1)) - if len(split) != 2: - raise SyntaxError('invalid set syntax: %r' % args) - - key, value = split - set_mode_config(uzbl, mode, key, value) - - -def toggle_modes(uzbl, modes): - '''Toggle or cycle between or through a list of modes.''' - - assert len(modes.strip()) - - modelist = filter(None, map(unicode.strip, modes.split(' '))) - mode = get_mode(uzbl) - - index = 0 - if mode in modelist: - index = (modelist.index(mode)+1) % len(modelist) - - set_mode(uzbl, modelist[index]) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'CONFIG_CHANGED': config_changed, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'MODE_CHANGED': mode_changed, - 'MODE_CONFIG': mode_config, - 'TOGGLE_MODES': toggle_modes, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_mode': get_mode, - 'get_mode_config': get_mode_config, - 'set_mode': set_mode, - 'set_mode_config': set_mode_config, - }) diff --git a/examples/data/uzbl/plugins/on_event.py b/examples/data/uzbl/plugins/on_event.py deleted file mode 100644 index b9c504a..0000000 --- a/examples/data/uzbl/plugins/on_event.py +++ /dev/null @@ -1,107 +0,0 @@ -'''Plugin provides arbitrary binding of uzbl events to uzbl commands. - -Formatting options: - %s = space separated string of the arguments - %r = escaped and quoted version of %s - %1 = argument 1 - %2 = argument 2 - %n = argument n - -Usage: - request ON_EVENT LINK_HOVER set selected_uri = $1 - --> LINK_HOVER http://uzbl.org/ - <-- set selected_uri = http://uzbl.org/ - - request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2 - --> CONFIG_CHANGED selected_uri http://uzbl.org/ - <-- print Config changed: selected_uri = http://uzbl.org/ -''' - -import sys -import re - -__export__ = ['get_on_events', 'on_event'] - -UZBLS = {} - - -def error(msg): - sys.stderr.write('on_event plugin: error: %s\n' % msg) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = {} - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_on_events(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def event_handler(uzbl, *args, **kargs): - '''This function handles all the events being watched by various - on_event definitions and responds accordingly.''' - - events = get_on_events(uzbl) - event = kargs['on_event'] - if event not in events: - return - - commands = events[event] - cmd_expand = uzbl.cmd_expand - for cmd in commands: - cmd = cmd_expand(cmd, args) - uzbl.send(cmd) - - -def on_event(uzbl, event, cmd): - '''Add a new event to watch and respond to.''' - - event = event.upper() - events = get_on_events(uzbl) - if event not in events: - uzbl.connect(event, event_handler, on_event=event) - events[event] = [] - - cmds = events[event] - if cmd not in cmds: - cmds.append(cmd) - - -def parse_on_event(uzbl, args): - '''Parse ON_EVENT events and pass them to the on_event function. - - Syntax: "event ON_EVENT commands".''' - - if not args: - return error("missing on_event arguments") - - split = args.split(' ', 1) - if len(split) != 2: - return error("invalid ON_EVENT syntax: %r" % args) - - event, cmd = split - on_event(uzbl, event, cmd) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'ON_EVENT': parse_on_event, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_on_events': get_on_events, - 'on_event': on_event, - }) diff --git a/examples/data/uzbl/plugins/plugin_template.py b/examples/data/uzbl/plugins/plugin_template.py deleted file mode 100644 index 565a999..0000000 --- a/examples/data/uzbl/plugins/plugin_template.py +++ /dev/null @@ -1,76 +0,0 @@ -'''Plugin template.''' - -# Holds the per-instance data dict. -UZBLS = {} - -# The default instance dict. -DEFAULTS = {} - - -def add_instance(uzbl, *args): - '''Add a new instance with default config options.''' - - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - '''Delete data stored for an instance.''' - - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_myplugin_dict(uzbl): - '''Get data stored for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def myplugin_function(uzbl, *args, **kargs): - '''Custom plugin function which is exported by the __export__ list at the - top of the file for use by other functions/callbacks.''' - - print "My plugin function arguments:", args, kargs - - # Get the per-instance data object. - data = get_myplugin_dict(uzbl) - - # Function logic goes here. - - -def myplugin_event_parser(uzbl, args): - '''Parses MYPLUGIN_EVENT raised by uzbl or another plugin.''' - - print "Got MYPLUGIN_EVENT with arguments: %r" % args - - # Parsing logic goes here. - - -def init(uzbl): - '''The main function of the plugin which is used to attach all the event - hooks that are going to be used throughout the plugins life. This function - is called each time a UzblInstance() object is created in the event - manager.''' - - # Make a dictionary comprising of {"EVENT_NAME": handler, ..} to the event - # handler stack: - uzbl.connect_dict({ - # event name function - 'INSTANCE_START': add_instance, - 'INSTANCE_EXIT': del_instance, - 'MYPLUGIN_EVENT': myplugin_event_parser, - }) - - # Or connect a handler to an event manually and supply additional optional - # arguments: - #uzbl.connect("MYOTHER_EVENT", myother_event_parser, True, limit=20) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.connect_dict({ - # external name function - 'myplugin_function': myplugin_function, - }) diff --git a/examples/data/uzbl/plugins/progress_bar.py b/examples/data/uzbl/plugins/progress_bar.py deleted file mode 100644 index 89ba175..0000000 --- a/examples/data/uzbl/plugins/progress_bar.py +++ /dev/null @@ -1,159 +0,0 @@ -import sys - -UZBLS = {} - -DEFAULTS = {'width': 8, - 'done': '=', - 'pending': '.', - 'format': '[%d%a%p]%c', - 'spinner': '-\\|/', - 'sprites': 'loading', - 'updates': 0, - 'progress': 100} - - -def error(msg): - sys.stderr.write("progress_bar plugin: error: %s\n" % msg) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_progress_config(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def update_progress(uzbl, prog=None): - '''Updates the progress_format variable on LOAD_PROGRESS update. - - The current substitution options are: - %d = done char * done - %p = pending char * remaining - %c = percent done - %i = int done - %s = -\|/ spinner - %t = percent pending - %o = int pending - %r = sprites - ''' - - prog_config = get_progress_config(uzbl) - config = uzbl.get_config() - - if prog is None: - prog = prog_config['progress'] - - else: - prog = int(prog) - prog_config['progress'] = prog - - prog_config['updates'] += 1 - format = prog_config['format'] - width = prog_config['width'] - - # Inflate the done and pending bars to stop the progress bar - # jumping around. - if '%c' in format or '%i' in format: - count = format.count('%c') + format.count('%i') - width += (3-len(str(prog))) * count - - if '%t' in format or '%o' in format: - count = format.count('%t') + format.count('%o') - width += (3-len(str(100-prog))) * count - - done = int(((prog/100.0)*width)+0.5) - pending = width - done - - if '%d' in format: - format = format.replace('%d', prog_config['done']*done) - - if '%p' in format: - format = format.replace('%p', prog_config['pending']*pending) - - if '%c' in format: - format = format.replace('%c', '%d%%' % prog) - - if '%i' in format: - format = format.replace('%i', '%d' % prog) - - if '%t' in format: - format = format.replace('%t', '%d%%' % (100-prog)) - - if '%o' in format: - format = format.replace('%o', '%d' % (100-prog)) - - if '%s' in format: - spinner = prog_config['spinner'] - spin = '-' if not spinner else spinner - index = 0 if prog == 100 else prog_config['updates'] % len(spin) - char = '\\\\' if spin[index] == '\\' else spin[index] - format = format.replace('%s', char) - - if '%r' in format: - sprites = prog_config['sprites'] - sprites = '-' if not sprites else sprites - index = int(((prog/100.0)*len(sprites))+0.5)-1 - sprite = '\\\\' if sprites[index] == '\\' else sprites[index] - format = format.replace('%r', sprite) - - if 'progress_format' not in config or config['progress_format'] != format: - config['progress_format'] = format - - -def progress_config(uzbl, args): - '''Parse PROGRESS_CONFIG events from the uzbl instance. - - Syntax: event PROGRESS_CONFIG = - ''' - - split = args.split('=', 1) - if len(split) != 2: - return error("invalid syntax: %r" % args) - - key, value = map(unicode.strip, split) - prog_config = get_progress_config(uzbl) - - if key not in prog_config: - return error("key error: %r" % args) - - if type(prog_config[key]) == type(1): - try: - value = int(value) - - except: - return error("invalid type: %r" % args) - - elif not value: - value = ' ' - - prog_config[key] = value - update_progress(uzbl) - - -def reset_progress(uzbl, args): - '''Reset the spinner counter, reset the progress int and re-draw the - progress bar on LOAD_COMMIT.''' - - prog_dict = get_progress_config(uzbl) - prog_dict['updates'] = prog_dict['progress'] = 0 - update_progress(uzbl) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'LOAD_COMMIT': reset_progress, - 'LOAD_PROGRESS': update_progress, - 'PROGRESS_CONFIG': progress_config, - }) diff --git a/examples/data/uzbl/scripts/cookies.sh b/examples/data/uzbl/scripts/cookies.sh deleted file mode 100755 index ee2ce51..0000000 --- a/examples/data/uzbl/scripts/cookies.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/sh - -set -n; - -# THIS IS EXPERIMENTAL AND COULD BE INSECURE !!!!!! - -# this is an example bash script of how you could manage your cookies. it is very raw and basic and not as good as uzbl-cookie-daemon -# we use the cookies.txt format (See http://kb.mozillazine.org/Cookies.txt) -# This is one textfile with entries like this: -# kb.mozillazine.org FALSE / FALSE 1146030396 wikiUserID 16993 -# domain alow-read-other-subdomains path http-required expiration name value -# you probably want your cookies config file in your $XDG_CONFIG_HOME ( eg $HOME/.config/uzbl/cookies) -# Note. in uzbl there is no strict definition on what a session is. it's YOUR job to clear cookies marked as end_session if you want to keep cookies only valid during a "session" -# MAYBE TODO: allow user to edit cookie before saving. this cannot be done with zenity :( -# TODO: different cookie paths per config (eg per group of uzbl instances) - -# TODO: correct implementation. -# see http://curl.haxx.se/rfc/cookie_spec.html -# http://en.wikipedia.org/wiki/HTTP_cookie - -# TODO : check expires= before sending. -# write sample script that cleans up cookies dir based on expires attribute. -# TODO: check uri against domain attribute. and path also. -# implement secure attribute. -# support blocking or not for 3rd parties -# http://kb.mozillazine.org/Cookies.txt -# don't always append cookies, sometimes we need to overwrite - -cookie_config=${XDG_CONFIG_HOME:-${HOME}/.config}/uzbl/cookies -[ "x$cookie_config" = x ] && exit 1 -[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/" ] &&\ -cookie_data=${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/cookies.txt || exit 1 - -notifier= -#notifier=notify-send -#notify_wrapper () { -# echo "$@" >> $HOME/cookielog -#} -#notifier=notifier_wrapper - -# if this variable is set, we will use it to inform you when and which cookies we store, and when/which we send. -# it's primarily used for debugging -notifier= -which zenity &>/dev/null || exit 2 - -# Example cookie: -# test_cookie=CheckForPermission; expires=Thu, 07-May-2009 19:17:55 GMT; path=/; domain=.doubleclick.net - -# uri=$6 -# uri=${uri/http:\/\/} # strip 'http://' part -# host=${uri/\/*/} -action=$8 # GET/PUT -shift -host=$9 -shift -path=$9 -shift -cookie=$9 - -field_domain=$host -field_path=$path -field_name= -field_value= -field_exp='end_session' - -notify() { - [ -n "$notifier" ] && $notifier "$@" -} - - -# FOR NOW LETS KEEP IT SIMPLE AND JUST ALWAYS PUT AND ALWAYS GET -parse_cookie() { - IFS=$';' - first_pair=1 - for pair in $cookie - do - if [ "x$first_pair" = x1 ] - then - field_name=${pair%%=*} - field_value=${pair#*=} - first_pair=0 - else - echo "$pair" | read -r pair #strip leading/trailing wite space - key=${pair%%=*} - val=${pair#*=} - [ "$key" == expires ] && field_exp=`date -u -d "$val" +'%s'` - # TODO: domain - [ "$key" == path ] && field_path=$val - fi - done - unset IFS -} - -# match cookies in cookies.txt against hostname and path -get_cookie() { - path_esc=${path//\//\\/} - search="^[^\t]*$host\t[^\t]*\t$path_esc" - cookie=`awk "/$search/" $cookie_data 2>/dev/null | tail -n 1` - if [ -z "$cookie" ] - then - notify "Get_cookie: search: $search in $cookie_data -> no result" - false - else - notify "Get_cookie: search: $search in $cookie_data -> result: $cookie" - echo "$cookie" | \ - read domain alow_read_other_subdomains path http_required expiration name \ - value; - cookie="$name=$value" - true - fi -} - -save_cookie() { - if parse_cookie - then - data="$field_domain\tFALSE\t$field_path\tFALSE\t$field_exp\t$field_name\t$field_value" - notify "save_cookie: adding $data to $cookie_data" - echo -e "$data" >> $cookie_data - else - notify "not saving a cookie. since we don't have policies yet, parse_cookie must have returned false. this is a bug" - fi -} - -[ "x$action" = xPUT ] && save_cookie -[ "x$action" = xGET ] && get_cookie && echo "$cookie" - -exit - - -# TODO: implement this later. -# $1 = section (TRUSTED or DENY) -# $2 =url -match() { - sed -n "/$1/,/^\$/p" $cookie_config 2>/dev/null | grep -q "^$host" -} - -fetch_cookie() { - cookie=`cat $cookie_data` -} - -store_cookie() { - echo $cookie > $cookie_data -} - -if match TRUSTED $host -then - [ "x$action" = xPUT ] && store_cookie $host - [ "x$action" = xGET ] && fetch_cookie && echo "$cookie" -elif ! match DENY $host -then - [ "x$action" = xPUT ] && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Accept this cookie from $host ?" --entry-text="$cookie"` && store_cookie $host - [ "x$action" = xGET ] && fetch_cookie && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Submit this cookie to $host ?" --entry-text="$cookie"` && echo $cookie -fi -exit 0 diff --git a/examples/data/uzbl/scripts/download.sh b/examples/data/uzbl/scripts/download.sh deleted file mode 100755 index 1c7d039..0000000 --- a/examples/data/uzbl/scripts/download.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -# just an example of how you could handle your downloads -# try some pattern matching on the uri to determine what we should do - -# Some sites block the default wget --user-agent.. -GET="wget --user-agent=Firefox" - -dest="$HOME" -url="$8" - -http_proxy="$9" -export http_proxy - -test "x$url" = "x" && { echo "you must supply a url! ($url)"; exit 1; } - -# only changes the dir for the $get sub process -if echo "$url" | grep -E '.*\.torrent' >/dev/null; -then - ( cd "$dest"; $GET "$url") -else - ( cd "$dest"; $GET "$url") -fi diff --git a/examples/data/uzbl/scripts/extedit.js b/examples/data/uzbl/scripts/extedit.js deleted file mode 100644 index 8ed346d..0000000 --- a/examples/data/uzbl/scripts/extedit.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Edit forms in external editor - * - * (c) 2009, Robert Manea - * utf8 functions are (c) by Webtoolkit.info (http://www.webtoolkit.info/) - * - * - * Installation: - * - Copy this script to $HOME/.local/share/uzbl/scripts - * - Add the following to $HOME/.config/uzbl/config: - * @bind E = script @scripts_dir/extedit.js - * - Set your preferred editor - * set editor = gvim - * - non-GUI editors - * set editor = xterm -e vim - * - * Usage: - * Select (click) an editable form, go to command mode and hit E - * -*/ - - -function utf8_decode ( str_data ) { - var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0; - - str_data += ''; - - while ( i < str_data.length ) { - c1 = str_data.charCodeAt(i); - if (c1 < 128) { - tmp_arr[ac++] = String.fromCharCode(c1); - i++; - } else if ((c1 > 191) && (c1 < 224)) { - c2 = str_data.charCodeAt(i+1); - tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); - i += 2; - } else { - c2 = str_data.charCodeAt(i+1); - c3 = str_data.charCodeAt(i+2); - tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); - i += 3; - } - } - - return tmp_arr.join(''); -} - - -function utf8_encode ( argString ) { - var string = (argString+''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n"); - - var utftext = ""; - var start, end; - var stringl = 0; - - start = end = 0; - stringl = string.length; - for (var n = 0; n < stringl; n++) { - var c1 = string.charCodeAt(n); - var enc = null; - - if (c1 < 128) { - end++; - } else if (c1 > 127 && c1 < 2048) { - enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128); - } else { - enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128); - } - if (enc !== null) { - if (end > start) { - utftext += string.substring(start, end); - } - utftext += enc; - start = end = n+1; - } - } - - if (end > start) { - utftext += string.substring(start, string.length); - } - - return utftext; -} - - -(function() { - var actelem = document.activeElement; - - if(actelem.type == 'text' || actelem.type == 'textarea') { - var editor = Uzbl.run("print @external_editor") || "gvim"; - var filename = Uzbl.run("print @(mktemp /tmp/uzbl_edit.XXXXXX)@"); - - if(actelem.value) - Uzbl.run("sh 'echo " + window.btoa(utf8_encode(actelem.value)) + " | base64 -d > " + filename + "'"); - - Uzbl.run("sync_sh '" + editor + " " + filename + "'"); - actelem.value = utf8_decode(window.atob(Uzbl.run("print @(base64 -w 0 " + filename + ")@"))); - - Uzbl.run("sh 'rm -f " + filename + "'"); - } - - })(); diff --git a/examples/data/uzbl/scripts/follow_Numbers.js b/examples/data/uzbl/scripts/follow_Numbers.js deleted file mode 100644 index 00b279e..0000000 --- a/examples/data/uzbl/scripts/follow_Numbers.js +++ /dev/null @@ -1,228 +0,0 @@ -/* This is the basic linkfollowing script. - * Its pretty stable, only using numbers to navigate. - * - * TODO: Some pages mess around a lot with the zIndex which - * lets some hints in the background. - * TODO: Some positions are not calculated correctly (mostly - * because of uber-fancy-designed-webpages. Basic HTML and CSS - * works good - * TODO: Still some links can't be followed/unexpected things - * happen. Blame some freaky webdesigners ;) - */ - -//Just some shortcuts and globals -var uzblid = 'uzbl_link_hint'; -var uzbldivid = uzblid + '_div_container'; -var doc = document; -var win = window; -var links = document.links; -var forms = document.forms; - -//Reset keycmd, modcmd and return to default mode. -function clearKeycmd() { Uzbl.run('set mode ='); } - -//Make onlick-links "clickable" -try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ - type: 'click' - }); - } - }; -} catch(e) {} -//Catch the ESC keypress to stop linkfollowing -function keyPressHandler(e) { - var kC = window.event ? event.keyCode: e.keyCode; - var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; - if (kC == Esc) { - removeAllHints(); - } -} -//Calculate element position to draw the hint -//Pretty accurate but on fails in some very fancy cases -function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; - var height = el.offsetHeight; - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return [up, left, width, height]; -} -//Calculate if an element is visible -function isVisible(el) { - if (el == doc) { - return true; - } - if (!el) { - return false; - } - if (!el.parentNode) { - return false; - } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); -} -//Calculate if an element is on the viewport. -function elementInViewport(el) { - offset = elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; - var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; -} -//Removes all hints/leftovers that might be generated -//by this script. -function removeAllHints() { - var elements = doc.getElementById(uzbldivid); - if (elements) { - elements.parentNode.removeChild(elements); - } -} -//Generate a hint for an element with the given label -//Here you can play around with the style of the hints! -function generateHint(el, label) { - var pos = elementPosition(el); - var hint = doc.createElement('div'); - hint.setAttribute('name', uzblid); - hint.innerText = label; - hint.style.display = 'inline'; - hint.style.backgroundColor = '#B9FF00'; - hint.style.border = '2px solid #4A6600'; - hint.style.color = 'black'; - hint.style.fontSize = '9px'; - hint.style.fontWeight = 'bold'; - hint.style.lineHeight = '9px'; - hint.style.margin = '0px'; - hint.style.padding = '1px'; - hint.style.position = 'absolute'; - hint.style.zIndex = '1000'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; - var img = el.getElementsByTagName('img'); - if (img.length > 0) { - hint.style.left = pos[1] + img[0].width / 2 + 'px'; - } - hint.style.textDecoration = 'none'; - hint.style.webkitBorderRadius = '6px'; - // Play around with this, pretty funny things to do :) - hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; - return hint; -} -//Here we choose what to do with an element if we -//want to "follow" it. On form elements we "select" -//or pass the focus, on links we try to perform a click, -//but at least set the href of the link. (needs some improvements) -function clickElem(item) { - removeAllHints(); - clearKeycmd(); - if (item) { - var name = item.tagName; - if (name == 'A') { - item.click(); - window.location = item.href; - } else if (name == 'INPUT') { - var type = item.getAttribute('type').toUpperCase(); - if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { - item.focus(); - item.select(); - } else { - item.click(); - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { - item.focus(); - item.select(); - } else { - item.click(); - window.location = item.href; - } - } -} -//Returns a list of all links (in this version -//just the elements itself, but in other versions, we -//add the label here. -function addLinks() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (isVisible(li) && elementInViewport(li)) { - res[0].push(li); - } - } - return res; -} -//Same as above, just for the form elements -function addFormElems() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { - res[0].push(el); - } - } - } - return res; -} -//Draw all hints for all elements passed. "len" is for -//the number of chars we should use to avoid collisions -function reDrawHints(elems, chars) { - removeAllHints(); - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - for (var i = 0; i < elems[0].length; i++) { - if (elems[0][i]) { - var label = elems[1][i].substring(chars); - var h = generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) { - document.body.appendChild(hintdiv); - } -} -//Put it all together -function followLinks(follow) { - var s = follow.split(''); - var linknr = parseInt(follow, 10); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = addLinks(); - var formelems = addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = (elems[0].length + '').length; - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { - clickElem(elems[0][linknr]); - } else { - for (var j = 0; j < elems[0].length; j++) { - var b = true; - var label = j + ''; - var n = label.length; - for (n; n < len; n++) { - label = '0' + label; - } - for (var k = 0; k < s.length; k++) { - b = b && label.charAt(k) == s[k]; - } - if (b) { - leftover[0].push(elems[0][j]); - leftover[1].push(label); - } - } - reDrawHints(leftover, s.length); - } -} -followLinks('%s'); diff --git a/examples/data/uzbl/scripts/follow_Numbers_Strings.js b/examples/data/uzbl/scripts/follow_Numbers_Strings.js deleted file mode 100644 index e50da5d..0000000 --- a/examples/data/uzbl/scripts/follow_Numbers_Strings.js +++ /dev/null @@ -1,212 +0,0 @@ -var uzblid = 'uzbl_link_hint'; -var uzbldivid = uzblid + '_div_container'; -var doc = document; -var win = window; -var links = document.links; -var forms = document.forms; - -//Reset keycmd, modcmd and return to default mode. -function clearKeycmd() { Uzbl.run('set mode ='); } - -try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ - type: 'click' - }); - } - }; -} catch(e) {} -function keyPressHandler(e) { - var kC = window.event ? event.keyCode: e.keyCode; - var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; - if (kC == Esc) { - removeAllHints(); - } -} -function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; - var height = el.offsetHeight; - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return [up, left, width, height]; -} -function isVisible(el) { - if (el == doc) { - return true; - } - if (!el) { - return false; - } - if (!el.parentNode) { - return false; - } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); -} -function elementInViewport(el) { - offset = elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; - var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; -} -function removeAllHints() { - var elements = doc.getElementById(uzbldivid); - if (elements) { - elements.parentNode.removeChild(elements); - } -} -function generateHint(el, label) { - var pos = elementPosition(el); - var hint = doc.createElement('div'); - hint.setAttribute('name', uzblid); - hint.innerText = label; - hint.style.display = 'inline'; - hint.style.backgroundColor = '#B9FF00'; - hint.style.border = '2px solid #4A6600'; - hint.style.color = 'black'; - hint.style.zIndex = '1000'; - hint.style.fontSize = '9px'; - hint.style.fontWeight = 'bold'; - hint.style.lineHeight = '9px'; - hint.style.margin = '0px'; - hint.style.padding = '1px'; - hint.style.position = 'absolute'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; - var img = el.getElementsByTagName('img'); - if (img.length > 0) { - hint.style.left = pos[1] + img[0].width / 2 + 'px'; - } - hint.style.textDecoration = 'none'; - hint.style.webkitBorderRadius = '6px'; - hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; - return hint; -} - -function clickElem(item) { - removeAllHints(); - clearKeycmd(); - if (item) { - var name = item.tagName; - if (name == 'A') { - item.click(); - window.location = item.href; - } else if (name == 'INPUT') { - var type = item.getAttribute('type').toUpperCase(); - if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { - item.focus(); - item.select(); - } else { - item.click(); - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { - item.focus(); - item.select(); - } else { - item.click(); - window.location = item.href; - } - } -} - -function addLinks() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (isVisible(li) && elementInViewport(li)) { - res[0].push(li); - res[1].push(li.innerText.toLowerCase()); - } - } - return res; -} -function addFormElems() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { - res[0].push(el); - if (el.getAttribute('value')) { - res[1].push(el.getAttribute('value').toLowerCase()); - } else { - res[1].push(el.getAttribute('name').toLowerCase()); - } - } - } - } - return res; -} -function reDrawHints(elems, len) { - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - hintdiv.style.opacity = '0.0'; - for (var i = 0; i < elems[0].length; i++) { - var label = i + ''; - var n = label.length; - for (n; n < len; n++) { - label = '0' + label; - } - if (elems[0][i]) { - var h = generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) { - document.body.appendChild(hintdiv); - hintdiv.style.opacity = '0.7' - } -} -function followLinks(follow) { - var s = follow.split(''); - var linknr = parseInt(follow, 10); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = addLinks(); - var formelems = addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = (elems[0].length + '').length; - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { - clickElem(elems[0][linknr]); - } else { - for (var j = 0; j < elems[0].length; j++) { - var b = true; - for (var k = 0; k < s.length; k++) { - b = b && elems[1][j].charAt(k) == s[k]; - } - if (!b) { - elems[0][j] = null; - elems[1][j] = null; - } else { - leftover[0].push(elems[0][j]); - leftover[1].push(elems[1][j]); - } - } - if (leftover[0].length == 1) { - clickElem(leftover[0][0]); - } else if (!oldDiv) { - if (linknr + 1 || s.length == 0) { - reDrawHints(elems, len); - } else { - reDrawHints(leftover, len); - } - } - } -} -followLinks('%s'); diff --git a/examples/data/uzbl/scripts/formfiller.pl b/examples/data/uzbl/scripts/formfiller.pl deleted file mode 100755 index 23da347..0000000 --- a/examples/data/uzbl/scripts/formfiller.pl +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/perl - -# a slightly more advanced form filler -# -# uses settings file like: $keydir/ -#TODO: fallback to $HOME/.local/share -# user arg 1: -# edit: force editing of the file (fetches if file is missing) -# load: fill forms from file (fetches if file is missing) -# new: fetch new file - -# usage example: -# bind LL = spawn /usr/share/uzbl/examples/scripts/formfiller.pl load -# bind LN = spawn /usr/share/uzbl/examples/scripts/formfiller.pl new -# bind LE = spawn /usr/share/uzbl/examples/scripts/formfiller.pl edit - -use strict; -use warnings; - -my $keydir = $ENV{XDG_CONFIG_HOME} . "/uzbl/forms"; -my ($config,$pid,$xid,$fifoname,$socket,$url,$title,$cmd) = @ARGV; -if (!defined $fifoname || $fifoname eq "") { die "No fifo"; } - -sub domain { - my ($url) = @_; - $url =~ s#http(s)?://([A-Za-z0-9\.-]+)(/.*)?#$2#; - return $url; -}; - -my $editor = "xterm -e vim"; -#my $editor = "gvim"; - -# ideally, there would be some way to ask uzbl for the html content instead of having to redownload it with -# Also, you may need to fake the user-agent on some sites (like facebook) - my $downloader = "curl -A 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.10) Gecko/2009042810 GranParadiso/3.0.10' "; -#my $downloader = "curl -s"; - -my @fields = ("type","name","value"); - -my %command; - -$command{load} = sub { - my ($domain) = @_; - my $filename = "$keydir/$domain"; - if (-e $filename){ - open(my $file, $filename) or die "Failed to open $filename: $!"; - my (@lines) = <$file>; - close($file); - $|++; - open(my $fifo, ">>", $fifoname) or die "Failed to open $fifoname: $!"; - foreach my $line (@lines) { - next if ($line =~ m/^#/); - my ($type,$name,$value) = ($line =~ /^\s*(\w+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*$/); - if ($type eq "checkbox") - { - printf $fifo 'js document.getElementsByName("%s")[0].checked = %s;', $name, $value; - } elsif ($type eq "submit") - { - printf $fifo 'js function fs (n) {try{n.submit()} catch (e){fs(n.parentNode)}}; fs(document.getElementsByName("%s")[0]);', $name; - } elsif ($type ne "") - { - printf $fifo 'js document.getElementsByName("%s")[0].value = "%s";', $name, $value; - } - print $fifo "\n"; - } - $|--; - } else { - $command{new}->($domain); - $command{edit}->($domain); - } -}; -$command{edit} = sub { - my ($domain) = @_; - my $file = "$keydir/$domain"; - if(-e $file){ - system ($editor, $file); - } else { - $command{new}->($domain); - } -}; -$command{new} = sub { - my ($domain) = @_; - my $filename = "$keydir/$domain"; - open (my $file,">>", $filename) or die "Failed to open $filename: $!"; - $|++; - print $file "# Make sure that there are no extra submits, since it may trigger the wrong one.\n"; - printf $file "#%-10s | %-10s | %s\n", @fields; - print $file "#------------------------------\n"; - my @data = `$downloader $url`; - foreach my $line (@data){ - if($line =~ m/].*?)>/i){ - $line =~ s/.*(].*?)>).*/$1/; - printf $file " %-10s | %-10s | %s\n", map { my ($r) = $line =~ /.*$_=["'](.*?)["']/;$r } @fields; - }; - }; - $|--; -}; - -$command{$cmd}->(domain($url)); diff --git a/examples/data/uzbl/scripts/formfiller.sh b/examples/data/uzbl/scripts/formfiller.sh deleted file mode 100755 index 10afaba..0000000 --- a/examples/data/uzbl/scripts/formfiller.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -# simple html form (eg for logins) filler (and manager) for uzbl. -# uses settings files like: $keydir/ -# files contain lines like: : - - -# user arg 1: -# edit: force editing the file (falls back to new if not found) -# new: start with a new file. -# load: try to load from file into form - -# something else (or empty): if file not available: new, otherwise load. - -keydir=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/forms -[ -d "`dirname $keydir`" ] || exit 1 -[ -d "$keydir" ] || mkdir "$keydir" - -editor=${VISUAL} -if [[ -z ${editor} ]]; then - #editor='gvim' - editor='urxvt -e vim' -fi - -config=$1; shift -pid=$1; shift -xid=$1; shift -fifo=$1; shift -socket=$1; shift -url=$1; shift -title=$1; shift -action=$1 - -[ -d $keydir ] || mkdir $keydir || exit 1 - -if [ "$action" != 'edit' -a "$action" != 'new' -a "$action" != 'load' ] -then - action=new - [[ -e $keydir/$domain ]] && action=load -elif [ "$action" == 'edit' ] && [[ ! -e $keydir/$domain ]] -then - action=new -fi -domain=$(echo $url | sed -re 's|(http\|https)+://([A-Za-z0-9\.]+)/.*|\2|') - - -#regex='s|.*.*|\1: |p' # sscj's first version, does not work on http://wiki.archlinux.org/index.php?title=Special:UserLogin&returnto=Main_Page - regex='s|.*> $fifo -else - if [ "$action" == 'new' ] - then - curl "$url" | grep ' $keydir/$domain - fi - [[ -e $keydir/$domain ]] || exit 3 #this should never happen, but you never know. - $editor $keydir/$domain #TODO: if user aborts save in editor, the file is already overwritten -fi diff --git a/examples/data/uzbl/scripts/hint.js b/examples/data/uzbl/scripts/hint.js deleted file mode 100644 index ec7f1e2..0000000 --- a/examples/data/uzbl/scripts/hint.js +++ /dev/null @@ -1,26 +0,0 @@ -for (var i=0; i < document.links.length; i++) { - var uzblid = 'uzbl_link_hint_'; - var li = document.links[i]; - var pre = document.getElementById(uzblid+i); - - if (pre) { - li.removeChild(pre); - } else { - var hint = document.createElement('div'); - hint.setAttribute('id',uzblid+i); - hint.innerHTML = i; - hint.style.display='inline'; - hint.style.lineHeight='90%'; - hint.style.backgroundColor='red'; - hint.style.color='white'; - hint.style.fontSize='small-xx'; - hint.style.fontWeight='light'; - hint.style.margin='0px'; - hint.style.padding='2px'; - hint.style.position='absolute'; - hint.style.textDecoration='none'; - hint.style.left=li.style.left; - hint.style.top=li.style.top; - li.insertAdjacentElement('afterBegin',hint); - } -} diff --git a/examples/data/uzbl/scripts/history.sh b/examples/data/uzbl/scripts/history.sh deleted file mode 100755 index 7c83aa6..0000000 --- a/examples/data/uzbl/scripts/history.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history -[ -d `dirname $file` ] || exit 1 -echo `date +'%Y-%m-%d %H:%M:%S'`" $6 $7" >> $file diff --git a/examples/data/uzbl/scripts/insert_bookmark.sh b/examples/data/uzbl/scripts/insert_bookmark.sh deleted file mode 100755 index c34e7db..0000000 --- a/examples/data/uzbl/scripts/insert_bookmark.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -[ -d "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl" ] || exit 1 -file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/bookmarks - -which zenity &>/dev/null || exit 2 - -entry=`zenity --entry --text="Add bookmark. add tags after the '\t', separated by spaces" --entry-text="$6 $7\t"` -exitstatus=$? -if [ $exitstatus -ne 0 ]; then exit $exitstatus; fi -url=`echo $entry | awk '{print $1}'` - -# TODO: check if already exists, if so, and tags are different: ask if you want to replace tags -echo "$entry" >/dev/null #for some reason we need this.. don't ask me why -echo -e "$entry" >> $file -true diff --git a/examples/data/uzbl/scripts/instance-select-wmii.sh b/examples/data/uzbl/scripts/instance-select-wmii.sh deleted file mode 100755 index 2bf13ba..0000000 --- a/examples/data/uzbl/scripts/instance-select-wmii.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh - - -# This script allows you to focus another uzbl window -# It considers all uzbl windows in the current tag -# you can select one from a list, or go to the next/previous one -# It does not change the layout (stacked/tiled/floating) nor does it -# changes the size or viewing mode of a uzbl window -# When your current uzbl window is maximized, the one you change to -# will be maximized as well. -# See http://www.uzbl.org/wiki/wmii for more info -# $1 must be one of 'list', 'next', 'prev' - -COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" - -if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch -else - DMENU="dmenu -i" -fi - -if [ "$1" == 'list' ] -then - list= - # get window id's of uzbl clients. we could also get the label in one shot but it's pretty tricky - for i in $(wmiir read /tag/sel/index | grep uzbl |cut -d ' ' -f2) - do - label=$(wmiir read /client/$i/label) - list="$list$i : $label\n" - done - window=$(echo -e "$list" | $DMENU $COLORS | cut -d ' ' -f1) - wmiir xwrite /tag/sel/ctl "select client $window" -elif [ "$1" == 'next' ] -then - current=$(wmiir read /client/sel/ctl | head -n 1) - # find the next uzbl window and focus it - next=$(wmiir read /tag/sel/index | grep -A 10000 " $current " | grep -m 1 uzbl | cut -d ' ' -f2) - if [ x"$next" != "x" ] - then - wmiir xwrite /tag/sel/ctl "select client $next" - fi -elif [ "$1" == 'prev' ] -then - current=$(wmiir read /client/sel/ctl | head -n 1) - prev=$(wmiir read /tag/sel/index | grep -B 10000 " $current " | tac | grep -m 1 uzbl | cut -d ' ' -f2) - if [ x"$prev" != "x" ] - then - wmiir xwrite /tag/sel/ctl "select client $prev" - fi -else - echo "\$1 not valid" >&2 - exit 2 -fi diff --git a/examples/data/uzbl/scripts/linkfollow.js b/examples/data/uzbl/scripts/linkfollow.js deleted file mode 100644 index 0eb629b..0000000 --- a/examples/data/uzbl/scripts/linkfollow.js +++ /dev/null @@ -1,269 +0,0 @@ -// link follower for uzbl -// requires http://github.com/DuClare/uzbl/commit/6c11777067bdb8aac09bba78d54caea04f85e059 -// -// first, it needs to be loaded before every time it is used. -// One way would be to use the load_commit_handler: -// set load_commit_handler = sh 'echo "script /usr/share/uzbl/examples/scripts/linkfollow.js" > "$4"' -// -// when script is loaded, it can be invoked with -// bind f* = js hints.set("%s", hints.open) -// bind f_ = js hints.follow("%s",hints.open) -// -// At the moment, it may be useful to have way of forcing uzbl to load the script -// bind :lf = script /usr/share/uzbl/examples/scripts/linkfollow.js -// -// The default style for the hints are pretty ugly, so it is recommended to add the following -// to config file -// set stylesheet_uri = /usr/share/uzbl/examples/data/style.css -// -// based on follow_Numbers.js -// -// TODO: fix styling for the first element -// TODO: emulate mouseover events when visiting some elements -// TODO: rewrite the element->action handling - - -function Hints(){ - - // Settings - //////////////////////////////////////////////////////////////////////////// - - // if set to true, you must explicitly call hints.follow(), otherwise it will - // follow the link if there is only one matching result - var requireReturn = true; - - // Case sensitivity flag - var matchCase = "i"; - - // For case sensitive matching, uncomment: - // var matchCase = ""; - - - var uzblid = 'uzbl_hint'; - var uzblclass = 'uzbl_highlight'; - var uzblclassfirst = 'uzbl_h_first'; - var doc = document; - var visible = []; - var hintdiv; - - this.set = hint; - this.follow = follow; - this.keyPressHandler = keyPressHandler; - - function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; var width = el.offsetWidth; - var height = el.offsetHeight; - - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return {up: up, left: left, width: width, height: height}; - } - - function elementInViewport(p) { - return (p.up < window.pageYOffset + window.innerHeight && - p.left < window.pageXOffset + window.innerWidth && - (p.up + p.height) > window.pageYOffset && - (p.left + p.width) > window.pageXOffset); - } - - function isVisible(el) { - if (el == doc) { return true; } - if (!el) { return false; } - if (!el.parentNode) { return false; } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); - } - - // the vimperator defaults minus the xhtml elements, since it gave DOM errors - var hintable = " //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select"; - - function Matcher(str){ - var numbers = str.replace(/[^\d]/g,""); - var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,matchCase)}); - this.test = test; - this.toString = toString; - this.numbers = numbers; - function matchAgainst(element){ - if(element.node.nodeName == "INPUT"){ - return element.node.value; - } else { - return element.node.textContent; - } - } - function test(element) { - // test all the regexp - var item = matchAgainst(element); - return words.every(function (regex) { return item.match(regex)}); - } - } - - function HintElement(node,pos){ - - this.node = node; - this.isHinted = false; - this.position = pos; - this.num = 0; - - this.addHint = function (labelNum) { - // TODO: fix uzblclassfirst - if(!this.isHinted){ - this.node.className += " " + uzblclass; - } - this.isHinted = true; - - // create hint - var hintNode = doc.createElement('div'); - hintNode.name = uzblid; - hintNode.innerText = labelNum; - hintNode.style.left = this.position.left + 'px'; - hintNode.style.top = this.position.up + 'px'; - hintNode.style.position = "absolute"; - doc.body.firstChild.appendChild(hintNode); - - } - this.removeHint = function(){ - if(this.isHinted){ - var s = (this.num)?uzblclassfirst:uzblclass; - this.node.className = this.node.className.replace(new RegExp(" "+s,"g"),""); - this.isHinted = false; - } - } - } - - function createHintDiv(){ - var hintdiv = doc.getElementById(uzblid); - if(hintdiv){ - hintdiv.parentNode.removeChild(hintdiv); - } - hintdiv = doc.createElement("div"); - hintdiv.setAttribute('id',uzblid); - doc.body.insertBefore(hintdiv,doc.body.firstChild); - return hintdiv; - } - - function init(){ - // WHAT? - doc.body.setAttribute("onkeyup","hints.keyPressHandler(event)"); - hintdiv = createHintDiv(); - visible = []; - - var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null); - for (var i = 0;i&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch - # show tags as well - goto=`$DMENU $COLORS < $file | awk '{print $1}'` -else - DMENU="dmenu -i" - # because they are all after each other, just show the url, not their tags. - goto=`awk '{print $1}' $file | $DMENU $COLORS` -fi - -#[ -n "$goto" ] && echo "uri $goto" > $4 -[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/uzbl/scripts/load_url_from_history.sh b/examples/data/uzbl/scripts/load_url_from_history.sh deleted file mode 100755 index 62e02ac..0000000 --- a/examples/data/uzbl/scripts/load_url_from_history.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -history_file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history -[ -r "$history_file" ] || exit 1 - -# choose from all entries, sorted and uniqued -# goto=`awk '{print $3}' $history_file | sort -u | dmenu -i` -COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" -if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]'; -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch - # choose an item in reverse order, showing also the date and page titles - # pick the last field from the first 3 fields. this way you can pick a url (prefixed with date & time) or type just a new url. - goto=`tac $history_file | $DMENU $COLORS | cut -d ' ' -f -3 | awk '{print $NF}'` -else - DMENU="dmenu -i" - # choose from all entries (no date or title), the first one being current url, and after that all others, sorted and uniqued, in ascending order - current=`tail -n 1 $history_file | awk '{print $3}'`; - goto=`(echo $current; awk '{print $3}' $history_file | grep -v "^$current\$" \ - | sort -u) | $DMENU $COLORS` -fi - -[ -n "$goto" ] && echo "uri $goto" > $4 -#[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/uzbl/scripts/scheme.py b/examples/data/uzbl/scripts/scheme.py deleted file mode 100755 index 0916466..0000000 --- a/examples/data/uzbl/scripts/scheme.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -import os, subprocess, sys, urlparse - -def detach_open(cmd): - # Thanks to the vast knowledge of Laurence Withers (lwithers) and this message: - # http://mail.python.org/pipermail/python-list/2006-November/587523.html - if not os.fork(): - null = os.open(os.devnull,os.O_WRONLY) - for i in range(3): os.dup2(null,i) - os.close(null) - subprocess.Popen(cmd) - print 'USED' - -if __name__ == '__main__': - uri = sys.argv[8] - u = urlparse.urlparse(uri) - if u.scheme == 'mailto': - detach_open(['xterm', '-e', 'mail', u.path]) - elif u.scheme == 'xmpp': - # Someone check for safe arguments to gajim-remote - detach_open(['gajim-remote', 'open_chat', uri]) - elif u.scheme == 'git': - detach_open(['git', 'clone', '--', uri], cwd=os.path.expanduser('~/src')) diff --git a/examples/data/uzbl/scripts/scroll-percentage.js b/examples/data/uzbl/scripts/scroll-percentage.js deleted file mode 100644 index c9a51aa..0000000 --- a/examples/data/uzbl/scripts/scroll-percentage.js +++ /dev/null @@ -1,68 +0,0 @@ -// VIM ruler style scroll message -(function() { - var run = Uzbl.run; - var update_message = function() { - var innerHeight = window.innerHeight; - var scrollY = window.scrollY; - var height = document.height; - var message; - - if (UzblZoom.type === "full") { - var zoom_level = UzblZoom.level; - innerHeight = Math.ceil(innerHeight * zoom_level); - scrollY = Math.ceil(scrollY * zoom_level); - height -= 1; - } - - if (! height) { - message = ""; - } - else if (height <= innerHeight) { - message = run("print @scroll_all_indicator") || "All"; - } - else if (scrollY === 0) { - message = run("print @scroll_top_indicator") || "Top"; - } - else if (scrollY + innerHeight >= height) { - message = run("print @scroll_bottom_indicator") || "Bot"; - } - else { - var percentage = Math.round(scrollY / (height - innerHeight) * 100); - message = percentage + "%"; - } - run("set scroll_message=" + message); - }; - - self.UzblZoom = { - get level() { - return Number(run("print @zoom_level")) || 1; - }, - set level(level) { - if (typeof level === "number" && level > 0) { - run("set zoom_level = " + level); - update_message(); - } - }, - get type() { - return run("print @zoom_type") || "text"; - }, - set type(type) { - if ((type === "text" || type === "full") && this.type != type) { - run("toggle_zoom_type"); - run("set zoom_type = " + type); - update_message(); - } - }, - toggle_type: function() { - this.type = (this.type === "text" ? "full" : "text"); - } - }; - - window.addEventListener("DOMContentLoaded", update_message, false); - window.addEventListener("load", update_message, false); - window.addEventListener("resize", update_message, false); - window.addEventListener("scroll", update_message, false); - update_message(); -})(); - -// vim: set noet ff=unix diff --git a/examples/data/uzbl/scripts/session.sh b/examples/data/uzbl/scripts/session.sh deleted file mode 100755 index 1059b5e..0000000 --- a/examples/data/uzbl/scripts/session.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh - -# Very simple session manager for uzbl-browser. When called with "endsession" as the -# argument, it'll backup $sessionfile, look for fifos in $fifodir and -# instruct each of them to store their current url in $sessionfile and -# terminate themselves. Run with "launch" as the argument and an instance of -# uzbl-browser will be launched for each stored url. "endinstance" is used internally -# and doesn't need to be called manually at any point. -# Add a line like 'bind quit = /path/to/session.sh endsession' to your config - -[ -d ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl ] || exit 1 -scriptfile=$0 # this script -sessionfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/browser-session # the file in which the "session" (i.e. urls) are stored -configfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/config # uzbl configuration file -UZBL="uzbl-browser -c $configfile" # add custom flags and whatever here. - -fifodir=/tmp # remember to change this if you instructed uzbl to put its fifos elsewhere -thisfifo="$4" -act="$8" -url="$6" - -if [ "$act." = "." ]; then - act="$1" -fi - - -case $act in - "launch" ) - urls=`cat $sessionfile` - if [ "$urls." = "." ]; then - $UZBL - else - for url in $urls; do - $UZBL --uri "$url" & - done - fi - exit 0 - ;; - - "endinstance" ) - if [ "$url" != "(null)" ]; then - echo "$url" >> $sessionfile; - fi - echo "exit" > "$thisfifo" - ;; - - "endsession" ) - mv "$sessionfile" "$sessionfile~" - for fifo in $fifodir/uzbl_fifo_*; do - if [ "$fifo" != "$thisfifo" ]; then - echo "spawn $scriptfile endinstance" > "$fifo" - fi - done - echo "spawn $scriptfile endinstance" > "$thisfifo" - ;; - - * ) echo "session manager: bad action" - echo "Usage: $scriptfile [COMMAND] where commands are:" - echo " launch - Restore a saved session or start a new one" - echo " endsession - Quit the running session. Must be called from uzbl" - ;; -esac diff --git a/examples/data/uzbl/scripts/uzbl-cookie-daemon b/examples/data/uzbl/scripts/uzbl-cookie-daemon deleted file mode 100755 index fde8b8e..0000000 --- a/examples/data/uzbl/scripts/uzbl-cookie-daemon +++ /dev/null @@ -1,664 +0,0 @@ -#!/usr/bin/env python - -# The Python Cookie Daemon for Uzbl. -# Copyright (c) 2009, Tom Adams -# Copyright (c) 2009, Dieter Plaetinck -# Copyright (c) 2009, Mason Larobina -# Copyright (c) 2009, Michael Fiano -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -''' -The Python Cookie Daemon -======================== - -This daemon is a re-write of the original cookies.py script found in uzbl's -master branch. This script provides more functionality than the original -cookies.py by adding numerous command line options to specify different cookie -jar locations, socket locations, verbose output, etc. This functionality is -very useful as it allows you to run multiple daemons at once serving cookies -to different groups of uzbl instances as required. - -Keeping up to date -================== - -Check the cookie daemon uzbl-wiki page for more information on where to -find the latest version of the cookie_daemon.py - - http://www.uzbl.org/wiki/cookie_daemon.py - -Command line options -==================== - -Use the following command to get a full list of the cookie_daemon.py command -line options: - - ./cookie_daemon.py --help - -Talking with uzbl -================= - -In order to get uzbl to talk to a running cookie daemon you add the following -to your uzbl config: - - set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket - -Or if you prefer using the $HOME variable: - - set cookie_handler = talk_to_socket $HOME/.cache/uzbl/cookie_daemon_socket - -Todo list -========= - - - Use a pid file to make force killing a running daemon possible. - -Reporting bugs / getting help -============================= - -The best way to report bugs and or get help with the cookie daemon is to -contact the maintainers it the #uzbl irc channel found on the Freenode IRC -network (irc.freenode.org). -''' - -import cookielib -import os -import sys -import urllib2 -import select -import socket -import time -import atexit -from traceback import print_exc -from signal import signal, SIGTERM -from optparse import OptionParser -from os.path import join - -try: - import cStringIO as StringIO - -except ImportError: - import StringIO - - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return join(os.environ['HOME'], default) - -# Setup xdg paths. -CACHE_DIR = join(xdghome('CACHE', '.cache/'), 'uzbl/') -DATA_DIR = join(xdghome('DATA', '.local/share/'), 'uzbl/') -CONFIG_DIR = join(xdghome('CONFIG', '.config/'), 'uzbl/') - -# Ensure data paths exist. -for path in [CACHE_DIR, DATA_DIR, CONFIG_DIR]: - if not os.path.exists(path): - os.makedirs(path) - -# Default config -config = { - - # Default cookie jar, whitelist, and daemon socket locations. - 'cookie_jar': join(DATA_DIR, 'cookies.txt'), - 'cookie_whitelist': join(CONFIG_DIR, 'cookie_whitelist'), - 'cookie_socket': join(CACHE_DIR, 'cookie_daemon_socket'), - - # Don't use a cookie whitelist policy by default. - 'use_whitelist': False, - - # Time out after x seconds of inactivity (set to 0 for never time out). - # WARNING: Do not use this option if you are manually launching the daemon. - 'daemon_timeout': 0, - - # Daemonise by default. - 'daemon_mode': True, - - # Optionally print helpful debugging messages to the terminal. - 'verbose': False, - -} # End of config dictionary. - - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - - -_SCRIPTNAME = os.path.basename(sys.argv[0]) -def echo(msg): - '''Prints only if the verbose flag has been set.''' - - if config['verbose']: - sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) - - -def error(msg): - '''Prints error message and exits.''' - - sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) - sys.exit(1) - - -def mkbasedir(filepath): - '''Create the base directories of the file in the file-path if the dirs - don't exist.''' - - dirname = os.path.dirname(filepath) - if not os.path.exists(dirname): - echo("creating dirs: %r" % dirname) - os.makedirs(dirname) - - -def daemon_running(cookie_socket): - '''Check if another process (hopefully a cookie_daemon.py) is listening - on the cookie daemon socket. If another process is found to be - listening on the socket exit the daemon immediately and leave the - socket alone. If the connect fails assume the socket has been abandoned - and delete it (to be re-created in the create socket function).''' - - if not os.path.exists(cookie_socket): - return False - - if os.path.isfile(cookie_socket): - raise Exception("regular file at %r is not a socket" % cookie_socket) - - - if os.path.isdir(cookie_socket): - raise Exception("directory at %r is not a socket" % cookie_socket) - - try: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - sock.connect(cookie_socket) - sock.close() - echo("detected daemon listening on %r" % cookie_socket) - return True - - except socket.error: - # Failed to connect to cookie_socket so assume it has been - # abandoned by another cookie daemon process. - if os.path.exists(cookie_socket): - echo("deleting abandoned socket at %r" % cookie_socket) - os.remove(cookie_socket) - - return False - - -def send_command(cookie_socket, cmd): - '''Send a command to a running cookie daemon.''' - - if not daemon_running(cookie_socket): - return False - - try: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - sock.connect(cookie_socket) - sock.send(cmd) - sock.close() - echo("sent command %r to %r" % (cmd, cookie_socket)) - return True - - except socket.error: - print_exc() - error("failed to send message %r to %r" % (cmd, cookie_socket)) - return False - - -def kill_daemon(cookie_socket): - '''Send the "EXIT" command to running cookie_daemon.''' - - if send_command(cookie_socket, "EXIT"): - # Now ensure the cookie_socket is cleaned up. - start = time.time() - while os.path.exists(cookie_socket): - time.sleep(0.1) - if (time.time() - start) > 5: - error("force deleting socket %r" % cookie_socket) - os.remove(cookie_socket) - return - - echo("stopped daemon listening on %r"% cookie_socket) - - else: - if os.path.exists(cookie_socket): - os.remove(cookie_socket) - echo("removed abandoned/broken socket %r" % cookie_socket) - - -def daemonize(): - '''Daemonize the process using the Stevens' double-fork magic.''' - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #1 failed") - sys.exit(1) - - os.chdir('/') - os.setsid() - os.umask(0) - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #2 failed") - sys.exit(1) - - sys.stdout.flush() - sys.stderr.flush() - - devnull = '/dev/null' - stdin = file(devnull, 'r') - stdout = file(devnull, 'a+') - stderr = file(devnull, 'a+', 0) - - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - -class CookieMonster: - '''The uzbl cookie daemon class.''' - - def __init__(self): - '''Initialise class variables.''' - - self.server_socket = None - self.jar = None - self.last_request = time.time() - self._running = False - - - def run(self): - '''Start the daemon.''' - - # The check healthy function will exit if another daemon is detected - # listening on the cookie socket and remove the abandoned socket if - # there isnt. - if os.path.exists(config['cookie_socket']): - if daemon_running(config['cookie_socket']): - sys.exit(1) - - # Create cookie daemon socket. - self.create_socket() - - # Daemonize process. - if config['daemon_mode']: - echo("entering daemon mode") - daemonize() - - # Register a function to cleanup on exit. - atexit.register(self.quit) - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) - - # Create cookie jar object from file. - self.open_cookie_jar() - - # Create a way to exit nested loops by setting a running flag. - self._running = True - - while self._running: - try: - # Enter main listen loop. - self.listen() - - except KeyboardInterrupt: - self._running = False - print - - except socket.error: - print_exc() - - except: - # Clean up - self.del_socket() - - # Raise exception - raise - - # Always delete the socket before calling create again. - self.del_socket() - # Create cookie daemon socket. - self.create_socket() - - - def load_whitelist(self): - '''Load the cookie jar whitelist policy.''' - - cookie_whitelist = config['cookie_whitelist'] - - if cookie_whitelist: - mkbasedir(cookie_whitelist) - - # Create cookie whitelist file if it does not exist. - if not os.path.exists(cookie_whitelist): - open(cookie_whitelist, 'w').close() - - # Read cookie whitelist file into list. - file = open(cookie_whitelist,'r') - domain_list = [line.rstrip('\n') for line in file] - file.close() - - # Define policy of allowed domains - policy = cookielib.DefaultCookiePolicy(allowed_domains=domain_list) - self.jar.set_policy(policy) - - # Save the last modified time of the whitelist. - self._whitelistmtime = os.stat(cookie_whitelist).st_mtime - - - def open_cookie_jar(self): - '''Open the cookie jar.''' - - cookie_jar = config['cookie_jar'] - cookie_whitelist = config['cookie_whitelist'] - - if cookie_jar: - mkbasedir(cookie_jar) - - # Create cookie jar object from file. - self.jar = cookielib.MozillaCookieJar(cookie_jar) - - # Load cookie whitelist policy. - if config['use_whitelist']: - self.load_whitelist() - - if cookie_jar: - try: - # Attempt to load cookies from the cookie jar. - self.jar.load(ignore_discard=True) - - # Ensure restrictive permissions are set on the cookie jar - # to prevent other users on the system from hi-jacking your - # authenticated sessions simply by copying your cookie jar. - os.chmod(cookie_jar, 0600) - - except: - pass - - - def reload_whitelist(self): - '''Reload the cookie whitelist.''' - - cookie_whitelist = config['cookie_whitelist'] - if os.path.exists(cookie_whitelist): - echo("reloading whitelist %r" % cookie_whitelist) - self.open_cookie_jar() - - - def create_socket(self): - '''Create AF_UNIX socket for communication with uzbl instances.''' - - cookie_socket = config['cookie_socket'] - mkbasedir(cookie_socket) - - self.server_socket = socket.socket(socket.AF_UNIX, - socket.SOCK_SEQPACKET) - - self.server_socket.bind(cookie_socket) - - # Set restrictive permissions on the cookie socket to prevent other - # users on the system from data-mining your cookies. - os.chmod(cookie_socket, 0600) - - - def listen(self): - '''Listen for incoming cookie PUT and GET requests.''' - - daemon_timeout = config['daemon_timeout'] - echo("listening on %r" % config['cookie_socket']) - - while self._running: - # This line tells the socket how many pending incoming connections - # to enqueue at once. Raising this number may or may not increase - # performance. - self.server_socket.listen(1) - - if bool(select.select([self.server_socket], [], [], 1)[0]): - client_socket, _ = self.server_socket.accept() - self.handle_request(client_socket) - self.last_request = time.time() - client_socket.close() - continue - - if daemon_timeout: - # Checks if the daemon has been idling for too long. - idle = time.time() - self.last_request - if idle > daemon_timeout: - self._running = False - - - def handle_request(self, client_socket): - '''Connection made, now to serve a cookie PUT or GET request.''' - - # Receive cookie request from client. - data = client_socket.recv(8192) - if not data: - return - - # Cookie argument list in packet is null separated. - argv = data.split("\0") - action = argv[0].upper().strip() - - # Catch the EXIT command sent to kill running daemons. - if action == "EXIT": - self._running = False - return - - # Catch whitelist RELOAD command. - elif action == "RELOAD": - self.reload_whitelist() - return - - # Return if command unknown. - elif action not in ['GET', 'PUT']: - error("unknown command %r." % argv) - return - - # Determine whether or not to print cookie data to terminal. - print_cookie = (config['verbose'] and not config['daemon_mode']) - if print_cookie: - print ' '.join(argv[:4]) - - uri = urllib2.urlparse.ParseResult( - scheme=argv[1], - netloc=argv[2], - path=argv[3], - params='', - query='', - fragment='').geturl() - - req = urllib2.Request(uri) - - if action == "GET": - self.jar.add_cookie_header(req) - if req.has_header('Cookie'): - cookie = req.get_header('Cookie') - client_socket.send(cookie) - if print_cookie: - print cookie - - else: - client_socket.send("\0") - - elif action == "PUT": - cookie = argv[4] if len(argv) > 3 else None - if print_cookie: - print cookie - - self.put_cookie(req, cookie) - - if print_cookie: - print - - - def put_cookie(self, req, cookie=None): - '''Put a cookie in the cookie jar.''' - - hdr = urllib2.httplib.HTTPMessage(\ - StringIO.StringIO('Set-Cookie: %s' % cookie)) - res = urllib2.addinfourl(StringIO.StringIO(), hdr, - req.get_full_url()) - self.jar.extract_cookies(res, req) - if config['cookie_jar']: - self.jar.save(ignore_discard=True) - - - def del_socket(self): - '''Remove the cookie_socket file on exit. In a way the cookie_socket - is the daemons pid file equivalent.''' - - if self.server_socket: - try: - self.server_socket.close() - - except: - pass - - self.server_socket = None - - cookie_socket = config['cookie_socket'] - if os.path.exists(cookie_socket): - echo("deleting socket %r" % cookie_socket) - os.remove(cookie_socket) - - - def quit(self): - '''Called on exit to make sure all loose ends are tied up.''' - - self.del_socket() - sys.exit(0) - - -def main(): - '''Main function.''' - - # Define command line parameters. - usage = "usage: %prog [options] {start|stop|restart|reload}" - parser = OptionParser(usage=usage) - parser.add_option('-n', '--no-daemon', dest='no_daemon', - action='store_true', help="don't daemonise the process.") - - parser.add_option('-v', '--verbose', dest="verbose", - action='store_true', help="print verbose output.") - - parser.add_option('-t', '--daemon-timeout', dest='daemon_timeout', - action="store", metavar="SECONDS", help="shutdown the daemon after x "\ - "seconds inactivity. WARNING: Do not use this when launching the "\ - "cookie daemon manually.") - - parser.add_option('-s', '--cookie-socket', dest="cookie_socket", - metavar="SOCKET", help="manually specify the socket location.") - - parser.add_option('-j', '--cookie-jar', dest='cookie_jar', - metavar="FILE", help="manually specify the cookie jar location.") - - parser.add_option('-m', '--memory', dest='memory', action='store_true', - help="store cookies in memory only - do not write to disk") - - parser.add_option('-u', '--use-whitelist', dest='usewhitelist', - action='store_true', help="use cookie whitelist policy") - - parser.add_option('-w', '--cookie-whitelist', dest='whitelist', - action='store', help="manually specify whitelist location", - metavar='FILE') - - # Parse the command line arguments. - (options, args) = parser.parse_args() - - expand = lambda p: os.path.realpath(os.path.expandvars(p)) - - initcommands = ['start', 'stop', 'restart', 'reload'] - for arg in args: - if arg not in initcommands: - error("unknown argument %r" % args[0]) - sys.exit(1) - - if len(args) > 1: - error("the daemon only accepts one {%s} action at a time." - % '|'.join(initcommands)) - sys.exit(1) - - if len(args): - action = args[0] - - else: - action = "start" - - if options.no_daemon: - config['daemon_mode'] = False - - if options.cookie_socket: - config['cookie_socket'] = expand(options.cookie_socket) - - if options.cookie_jar: - config['cookie_jar'] = expand(options.cookie_jar) - - if options.memory: - config['cookie_jar'] = None - - if options.whitelist: - config['cookie_whitelist'] = expand(options.whitelist) - - if options.whitelist or options.usewhitelist: - config['use_whitelist'] = True - - if options.daemon_timeout: - try: - config['daemon_timeout'] = int(options.daemon_timeout) - - except ValueError: - error("expected int argument for -t, --daemon-timeout") - - # Expand $VAR's in config keys that relate to paths. - for key in ['cookie_socket', 'cookie_jar', 'cookie_whitelist']: - if config[key]: - config[key] = os.path.expandvars(config[key]) - - if options.verbose: - config['verbose'] = True - import pprint - sys.stderr.write("%s\n" % pprint.pformat(config)) - - # It would be better if we didn't need to start this python process just - # to send a command to the socket, but unfortunately socat doesn't seem - # to support SEQPACKET. - if action == "reload": - send_command(config['cookie_socket'], "RELOAD") - - if action in ['stop', 'restart']: - kill_daemon(config['cookie_socket']) - - if action in ['start', 'restart']: - CookieMonster().run() - - -if __name__ == "__main__": - main() diff --git a/examples/data/uzbl/scripts/uzbl-event-manager b/examples/data/uzbl/scripts/uzbl-event-manager deleted file mode 100755 index b3fdb3c..0000000 --- a/examples/data/uzbl/scripts/uzbl-event-manager +++ /dev/null @@ -1,833 +0,0 @@ -#!/usr/bin/env python - -# Event Manager for Uzbl -# Copyright (c) 2009, Mason Larobina -# Copyright (c) 2009, Dieter Plaetinck -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -''' - -E V E N T _ M A N A G E R . P Y -=============================== - -Event manager for uzbl written in python. - -''' - -import imp -import os -import sys -import re -import socket -import pprint -import time -import atexit -from select import select -from signal import signal, SIGTERM -from optparse import OptionParser -from traceback import print_exc -from functools import partial - - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return os.path.join(os.environ['HOME'], default) - - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -# `make install` will put the correct value here for your system -PREFIX = '/usr/local/' - -# Setup xdg paths. -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') -CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') - -# Event manager config dictionary. This is not to be confused with the config -# dict that tracks variables in the uzbl instance. -CONFIG = { - 'verbose': False, - 'daemon_mode': True, - 'auto_close': False, - - 'plugins_load': [], - 'plugins_ignore': [], - - 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'), - os.path.join(PREFIX, 'share/uzbl/examples/data/uzbl/plugins/')], - - 'server_socket': os.path.join(CACHE_DIR, 'event_daemon'), - 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'), -} - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - - -# Define some globals. -SCRIPTNAME = os.path.basename(sys.argv[0]) -FINDSPACES = re.compile("\s+") - - -class ArgumentError(Exception): - pass - - -def echo(msg): - '''Prints only if the verbose flag has been set.''' - - if CONFIG['verbose']: - sys.stdout.write("%s: %s\n" % (SCRIPTNAME, msg)) - - -def error(msg): - '''Prints error messages to stderr.''' - - sys.stderr.write("%s: error: %s\n" % (SCRIPTNAME, msg)) - - -def counter(): - '''Generate unique object id's.''' - - i = 0 - while True: - i += 1 - yield i - - -def find_plugins(plugin_dirs): - '''Find all event manager plugins in the plugin dirs and return a - dictionary of {'plugin-name.py': '/full/path/to/plugin-name.py', ...}''' - - plugins = {} - - for plugin_dir in plugin_dirs: - plugin_dir = os.path.realpath(os.path.expandvars(plugin_dir)) - if not os.path.isdir(plugin_dir): - continue - - for filename in os.listdir(plugin_dir): - if not filename.lower().endswith('.py'): - continue - - path = os.path.join(plugin_dir, filename) - if not os.path.isfile(path): - continue - - if filename not in plugins: - plugins[filename] = plugin_dir - - return plugins - - -def load_plugins(plugin_dirs, load=None, ignore=None): - '''Load event manager plugins found in the plugin_dirs.''' - - load = [] if load is None else load - ignore = [] if ignore is None else ignore - - # Find the plugins in the plugin_dirs. - found = find_plugins(plugin_dirs) - - if load: - # Ignore anything not in the load list. - for plugin in found.keys(): - if plugin not in load: - del found[plugin] - - if ignore: - # Ignore anything in the ignore list. - for plugin in found.keys(): - if plugin in ignore: - del found[plugin] - - # Print plugin list to be loaded. - pprint.pprint(found) - - loaded = {} - # Load all found plugins into the loaded dict. - for (filename, plugin_dir) in found.items(): - name = filename[:-3] - info = imp.find_module(name, [plugin_dir]) - plugin = imp.load_module(name, *info) - loaded[(plugin_dir, filename)] = plugin - - return loaded - - -def daemonize(): - '''Daemonize the process using the Stevens' double-fork magic.''' - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #1 failed") - sys.exit(1) - - os.chdir('/') - os.setsid() - os.umask(0) - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #2 failed") - sys.exit(1) - - sys.stdout.flush() - sys.stderr.flush() - - devnull = '/dev/null' - stdin = file(devnull, 'r') - stdout = file(devnull, 'a+') - stderr = file(devnull, 'a+', 0) - - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - -def make_dirs(path): - '''Make all basedirs recursively as required.''' - - dirname = os.path.dirname(path) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - -def make_pid_file(pid_file): - '''Make pid file at given pid_file location.''' - - make_dirs(pid_file) - fileobj = open(pid_file, 'w') - fileobj.write('%d' % os.getpid()) - fileobj.close() - - -def del_pid_file(pid_file): - '''Delete pid file at given pid_file location.''' - - if os.path.isfile(pid_file): - os.remove(pid_file) - - -def get_pid(pid_file): - '''Read pid from pid_file.''' - - try: - fileobj = open(pid_file, 'r') - pid = int(fileobj.read()) - fileobj.close() - return pid - - except IOError, ValueError: - print_exc() - return None - - -def pid_running(pid): - '''Returns True if a process with the given pid is running.''' - - try: - os.kill(pid, 0) - - except OSError: - return False - - else: - return True - - -def term_process(pid): - '''Send a SIGTERM signal to the process with the given pid.''' - - if not pid_running(pid): - return False - - os.kill(pid, SIGTERM) - - start = time.time() - while True: - if not pid_running(pid): - return True - - if time.time() - start > 5: - raise OSError('failed to stop process with pid: %d' % pid) - - time.sleep(0.25) - - -def parse_msg(uzbl, msg): - '''Parse an incoming msg from a uzbl instance. All non-event messages - will be printed here and not be passed to the uzbl instance event - handler function.''' - - if not msg: - return - - cmd = FINDSPACES.split(msg, 3) - if not cmd or cmd[0] != 'EVENT': - # Not an event message. - print '---', msg - return - - while len(cmd) < 4: - cmd.append('') - - event, args = cmd[2], cmd[3] - if not event: - return - - try: - uzbl.event(event, args) - - except: - print_exc() - - -class EventHandler(object): - - nexthid = counter().next - - def __init__(self, event, handler, *args, **kargs): - if not callable(handler): - raise ArgumentError("EventHandler object requires a callable " - "object function for the handler argument not: %r" % handler) - - self.function = handler - self.args = args - self.kargs = kargs - self.event = event - self.hid = self.nexthid() - - - def __repr__(self): - args = ["event=%s" % self.event, "hid=%d" % self.hid, - "function=%r" % self.function] - - if self.args: - args.append("args=%r" % self.args) - - if self.kargs: - args.append("kargs=%r" % self.kargs) - - return "" % ', '.join(args) - - -class UzblInstance(object): - - # Give all plugins access to the main config dict. - config = CONFIG - - def __init__(self, parent, client_socket): - - # Internal variables. - self.exports = {} - self.handlers = {} - self.parent = parent - self.client_socket = client_socket - - self.depth = 0 - self.buffer = '' - self.pid = None - - # Call the init function in every plugin. The init function in each - # plugin is where that plugin connects functions to events and exports - # functions to the uzbl object. - for plugin in self.parent['plugins'].values(): - try: - plugin.init(self) - - except: - raise - - - def send(self, msg): - '''Send a command to the uzbl instance via the socket file.''' - - msg = msg.strip() - if self.client_socket: - print '%s<-- %s' % (' ' * self.depth, msg) - self.client_socket.send(("%s\n" % msg).encode('utf-8')) - - else: - print '%s!-- %s' % (' ' * self.depth, msg) - - - def export(self, name, function): - '''Export `function(uzbl, *args, ..)` inside a plugin to the uzbl - object like so `uzbl.function(*args, ..)`. This will allow other - plugins to call functions inside the current plugin (which is currently - calling this function) via the uzbl object.''' - - self.__dict__.__setitem__(name, partial(function, self)) - - - def export_dict(self, export_dict): - '''Export multiple (name, function)'s at once inside a dict of the - form `{name1: function1, name2: function2, ...}`.''' - - for (name, function) in export_dict.items(): - self.export(name, function) - - - def connect(self, event, handler, *args, **kargs): - '''Connect a uzbl event with a handler. Handlers can either be a - function or a uzbl command string.''' - - event = event.upper().strip() - assert event and ' ' not in event - - if event not in self.handlers.keys(): - self.handlers[event] = [] - - handlerobj = EventHandler(event, handler, *args, **kargs) - self.handlers[event].append(handlerobj) - print handlerobj - - - def connect_dict(self, connect_dict): - '''Connect a dictionary comprising of {"EVENT_NAME": handler, ..} to - the event handler stack. - - If you need to supply args or kargs to an event use the normal connect - function.''' - - for (event, handler) in connect_dict.items(): - self.connect(event, handler) - - - def remove_by_id(self, hid): - '''Remove connected event handler by unique handler id.''' - - for (event, handlers) in self.handlers.items(): - for handler in list(handlers): - if hid != handler.hid: - continue - - echo("removed %r" % handler) - handlers.remove(handler) - return - - echo('unable to find & remove handler with id: %d' % hid) - - - def remove(self, handler): - '''Remove connected event handler.''' - - for (event, handlers) in self.handlers.items(): - if handler in handlers: - echo("removed %r" % handler) - handlers.remove(handler) - return - - echo('unable to find & remove handler: %r' % handler) - - - def exec_handler(self, handler, *args, **kargs): - '''Execute event handler function.''' - - args += handler.args - kargs = dict(handler.kargs.items()+kargs.items()) - handler.function(self, *args, **kargs) - - - def event(self, event, *args, **kargs): - '''Raise an event.''' - - event = event.upper() - elems = [event,] - if args: elems.append(unicode(args)) - if kargs: elems.append(unicode(kargs)) - print "%s--> %s" % (' ' * self.depth, ' '.join(elems)) - - if event == "INSTANCE_START" and args: - self.pid = int(args[0]) - - if event not in self.handlers: - return - - for handler in self.handlers[event]: - self.depth += 1 - try: - self.exec_handler(handler, *args, **kargs) - - except: - print_exc() - - self.depth -= 1 - - - def close(self): - '''Close the client socket and clean up.''' - - try: - self.client_socket.close() - - except: - pass - - for (name, plugin) in self.parent['plugins'].items(): - if hasattr(plugin, 'cleanup'): - plugin.cleanup(self) - - -class UzblEventDaemon(dict): - def __init__(self): - - # Init variables and dict keys. - dict.__init__(self, {'uzbls': {}}) - self.running = None - self.server_socket = None - self.socket_location = None - - # Register that the event daemon server has started by creating the - # pid file. - make_pid_file(CONFIG['pid_file']) - - # Register a function to clean up the socket and pid file on exit. - atexit.register(self.quit) - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) - - # Load plugins, first-build of the plugins may be a costly operation. - self['plugins'] = load_plugins(CONFIG['plugin_dirs'], - CONFIG['plugins_load'], CONFIG['plugins_ignore']) - - - def _create_server_socket(self): - '''Create the event manager daemon socket for uzbl instance duplex - communication.''' - - server_socket = CONFIG['server_socket'] - server_socket = os.path.realpath(os.path.expandvars(server_socket)) - self.socket_location = server_socket - - # Delete socket if it exists. - if os.path.exists(server_socket): - os.remove(server_socket) - - self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.server_socket.bind(server_socket) - self.server_socket.listen(5) - - - def _close_server_socket(self): - '''Close and delete the server socket.''' - - try: - self.server_socket.close() - self.server_socket = None - - if os.path.exists(self.socket_location): - os.remove(self.socket_location) - - except: - pass - - - def run(self): - '''Main event daemon loop.''' - - # Create event daemon socket. - self._create_server_socket() - echo('listening on: %s' % self.socket_location) - - if CONFIG['daemon_mode']: - echo('entering daemon mode.') - daemonize() - # The pid has changed so update the pid file. - make_pid_file(CONFIG['pid_file']) - - # Now listen for incoming connections and or data. - self.listen() - - # Clean up. - self.quit() - - - def listen(self): - '''Accept incoming connections and constantly poll instance sockets - for incoming data.''' - - self.running = True - while self.running: - - sockets = [self.server_socket] + self['uzbls'].keys() - - reads, _, errors = select(sockets, [], sockets, 1) - - if self.server_socket in reads: - self.accept_connection() - reads.remove(self.server_socket) - - for client in reads: - self.read_socket(client) - - for client in errors: - error('Unknown error on socket: %r' % client) - self.close_connection(client) - - - def read_socket(self, client): - '''Read data from an instance socket and pass to the uzbl objects - event handler function.''' - - uzbl = self['uzbls'][client] - try: - raw = unicode(client.recv(8192), 'utf-8', 'ignore') - - except: - print_exc() - raw = None - - if not raw: - # Read null byte, close socket. - return self.close_connection(client) - - uzbl.buffer += raw - msgs = uzbl.buffer.split('\n') - uzbl.buffer = msgs.pop() - - for msg in msgs: - try: - parse_msg(uzbl, msg.strip()) - - except: - print_exc() - - - def accept_connection(self): - '''Accept incoming connection to the server socket.''' - - client_socket = self.server_socket.accept()[0] - - uzbl = UzblInstance(self, client_socket) - self['uzbls'][client_socket] = uzbl - - - def close_connection(self, client): - '''Clean up after instance close.''' - - try: - if client in self['uzbls']: - uzbl = self['uzbls'][client] - uzbl.close() - del self['uzbls'][client] - - except: - print_exc() - - if not len(self['uzbls']) and CONFIG['auto_close']: - echo('auto closing event manager.') - self.running = False - - - def quit(self): - '''Close all instance socket objects, server socket and delete the - pid file.''' - - echo('shutting down event manager.') - - for client in self['uzbls'].keys(): - self.close_connection(client) - - echo('unlinking: %r' % self.socket_location) - self._close_server_socket() - - echo('deleting pid file: %r' % CONFIG['pid_file']) - del_pid_file(CONFIG['pid_file']) - - -def stop_action(): - '''Stop the event manager daemon.''' - - pid_file = CONFIG['pid_file'] - if not os.path.isfile(pid_file): - return echo('no running daemon found.') - - echo('found pid file: %r' % pid_file) - pid = get_pid(pid_file) - if not pid_running(pid): - echo('no process with pid: %d' % pid) - return os.remove(pid_file) - - echo("terminating process with pid: %d" % pid) - term_process(pid) - if os.path.isfile(pid_file): - os.remove(pid_file) - - echo('stopped event daemon.') - - -def start_action(): - '''Start the event manager daemon.''' - - pid_file = CONFIG['pid_file'] - if os.path.isfile(pid_file): - echo('found pid file: %r' % pid_file) - pid = get_pid(pid_file) - if pid_running(pid): - return echo('event daemon already started with pid: %d' % pid) - - echo('no process with pid: %d' % pid) - os.remove(pid_file) - - echo('starting event manager.') - UzblEventDaemon().run() - - -def restart_action(): - '''Restart the event manager daemon.''' - - echo('restarting event manager daemon.') - stop_action() - start_action() - - -def list_action(): - '''List all the plugins being loaded by the event daemon.''' - - plugins = find_plugins(CONFIG['plugin_dirs']) - dirs = {} - - for (plugin, plugin_dir) in plugins.items(): - if plugin_dir not in dirs: - dirs[plugin_dir] = [] - - dirs[plugin_dir].append(plugin) - - for (index, (plugin_dir, plugin_list)) in enumerate(sorted(dirs.items())): - if index: - print - - print "%s:" % plugin_dir - for plugin in sorted(plugin_list): - print " %s" % plugin - - -if __name__ == "__main__": - USAGE = "usage: %prog [options] {start|stop|restart|list}" - PARSER = OptionParser(usage=USAGE) - PARSER.add_option('-v', '--verbose', dest='verbose', action="store_true", - help="print verbose output.") - - PARSER.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store", - metavar="DIRS", help="Specify plugin directories in the form of "\ - "'dir1:dir2:dir3'.") - - PARSER.add_option('-l', '--load-plugins', dest="load", action="store", - metavar="PLUGINS", help="comma separated list of plugins to load") - - PARSER.add_option('-i', '--ignore-plugins', dest="ignore", action="store", - metavar="PLUGINS", help="comma separated list of plugins to ignore") - - PARSER.add_option('-p', '--pid-file', dest='pid', action='store', - metavar='FILE', help="specify pid file location") - - PARSER.add_option('-s', '--server-socket', dest='socket', action='store', - metavar='SOCKET', help="specify the daemon socket location") - - PARSER.add_option('-n', '--no-daemon', dest="daemon", - action="store_true", help="don't enter daemon mode.") - - PARSER.add_option('-a', '--auto-close', dest='autoclose', - action='store_true', help='auto close after all instances disconnect.') - - (OPTIONS, ARGS) = PARSER.parse_args() - - # init like {start|stop|..} daemon actions dict. - DAEMON_ACTIONS = {'start': start_action, 'stop': stop_action, - 'restart': restart_action, 'list': list_action} - - if not ARGS: - ACTION = 'start' - - elif len(ARGS) == 1: - ACTION = ARGS[0] - if ACTION not in DAEMON_ACTIONS: - raise ArgumentError("unknown argument: %r" % ACTION) - - else: - raise ArgumentError("too many arguments: %r" % ARGS) - - # parse other flags & options. - if OPTIONS.verbose: - CONFIG['verbose'] = True - - if OPTIONS.plugin_dirs: - PLUGIN_DIRS = [] - for DIR in OPTIONS.plugin_dirs.split(':'): - if not DIR: - continue - - PLUGIN_DIRS.append(os.path.realpath(DIR)) - - CONFIG['plugin_dirs'] = PLUGIN_DIRS - echo("plugin search dirs: %r" % PLUGIN_DIRS) - - if OPTIONS.load and OPTIONS.ignore: - error("you can't load and ignore at the same time.") - sys.exit(1) - - elif OPTIONS.load: - LOAD = CONFIG['plugins_load'] - for PLUGIN in OPTIONS.load.split(','): - if PLUGIN.strip(): - LOAD.append(PLUGIN.strip()) - - echo('only loading plugin(s): %s' % ', '.join(LOAD)) - - elif OPTIONS.ignore: - IGNORE = CONFIG['plugins_ignore'] - for PLUGIN in OPTIONS.ignore.split(','): - if PLUGIN.strip(): - IGNORE.append(PLUGIN.strip()) - - echo('ignoring plugin(s): %s' % ', '.join(IGNORE)) - - if OPTIONS.autoclose: - CONFIG['auto_close'] = True - echo('will auto close.') - - if OPTIONS.pid: - CONFIG['pid_file'] = os.path.realpath(OPTIONS.pid) - echo("pid file location: %r" % CONFIG['pid_file']) - - if OPTIONS.socket: - CONFIG['server_socket'] = os.path.realpath(OPTIONS.socket) - echo("daemon socket location: %s" % CONFIG['server_socket']) - - if OPTIONS.daemon: - CONFIG['daemon_mode'] = False - - # Now {start|stop|...} - DAEMON_ACTIONS[ACTION]() diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed deleted file mode 100755 index 5d1a9f8..0000000 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ /dev/null @@ -1,1417 +0,0 @@ -#!/usr/bin/env python - -# Uzbl tabbing wrapper using a fifo socket interface -# Copyright (c) 2009, Tom Adams -# Copyright (c) 2009, Chris van Dijk -# Copyright (c) 2009, Mason Larobina -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# Author(s): -# Tom Adams -# Wrote the original uzbl_tabbed.py as a proof of concept. -# -# Chris van Dijk (quigybo) -# Made signifigant headway on the old uzbl_tabbing.py script on the -# uzbl wiki -# -# Mason Larobina -# Rewrite of the uzbl_tabbing.py script to use a fifo socket interface -# and inherit configuration options from the user's uzbl config. -# -# Contributor(s): -# mxey -# uzbl_config path now honors XDG_CONFIG_HOME if it exists. -# -# Romain Bignon -# Fix for session restoration code. -# -# Jake Probst -# Wrote a patch that overflows tabs in the tablist on to new lines when -# running of room. -# -# Devon Jones -# Fifo command bring_to_front which brings the gtk window to focus. -# -# Simon Lipp (sloonz) -# Various - - -# Dependencies: -# pygtk - python bindings for gtk. -# pango - python bindings needed for text rendering & layout in gtk widgets. -# pygobject - GLib's GObject bindings for python. -# -# Optional dependencies: -# simplejson - save uzbl_tabbed.py sessions & presets in json. -# -# Note: I haven't included version numbers with this dependency list because -# I've only ever tested uzbl_tabbed.py on the latest stable versions of these -# packages in Gentoo's portage. Package names may vary on different systems. - - -# Configuration: -# Because this version of uzbl_tabbed is able to inherit options from your main -# uzbl configuration file you may wish to configure uzbl tabbed from there. -# Here is a list of configuration options that can be customised and some -# example values for each: -# -# General tabbing options: -# show_tablist = 1 -# show_gtk_tabs = 0 -# tablist_top = 1 -# gtk_tab_pos = (top|left|bottom|right) -# gtk_refresh = 1000 -# switch_to_new_tabs = 1 -# capture_new_windows = 1 -# multiline_tabs = 1 -# -# Tab title options: -# tab_titles = 1 -# tab_indexes = 1 -# new_tab_title = Loading -# max_title_len = 50 -# show_ellipsis = 1 -# -# Session options: -# save_session = 1 -# json_session = 0 -# session_file = $HOME/.local/share/uzbl/session -# -# Inherited uzbl options: -# fifo_dir = /tmp -# socket_dir = /tmp -# icon_path = $HOME/.local/share/uzbl/uzbl.png -# status_background = #303030 -# -# Misc options: -# window_size = 800,800 -# verbose = 0 -# -# And uzbl_tabbed.py takes care of the actual binding of the commands via each -# instances fifo socket. -# -# Custom tab styling: -# tab_colours = foreground = "#888" background = "#303030" -# tab_text_colours = foreground = "#bbb" -# selected_tab = foreground = "#fff" -# selected_tab_text = foreground = "green" -# tab_indicate_https = 1 -# https_colours = foreground = "#888" -# https_text_colours = foreground = "#9c8e2d" -# selected_https = foreground = "#fff" -# selected_https_text = foreground = "gold" -# -# How these styling values are used are soley defined by the syling policy -# handler below (the function in the config section). So you can for example -# turn the tab text colour Firetruck-Red in the event "error" appears in the -# tab title or some other arbitrary event. You may wish to make a trusted -# hosts file and turn tab titles of tabs visiting trusted hosts purple. - - -# Issues: -# - new windows are not caught and opened in a new tab. -# - when uzbl_tabbed.py crashes it takes all the children with it. -# - when a new tab is opened when using gtk tabs the tab button itself -# grabs focus from its child for a few seconds. -# - when switch_to_new_tabs is not selected the notebook page is -# maintained but the new window grabs focus (try as I might to stop it). - - -# Todo: -# - add command line options to use a different session file, not use a -# session file and or open a uri on starup. -# - ellipsize individual tab titles when the tab-list becomes over-crowded -# - add "<" & ">" arrows to tablist to indicate that only a subset of the -# currently open tabs are being displayed on the tablist. -# - add the small tab-list display when both gtk tabs and text vim-like -# tablist are hidden (I.e. [ 1 2 3 4 5 ]) -# - check spelling. -# - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into -# the collective. Resistance is futile! - - -import pygtk -import gtk -import subprocess -import os -import re -import time -import getopt -import pango -import select -import sys -import gobject -import socket -import random -import hashlib -import atexit -import types - -from gobject import io_add_watch, source_remove, timeout_add, IO_IN, IO_HUP -from signal import signal, SIGTERM, SIGINT -from optparse import OptionParser, OptionGroup - - -pygtk.require('2.0') - -_SCRIPTNAME = os.path.basename(sys.argv[0]) -def error(msg): - sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return os.path.join(os.environ['HOME'], default) - -# Setup xdg paths. -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') - -# Ensure uzbl xdg paths exist -if not os.path.exists(DATA_DIR): - os.makedirs(DATA_DIR) - -# All of these settings can be inherited from your uzbl config file. -config = { - # Tab options - 'show_tablist': True, # Show text uzbl like statusbar tab-list - 'show_gtk_tabs': False, # Show gtk notebook tabs - 'tablist_top': True, # Display tab-list at top of window - 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) - 'gtk_refresh': 1000, # Tablist refresh millisecond interval - 'switch_to_new_tabs': True, # Upon opening a new tab switch to it - 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows - 'multiline_tabs': True, # Tabs overflow onto new tablist lines. - - # Tab title options - 'tab_titles': True, # Display tab titles (else only tab-nums) - 'tab_indexes': True, # Display tab nums (else only tab titles) - 'new_tab_title': 'Loading', # New tab title - 'max_title_len': 50, # Truncate title at n characters - 'show_ellipsis': True, # Show ellipsis when truncating titles - - # Session options - 'save_session': True, # Save session in file when quit - 'json_session': False, # Use json to save session. - 'saved_sessions_dir': os.path.join(DATA_DIR, 'sessions/'), - 'session_file': os.path.join(DATA_DIR, 'session'), - - # Inherited uzbl options - 'fifo_dir': '/tmp', # Path to look for uzbl fifo. - 'socket_dir': '/tmp', # Path to look for uzbl socket. - 'icon_path': os.path.join(DATA_DIR, 'uzbl.png'), - 'status_background': "#303030", # Default background for all panels. - - # Misc options - 'window_size': "800,800", # width,height in pixels. - 'verbose': False, # Print verbose output. - - # Add custom tab style definitions to be used by the tab colour policy - # handler here. Because these are added to the config dictionary like - # any other uzbl_tabbed configuration option remember that they can - # be superseeded from your main uzbl config file. - 'tab_colours': 'foreground = "#888" background = "#303030"', - 'tab_text_colours': 'foreground = "#bbb"', - 'selected_tab': 'foreground = "#fff"', - 'selected_tab_text': 'foreground = "green"', - 'tab_indicate_https': True, - 'https_colours': 'foreground = "#888"', - 'https_text_colours': 'foreground = "#9c8e2d"', - 'selected_https': 'foreground = "#fff"', - 'selected_https_text': 'foreground = "gold"', - -} # End of config dict. - -UZBL_TABBED_VARS = config.keys() - -# This is the tab style policy handler. Every time the tablist is updated -# this function is called to determine how to colourise that specific tab -# according the simple/complex rules as defined here. You may even wish to -# move this function into another python script and import it using: -# from mycustomtabbingconfig import colour_selector -# Remember to rename, delete or comment out this function if you do that. - -def colour_selector(tabindex, currentpage, uzbl): - '''Tablist styling policy handler. This function must return a tuple of - the form (tab style, text style).''' - - # Just as an example: - # if 'error' in uzbl.title: - # if tabindex == currentpage: - # return ('foreground="#fff"', 'foreground="red"') - # return ('foreground="#888"', 'foreground="red"') - - # Style tabs to indicate connected via https. - if config['tab_indicate_https'] and uzbl.uri.startswith("https://"): - if tabindex == currentpage: - return (config['selected_https'], config['selected_https_text']) - return (config['https_colours'], config['https_text_colours']) - - # Style to indicate selected. - if tabindex == currentpage: - return (config['selected_tab'], config['selected_tab_text']) - - # Default tab style. - return (config['tab_colours'], config['tab_text_colours']) - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def echo(msg): - if config['verbose']: - sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) - - -def counter(): - '''To infinity and beyond!''' - - i = 0 - while True: - i += 1 - yield i - - -def escape(s): - '''Replaces html markup in tab titles that screw around with pango.''' - - for (split, glue) in [('&','&'), ('<', '<'), ('>', '>')]: - s = s.replace(split, glue) - return s - - -class SocketClient: - '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance''' - - # List of UzblInstance objects not already linked with a SocketClient - instances_queue = {} - - def __init__(self, socket): - self._buffer = "" - self._socket = socket - self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\ - io_add_watch(socket, IO_HUP, self._socket_closed)] - self.uzbl = None - - - def _socket_recv(self, fd, condition): - '''Data available on socket, process it''' - - self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ? - return True - - - def _socket_closed(self, fd, condition): - '''Remote client exited''' - self.uzbl.close() - return False - - - def _feed(self, data): - '''An Uzbl instance sent some data, parse it''' - - self._buffer += data - if self.uzbl: - if "\n" in self._buffer: - cmds = self._buffer.split("\n") - - if cmds[-1]: # Last command has been received incomplete, don't process it - self._buffer, cmds = cmds[-1], cmds[:-1] - else: - self._buffer = "" - - for cmd in cmds: - if cmd: - self.uzbl.parse_command(cmd) - else: - name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M) - uzbl = self.instances_queue.get(name[0]) - if uzbl: - del self.instances_queue[name[0]] - self.uzbl = uzbl - self.uzbl.got_socket(self) - self._feed("") - - def send(self, data): - '''Child socket send function.''' - - self._socket.send(data + "\n") - - def close(self): - '''Close the connection''' - - if self._socket: - self._socket.close() - self._socket = None - map(source_remove, self._watchers) - self._watchers = [] - - -class UzblInstance: - '''Uzbl instance meta-data/meta-action object.''' - - def __init__(self, parent, tab, name, uri, title, switch): - - self.parent = parent - self.tab = tab - self.name = name - self.title = title - self.tabtitle = "" - self.uri = uri - self._client = None - self._switch = switch # Switch to tab after loading ? - self.title_changed() - - - def got_socket(self, client): - '''Uzbl instance is now connected''' - - self._client = client - self.parent.config_uzbl(self) - if self._switch: - tabid = self.parent.notebook.page_num(self.tab) - self.parent.goto_tab(tabid) - - - def title_changed(self, gtk_only = True): # GTK-only is for indexes - '''self.title has changed, update the tabs list''' - - tab_titles = config['tab_titles'] - tab_indexes = config['tab_indexes'] - show_ellipsis = config['show_ellipsis'] - max_title_len = config['max_title_len'] - - # Unicode heavy strings do not like being truncated/sliced so by - # re-encoding the string sliced of limbs are removed. - self.tabtitle = self.title[:max_title_len + int(show_ellipsis)] - if type(self.tabtitle) != types.UnicodeType: - self.tabtitle = unicode(self.tabtitle, 'utf-8', 'ignore') - - self.tabtitle = self.tabtitle.encode('utf-8', 'ignore').strip() - - if show_ellipsis and len(self.tabtitle) != len(self.title): - self.tabtitle += "\xe2\x80\xa6" - - gtk_tab_format = "%d %s" - index = self.parent.notebook.page_num(self.tab) - if tab_titles and tab_indexes: - self.parent.notebook.set_tab_label_text(self.tab, - gtk_tab_format % (index, self.tabtitle)) - elif tab_titles: - self.parent.notebook.set_tab_label_text(self.tab, self.tabtitle) - else: - self.parent.notebook.set_tab_label_text(self.tab, str(index)) - - # If instance is current tab, update window title - if index == self.parent.notebook.get_current_page(): - title_format = "%s - Uzbl Browser" - self.parent.window.set_title(title_format % self.title) - - # Non-GTK tabs - if not gtk_only: - self.parent.update_tablist() - - - def set(self, key, val): - ''' Send the SET command to Uzbl ''' - - if self._client: - self._client.send('set %s = %s') #TODO: escape chars ? - - - def exit(self): - ''' Ask the Uzbl instance to close ''' - - if self._client: - self._client.send('exit') - - - def parse_command(self, cmd): - ''' Parse event givent by the Uzbl instance ''' - - type, _, args = cmd.split(" ", 2) - if type == "EVENT": - type, args = args.split(" ", 1) - if type == "TITLE_CHANGED": - self.title = args - self.title_changed() - elif type == "VARIABLE_SET": - var, _, val = args.split(" ", 2) - try: - val = int(val) - except: - pass - - if var in UZBL_TABBED_VARS: - if config[var] != val: - config[var] = val - if var == "show_gtk_tabs": - self.parent.notebook.set_show_tabs(bool(val)) - elif var == "show_tablist" or var == "tablist_top": - self.parent.update_tablist_display() - elif var == "gtk_tab_pos": - self.parent.update_gtk_tab_pos() - elif var == "status_background": - col = gtk.gdk.color_parse(config['status_background']) - self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) - elif var == "tab_titles" or var == "tab_indexes": - for tab in self.parent.notebook: - self.parent.tabs[tab].title_changed(True) - - self.parent.update_tablist() - else: - config[var] = val - - if var == "uri": - self.uri = var - self.parent.update_tablist() - elif type == "NEW_TAB": - self.parent.new_tab(args) - elif type == "NEXT_TAB": - if args: - self.parent.next_tab(int(args)) - else: - self.parent.next_tab() - elif type == "PREV_TAB": - if args: - self.parent.prev_tab(int(args)) - else: - self.parent.prev_tab() - elif type == "GOTO_TAB": - self.parent.goto_tab(int(args)) - elif type == "FIRST_TAB": - self.parent.goto_tab(0) - elif type == "LAST_TAB": - self.parent.goto_tab(-1) - elif type == "PRESET_TABS": - self.parent.parse_command(["preset"] + args.split()) - elif type == "BRING_TO_FRONT": - self.parent.window.present() - elif type == "CLEAN_TABS": - self.parent.clean_slate() - elif type == "EXIT_ALL_TABS": - self.parent.quitrequest() - - - def close(self): - '''The remote instance exited''' - - if self._client: - self._client.close() - self._client = None - - -class UzblTabbed: - '''A tabbed version of uzbl using gtk.Notebook''' - - def __init__(self): - '''Create tablist, window and notebook.''' - - self._timers = {} - self._buffer = "" - self._killed = False - - # A list of the recently closed tabs - self._closed = [] - - # Holds metadata on the uzbl childen open. - self.tabs = {} - - # Uzbl sockets (socket => SocketClient) - self.clients = {} - - # Generates a unique id for uzbl socket filenames. - self.next_pid = counter().next - - # Create main window - self.window = gtk.Window() - try: - window_size = map(int, config['window_size'].split(',')) - self.window.set_default_size(*window_size) - - except: - error("Invalid value for default_size in config file.") - - self.window.set_title("Uzbl Browser") - self.window.set_border_width(0) - - # Set main window icon - icon_path = config['icon_path'] - if os.path.exists(icon_path): - self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) - - else: - icon_path = '/usr/share/uzbl/examples/data/uzbl/uzbl.png' - if os.path.exists(icon_path): - self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) - - # Attach main window event handlers - self.window.connect("delete-event", self.quitrequest) - - # Create tab list - vbox = gtk.VBox() - self.vbox = vbox - self.window.add(vbox) - ebox = gtk.EventBox() - self.ebox = ebox - self.tablist = gtk.Label() - - self.tablist.set_use_markup(True) - self.tablist.set_justify(gtk.JUSTIFY_LEFT) - self.tablist.set_line_wrap(False) - self.tablist.set_selectable(False) - self.tablist.set_padding(2,2) - self.tablist.set_alignment(0,0) - self.tablist.set_ellipsize(pango.ELLIPSIZE_END) - self.tablist.set_text(" ") - self.tablist.show() - ebox.add(self.tablist) - ebox.show() - bgcolor = gtk.gdk.color_parse(config['status_background']) - ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) - - # Create notebook - self.notebook = gtk.Notebook() - self.notebook.set_show_tabs(config['show_gtk_tabs']) - - # Set tab position - self.update_gtk_tab_pos() - - self.notebook.set_show_border(False) - self.notebook.set_scrollable(True) - self.notebook.set_border_width(0) - - self.notebook.connect("page-removed", self.tab_closed) - self.notebook.connect("switch-page", self.tab_changed) - self.notebook.connect("page-added", self.tab_opened) - - self.notebook.show() - vbox.pack_start(self.notebook, True, True, 0) - vbox.reorder_child(self.notebook, 1) - self.update_tablist_display() - - self.vbox.show() - self.window.show() - self.wid = self.notebook.window.xid - - # Store information about the applications fifo and socket. - fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid() - socket_filename = 'uzbltabbed_%d.socket' % os.getpid() - self._fifo = None - self._socket = None - self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename) - self.socket_path = os.path.join(config['socket_dir'], socket_filename) - - # Now initialise the fifo and the socket - self.init_fifo() - self.init_socket() - - # If we are using sessions then load the last one if it exists. - if config['save_session']: - self.load_session() - - - def run(self): - '''UzblTabbed main function that calls the gtk loop.''' - - if not self.clients and not SocketClient.instances_queue and not self.tabs: - self.new_tab() - - gtk_refresh = int(config['gtk_refresh']) - if gtk_refresh < 100: - gtk_refresh = 100 - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) - - # Catch keyboard interrupts - signal(SIGINT, lambda signum, stack_frame: self.terminate(SIGINT)) - - try: - gtk.main() - - except: - error("encounted error %r" % sys.exc_info()[1]) - - # Unlink fifo socket - self.unlink_fifo() - self.close_socket() - - # Attempt to close all uzbl instances nicely. - self.quitrequest() - - # Allow time for all the uzbl instances to quit. - time.sleep(1) - - raise - - - def terminate(self, termsig=None): - '''Handle termination signals and exit safely and cleanly.''' - - # Not required but at least it lets the user know what killed his - # browsing session. - if termsig == SIGTERM: - error("caught SIGTERM signal") - - elif termsig == SIGINT: - error("caught keyboard interrupt") - - else: - error("caught unknown signal") - - error("commencing infanticide!") - - # Sends the exit signal to all uzbl instances. - self.quitrequest() - - - def init_socket(self): - '''Create interprocess communication socket.''' - - def accept(sock, condition): - '''A new uzbl instance was created''' - - client, _ = sock.accept() - self.clients[client] = SocketClient(client) - - return True - - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.bind(self.socket_path) - sock.listen(1) - - # Add event handler for IO_IN event. - self._socket = (sock, io_add_watch(sock, IO_IN, accept)) - - echo("[socket] listening at %r" % self.socket_path) - - # Add atexit register to destroy the socket on program termination. - atexit.register(self.close_socket) - - - def close_socket(self): - '''Close the socket when closing the application''' - - if self._socket: - (fd, watcher) = self._socket - source_remove(watcher) - fd.close() - os.unlink(self.socket_path) - self._socket = None - - - def init_fifo(self): - '''Create interprocess communication fifo.''' - - if os.path.exists(self.fifo_path): - if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK): - os.mkfifo(self.fifo_path) - - else: - basedir = os.path.dirname(self.fifo_path) - if not os.path.exists(basedir): - os.makedirs(basedir) - - os.mkfifo(self.fifo_path) - - # Add event handlers for IO_IN & IO_HUP events. - self.setup_fifo_watchers() - - echo("[fifo] listening at %r" % self.fifo_path) - - # Add atexit register to destroy the fifo on program termination. - atexit.register(self.unlink_fifo) - - - def unlink_fifo(self): - '''Unlink the fifo socket. Note: This function is called automatically - on exit by an atexit register.''' - - # Make sure the fifo fd is closed. - self.close_fifo() - - # And unlink if the real fifo exists. - if os.path.exists(self.fifo_path): - os.unlink(self.fifo_path) - echo("unlinked %r" % self.fifo_path) - - - def close_fifo(self): - '''Remove all event handlers watching the fifo and close the fd.''' - - # Already closed - if self._fifo is None: return - - (fd, watchers) = self._fifo - os.close(fd) - - # Stop all gobject io watchers watching the fifo. - for gid in watchers: - source_remove(gid) - - self._fifo = None - - - def setup_fifo_watchers(self): - '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event - handlers.''' - - # Close currently open fifo fd and kill all watchers - self.close_fifo() - - fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK) - - # Add gobject io event handlers to the fifo socket. - watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\ - io_add_watch(fd, IO_HUP, self.main_fifo_hangup)] - - self._fifo = (fd, watchers) - - - def main_fifo_hangup(self, fd, cb_condition): - '''Handle main fifo socket hangups.''' - - # Close old fd, open new fifo socket and add io event handlers. - self.setup_fifo_watchers() - - # Kill the gobject event handler calling this handler function. - return False - - - def main_fifo_read(self, fd, cb_condition): - '''Read from main fifo socket.''' - - self._buffer = os.read(fd, 1024) - temp = self._buffer.split("\n") - self._buffer = temp.pop() - cmds = [s.strip().split() for s in temp if len(s.strip())] - - for cmd in cmds: - try: - #print cmd - self.parse_command(cmd) - - except: - error("parse_command: invalid command %s" % ' '.join(cmd)) - raise - - return True - - - def parse_command(self, cmd): - '''Parse instructions from uzbl child processes.''' - - # Commands ( [] = optional, {} = required ) - # new [uri] - # open new tab and head to optional uri. - # close [tab-num] - # close current tab or close via tab id. - # next [n-tabs] - # open next tab or n tabs down. Supports negative indexing. - # prev [n-tabs] - # open prev tab or n tabs down. Supports negative indexing. - # goto {tab-n} - # goto tab n. - # first - # goto first tab. - # last - # goto last tab. - # title {pid} {document-title} - # updates tablist title. - # uri {pid} {document-location} - # updates tablist uri - # bring_to_front - # brings the gtk window to focus. - # exit - # exits uzbl_tabbed.py - - if cmd[0] == "new": - if len(cmd) == 2: - self.new_tab(cmd[1]) - - else: - self.new_tab() - - elif cmd[0] == "newfromclip": - uri = subprocess.Popen(['xclip','-selection','clipboard','-o'],\ - stdout=subprocess.PIPE).communicate()[0] - if uri: - self.new_tab(uri) - - elif cmd[0] == "close": - if len(cmd) == 2: - self.close_tab(int(cmd[1])) - - else: - self.close_tab() - - elif cmd[0] == "next": - if len(cmd) == 2: - self.next_tab(int(cmd[1])) - - else: - self.next_tab() - - elif cmd[0] == "prev": - if len(cmd) == 2: - self.prev_tab(int(cmd[1])) - - else: - self.prev_tab() - - elif cmd[0] == "goto": - self.goto_tab(int(cmd[1])) - - elif cmd[0] == "first": - self.goto_tab(0) - - elif cmd[0] == "last": - self.goto_tab(-1) - - elif cmd[0] in ["title", "uri"]: - if len(cmd) > 2: - uzbl = self.get_tab_by_name(int(cmd[1])) - if uzbl: - old = getattr(uzbl, cmd[0]) - new = ' '.join(cmd[2:]) - setattr(uzbl, cmd[0], new) - if old != new: - self.update_tablist() - - else: - error("parse_command: no uzbl with name %r" % int(cmd[1])) - - elif cmd[0] == "preset": - if len(cmd) < 3: - error("parse_command: invalid preset command") - - elif cmd[1] == "save": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - self.save_session(path) - - elif cmd[1] == "load": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - self.load_session(path) - - elif cmd[1] == "del": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - if os.path.isfile(path): - os.remove(path) - - else: - error("parse_command: preset %r does not exist." % path) - - elif cmd[1] == "list": - uzbl = self.get_tab_by_name(int(cmd[2])) - if uzbl: - if not os.path.isdir(config['saved_sessions_dir']): - js = "js alert('No saved presets.');" - uzbl._client.send(js) - - else: - listdir = os.listdir(config['saved_sessions_dir']) - listdir = "\\n".join(listdir) - js = "js alert('Session presets:\\n\\n%s');" % listdir - uzbl._client.send(js) - - else: - error("parse_command: unknown tab name.") - - else: - error("parse_command: unknown parse command %r"\ - % ' '.join(cmd)) - - elif cmd[0] == "bring_to_front": - self.window.present() - - elif cmd[0] == "clean": - self.clean_slate() - - elif cmd[0] == "exit": - self.quitrequest() - - else: - error("parse_command: unknown command %r" % ' '.join(cmd)) - - - def get_tab_by_name(self, name): - '''Return uzbl instance by name.''' - - for (tab, uzbl) in self.tabs.items(): - if uzbl.name == name: - return uzbl - - return False - - - def new_tab(self, uri='', title='', switch=None): - '''Add a new tab to the notebook and start a new instance of uzbl. - Use the switch option to negate config['switch_to_new_tabs'] option - when you need to load multiple tabs at a time (I.e. like when - restoring a session from a file).''' - - tab = gtk.Socket() - tab.show() - self.notebook.append_page(tab) - sid = tab.get_id() - uri = uri.strip() - name = "%d-%d" % (os.getpid(), self.next_pid()) - - if switch is None: - switch = config['switch_to_new_tabs'] - - if not title: - title = config['new_tab_title'] - - cmd = ['uzbl-browser', '-n', name, '-s', str(sid), - '--connect-socket', self.socket_path, '--uri', uri] - subprocess.Popen(cmd) # TODO: do i need close_fds=True ? - - uzbl = UzblInstance(self, tab, name, uri, title, switch) - SocketClient.instances_queue[name] = uzbl - self.tabs[tab] = uzbl - - - def clean_slate(self): - '''Close all open tabs and open a fresh brand new one.''' - - self.new_tab() - tabs = self.tabs.keys() - for tab in list(self.notebook)[:-1]: - if tab not in tabs: continue - uzbl = self.tabs[tab] - uzbl.exit() - - - def config_uzbl(self, uzbl): - '''Send bind commands for tab new/close/next/prev to a uzbl - instance.''' - - # Set definitions here - # set(key, command back to fifo) - if config['capture_new_windows']: - uzbl.set("new_window", r'new $8') - - - def goto_tab(self, index): - '''Goto tab n (supports negative indexing).''' - - title_format = "%s - Uzbl Browser" - - tabs = list(self.notebook) - if 0 <= index < len(tabs): - self.notebook.set_current_page(index) - uzbl = self.tabs[self.notebook.get_nth_page(index)] - self.window.set_title(title_format % uzbl.title) - self.update_tablist() - return None - - try: - tab = tabs[index] - # Update index because index might have previously been a - # negative index. - index = tabs.index(tab) - self.notebook.set_current_page(index) - uzbl = self.tabs[self.notebook.get_nth_page(index)] - self.window.set_title(title_format % uzbl.title) - self.update_tablist() - - except IndexError: - pass - - - def next_tab(self, step=1): - '''Switch to next tab or n tabs right.''' - - if step < 1: - error("next_tab: invalid step %r" % step) - return None - - ntabs = self.notebook.get_n_pages() - tabn = (self.notebook.get_current_page() + step) % ntabs - self.goto_tab(tabn) - - - def prev_tab(self, step=1): - '''Switch to prev tab or n tabs left.''' - - if step < 1: - error("prev_tab: invalid step %r" % step) - return None - - ntabs = self.notebook.get_n_pages() - tabn = self.notebook.get_current_page() - step - while tabn < 0: tabn += ntabs - self.goto_tab(tabn) - - - def close_tab(self, tabn=None): - '''Closes current tab. Supports negative indexing.''' - - if tabn is None: - tabn = self.notebook.get_current_page() - - else: - try: - tab = list(self.notebook)[tabn] - - except IndexError: - error("close_tab: invalid index %r" % tabn) - return None - - self.notebook.remove_page(tabn) - - - def tab_opened(self, notebook, tab, index): - '''Called upon tab creation. Called by page-added signal.''' - - if config['switch_to_new_tabs']: - self.notebook.set_focus_child(tab) - - else: - oldindex = self.notebook.get_current_page() - oldtab = self.notebook.get_nth_page(oldindex) - self.notebook.set_focus_child(oldtab) - - - def tab_closed(self, notebook, tab, index): - '''Close the window if no tabs are left. Called by page-removed - signal.''' - - if tab in self.tabs.keys(): - uzbl = self.tabs[tab] - uzbl.close() - - self._closed.append((uzbl.uri, uzbl.title)) - self._closed = self._closed[-10:] - del self.tabs[tab] - - if self.notebook.get_n_pages() == 0: - if not self._killed and config['save_session']: - if os.path.exists(config['session_file']): - os.remove(config['session_file']) - - self.quit() - - for tab in self.notebook: - self.tabs[tab].title_changed(True) - self.update_tablist() - - return True - - - def tab_changed(self, notebook, page, index): - '''Refresh tab list. Called by switch-page signal.''' - - tab = self.notebook.get_nth_page(index) - self.notebook.set_focus_child(tab) - self.update_tablist(index) - return True - - - def update_tablist_display(self): - '''Called when show_tablist or tablist_top has changed''' - - if self.ebox in self.vbox.get_children(): - self.vbox.remove(self.ebox) - - if config['show_tablist']: - self.vbox.pack_start(self.ebox, False, False, 0) - if config['tablist_top']: - self.vbox.reorder_child(self.ebox, 0) - else: - self.vbox.reorder_child(self.ebox, 2) - - def update_gtk_tab_pos(self): - ''' Called when gtk_tab_pos has changed ''' - - allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, - 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} - if config['gtk_tab_pos'] in allposes.keys(): - self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) - - - def update_tablist(self, curpage=None): - '''Upate tablist status bar.''' - - if not config['show_tablist']: - return True - - tab_titles = config['tab_titles'] - tab_indexes = config['tab_indexes'] - multiline_tabs = config['multiline_tabs'] - - if multiline_tabs: - multiline = [] - - tabs = self.tabs.keys() - if curpage is None: - curpage = self.notebook.get_current_page() - - pango = "" - normal = (config['tab_colours'], config['tab_text_colours']) - selected = (config['selected_tab'], config['selected_tab_text']) - - if tab_titles and tab_indexes: - tab_format = " [ %(index)d %(title)s ] " - elif tab_titles: - tab_format = " [ %(title)s ] " - else: - tab_format = " [ %(index)d ] " - - for index, tab in enumerate(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - title = escape(uzbl.tabtitle) - - style = colour_selector(index, curpage, uzbl) - (tabc, textc) = style - - if multiline_tabs: - opango = pango - - pango += tab_format % locals() - - self.tablist.set_markup(pango) - listwidth = self.tablist.get_layout().get_pixel_size()[0] - winwidth = self.window.get_size()[0] - - if listwidth > (winwidth - 20): - multiline.append(opango) - pango = tab_format % locals() - else: - pango += tab_format % locals() - - if multiline_tabs: - multiline.append(pango) - self.tablist.set_markup(' '.join(multiline)) - - else: - self.tablist.set_markup(pango) - - return True - - - def save_session(self, session_file=None): - '''Save the current session to file for restoration on next load.''' - - strip = str.strip - - if session_file is None: - session_file = config['session_file'] - - tabs = self.tabs.keys() - state = [] - for tab in list(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - if not uzbl.uri: continue - state += [(uzbl.uri, uzbl.title),] - - session = {'curtab': self.notebook.get_current_page(), - 'tabs': state} - - if config['json_session']: - raw = json.dumps(session) - - else: - lines = ["curtab = %d" % session['curtab'],] - for (uri, title) in session['tabs']: - lines += ["%s\t%s" % (strip(uri), strip(title)),] - - raw = "\n".join(lines) - - if not os.path.isfile(session_file): - dirname = os.path.dirname(session_file) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - h = open(session_file, 'w') - h.write(raw) - h.close() - - - def load_session(self, session_file=None): - '''Load a saved session from file.''' - - default_path = False - strip = str.strip - json_session = config['json_session'] - delete_loaded = False - - if session_file is None: - default_path = True - delete_loaded = True - session_file = config['session_file'] - - if not os.path.isfile(session_file): - return False - - h = open(session_file, 'r') - raw = h.read() - h.close() - if json_session: - if sum([1 for s in raw.split("\n") if strip(s)]) != 1: - error("Warning: The session file %r does not look json. "\ - "Trying to load it as a non-json session file."\ - % session_file) - json_session = False - - if json_session: - try: - session = json.loads(raw) - curtab, tabs = session['curtab'], session['tabs'] - - except: - error("Failed to load jsonifed session from %r"\ - % session_file) - return None - - else: - tabs = [] - strip = str.strip - curtab, tabs = 0, [] - lines = [s for s in raw.split("\n") if strip(s)] - if len(lines) < 2: - error("Warning: The non-json session file %r looks invalid."\ - % session_file) - return None - - try: - for line in lines: - if line.startswith("curtab"): - curtab = int(line.split()[-1]) - - else: - uri, title = line.split("\t",1) - tabs += [(strip(uri), strip(title)),] - - except: - error("Warning: failed to load session file %r" % session_file) - return None - - session = {'curtab': curtab, 'tabs': tabs} - - # Now populate notebook with the loaded session. - for (index, (uri, title)) in enumerate(tabs): - self.new_tab(uri=uri, title=title, switch=(curtab==index)) - - # A saved session has been loaded now delete it. - if delete_loaded and os.path.exists(session_file): - os.remove(session_file) - - # There may be other state information in the session dict of use to - # other functions. Of course however the non-json session object is - # just a dummy object of no use to no one. - return session - - - def quitrequest(self, *args): - '''Attempt to close all uzbl instances nicely and exit.''' - - self._killed = True - - if config['save_session']: - if len(list(self.notebook)) > 1: - self.save_session() - - else: - # Notebook has one page open so delete the session file. - if os.path.isfile(config['session_file']): - os.remove(config['session_file']) - - for (tab, uzbl) in self.tabs.items(): - uzbl.exit() - - # Add a gobject timer to make sure the application force-quits after a - # reasonable period. Calling quit when all the tabs haven't had time to - # close should be a last resort. - timer = "force-quit" - timerid = timeout_add(5000, self.quit, timer) - self._timers[timer] = timerid - - - def quit(self, *args): - '''Cleanup and quit. Called by delete-event signal.''' - - # Close the fifo socket, remove any gobject io event handlers and - # delete socket. - self.unlink_fifo() - self.close_socket() - - # Remove all gobject timers that are still ticking. - for (timerid, gid) in self._timers.items(): - source_remove(gid) - del self._timers[timerid] - - try: - gtk.main_quit() - - except: - pass - - -if __name__ == "__main__": - - # Build command line parser - usage = "usage: %prog [OPTIONS] {URIS}..." - parser = OptionParser(usage=usage) - parser.add_option('-n', '--no-session', dest='nosession', - action='store_true', help="ignore session saving a loading.") - parser.add_option('-v', '--verbose', dest='verbose', - action='store_true', help='print verbose output.') - - # Parse command line options - (options, uris) = parser.parse_args() - - if options.nosession: - config['save_session'] = False - - if options.verbose: - config['verbose'] = True - - if config['json_session']: - try: - import simplejson as json - - except: - error("Warning: json_session set but cannot import the python "\ - "module simplejson. Fix: \"set json_session = 0\" or "\ - "install the simplejson python module to remove this warning.") - config['json_session'] = False - - if config['verbose']: - import pprint - sys.stderr.write("%s\n" % pprint.pformat(config)) - - uzbl = UzblTabbed() - - # All extra arguments given to uzbl_tabbed.py are interpreted as - # web-locations to opened in new tabs. - lasturi = len(uris)-1 - for (index,uri) in enumerate(uris): - uzbl.new_tab(uri, switch=(index==lasturi)) - - uzbl.run() diff --git a/examples/data/uzbl/scripts/uzblcat b/examples/data/uzbl/scripts/uzblcat deleted file mode 100755 index e955608..0000000 --- a/examples/data/uzbl/scripts/uzblcat +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python -# uzblcat - safely push html to uzbl -# See http://www.uzbl.org/wiki/html-mode - -from sys import stdin, stdout - -stdout.write("uri data:text/html,") -for line in stdin: - stdout.write(line[0:-1]) - -# vim: set noet ff=unix - diff --git a/examples/data/uzbl/style.css b/examples/data/uzbl/style.css deleted file mode 100644 index f9b111e..0000000 --- a/examples/data/uzbl/style.css +++ /dev/null @@ -1,25 +0,0 @@ -.uzbl_highlight { background-color: yellow;} -.uzbl_h_first { background-color: lightgreen;} - -.uzbl_follow { border-style: dotted; - border-width: thin; -} - -#uzbl_hint > div { - display: inline; - border: 2px solid #4a6600; - background-color: #b9ff00; - color: black; - font-size: 9px; - font-weight: bold; - line-height: 9px; - margin: 0px; - padding: 0px; - position: absolute; - z-index: 1000; - -webkit-border-radius: 6px; - text-decoration: none; - -wekit-transform: scale(1) rotate(0deg) translate(-6px,-5px); -} - -/* vim:set et ts=4: */ diff --git a/examples/data/uzbl/uzbl.png b/examples/data/uzbl/uzbl.png deleted file mode 100644 index 773ea84..0000000 Binary files a/examples/data/uzbl/uzbl.png and /dev/null differ diff --git a/sandbox/env.sh b/sandbox/env.sh index 0bf812a..122a7f2 100755 --- a/sandbox/env.sh +++ b/sandbox/env.sh @@ -5,8 +5,9 @@ # - executing limits scope of variables too much (even with exporting) # maybe we should spawn processes from here with an 'exec' at the end? -export XDG_DATA_HOME=./sandbox/examples/data -export XDG_CACHE_HOME=./sandbox/examples/cache -export XDG_CONFIG_HOME=./sandbox/examples/config -#export PATH="./sandbox/usr/local/share/uzbl/examples/data/uzbl/scripts/:$PATH" # needed when running uzbl-browser from here? don't think so.. +export HOME=./sandbox/home +export XDG_DATA_HOME=$HOME/.local/share +export XDG_CACHE_HOME=$HOME/.cache +export XDG_CONFIG_HOME=$HOME/.config +#export PATH="./sandbox/usr/local/share/uzbl/examples/data/scripts/:$PATH" # needed when running uzbl-browser from here? don't think so.. export PATH="./sandbox/usr/local/bin:$PATH" # needed to run uzbl-browser etc from here diff --git a/src/uzbl-browser b/src/uzbl-browser index d9b9752..8a7ab36 100755 --- a/src/uzbl-browser +++ b/src/uzbl-browser @@ -40,7 +40,7 @@ done # if no config exists yet in the recommended location, put the default (recommended) config there if [ ! -f $XDG_CONFIG_HOME/uzbl/config ] then - if ! cp $PREFIX/share/uzbl/examples/config/uzbl/config $XDG_CONFIG_HOME/uzbl/config + if ! cp $PREFIX/share/uzbl/examples/config/config $XDG_CONFIG_HOME/uzbl/config then echo "Could not copy default config to $XDG_CONFIG_HOME/uzbl/config" >&2 exit 3 -- cgit v1.2.3