diff options
Diffstat (limited to 'examples')
25 files changed, 293 insertions, 2684 deletions
diff --git a/examples/config/config b/examples/config/config index bcd6d3e..8c706df 100644 --- a/examples/config/config +++ b/examples/config/config @@ -4,9 +4,10 @@ # === Core settings ========================================================== # common directory locations -set prefix = @(echo $PREFIX)@ -set data_home = @(echo $XDG_DATA_HOME)@ -set cache_home = @(echo $XDG_CACHE_HOME)@ +set prefix = @(echo $PREFIX)@ +set data_home = @(echo $XDG_DATA_HOME)@ +set cache_home = @(echo $XDG_CACHE_HOME)@ +set config_home = @(echo $XDG_CONFIG_HOME)@ # Interface paths. set fifo_dir = /tmp @@ -90,12 +91,12 @@ set download_handler = sync_spawn @scripts_dir/download.sh #@on_event CONFIG_CHANGED print Config changed: %1 = %2 # Scroll percentage calculation -@on_event SCROLL_VERT set scroll_message = \@<(function(){var p='--';if(%3!=%2){p=(%1/(%3-%4));p=Math.round(10000*p)/100;};return p+'%';})()>\@ +@on_event SCROLL_VERT set scroll_message = \@<(function(){var p='--';if(%3<=%4){p=(%1/(%3-%4));p=Math.round(10000*p)/100;};return p+'%';})()>\@ # === Behaviour and appearance =============================================== # Custom CSS can be defined here, including link follower hint styles -set stylesheet_uri = file://@data_home/uzbl/style.css +set stylesheet_uri = file://@config_home/uzbl/style.css set show_status = 1 set status_top = 0 @@ -153,15 +154,11 @@ set useragent = Uzbl (Webkit @{WEBKIT_MAJOR}.@{WEBKIT_MINOR}) (@(+uname @modmap <space> <Space> @modmap <KP_Enter> <Enter> -#modkey_addition <Key1> <Key2> <Result> -@modkey_addition <Shift> <Ctrl> <Meta> -@modkey_addition <Shift> <Tab> <Shift-Tab> -@modkey_addition <Shift> <Insert> <Shift-Insert> - #ignore_key <glob> @ignore_key <ISO_*> @ignore_key <Shift> @ignore_key <Multi_key> +@ignore_key <Mod2> # --- Bind aliases ----------------------------------------------------------- @@ -185,6 +182,7 @@ set ebind = @mode_bind global,-insert # Resets keycmd and returns to default mode. @on_event ESCAPE @set_mode @on_event ESCAPE event KEYCMD_CLEAR +@on_event ESCAPE js uzbl.follow.clearHints() @bind <Escape> = event ESCAPE @bind <Ctrl>[ = event ESCAPE @@ -198,7 +196,7 @@ set ebind = @mode_bind global,-insert @ebind <Delete> = event KEYCMD_DELETE @ebind <Tab> = event START_COMPLETION # Readline-ish bindings. -@ebind <Ctrl>w = event KEYCMD_STRIP_WORD +@ebind <Ctrl>w = event KEYCMD_STRIP_WORD \ -./&?= @ebind <Ctrl>u = event SET_KEYCMD @ebind <Ctrl>a = event SET_CURSOR_POS 0 @ebind <Ctrl>e = event SET_CURSOR_POS -1 @@ -267,6 +265,9 @@ set ebind = @mode_bind global,-insert @cbind n = search @cbind N = search_reverse +# Print pages to a printer +@cbind <Ctrl>p = hardcopy + # Web searching binds @cbind gg<Google:>_ = uri http://www.google.com/search?q=\@<encodeURIComponent(%r)>\@ @cbind ddg<DuckDuckGo:>_ = uri http://duckduckgo.com/?q=%s @@ -285,8 +286,8 @@ set ebind = @mode_bind global,-insert # Use socat to directly inject commands into uzbl-core and view events # raised by uzbl-core: -@cbind <Ctrl><Alt>t = sh 'xterm -e "socat unix-connect:\"$UZBL_SOCKET\" -"' -#@cbind <Ctrl><Alt>t = sh 'urxvt -e socat unix-connect:"$UZBL_SOCKET" -' +@cbind <Ctrl><Mod1>t = sh 'xterm -e "socat unix-connect:\"$UZBL_SOCKET\" -"' +#@cbind <Ctrl><Mod1>t = sh 'urxvt -e socat unix-connect:"$UZBL_SOCKET" -' # Uri opening prompts @cbind o<uri:>_ = uri %s @@ -300,6 +301,13 @@ set ebind = @mode_bind global,-insert # Hard-bound bookmarks @cbind gh = uri http://www.uzbl.org +# New window binds +@cbind gw = event REQ_NEW_WINDOW + +# SSL-ify bindings +@cbind zs = uri \@(echo "$UZBL_URI" | sed -e 's/^http:/https:/')\@ +@cbind zS = event REQ_NEW_WINDOW \@(echo "$UZBL_URI" | sed -e 's/^http:/https:/')\@ + # Yanking & pasting binds @cbind yu = sh 'echo -n "$UZBL_URI" | xclip' @cbind yU = sh 'echo -n "$1" | xclip' \@SELECTED_URI @@ -314,7 +322,7 @@ set ebind = @mode_bind global,-insert # Start a new uzbl instance from the page in primary selection @cbind 'p = sh 'echo "event REQ_NEW_WINDOW $(xclip -o)" > "$UZBL_FIFO"' # paste primary selection into keycmd at the cursor position -@bind <Shift-Insert> = sh 'echo "event INJECT_KEYCMD $(xclip -o | sed s/\\\@/%40/g)" > "$UZBL_FIFO"' +@bind <Shift><Insert> = sh 'echo "event INJECT_KEYCMD $(xclip -o | sed s/\\\@/%40/g)" > "$UZBL_FIFO"' # Bookmark inserting binds @cbind <Ctrl>b<tags:>_ = sh 'echo `printf "$UZBL_URI %s"` >> "$XDG_DATA_HOME"/uzbl/bookmarks' @@ -325,6 +333,10 @@ set ebind = @mode_bind global,-insert @cbind U = spawn @scripts_dir/load_url_from_history.sh @cbind u = spawn @scripts_dir/load_url_from_bookmarks.sh +# Temporary bookmarks +@cbind <Ctrl>d = spawn @scripts_dir/insert_temp.sh +@cbind D = spawn @scripts_dir/load_url_from_temps.sh + # Link following (similar to vimperator and konqueror) # Set custom keys you wish to use for navigation. Some common examples: set follow_hint_keys = 0123456789 @@ -333,7 +345,7 @@ set follow_hint_keys = 0123456789 #set follow_hint_keys = thsnd-rcgmvwb/;789aefijkopquxyz234 @cbind fl* = spawn @scripts_dir/follow.sh \@< uzbl.follow("\@follow_hint_keys", "%s", 0) >\@ @cbind Fl* = spawn @scripts_dir/follow.sh \@< uzbl.follow("\@follow_hint_keys", "%s", 1) >\@ -@cbind gi = spawn @scripts_dir/go_input.sh +@cbind fi = spawn @scripts_dir/go_input.sh # Form filler binds # This script allows you to configure (per domain) values to fill in form @@ -341,10 +353,10 @@ set follow_hint_keys = 0123456789 # This implementation allows you to save multiple profiles for each form # (think about multiple accounts on some website). set formfiller = spawn @scripts_dir/formfiller.sh -@cbind za = @formfiller add @cbind ze = @formfiller edit @cbind zn = @formfiller new @cbind zl = @formfiller load +@cbind zo = @formfiller once # --- Uzbl tabbed binds ------------------------------------------------------ @@ -370,7 +382,8 @@ set preset = event PRESET_TABS @cbind gs<preset save:>_ = @preset save %s @cbind glo<preset load:>_ = @preset load %s @cbind gd<preset del:>_ = @preset del %s -@cbind gli = @preset list +# This doesn't work right now. +#@cbind gli = @preset list # === Context menu items ===================================================== diff --git a/examples/data/style.css b/examples/config/style.css index ff055d1..a368aa0 100644 --- a/examples/data/style.css +++ b/examples/config/style.css @@ -1,12 +1,11 @@ #uzbl_link_hints > span { z-index: 1000 !important; - background-color: #aaff00 !important; - border: 2px solid #556600 !important; - margin: 0 !important; - padding: 1px !important; + background-color: #333 !important; + margin: 0 !important; + padding: 3px !important; - color: black !important; + color: #ccc !important; font-size: 9px !important; line-height: 9px !important; font-weight: bold !important; @@ -14,12 +13,16 @@ text-decoration: none !important; -webkit-transform: translate(-5px,-5px); - /* opacity: 0.7; */ + opacity: 0.8; + -webkit-border-radius: 6px !important; + /* Play around with this, pretty fun things to do :) */ + /* -webkit-transform: scale(1) rotate(0deg) translate(-6px,-5px) !important; */ } /* we can have different colours for different types of hints! */ #uzbl_link_hints.new-window > span { background-color: #ffff00 !important; + color: black !important; } /* vim:set et ts=4: */ diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py index 69fd863..fc8b392 100644 --- a/examples/data/plugins/bind.py +++ b/examples/data/plugins/bind.py @@ -372,11 +372,11 @@ def mode_changed(uzbl, mode): uzbl.bindlet.reset() -def match_and_exec(uzbl, bind, depth, keylet, bindlet): +def match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet): (on_exec, has_args, mod_cmd, glob, more) = bind[depth] cmd = keylet.modcmd if mod_cmd else keylet.keycmd - if mod_cmd and keylet.held != mod_cmd: + if mod_cmd and modstate != mod_cmd: return False if has_args: @@ -415,7 +415,7 @@ def match_and_exec(uzbl, bind, depth, keylet, bindlet): return True -def key_event(uzbl, keylet, mod_cmd=False, on_exec=False): +def key_event(uzbl, modstate, keylet, mod_cmd=False, on_exec=False): bindlet = uzbl.bindlet depth = bindlet.depth for bind in bindlet.get_binds(): @@ -423,7 +423,7 @@ def key_event(uzbl, keylet, mod_cmd=False, on_exec=False): if (bool(t[MOD_CMD]) != mod_cmd) or (t[ON_EXEC] != on_exec): continue - if match_and_exec(uzbl, bind, depth, keylet, bindlet): + if match_and_exec(uzbl, bind, depth, modstate, keylet, bindlet): return bindlet.after() diff --git a/examples/data/plugins/history.py b/examples/data/plugins/history.py index 5e9e4e1..f42f86f 100644 --- a/examples/data/plugins/history.py +++ b/examples/data/plugins/history.py @@ -83,7 +83,7 @@ class History(object): def __str__(self): return "(History %s, %s)" % (self.cursor, self.prompt) -def keycmd_exec(uzbl, keylet): +def keycmd_exec(uzbl, modstate, keylet): cmd = keylet.get_keycmd() if cmd: uzbl.history.add(cmd) diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py index 928c597..1bb70e3 100644 --- a/examples/data/plugins/keycmd.py +++ b/examples/data/plugins/keycmd.py @@ -17,13 +17,10 @@ def uzbl_escape(str): class Keylet(object): - '''Small per-instance object that tracks all the keys held and characters - typed.''' + '''Small per-instance object that tracks characters typed.''' def __init__(self): # Modcmd tracking - self.held = set() - self.ignored = set() self.modcmd = '' self.is_modcmd = False @@ -33,7 +30,6 @@ class Keylet(object): self.modmaps = {} self.ignores = {} - self.additions = {} def get_keycmd(self): @@ -48,7 +44,7 @@ class Keylet(object): if not self.is_modcmd: return '' - return ''.join(self.held) + self.modcmd + return self.modcmd def modmap_key(self, key): @@ -65,28 +61,6 @@ class Keylet(object): 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.''' @@ -104,9 +78,6 @@ class Keylet(object): 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()) @@ -132,10 +103,7 @@ def add_modmap(uzbl, key, map): assert len(key) modmaps = uzbl.keylet.modmaps - if key[0] == "<" and key[-1] == ">": - key = key[1:-1] - - modmaps[key] = map + modmaps[key.strip('<>')] = map.strip('<>') uzbl.event("NEW_MODMAP", key, map) @@ -171,45 +139,6 @@ def add_key_ignore(uzbl, glob): 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 <Shift> <Control> <Meta> - @mod_addition <Left> <Up> <Left-Up> - @mod_addition <Right> <Up> <Right-Up> - ... - - Then: - @bind <Right-Up> = <command1> - @bind <Meta>o = <command2> - ... - ''' - - additions = uzbl.keylet.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 clear_keycmd(uzbl, *args): '''Clear the keycmd for this uzbl instance.''' @@ -220,15 +149,12 @@ def clear_keycmd(uzbl, *args): uzbl.event('KEYCMD_CLEARED') -def clear_modcmd(uzbl, clear_held=False): +def clear_modcmd(uzbl): '''Clear the modcmd for this uzbl instance.''' k = uzbl.keylet k.modcmd = '' k.is_modcmd = False - if clear_held: - k.ignored = set() - k.held = set() del uzbl.config['modcmd'] uzbl.event('MODCMD_CLEARED') @@ -244,28 +170,22 @@ def clear_current(uzbl): 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): +def update_event(uzbl, modstate, k, execute=True): '''Raise keycmd & modcmd update events.''' - keycmd, modcmd = k.get_keycmd(), k.get_modcmd() + keycmd, modcmd = k.get_keycmd(), ''.join(modstate) + k.get_modcmd() if k.is_modcmd: - uzbl.event('MODCMD_UPDATE', k) + logger.debug('modcmd_update, %s' % modcmd) + uzbl.event('MODCMD_UPDATE', modstate, k) else: - uzbl.event('KEYCMD_UPDATE', k) + logger.debug('keycmd_update, %s' % keycmd) + uzbl.event('KEYCMD_UPDATE', modstate, k) if uzbl.config.get('modcmd_updates', '1') == '1': - new_modcmd = k.get_modcmd() - if not new_modcmd: + new_modcmd = ''.join(modstate) + k.get_modcmd() + if not new_modcmd or not k.is_modcmd: del uzbl.config['modcmd'] elif new_modcmd == modcmd: @@ -293,54 +213,38 @@ def inject_str(str, index, inj): 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.''' - +def parse_key_event(uzbl, key): + ''' Build a set from the modstate part of the event, and pass all keys through modmap ''' keylet = uzbl.keylet - 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) + modstate, key = splitquoted(key) + modstate = set(['<%s>' % keylet.modmap_key(k) for k in modstate.split('|') if k]) + + key = keylet.modmap_key(key) + return modstate, key def key_press(uzbl, key): '''Handle KEY_PRESS events. Things done by this function include: 1. Ignore all shift key presses (shift can be detected by capital chars) - 3. In non-modcmd mode: + 2. 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.''' + 3. If not in modcmd mode and a modkey was pressed set modcmd mode. + 4. Keycmd is updated and events raised if anything is changed.''' - (k, key) = get_keylet_and_key(uzbl, key.strip()) - if not key: - return + k = uzbl.keylet + modstate, key = parse_key_event(uzbl, key) + k.is_modcmd = any(not k.key_ignored(m) for m in modstate) - if key.lower() == '<space>' and not k.held and k.keycmd: + logger.debug('key press modstate=%s' % str(modstate)) + if key.lower() == 'space' and not k.is_modcmd and k.keycmd: k.keycmd = inject_str(k.keycmd, k.cursor, ' ') k.cursor += 1 - elif not k.held and len(key) == 1: - + elif not k.is_modcmd and len(key) == 1: if uzbl.config.get('keycmd_events', '1') != '1': + # TODO, make a note on what's going on here k.keycmd = '' k.cursor = 0 del uzbl.config['keycmd'] @@ -349,33 +253,29 @@ def key_press(uzbl, key): 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) + elif len(key) == 1: + k.modcmd += key else: - k.is_modcmd = True - k.modcmd += key + if not k.key_ignored('<%s>' % key): + modstate.add('<%s>' % key) + k.is_modcmd = True - update_event(uzbl, k) + update_event(uzbl, modstate, 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) + 1. If in a mod-command then raise a MODCMD_EXEC. + 2. Update the keycmd uzbl variable if anything changed.''' + k = uzbl.keylet + modstate, key = parse_key_event(uzbl, key) - if key in k.held: + if len(key) > 1: if k.is_modcmd: - uzbl.event('MODCMD_EXEC', k) + uzbl.event('MODCMD_EXEC', modstate, k) - k.held.remove(key) clear_modcmd(uzbl) @@ -385,7 +285,7 @@ def set_keycmd(uzbl, keycmd): k = uzbl.keylet k.keycmd = keycmd k.cursor = len(keycmd) - update_event(uzbl, k, False) + update_event(uzbl, set(), k, False) def inject_keycmd(uzbl, keycmd): @@ -394,7 +294,7 @@ def inject_keycmd(uzbl, keycmd): k = uzbl.keylet k.keycmd = inject_str(k.keycmd, k.cursor, keycmd) k.cursor += len(keycmd) - update_event(uzbl, k, False) + update_event(uzbl, set(), k, False) def append_keycmd(uzbl, keycmd): @@ -403,23 +303,29 @@ def append_keycmd(uzbl, keycmd): k = uzbl.keylet k.keycmd += keycmd k.cursor = len(k.keycmd) - update_event(uzbl, k, False) + update_event(uzbl, set(), k, False) -def keycmd_strip_word(uzbl, sep): +def keycmd_strip_word(uzbl, seps): ''' Removes the last word from the keycmd, similar to readline ^W ''' - sep = sep or ' ' + seps = seps or ' ' k = uzbl.keylet if not k.keycmd: return - head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] - rfind = head.rfind(sep) + head, tail = k.keycmd[:k.cursor].rstrip(seps), k.keycmd[k.cursor:] + rfind = -1 + for sep in seps: + p = head.rfind(sep) + if p >= 0 and rfind < p + 1: + rfind = p + 1 + if rfind == len(head) and head[-1] in seps: + rfind -= 1 head = head[:rfind] if rfind + 1 else '' k.keycmd = head + tail k.cursor = len(head) - update_event(uzbl, k, False) + update_event(uzbl, set(), k, False) def keycmd_backspace(uzbl, *args): @@ -431,7 +337,7 @@ def keycmd_backspace(uzbl, *args): k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] k.cursor -= 1 - update_event(uzbl, k, False) + update_event(uzbl, set(), k, False) def keycmd_delete(uzbl, *args): @@ -442,14 +348,14 @@ def keycmd_delete(uzbl, *args): return k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] - update_event(uzbl, k, False) + update_event(uzbl, set(), k, False) def keycmd_exec_current(uzbl, *args): '''Raise a KEYCMD_EXEC with the current keylet and then clear the keycmd.''' - uzbl.event('KEYCMD_EXEC', uzbl.keylet) + uzbl.event('KEYCMD_EXEC', set(), uzbl.keylet) clear_keycmd(uzbl) @@ -476,7 +382,7 @@ def set_cursor_pos(uzbl, index): cursor = len(k.keycmd) k.cursor = cursor - update_event(uzbl, k, False) + update_event(uzbl, set(), k, False) # plugin init hook @@ -485,8 +391,6 @@ def init(uzbl): connect_dict(uzbl, { 'APPEND_KEYCMD': append_keycmd, - 'FOCUS_GAINED': focus_changed, - 'FOCUS_LOST': focus_changed, 'IGNORE_KEY': add_key_ignore, 'INJECT_KEYCMD': inject_keycmd, 'KEYCMD_BACKSPACE': keycmd_backspace, @@ -496,7 +400,8 @@ def init(uzbl): 'KEYCMD_CLEAR': clear_keycmd, 'KEY_PRESS': key_press, 'KEY_RELEASE': key_release, - 'MODKEY_ADDITION': modkey_addition_parse, + 'MOD_PRESS': key_press, + 'MOD_RELEASE': key_release, 'MODMAP': modmap_parse, 'SET_CURSOR_POS': set_cursor_pos, 'SET_KEYCMD': set_keycmd, @@ -504,7 +409,6 @@ def init(uzbl): export_dict(uzbl, { 'add_key_ignore': add_key_ignore, - 'add_modkey_addition': add_modkey_addition, 'add_modmap': add_modmap, 'append_keycmd': append_keycmd, 'clear_current': clear_current, diff --git a/examples/data/scripts/auth.py b/examples/data/scripts/auth.py index 592a2c6..49fa41e 100755 --- a/examples/data/scripts/auth.py +++ b/examples/data/scripts/auth.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python import gtk import sys diff --git a/examples/data/scripts/download.sh b/examples/data/scripts/download.sh index c410ad2..dbc9caf 100755 --- a/examples/data/scripts/download.sh +++ b/examples/data/scripts/download.sh @@ -1,25 +1,63 @@ #!/bin/sh -# # uzbl's example configuration sets this script up as its download_handler. -# when uzbl starts a download it runs this script. +# this script is run when uzbl encounters a URL that it can't display, and when +# a download is requested using the 'download' command. +# # if the script prints a file path to stdout, uzbl will save the download to -# that path. -# if nothing is printed to stdout, the download will be cancelled. +# that path using it's internal downloader. +# +# if nothing is printed to stdout, the internal download will be cancelled. +# you could do your own download handling in your script that way. -. $UZBL_UTIL_DIR/uzbl-dir.sh +# if $5 is set, it is the path that was passed to uzbl's "download" command. +# we want to use that if it's available. +[ -n "$5" ] && echo "$5" && exit + +. "$UZBL_UTIL_DIR/uzbl-dir.sh" # the URL that is being downloaded -uri=$1 +uri="$1" +shift + +safe_uri="$( echo "$uri" | sed -e 's/\W/-/g' )" # a filename suggested by the server or based on the URL -suggested_filename=${2:-$(echo "$uri" | sed 's/\W/-/g')} +suggested_filename="${1:-$safe_uri}" +shift # the mimetype of the file being downloaded -content_type=$3 +content_type="$1" +shift # the size of the downloaded file in bytes. this is not always accurate, since # the server might not have sent a size with its response headers. -total_size=$4 +total_size="$1" +shift + +case "$suggested_filename" in + # example: save torrents to a separate directory + #*.torrent) + # path="$UZBL_DOWNLOAD_DIR/torrents/$suggested_filename" + # ;; + # Default case + *) + path="$UZBL_DOWNLOAD_DIR/$suggested_filename" + ;; +esac + +# Do nothing if we don't want to save the file +[ -z "$path" ] && exit 0 + +# Check if the file exists +if [ ! -e "$path" ]; then + echo "$path" + exit 0 +fi + +# Try to make a unique filename +count=1 +while [ -e "$path.$count" ]; do + count=$(( $count + 1 )) +done -# just save the file to the default directory with the suggested name -echo $UZBL_DOWNLOAD_DIR/$suggested_filename +echo "$path.$count" diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js index 536256b..b7b0d82 100644 --- a/examples/data/scripts/follow.js +++ b/examples/data/scripts/follow.js @@ -98,12 +98,17 @@ uzbl.follow.elementInViewport = function(el) { } // Removes all hints/leftovers that might be generated -// by this script. -uzbl.follow.removeAllHints = function(doc) { +// by this script in the given document. +uzbl.follow.removeHints = function(doc) { var elements = doc.getElementById(uzbldivid); if (elements) elements.parentNode.removeChild(elements); } +// Clears all hints in every document +uzbl.follow.clearHints = function() { + this.documents().forEach(uzbl.follow.removeHints); +} + // Generate a hint for an element with the given label // Here you can play around with the style of the hints! uzbl.follow.generateHint = function(doc, el, label, top, left) { @@ -153,7 +158,7 @@ uzbl.follow.reDrawHints = function(elems, chars) { var positions = elements.map(uzbl.follow.elementPosition); this.documents().forEach(function(doc) { - uzbl.follow.removeAllHints(doc); + uzbl.follow.removeHints(doc); if (!doc.body) return; doc.hintdiv = doc.createElement('div'); doc.hintdiv.id = uzbldivid; @@ -211,7 +216,7 @@ uzbl.follow.followLinks = function(follow) { var s = follow.split(''); var linknr = this.labelToInt(follow); - var followable = 'a, area, textarea, select, input:not([type=hidden]), button'; + var followable = 'a, area, textarea, select, input:not([type=hidden]), button, *[onclick]'; var uri = 'a, area, frame, iframe'; //var focusable = 'a, area, textarea, select, input:not([type=hidden]), button, frame, iframe, applet, object'; //var desc = '*[title], img[alt], applet[alt], area[alt], input[alt]'; @@ -230,7 +235,7 @@ uzbl.follow.followLinks = function(follow) { var el = elems[linknr]; // clear all of our hints - this.documents().forEach(uzbl.follow.removeAllHints); + this.clearHints(); if (newwindow) { // we're opening a new window using the URL attached to this element diff --git a/examples/data/scripts/follow.sh b/examples/data/scripts/follow.sh index 1f8947d..014793e 100755 --- a/examples/data/scripts/follow.sh +++ b/examples/data/scripts/follow.sh @@ -2,10 +2,12 @@ # This scripts acts on the return value of followLinks in follow.js case "$1" in - XXXEMIT_FORM_ACTIVEXXX) - # a form element was selected - printf 'event FORM_ACTIVE\nevent KEYCMD_CLEAR\n' > "$UZBL_FIFO" ;; - XXXRESET_MODEXXX) - # a link was selected, reset uzbl's input mode - printf 'set mode=\nevent KEYCMD_CLEAR\n' > "$UZBL_FIFO" ;; + XXXEMIT_FORM_ACTIVEXXX) + # a form element was selected + printf 'event FORM_ACTIVE\nevent KEYCMD_CLEAR\n' > "$UZBL_FIFO" + ;; + XXXRESET_MODEXXX) + # a link was selected, reset uzbl's input mode + printf 'set mode=\nevent KEYCMD_CLEAR\n' > "$UZBL_FIFO" + ;; esac diff --git a/examples/data/scripts/formfiller.sh b/examples/data/scripts/formfiller.sh index 3dc9dc4..c1171a0 100755 --- a/examples/data/scripts/formfiller.sh +++ b/examples/data/scripts/formfiller.sh @@ -66,22 +66,22 @@ ParseFields () field = $0 sub ( /[^:]*:/, "", field ) - if ( parts[2] ~ /(text|password|search)/ ) + if ( parts[2] ~ /^(text|password|search)$/ ) printf( "js uzbl.formfiller.insert(\"%s\",\"%s\",\"%s\",0);\n", parts[1], parts[2], field ) - else if ( parts[2] ~ /(checkbox|radio)/ ) + else if ( parts[2] ~ /^(checkbox|radio)$/ ) printf( "js uzbl.formfiller.insert(\"%s\",\"%s\",\"%s\",%s);\n", parts[1], parts[2], parts[3], field ) - else if ( parts[2] == "textarea" ) { + else if ( parts[2] ~ /^textarea$/ ) { field = "" while (getline) { if ( /^%/ ) break sub ( /^\\/, "" ) gsub ( /"/, "\\\"" ) gsub ( /\\/, "\\\\" ) - field = field $0 "\\n" + field = field $0 "\\\\n" } printf( "js uzbl.formfiller.insert(\"%s\",\"%s\",\"%s\",0);\n", parts[1], parts[2], field ) @@ -116,7 +116,7 @@ Load () ParseProfile $option < "$file" \ | ParseFields \ - | sed 's/@/\\@/' \ + | sed 's/@/\\@/g' \ > "$UZBL_FIFO" } @@ -132,7 +132,7 @@ Once () test -e "$tmpfile" && ParseFields < "$tmpfile" \ - | sed 's/@/\\@' \ + | sed 's/@/\\@/g' \ > "$UZBL_FIFO" } diff --git a/examples/data/scripts/go_input.sh b/examples/data/scripts/go_input.sh index ace0e79..9797788 100755 --- a/examples/data/scripts/go_input.sh +++ b/examples/data/scripts/go_input.sh @@ -1,5 +1,7 @@ #!/bin/sh -case $(echo 'script @scripts_dir/go_input.js' | socat - unix-connect:"$UZBL_SOCKET") in - *XXXEMIT_FORM_ACTIVEXXX*) echo 'event FORM_ACTIVE' > "$UZBL_FIFO" ;; +case "$( echo "script @scripts_dir/go_input.js" | socat - "unix-connect:$UZBL_SOCKET" )" in + *XXXEMIT_FORM_ACTIVEXXX*) + echo "event FORM_ACTIVE" > "$UZBL_FIFO" + ;; esac diff --git a/examples/data/scripts/history.sh b/examples/data/scripts/history.sh index 266d65d..0709b5e 100755 --- a/examples/data/scripts/history.sh +++ b/examples/data/scripts/history.sh @@ -1,7 +1,7 @@ #!/bin/sh -. $UZBL_UTIL_DIR/uzbl-dir.sh +. "$UZBL_UTIL_DIR/uzbl-dir.sh" -[ -w "$UZBL_HISTORY_FILE" ] || [ ! -a "$UZBL_HISTORY_FILE" ] || exit 1 +>> "$UZBL_HISTORY_FILE" || exit 1 -echo $(date +'%Y-%m-%d %H:%M:%S')" $UZBL_URI $UZBL_TITLE" >> $UZBL_HISTORY_FILE +echo "$( date +'%Y-%m-%d %H:%M:%S' ) $UZBL_URI $UZBL_TITLE" >> "$UZBL_HISTORY_FILE" diff --git a/examples/data/scripts/insert_bookmark.sh b/examples/data/scripts/insert_bookmark.sh index f67e67a..f310e49 100755 --- a/examples/data/scripts/insert_bookmark.sh +++ b/examples/data/scripts/insert_bookmark.sh @@ -1,15 +1,14 @@ #!/bin/sh -. "$UZBL_UTIL_DIR"/uzbl-dir.sh +. "$UZBL_UTIL_DIR/uzbl-dir.sh" -[ -d "$UZBL_DATA_DIR" ] || exit 1 -[ -w "$UZBL_BOOKMARKS_FILE" ] || [ ! -a "$UZBL_BOOKMARKS_FILE" ] || exit 1 +>> "$UZBL_BOOKMARKS_FILE" || exit 1 which zenity >/dev/null 2>&1 || exit 2 -tags=$(zenity --entry --text="Enter space-separated tags for bookmark $UZBL_URI:") -exitstatus=$? -[ $exitstatus -eq 0 ] || exit $exitstatus +tags="$( zenity --entry --text="Enter space-separated tags for bookmark $UZBL_URI:" )" +exitstatus="$?" +[ "$exitstatus" -eq 0 ] || exit "$exitstatus" # TODO: check if already exists, if so, and tags are different: ask if you want to replace tags echo "$UZBL_URI $tags" >> "$UZBL_BOOKMARKS_FILE" diff --git a/examples/data/scripts/insert_temp.sh b/examples/data/scripts/insert_temp.sh new file mode 100755 index 0000000..7ed8d22 --- /dev/null +++ b/examples/data/scripts/insert_temp.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. "$UZBL_UTIL_DIR/uzbl-dir.sh" + +>> "$UZBL_TEMPS_FILE" || exit 1 + +echo "$UZBL_URI $UZBL_TITLE" >> "$UZBL_TEMPS_FILE" diff --git a/examples/data/scripts/instance-select-wmii.sh b/examples/data/scripts/instance-select-wmii.sh index 19d04e8..b2aadbb 100755 --- a/examples/data/scripts/instance-select-wmii.sh +++ b/examples/data/scripts/instance-select-wmii.sh @@ -1,6 +1,5 @@ #!/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 @@ -13,30 +12,30 @@ DMENU_SCHEME="wmii" -. $UZBL_UTIL_DIR/dmenu.sh +. "$UZBL_UTIL_DIR/dmenu.sh" case "$1" in - "list" ) - list= + "list") + 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) + for i in $( wmiir read /tag/sel/index | grep uzbl | cut -d ' ' -f 2 ); do + label="$( wmiir read /client/$i/label )" list="$list$i : $label\n" done - window=$(printf "$list\n" | $DMENU | cut -d ' ' -f1) + window="$( echo "$list" | $DMENU | cut -d ' ' -f 1 )" wmiir xwrite /tag/sel/ctl "select client $window" ;; - "next" ) - current=$(wmiir read /client/sel/ctl | head -n 1) + "next") + 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) + next="$( wmiir read /tag/sel/index | grep -A 10000 " $current " | grep -m 1 uzbl | cut -d ' ' -f 2 )" if [ -n "$next" ]; then wmiir xwrite /tag/sel/ctl "select client $next" fi ;; - "prev" ) - 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) + "prev") + 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 ' ' -f 2 )" if [ -n "$prev" ]; then wmiir xwrite /tag/sel/ctl "select client $prev" fi diff --git a/examples/data/scripts/load_cookies.sh b/examples/data/scripts/load_cookies.sh index 17ec2ad..c7fcc58 100755 --- a/examples/data/scripts/load_cookies.sh +++ b/examples/data/scripts/load_cookies.sh @@ -1,20 +1,21 @@ #!/bin/sh -if [ "$1" != "" ]; then - cookie_file=$1 +if [ -n "$1" ]; then + cookie_file="$1" + shift else - cookie_file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/cookies.txt + cookie_file="${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/cookies.txt" fi awk -F \\t ' BEGIN { - scheme["TRUE"] = "https"; - scheme["FALSE"] = "http"; + scheme["TRUE"] = "https"; + scheme["FALSE"] = "http"; } $0 ~ /^#HttpOnly_/ { -printf("add_cookie \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n", substr($1,length("#HttpOnly_"),length($1)), $3, $6, $7, scheme[$4], $5) + printf("add_cookie \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n", substr($1,length("#HttpOnly_"),length($1)), $3, $6, $7, scheme[$4], $5) } $0 !~ /^#/ { -printf("add_cookie \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n", $1, $3, $6, $7, scheme[$4], $5) + printf("add_cookie \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n", $1, $3, $6, $7, scheme[$4], $5) } -' $cookie_file +' "$cookie_file" diff --git a/examples/data/scripts/load_url_from_bookmarks.sh b/examples/data/scripts/load_url_from_bookmarks.sh index a5d9586..a03db4b 100755 --- a/examples/data/scripts/load_url_from_bookmarks.sh +++ b/examples/data/scripts/load_url_from_bookmarks.sh @@ -5,18 +5,18 @@ DMENU_SCHEME="bookmarks" DMENU_OPTIONS="xmms vertical resize" -. "$UZBL_UTIL_DIR"/dmenu.sh -. "$UZBL_UTIL_DIR"/uzbl-dir.sh +. "$UZBL_UTIL_DIR/dmenu.sh" +. "$UZBL_UTIL_DIR/uzbl-dir.sh" [ -r "$UZBL_BOOKMARKS_FILE" ] || exit 1 if [ -z "$DMENU_HAS_VERTICAL" ]; then # because they are all after each other, just show the url, not their tags. - goto=$(awk '{print $1}' "$UZBL_BOOKMARKS_FILE" | $DMENU) + goto="$( awk '{ print $1 }' "$UZBL_BOOKMARKS_FILE" | $DMENU )" else # show tags as well - goto=$($DMENU < "$UZBL_BOOKMARKS_FILE" | awk '{print $1}') + goto="$( $DMENU < "$UZBL_BOOKMARKS_FILE" | cut -d ' ' -f 1 )" fi [ -n "$goto" ] && echo "uri $goto" > "$UZBL_FIFO" -#[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:"$UZBL_SOCKET" +#[ -n "$goto" ] && echo "uri $goto" | socat - "unix-connect:$UZBL_SOCKET" diff --git a/examples/data/scripts/load_url_from_history.sh b/examples/data/scripts/load_url_from_history.sh index 59ad492..24bfdce 100755 --- a/examples/data/scripts/load_url_from_history.sh +++ b/examples/data/scripts/load_url_from_history.sh @@ -3,21 +3,20 @@ DMENU_SCHEME="history" DMENU_OPTIONS="xmms vertical resize" -. "$UZBL_UTIL_DIR"/dmenu.sh -. "$UZBL_UTIL_DIR"/uzbl-dir.sh +. "$UZBL_UTIL_DIR/dmenu.sh" +. "$UZBL_UTIL_DIR/uzbl-dir.sh" [ -r "$UZBL_HISTORY_FILE" ] || exit 1 # choose from all entries, sorted and uniqued -# goto=$(awk '{print $3}' $history_file | sort -u | dmenu -i) if [ -z "$DMENU_HAS_VERTICAL" ]; then - current=$(tail -n 1 "$UZBL_HISTORY_FILE" | awk '{print $3}'); - goto=$( (echo $current; awk '{print $3}' "$UZBL_HISTORY_FILE" | grep -v "^$current\$" | sort -u) | $DMENU) + current="$( tail -n 1 "$UZBL_HISTORY_FILE" | cut -d ' ' -f 3 )" + goto="$( ( echo "$current"; awk '{ print $3 }' "$UZBL_HISTORY_FILE" | grep -v "^$current\$" | sort -u ) | $DMENU )" else # 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 "$UZBL_HISTORY_FILE" | $DMENU | cut -d ' ' -f -3 | awk '{print $NF}') + goto="$( tac "$UZBL_HISTORY_FILE" | $DMENU | cut -d ' ' -f -3 | awk '{ print $NF }' )" fi [ -n "$goto" ] && echo "uri $goto" > "$UZBL_FIFO" -#[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:"$UZBL_SOCKET" +#[ -n "$goto" ] && echo "uri $goto" | socat - "unix-connect:$UZBL_SOCKET" diff --git a/examples/data/scripts/load_url_from_temps.sh b/examples/data/scripts/load_url_from_temps.sh new file mode 100755 index 0000000..b46687b --- /dev/null +++ b/examples/data/scripts/load_url_from_temps.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +DMENU_SCHEME="temps" +DMENU_OPTIONS="xmms vertical resize" + +. "$UZBL_UTIL_DIR/dmenu.sh" +. "$UZBL_UTIL_DIR/uzbl-dir.sh" + +[ -r "$UZBL_TEMPS_FILE" ] || exit 1 + +if [ -z "$DMENU_HAS_VERTICAL" ]; then + # because they are all after each other, just show the url, not their titles. + goto=$( awk '{ print $1 }' "$UZBL_TEMPS_FILE" | $DMENU ) +else + # show titles + goto=$( $DMENU < "$UZBL_TEMPS_FILE" | cut -d ' ' -f 1 ) +fi + +sed -i -e "\<^$goto <d" $UZBL_TEMPS_FILE + +[ -n "$goto" ] && echo "uri $goto" > "$UZBL_FIFO" +#[ -n "$goto" ] && echo "uri $goto" | socat - "unix-connect:$UZBL_SOCKET" diff --git a/examples/data/scripts/session.sh b/examples/data/scripts/session.sh index ee09cf2..4e7bfd1 100755 --- a/examples/data/scripts/session.sh +++ b/examples/data/scripts/session.sh @@ -18,28 +18,30 @@ if [ -z "$UZBL_UTIL_DIR" ]; then # we're being run standalone, we have to figure out where $UZBL_UTIL_DIR is # using the same logic as uzbl-browser does. - UZBL_UTIL_DIR=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts/util + UZBL_UTIL_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts/util" if ! [ -d "$UZBL_UTIL_DIR" ]; then - PREFIX=$(grep '^PREFIX' "$(which uzbl-browser)" | sed 's/.*=//') - UZBL_UTIL_DIR=$PREFIX/share/uzbl/examples/data/scripts/util + PREFIX="$( grep '^PREFIX' "$( which uzbl-browser )" | sed -e 's/.*=//' )" + UZBL_UTIL_DIR="$PREFIX/share/uzbl/examples/data/scripts/util" fi fi -. "$UZBL_UTIL_DIR"/uzbl-dir.sh +. "$UZBL_UTIL_DIR/uzbl-dir.sh" + [ -d "$UZBL_DATA_DIR" ] || exit 1 -UZBL="uzbl-browser -c $UZBL_CONFIG_FILE" # add custom flags and whatever here. +UZBL="uzbl-browser -c \"$UZBL_CONFIG_FILE\"" # add custom flags and whatever here. -scriptfile=$(readlink -f $0) # this script +scriptfile="$( readlink -f "$0" )" # this script act="$1" +shift if [ -z "$act" ]; then [ -f "$UZBL_SESSION_FILE" ] && act="launch" || act="endsession" fi case $act in - "launch" ) - urls=$(cat "$UZBL_SESSION_FILE") + "launch") + urls="$( cat "$UZBL_SESSION_FILE" )" if [ -z "$urls" ]; then $UZBL else @@ -50,17 +52,17 @@ case $act in fi ;; - "endinstance" ) + "endinstance") if [ -z "$UZBL_FIFO" ]; then echo "session manager: endinstance must be called from uzbl" exit 1 fi [ "$UZBL_URI" != "(null)" ] && echo "$UZBL_URI" >> "$UZBL_SESSION_FILE" - echo exit > "$UZBL_FIFO" + echo "exit" > "$UZBL_FIFO" ;; - "endsession" ) - for fifo in "$UZBL_FIFO_DIR"/uzbl_fifo_*; do + "endsession") + for fifo in "$UZBL_FIFO_DIR/uzbl_fifo_*"; do if [ "$fifo" != "$UZBL_FIFO" ]; then echo "spawn $scriptfile endinstance" > "$fifo" fi @@ -68,7 +70,7 @@ case $act in [ -z "$UZBL_FIFO" ] || echo "spawn $scriptfile endinstance" > "$UZBL_FIFO" ;; - * ) + *) echo "session manager: bad action" echo "Usage: $scriptfile [COMMAND] where commands are:" echo " launch - Restore a saved session or start a new one" diff --git a/examples/data/scripts/util/dmenu.sh b/examples/data/scripts/util/dmenu.sh index 354d7d1..0b7272e 100644 --- a/examples/data/scripts/util/dmenu.sh +++ b/examples/data/scripts/util/dmenu.sh @@ -30,6 +30,13 @@ case "$DMENU_SCHEME" in SB="#ccffaa" SF="#303030" ;; + # Temps + "temps" ) + NB="#303030" + NF="khaki" + SB="#ccffaa" + SF="#303030" + ;; # Default * ) NB="#303030" @@ -98,7 +105,7 @@ if dmenu --help 2>&1 | grep -q '\[-l <\?lines>\?\]'; then fi # Detect placement patch -if dmenu --help 2>&1 | grep -q '\[-x <xoffset>\]'; then +if dmenu --help 2>&1 | grep -q '\[-x <\?xoffset>\?\]'; then DMENU_PLACE_X="-x" DMENU_PLACE_Y="-y" DMENU_PLACE_WIDTH="-w" diff --git a/examples/data/scripts/util/uzbl-dir.sh b/examples/data/scripts/util/uzbl-dir.sh index bb56954..82510d8 100644 --- a/examples/data/scripts/util/uzbl-dir.sh +++ b/examples/data/scripts/util/uzbl-dir.sh @@ -2,18 +2,19 @@ # Common directories and files used in scripts # Common things first -UZBL_DATA_DIR=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl -UZBL_CONFIG_DIR=${XDG_CONFIG_DIR:-$HOME/.config}/uzbl +UZBL_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/uzbl" +UZBL_CONFIG_DIR="${XDG_CONFIG_DIR:-$HOME/.config}/uzbl" UZBL_FIFO_DIR=/tmp UZBL_SOCKET_DIR=/tmp # Directories -UZBL_DOWNLOAD_DIR=${XDG_DOWNLOAD_DIR:-$HOME} -UZBL_FORMS_DIR=$UZBL_DATA_DIR/dforms +UZBL_DOWNLOAD_DIR="${XDG_DOWNLOAD_DIR:-$HOME}" +UZBL_FORMS_DIR="$UZBL_DATA_DIR/dforms" # Data files -UZBL_CONFIG_FILE=$UZBL_CONFIG_DIR/config -UZBL_COOKIE_FILE=$UZBL_DATA_DIR/cookies.txt -UZBL_BOOKMARKS_FILE=$UZBL_DATA_DIR/bookmarks -UZBL_HISTORY_FILE=$UZBL_DATA_DIR/history -UZBL_SESSION_FILE=$UZBL_DATA_DIR/browser-session +UZBL_CONFIG_FILE="$UZBL_CONFIG_DIR/config" +UZBL_COOKIE_FILE="$UZBL_DATA_DIR/cookies.txt" +UZBL_BOOKMARKS_FILE="$UZBL_DATA_DIR/bookmarks" +UZBL_TEMPS_FILE="$UZBL_DATA_DIR/temps" +UZBL_HISTORY_FILE="$UZBL_DATA_DIR/history" +UZBL_SESSION_FILE="$UZBL_DATA_DIR/browser-session" diff --git a/examples/data/scripts/util/uzbl-window.sh b/examples/data/scripts/util/uzbl-window.sh index a7e92eb..4b96372 100644 --- a/examples/data/scripts/util/uzbl-window.sh +++ b/examples/data/scripts/util/uzbl-window.sh @@ -1,11 +1,11 @@ #!/bin/sh # uzbl window detection -UZBL_WIN_POS=$(xwininfo -id $UZBL_XID | \ - sed -ne 's/Corners:[ ]*[+-]\([0-9]*\)[+-]\([0-9]*\).*$/\1 \2/p') -UZBL_WIN_SIZE=$(xwininfo -id $UZBL_XID | \ - sed -ne 's/-geometry[ ]*\([0-9]*\)x\([0-9]*\).*$/\1 \2/p') -UZBL_WIN_POS_X=$(echo $UZBL_WIN_POS | cut -d\ -f1) -UZBL_WIN_POS_Y=$(echo $UZBL_WIN_POS | cut -d\ -f2) -UZBL_WIN_WIDTH=$(echo $UZBL_WIN_SIZE | cut -d\ -f1) -UZBL_WIN_HEIGHT=$(echo $UZBL_WIN_SIZE | cut -d\ -f2) +UZBL_WIN_POS="$( xwininfo -id "$UZBL_XID" | \ + sed -n -e '[ ]*s/Corners:[ ]*[+-]\([0-9]*\)[+-]\([0-9]*\).*$/\1 \2/p' )" +UZBL_WIN_SIZE="$( xwininfo -id "$UZBL_XID" | \ + sed -n -e '[ ]*s/-geometry[ ]*\([0-9]*\)x\([0-9]*\).*$/\1 \2/p' )" +UZBL_WIN_POS_X="$( echo "$UZBL_WIN_POS" | cut -d ' ' -f 1 )" +UZBL_WIN_POS_Y="$( echo "$UZBL_WIN_POS" | cut -d ' ' -f 2 )" +UZBL_WIN_WIDTH="$( echo "$UZBL_WIN_SIZE" | cut -d ' ' -f 1 )" +UZBL_WIN_HEIGHT="$( echo "$UZBL_WIN_SIZE" | cut -d ' ' -f 2 )" diff --git a/examples/data/scripts/uzbl-event-manager b/examples/data/scripts/uzbl-event-manager deleted file mode 100755 index cb462c7..0000000 --- a/examples/data/scripts/uzbl-event-manager +++ /dev/null @@ -1,991 +0,0 @@ -#!/usr/bin/env python - -# Event Manager for Uzbl -# Copyright (c) 2009-2010, Mason Larobina <mason.larobina@gmail.com> -# Copyright (c) 2009, Dieter Plaetinck <dieter@plaetinck.be> -# -# 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 <http://www.gnu.org/licenses/>. - -''' - -E V E N T _ M A N A G E R . P Y -=============================== - -Event manager for uzbl written in python. - -''' - -import atexit -import imp -import logging -import os -import socket -import sys -import time -import weakref -import re -from collections import defaultdict -from functools import partial -from glob import glob -from itertools import count -from optparse import OptionParser -from select import select -from signal import signal, SIGTERM, SIGINT -from socket import socket, AF_UNIX, SOCK_STREAM -from traceback import format_exc - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return os.path.join(os.environ['HOME'], default) - -# `make install` will put the correct value here for your system -PREFIX = '/usr/local/' - -# Setup xdg paths. -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') -CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') - -# Define some globals. -SCRIPTNAME = os.path.basename(sys.argv[0]) - -def get_exc(): - '''Format `format_exc` for logging.''' - return "\n%s" % format_exc().rstrip() - -def expandpath(path): - '''Expand and realpath paths.''' - return os.path.realpath(os.path.expandvars(path)) - -def ascii(u): - '''Convert unicode strings into ascii for transmission over - ascii-only streams/sockets/devices.''' - return u.encode('utf-8') - - -def daemonize(): - '''Daemonize the process using the Stevens' double-fork magic.''' - - logger.info('entering daemon mode') - - try: - if os.fork(): - os._exit(0) - - except OSError: - logger.critical(get_exc()) - sys.exit(1) - - os.chdir('/') - os.setsid() - os.umask(0) - - try: - if os.fork(): - os._exit(0) - - except OSError: - logger.critical(get_exc()) - sys.exit(1) - - if sys.stdout.isatty(): - sys.stdout.flush() - sys.stderr.flush() - - devnull = '/dev/null' - stdin = file(devnull, 'r') - stdout = file(devnull, 'a+') - stderr = file(devnull, 'a+', 0) - - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - logger.info('entered daemon mode') - - -def make_dirs(path): - '''Make all basedirs recursively as required.''' - - try: - dirname = os.path.dirname(path) - if not os.path.isdir(dirname): - logger.debug('creating directories %r' % dirname) - os.makedirs(dirname) - - except OSError: - logger.error(get_exc()) - - -class EventHandler(object): - '''Event handler class. Used to store args and kwargs which are merged - come time to call the callback with the event args and kwargs.''' - - nextid = count().next - - def __init__(self, plugin, event, callback, args, kwargs): - self.id = self.nextid() - self.plugin = plugin - self.event = event - self.callback = callback - self.args = args - self.kwargs = kwargs - - - def __repr__(self): - elems = ['id=%d' % self.id, 'event=%s' % self.event, - 'callback=%r' % self.callback] - - if self.args: - elems.append('args=%s' % repr(self.args)) - - if self.kwargs: - elems.append('kwargs=%s' % repr(self.kwargs)) - - elems.append('plugin=%s' % self.plugin.name) - return u'<handler(%s)>' % ', '.join(elems) - - - def call(self, uzbl, *args, **kwargs): - '''Execute the handler function and merge argument lists.''' - - args = args + self.args - kwargs = dict(self.kwargs.items() + kwargs.items()) - self.callback(uzbl, *args, **kwargs) - - - - - -class Plugin(object): - '''Plugin module wrapper object.''' - - # Special functions exported from the Plugin instance to the - # plugin namespace. - special_functions = ['require', 'export', 'export_dict', 'connect', - 'connect_dict', 'logger', 'unquote', 'splitquoted'] - - - def __init__(self, parent, name, path, plugin): - self.parent = parent - self.name = name - self.path = path - self.plugin = plugin - self.logger = get_logger('plugin.%s' % name) - - # Weakrefs to all handlers created by this plugin - self.handlers = set([]) - - # Plugins init hook - init = getattr(plugin, 'init', None) - self.init = init if callable(init) else None - - # Plugins optional after hook - after = getattr(plugin, 'after', None) - self.after = after if callable(after) else None - - # Plugins optional cleanup hook - cleanup = getattr(plugin, 'cleanup', None) - self.cleanup = cleanup if callable(cleanup) else None - - assert init or after or cleanup, "missing hooks in plugin" - - # Export plugin's instance methods to plugin namespace - for attr in self.special_functions: - plugin.__dict__[attr] = getattr(self, attr) - - - def __repr__(self): - return u'<plugin(%r)>' % self.plugin - - - def export(self, uzbl, attr, object, prepend=True): - '''Attach `object` to `uzbl` instance. This is the preferred method - of sharing functionality, functions, data and objects between - plugins. - - If the object is callable you may wish to turn the callable object - in to a meta-instance-method by prepending `uzbl` to the call stack. - You can change this behaviour with the `prepend` argument. - ''' - - assert attr not in uzbl.exports, "attr %r already exported by %r" %\ - (attr, uzbl.exports[attr][0]) - - prepend = True if prepend and callable(object) else False - uzbl.__dict__[attr] = partial(object, uzbl) if prepend else object - uzbl.exports[attr] = (self, object, prepend) - uzbl.logger.info('exported %r to %r by plugin %r, prepended %r' - % (object, 'uzbl.%s' % attr, self.name, prepend)) - - - def export_dict(self, uzbl, exports): - for (attr, object) in exports.items(): - self.export(uzbl, attr, object) - - - def find_handler(self, event, callback, args, kwargs): - '''Check if a handler with the identical callback and arguments - exists and return it.''' - - # Remove dead refs - self.handlers -= set(filter(lambda ref: not ref(), self.handlers)) - - # Find existing identical handler - for handler in [ref() for ref in self.handlers]: - if handler.event == event and handler.callback == callback \ - and handler.args == args and handler.kwargs == kwargs: - return handler - - - def connect(self, uzbl, event, callback, *args, **kwargs): - '''Create an event handler object which handles `event` events. - - Arguments passed to the connect function (`args` and `kwargs`) are - stored in the handler object and merged with the event arguments - come handler execution. - - All handler functions must behave like a `uzbl` instance-method (that - means `uzbl` is prepended to the callback call arguments).''' - - # Sanitise and check event name - event = event.upper().strip() - assert event and ' ' not in event - - assert callable(callback), 'callback must be callable' - - # Check if an identical handler already exists - handler = self.find_handler(event, callback, args, kwargs) - if not handler: - # Create a new handler - handler = EventHandler(self, event, callback, args, kwargs) - self.handlers.add(weakref.ref(handler)) - self.logger.info('new %r' % handler) - - uzbl.handlers[event].append(handler) - uzbl.logger.info('connected %r' % handler) - return handler - - - def connect_dict(self, uzbl, connects): - for (event, callback) in connects.items(): - self.connect(uzbl, event, callback) - - - def require(self, plugin): - '''Check that plugin with name `plugin` has been loaded. Use this to - ensure that your plugins dependencies have been met.''' - - assert plugin in self.parent.plugins, self.logger.critical( - 'plugin %r required by plugin %r' (plugin, self.name)) - - @classmethod - def unquote(cls, s): - '''Removes quotation marks around strings if any and interprets - \\-escape sequences using `string_escape`''' - if s and s[0] == s[-1] and s[0] in ['"', "'"]: - s = s[1:-1] - return s.encode('utf-8').decode('string_escape').decode('utf-8') - - _splitquoted = re.compile("( |\"(?:\\\\.|[^\"])*?\"|'(?:\\\\.|[^'])*?')") - @classmethod - def splitquoted(cls, text): - '''Splits string on whitespace while respecting quotations''' - return [cls.unquote(p) for p in cls._splitquoted.split(text) if p.strip()] - - -class Uzbl(object): - def __init__(self, parent, child_socket): - self.opts = opts - self.parent = parent - self.child_socket = child_socket - self.time = time.time() - self.pid = None - self.name = None - - # Flag if the instance has raised the INSTANCE_START event. - self.instance_start = False - - # Use name "unknown" until name is discovered. - self.logger = get_logger('uzbl-instance[]') - - # Track plugin event handlers and exported functions. - self.exports = {} - self.handlers = defaultdict(list) - - # Internal vars - self._depth = 0 - self._buffer = '' - - - def __repr__(self): - return '<uzbl(%s)>' % ', '.join([ - 'pid=%s' % (self.pid if self.pid else "Unknown"), - 'name=%s' % ('%r' % self.name if self.name else "Unknown"), - 'uptime=%f' % (time.time()-self.time), - '%d exports' % len(self.exports.keys()), - '%d handlers' % sum([len(l) for l in self.handlers.values()])]) - - - def init_plugins(self): - '''Call the init and after hooks in all loaded plugins for this - instance.''' - - # Initialise each plugin with the current uzbl instance. - for plugin in self.parent.plugins.values(): - if plugin.init: - self.logger.debug('calling %r plugin init hook' % plugin.name) - plugin.init(self) - - # Allow plugins to use exported features of other plugins by calling an - # optional `after` function in the plugins namespace. - for plugin in self.parent.plugins.values(): - if plugin.after: - self.logger.debug('calling %r plugin after hook'%plugin.name) - plugin.after(self) - - - def send(self, msg): - '''Send a command to the uzbl instance via the child socket - instance.''' - - msg = msg.strip() - assert self.child_socket, "socket inactive" - - if opts.print_events: - print ascii(u'%s<-- %s' % (' ' * self._depth, msg)) - - self.child_socket.send(ascii("%s\n" % msg)) - - - def read(self): - '''Read data from the child socket and pass lines to the parse_msg - function.''' - - try: - raw = unicode(self.child_socket.recv(8192), 'utf-8', 'ignore') - if not raw: - self.logger.debug('read null byte') - return self.close() - - except: - self.logger.error(get_exc()) - return self.close() - - lines = (self._buffer + raw).split('\n') - self._buffer = lines.pop() - - for line in filter(None, map(unicode.strip, lines)): - try: - self.parse_msg(line.strip()) - - except: - self.logger.error(get_exc()) - self.logger.error('erroneous event: %r' % line) - - - def parse_msg(self, line): - '''Parse an incoming message from a uzbl instance. Event strings - will be parsed into `self.event(event, args)`.''' - - # Split by spaces (and fill missing with nulls) - elems = (line.split(' ', 3) + ['',]*3)[:4] - - # Ignore non-event messages. - if elems[0] != 'EVENT': - logger.info('non-event message: %r' % line) - if opts.print_events: - print '--- %s' % ascii(line) - return - - # Check event string elements - (name, event, args) = elems[1:] - assert name and event, 'event string missing elements' - if not self.name: - self.name = name - self.logger = get_logger('uzbl-instance%s' % name) - self.logger.info('found instance name %r' % name) - - assert self.name == name, 'instance name mismatch' - - # Handle the event with the event handlers through the event method - self.event(event, args) - - - def event(self, event, *args, **kargs): - '''Raise an event.''' - - event = event.upper() - - if not opts.daemon_mode and opts.print_events: - elems = [event,] - if args: elems.append(unicode(args)) - if kargs: elems.append(unicode(kargs)) - print ascii(u'%s--> %s' % (' ' * self._depth, ' '.join(elems))) - - if event == "INSTANCE_START" and args: - assert not self.instance_start, 'instance already started' - - self.pid = int(args[0]) - self.logger.info('found instance pid %r' % self.pid) - - self.init_plugins() - - elif event == "INSTANCE_EXIT": - self.logger.info('uzbl instance exit') - self.close() - - if event not in self.handlers: - return - - for handler in self.handlers[event]: - self._depth += 1 - try: - handler.call(self, *args, **kargs) - - except: - self.logger.error(get_exc()) - - self._depth -= 1 - - - def close_connection(self, child_socket): - '''Close child socket and delete the uzbl instance created for that - child socket connection.''' - - - def close(self): - '''Close the client socket and call the plugin cleanup hooks.''' - - self.logger.debug('called close method') - - # Remove self from parent uzbls dict. - if self.child_socket in self.parent.uzbls: - self.logger.debug('removing self from uzbls list') - del self.parent.uzbls[self.child_socket] - - try: - if self.child_socket: - self.logger.debug('closing child socket') - self.child_socket.close() - - except: - self.logger.error(get_exc()) - - finally: - self.child_socket = None - - # Call plugins cleanup hooks. - for plugin in self.parent.plugins.values(): - if plugin.cleanup: - self.logger.debug('calling %r plugin cleanup hook' - % plugin.name) - plugin.cleanup(self) - - logger.info('removed %r' % self) - - -class UzblEventDaemon(object): - def __init__(self): - self.opts = opts - self.server_socket = None - self._quit = False - - # Hold uzbl instances - # {child socket: Uzbl instance, ..} - self.uzbls = {} - - # Hold plugins - # {plugin name: Plugin instance, ..} - self.plugins = {} - - # Register that the event daemon server has started by creating the - # pid file. - make_pid_file(opts.pid_file) - - # Register a function to clean up the socket and pid file on exit. - atexit.register(self.quit) - - # Add signal handlers. - for sigint in [SIGTERM, SIGINT]: - signal(sigint, self.quit) - - # Load plugins into self.plugins - self.load_plugins(opts.plugins) - - - def load_plugins(self, plugins): - '''Load event manager plugins.''' - - for path in plugins: - logger.debug('loading plugin %r' % path) - (dir, file) = os.path.split(path) - name = file[:-3] if file.lower().endswith('.py') else file - - info = imp.find_module(name, [dir,]) - module = imp.load_module(name, *info) - - # Check if the plugin has a callable hook. - hooks = filter(callable, [getattr(module, attr, None) \ - for attr in ['init', 'after', 'cleanup']]) - assert hooks, "no hooks in plugin %r" % module - - logger.debug('creating plugin instance for %r plugin' % name) - plugin = Plugin(self, name, path, module) - self.plugins[name] = plugin - logger.info('new %r' % plugin) - - - def create_server_socket(self): - '''Create the event manager daemon socket for uzbl instance duplex - communication.''' - - # Close old socket. - self.close_server_socket() - - sock = socket(AF_UNIX, SOCK_STREAM) - sock.bind(opts.server_socket) - sock.listen(5) - - self.server_socket = sock - logger.debug('bound server socket to %r' % opts.server_socket) - - - def run(self): - '''Main event daemon loop.''' - - logger.debug('entering main loop') - - # Create and listen on the server socket - self.create_server_socket() - - if opts.daemon_mode: - # Daemonize the process - daemonize() - - # Update the pid file - make_pid_file(opts.pid_file) - - try: - # Accept incoming connections and listen for incoming data - self.listen() - - except: - if not self._quit: - logger.critical(get_exc()) - - # Clean up and exit - self.quit() - - logger.debug('exiting main loop') - - - def listen(self): - '''Accept incoming connections and constantly poll instance sockets - for incoming data.''' - - logger.info('listening on %r' % opts.server_socket) - - # Count accepted connections - connections = 0 - - while (self.uzbls or not connections) or (not opts.auto_close): - socks = [self.server_socket] + self.uzbls.keys() - reads, _, errors = select(socks, [], socks, 1) - - if self.server_socket in reads: - reads.remove(self.server_socket) - - # Accept connection and create uzbl instance. - child_socket = self.server_socket.accept()[0] - self.uzbls[child_socket] = Uzbl(self, child_socket) - connections += 1 - - for uzbl in [self.uzbls[s] for s in reads]: - uzbl.read() - - for uzbl in [self.uzbls[s] for s in errors]: - uzbl.logger.error('socket read error') - uzbl.close() - - logger.info('auto closing') - - - def close_server_socket(self): - '''Close and delete the server socket.''' - - try: - if self.server_socket: - logger.debug('closing server socket') - self.server_socket.close() - self.server_socket = None - - if os.path.exists(opts.server_socket): - logger.info('unlinking %r' % opts.server_socket) - os.unlink(opts.server_socket) - - except: - logger.error(get_exc()) - - - def quit(self, sigint=None, *args): - '''Close all instance socket objects, server socket and delete the - pid file.''' - - if sigint == SIGTERM: - logger.critical('caught SIGTERM, exiting') - - elif sigint == SIGINT: - logger.critical('caught SIGINT, exiting') - - elif not self._quit: - logger.debug('shutting down event manager') - - self.close_server_socket() - - for uzbl in self.uzbls.values(): - uzbl.close() - - del_pid_file(opts.pid_file) - - if not self._quit: - logger.info('event manager shut down') - self._quit = True - - -def make_pid_file(pid_file): - '''Creates a pid file at `pid_file`, fails silently.''' - - try: - logger.debug('creating pid file %r' % pid_file) - make_dirs(pid_file) - pid = os.getpid() - fileobj = open(pid_file, 'w') - fileobj.write('%d' % pid) - fileobj.close() - logger.info('created pid file %r with pid %d' % (pid_file, pid)) - - except: - logger.error(get_exc()) - - -def del_pid_file(pid_file): - '''Deletes a pid file at `pid_file`, fails silently.''' - - if os.path.isfile(pid_file): - try: - logger.debug('deleting pid file %r' % pid_file) - os.remove(pid_file) - logger.info('deleted pid file %r' % pid_file) - - except: - logger.error(get_exc()) - - -def get_pid(pid_file): - '''Reads a pid from pid file `pid_file`, fails None.''' - - try: - logger.debug('reading pid file %r' % pid_file) - fileobj = open(pid_file, 'r') - pid = int(fileobj.read()) - fileobj.close() - logger.info('read pid %d from pid file %r' % (pid, pid_file)) - return pid - - except (IOError, ValueError): - logger.error(get_exc()) - return None - - -def pid_running(pid): - '''Checks if a process with a pid `pid` is running.''' - - try: - os.kill(pid, 0) - except OSError: - return False - else: - return True - - -def term_process(pid): - '''Asks nicely then forces process with pid `pid` to exit.''' - - try: - logger.info('sending SIGTERM to process with pid %r' % pid) - os.kill(pid, SIGTERM) - - except OSError: - logger.error(get_exc()) - - logger.debug('waiting for process with pid %r to exit' % pid) - start = time.time() - while True: - if not pid_running(pid): - logger.debug('process with pid %d exit' % pid) - return True - - if (time.time()-start) > 5: - logger.warning('process with pid %d failed to exit' % pid) - logger.info('sending SIGKILL to process with pid %d' % pid) - try: - os.kill(pid, SIGKILL) - except: - logger.critical(get_exc()) - raise - - if (time.time()-start) > 10: - logger.critical('unable to kill process with pid %d' % pid) - raise OSError - - time.sleep(0.25) - - -def stop_action(): - '''Stop the event manager daemon.''' - - pid_file = opts.pid_file - if not os.path.isfile(pid_file): - logger.error('could not find running event manager with pid file %r' - % opts.pid_file) - return - - pid = get_pid(pid_file) - if not pid_running(pid): - logger.debug('no process with pid %r' % pid) - del_pid_file(pid_file) - return - - logger.debug('terminating process with pid %r' % pid) - term_process(pid) - del_pid_file(pid_file) - logger.info('stopped event manager process with pid %d' % pid) - - -def start_action(): - '''Start the event manager daemon.''' - - pid_file = opts.pid_file - if os.path.isfile(pid_file): - pid = get_pid(pid_file) - if pid_running(pid): - logger.error('event manager already started with pid %d' % pid) - return - - logger.info('no process with pid %d' % pid) - del_pid_file(pid_file) - - UzblEventDaemon().run() - - -def restart_action(): - '''Restart the event manager daemon.''' - - stop_action() - start_action() - - -def list_action(): - '''List all the plugins that would be loaded in the current search - dirs.''' - - names = {} - for plugin in opts.plugins: - (head, tail) = os.path.split(plugin) - if tail not in names: - names[tail] = plugin - - for plugin in sorted(names.values()): - print plugin - - -if __name__ == "__main__": - parser = OptionParser('usage: %prog [options] {start|stop|restart|list}') - add = parser.add_option - - add('-v', '--verbose', - dest='verbose', default=2, action='count', - help='increase verbosity') - - add('-d', '--plugin-dir', - dest='plugin_dirs', action='append', metavar="DIR", default=[], - help='add extra plugin search dir, same as `-l "DIR/*.py"`') - - add('-l', '--load-plugin', - dest='load_plugins', action='append', metavar="PLUGIN", default=[], - help='load plugin, loads before plugins in search dirs') - - socket_location = os.path.join(CACHE_DIR, 'event_daemon') - add('-s', '--server-socket', - dest='server_socket', metavar="SOCKET", default=socket_location, - help='server AF_UNIX socket location') - - add('-p', '--pid-file', - metavar="FILE", dest='pid_file', - help='pid file location, defaults to server socket + .pid') - - add('-n', '--no-daemon', - dest='daemon_mode', action='store_false', default=True, - help='do not daemonize the process') - - add('-a', '--auto-close', - dest='auto_close', action='store_true', default=False, - help='auto close after all instances disconnect') - - add('-i', '--no-default-dirs', - dest='default_dirs', action='store_false', default=True, - help='ignore the default plugin search dirs') - - add('-o', '--log-file', - dest='log_file', metavar='FILE', - help='write logging output to a file, defaults to server socket +' - ' .log') - - add('-q', '--quiet-events', - dest='print_events', action="store_false", default=True, - help="silence the printing of events to stdout") - - (opts, args) = parser.parse_args() - - opts.server_socket = expandpath(opts.server_socket) - - # Set default pid file location - if not opts.pid_file: - opts.pid_file = "%s.pid" % opts.server_socket - - else: - opts.pid_file = expandpath(opts.pid_file) - - # Set default log file location - if not opts.log_file: - opts.log_file = "%s.log" % opts.server_socket - - else: - opts.log_file = expandpath(opts.log_file) - - # Logging setup - log_level = logging.CRITICAL - opts.verbose*10 - - # Console logging handler - ch = logging.StreamHandler() - ch.setLevel(max(log_level+10, 10)) - ch.setFormatter(logging.Formatter( - '%(name)s: %(levelname)s: %(message)s')) - - # File logging handler - fh = logging.FileHandler(opts.log_file, 'w', 'utf-8', 1) - fh.setLevel(max(log_level, 10)) - fh.setFormatter(logging.Formatter( - '[%(created)f] %(name)s: %(levelname)s: %(message)s')) - - # logging.getLogger wrapper which sets the levels and adds the - # file and console handlers automagically - def get_logger(name): - handlers = [ch, fh] - level = [max(log_level, 10),] - logger = logging.getLogger(name) - logger.setLevel(level[0]) - for handler in handlers: - logger.addHandler(handler) - - return logger - - # Get main logger - logger = get_logger(SCRIPTNAME) - logger.info('logging to %r' % opts.log_file) - - plugins = {} - - # Load all `opts.load_plugins` into the plugins list - for path in opts.load_plugins: - path = expandpath(path) - matches = glob(path) - if not matches: - parser.error('cannot find plugin(s): %r' % path) - - for plugin in matches: - (head, tail) = os.path.split(plugin) - if tail not in plugins: - logger.debug('found plugin: %r' % plugin) - plugins[tail] = plugin - - else: - logger.debug('ignoring plugin: %r' % plugin) - - # Add default plugin locations - if opts.default_dirs: - logger.debug('adding default plugin dirs to plugin dirs list') - opts.plugin_dirs += [os.path.join(DATA_DIR, 'plugins/'), - os.path.join(PREFIX, 'share/uzbl/examples/data/plugins/')] - - else: - logger.debug('ignoring default plugin dirs') - - # Load all plugins in `opts.plugin_dirs` into the plugins list - for dir in opts.plugin_dirs: - dir = expandpath(dir) - logger.debug('searching plugin dir: %r' % dir) - for plugin in glob(os.path.join(dir, '*.py')): - (head, tail) = os.path.split(plugin) - if tail not in plugins: - logger.debug('found plugin: %r' % plugin) - plugins[tail] = plugin - - else: - logger.debug('ignoring plugin: %r' % plugin) - - plugins = plugins.values() - - # Check all the paths in the plugins list are files - for plugin in plugins: - if not os.path.isfile(plugin): - parser.error('plugin not a file: %r' % plugin) - - if opts.auto_close: logger.debug('will auto close') - else: logger.debug('will not auto close') - - if opts.daemon_mode: logger.debug('will daemonize') - else: logger.debug('will not daemonize') - - opts.plugins = plugins - - # init like {start|stop|..} daemon actions - daemon_actions = {'start': start_action, 'stop': stop_action, - 'restart': restart_action, 'list': list_action} - - if len(args) == 1: - action = args[0] - if action not in daemon_actions: - parser.error('invalid action: %r' % action) - - elif not args: - logger.warning('no daemon action given, assuming %r' % 'start') - action = 'start' - - else: - parser.error('invalid action argument: %r' % args) - - logger.info('daemon action %r' % action) - # Do action - daemon_actions[action]() - - logger.debug('process CPU time: %f' % time.clock()) - -# vi: set et ts=4: diff --git a/examples/data/scripts/uzbl-tabbed b/examples/data/scripts/uzbl-tabbed deleted file mode 100755 index de71c2c..0000000 --- a/examples/data/scripts/uzbl-tabbed +++ /dev/null @@ -1,1404 +0,0 @@ -#!/usr/bin/env python - -# Uzbl tabbing wrapper using a fifo socket interface -# Copyright (c) 2009, Tom Adams <tom@holizz.com> -# Copyright (c) 2009, Chris van Dijk <cn.vandijk@hotmail.com> -# Copyright (c) 2009, Mason Larobina <mason.larobina@gmail.com> -# -# 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 <http://www.gnu.org/licenses/>. - - -# Author(s): -# Tom Adams <tom@holizz.com> -# Wrote the original uzbl_tabbed.py as a proof of concept. -# -# Chris van Dijk (quigybo) <cn.vandijk@hotmail.com> -# Made signifigant headway on the old uzbl_tabbing.py script on the -# uzbl wiki <http://www.uzbl.org/wiki/uzbl_tabbed> -# -# Mason Larobina <mason.larobina@gmail.com> -# 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 <mxey@ghosthacking.net> -# uzbl_config path now honors XDG_CONFIG_HOME if it exists. -# -# Romain Bignon <romain@peerfuse.org> -# Fix for session restoration code. -# -# Jake Probst <jake.probst@gmail.com> -# Wrote a patch that overflows tabs in the tablist on to new lines when -# running of room. -# -# Devon Jones <devon.jones@gmail.com> -# 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. -# -# 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 -from traceback import print_exc - - -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 - '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.strip() - self.title_changed(False) - 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": - if config['status_background'].strip(): - try: - col = gtk.gdk.color_parse(config['status_background']) - self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) - except ValueError: - pass # got an invalid colour, just ignore it - 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 = val.strip() - self.parent.update_tablist() - elif type == "LOAD_COMMIT": - self.uri = args - elif type == "NEW_TAB": - self.parent.new_tab(args) - elif type == "NEW_BG_TAB": - self.parent.new_tab(args, '', 0) - elif type == "NEW_TAB_NEXT": - self.parent.new_tab(args, next=True) - elif type == "NEW_BG_TAB_NEXT": - self.parent.new_tab(args, '', 0, next=True) - - 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: - print_exc() - 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.connect("page-reordered", self.tab_reordered) - - 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: - print_exc() - 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: - print_exc() - 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. - # newbg [uri] - # open a new tab in the background - # 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] == "newbg": - if len(cmd) == 2: - self.new_tab(cmd[1], switch=False) - else: - self.new_tab(switch=False) - - 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, next=False): - '''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.insert_page(tab, position=next and self.notebook.get_current_page() + 1 or -1) - self.notebook.set_tab_reorderable(tab, True) - 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] - gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH) - - 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 tab_reordered(self, notebook, page, index): - '''Refresh tab titles. Called by page-reordered signal.''' - - for tab in self.notebook: - self.tabs[tab].title_changed(True) - 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 = "<span %(tabc)s> [ %(index)d <span %(textc)s> %(title)s</span> ] </span>" - elif tab_titles: - tab_format = "<span %(tabc)s> [ <span %(textc)s>%(title)s</span> ] </span>" - else: - tab_format = "<span %(tabc)s> [ <span %(textc)s>%(index)d</span> ] </span>" - - 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.''' - - if session_file is None: - session_file = config['session_file'] - - tabs = self.tabs.keys() - lines = "curtab = %d\n" % self.notebook.get_current_page() - for tab in list(self.notebook): - if tab not in tabs: - continue - - uzbl = self.tabs[tab] - if not uzbl.uri: - continue - - lines += "%s %s\n" % (uzbl.uri, uzbl.title) - - if not os.path.isfile(session_file): - dirname = os.path.dirname(session_file) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - fh = open(session_file, 'w') - fh.write(lines) - fh.close() - - - def load_session(self, session_file=None): - '''Load a saved session from file.''' - - default_path = False - 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 - - fh = open(session_file, 'r') - raw = fh.read() - fh.close() - - tabs = [] - curtab = 0 - - lines = filter(None, map(str.strip, raw.split('\n'))) - if len(lines) < 2: - error("Error: The session file %r looks invalid." % session_file) - if delete_loaded and os.path.exists(session_file): - os.remove(session_file) - - return None - - try: - for line in lines: - if line.startswith("curtab"): - curtab = int(line.split()[-1]) - - else: - uri, title = map(str.strip, line.split(" ", 1)) - tabs += [(uri, title),] - - except: - print_exc() - error("Warning: failed to load session file %r" % session_file) - return None - - # 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) - - - 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['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() |