diff options
37 files changed, 1087 insertions, 381 deletions
@@ -3,3 +3,4 @@ uzbl-core *.pyc *~ tags +uzbl-cookie-manager @@ -35,7 +35,7 @@ In alphabetical order: Daiki Ueno (ueno) - fix for crash when opening image in new window Damien Leon - misc Daniel M. Hackney - documentation cleanups - David Keijser - various C and python patches. + David Keijser (keis) - the add_cookie/delete_cookie + distributor system, various C and python patches. Devon Jones <devon.jones@gmail.com> - uzbl_tabbed: bring_to_front Dieter Plaetinck (Dieter@be) <dieter AT plaetinck.be> - several contributions Dmytro Milinevskyy - uzbl-tabbed useability patches @@ -51,6 +51,7 @@ In alphabetical order: James S Wheaton (uranther) - zoom level, test framework Jan Kolkmeier (jouz) - scrolling, link following Jason Woofenden (JasonWoof) - geometry=maximized, link following + Jochen Sprickerhof - session.sh enhancements Lars-Dominik Braun (PromyLOPh) - added ability to enable/disable the webkit page cache Laurence Withers (lwithers) - talk_to_socket Luca Bruno <lucab@debian.org> - bashims fixes @@ -119,9 +119,6 @@ install-uzbl-core: all install-dirs install -m644 AUTHORS $(DOCDIR)/ cp -r examples $(INSTALLDIR)/share/uzbl/ chmod 755 $(INSTALLDIR)/share/uzbl/examples/data/scripts/* - mv $(INSTALLDIR)/share/uzbl/examples/config/config $(INSTALLDIR)/share/uzbl/examples/config/config.bak - sed 's#^set prefix.*=.*#set prefix = $(RUN_PREFIX)#' < $(INSTALLDIR)/share/uzbl/examples/config/config.bak > $(INSTALLDIR)/share/uzbl/examples/config/config - rm $(INSTALLDIR)/share/uzbl/examples/config/config.bak install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core install-uzbl-browser: uzbl-cookie-manager install-dirs @@ -260,6 +260,11 @@ The following commands are recognized: - Read contents of `<file>` and interpret as a set of `uzbl` commands. * `show_inspector` - Show the WebInspector +* `add_cookie <domain> <path> <name> <value> <scheme> <expires>` + - Adds a new cookie to the cookie jar +* 'delete_cookie <domain> <path> <name> <value> [<scheme> <expires>]` + - Deletes a matching cookie from the cookie jar. scheme and expire time + is currently not considered when matching. ### VARIABLES AND CONSTANTS @@ -305,6 +310,9 @@ file). - `data`: The cookie data. Only included for "PUT" requests. * `scheme_handler`: handler to execute for each URI navigated to - the navigation request will be ignored if handler prints "USED\n" +* `download_handler`: executed when a download is started. the handler script + should print a path that the download should be saved to, or print nothing + to cancel the download. * `fifo_dir`: location to store FIFOs. * `socket_dir`: location to store sockets. * `http_debug`: HTTP debug mode (value 0-3). @@ -480,23 +488,29 @@ You can use external scripts with Uzbl the following ways: Have a look at the sample configs and scripts! -Handler scripts that are called by `uzbl` are passed the following arguments: +Scripts called by `uzbl` (with `spawn`, `sync_spawn`, `sh` or `sync_sh`) have +access to the following environment variables: -* `$1 config`: The configuration file loaded by this `uzbl` instance. -* `$2 pid`: The process ID of this `uzbl` instance. -* `$3 x_id`: The X Windows ID of the process. -* `$4 fifo`: The filename of the FIFO being used, if any. -* `$5 socket`: The filename of the Unix socket being used, if any. -* `$6 uri`: The URI of the current page. -* `$7 title`: The current page title. -* `.. [ script specific ] (optional)` +* `$UZBL_CONFIG`: The configuration file loaded by this `uzbl` instance. +* `$UZBL_PID`: The process ID of this `uzbl` instance. +* `$UZBL_XID`: The X Windows ID of the process. +* `$UZBL_FIFO`: The filename of the FIFO being used, if any. +* `$UZBL_SOCKET`: The filename of the Unix socket being used, if any. +* `$UZBL_URI`: The URI of the current page. +* `$UZBL_TITLE`: The current page title. -The script specific arguments are: +These variables are also available as positional arguments `$1` through `$7`, +but this is deprecated and will be removed. -* download +Handler scripts (`download_handler`, `cookie_handler`, `scheme_handler` and +`authentication_handler`) are called with special arguments: + +* download handler - `$8 url`: The URL of the item to be downloaded. - - `$9 proxy`: (optional) The URL of an HTTP proxy. + - `$9 suggested_filename`: A filename suggested by the server or based on the URL. + - `$10 content_type`: The mimetype of the file to be downloaded. + - `$11 total_size`: The size of the file to be downloaded in bytes. This may be inaccurate. * cookie handler @@ -514,13 +528,10 @@ The script specific arguments are: * authentication handler: - $8 authentication zone unique identifier - $9 domain part of URL that requests authentication - $10 authentication realm - $11 FALSE if this is the first attempt to authenticate, TRUE otherwise - -Custom, userdefined scripts (`spawn foo bar`) get first the arguments as -specified in the config and then the above 7 are added at the end. + - `$8`: authentication zone unique identifier + - `$9`: domain part of URL that requests authentication + - `$10`: authentication realm + - `$11`: FALSE if this is the first attempt to authenticate, TRUE otherwise ### Formfiller.sh @@ -626,11 +637,10 @@ The EM allows: * Many fine-grained events (`hover_over_link`, `key_press`, `key_release`,..) * See example `uzbl-event-manager`. -**Note**: Cookie events are not sent to an event handler but handled internally - through the cookie handler because of their synchronous nature. Cookie events - are really something completely different from all other events. Maybe someday - we'll use HTTP proxies or synchronous events (which also have other nice use - cases), but for now we still use the handler code. +**Note**: Cookie events are sent in addition to (optionally) being handled by + the cookie handler (set by the cookie_handler var). If using a handler it will + take precedence before the internal state configured by (add|delete)_cookie + commands. Events have this format: @@ -668,8 +678,13 @@ Events have this format: * `EVENT [uzbl_instance_name] TITLE_CHANGED title_name`: When the title of the page (and hence maybe, the window title) changed. `title_name` is the new title. -* `EVENT [uzbl_instance_name] DOWNLOAD_REQUEST download_uri`: When content needs - to be downloaded, `download_uri` is the URI to get. +* `EVENT [uzbl_instance_name] DOWNLOAD_STARTED destination_path`: A download + has been started, the file will be saved to `destination_path`. +* `EVENT [uzbl_instance_name] DOWNLOAD_PROGRESS destination_path progress`: + While a download is active this event notifies you of the progress. + `progress` is a decimal between 0 and 1. +* `EVENT [uzbl_instance_name] DOWNLOAD_COMPLETE destination_path`: The + download being saved to `destination_path` is now complete. * `EVENT [uzbl_instance_name] LINK_HOVER uri`: The mouse hovers over the link `uri`. * `EVENT [uzbl_instance_name] LINK_UNHOVER uri`: The mouse leaves the link @@ -699,6 +714,11 @@ Events have this format: Xembed mode, `plug_id` is the Xembed ID used. * `EVENT [uzbl_instance_name] BUILTINS command_list`: Shows a list of all `uzbl` commands, whitespace separated, on startup. +* `EVENT [uzbl_instance_name] ADD_COOKIE domain path name value scheme expire`: + When a cookie was added or replaced. scheme is 'http' or 'https', expire will + be a unix-timestamp or empty +* `EVENT [uzbl_instance_name] DELETE_COOKIE domain path name value scheme expire`: + When a cookie was deleted. arguments as ADD_COOKIE Events/requests which the EM and its plugins listens for @@ -773,6 +793,15 @@ Events/requests which the EM and its plugins listens for `<index>` is `+`, advance the cursor by one character, and if it is `-`, move the cursor back by one character. * `START_COMPLETION`: TODO explain completion +* `BLACKLIST_COOKIE`: add a rule for blacklisting cookies + - `request BLACKLIST_COOKIE <component> <regexp>`: Blacklist cookies where + `<component>` matches `<regexp>`. `<component>` is one of `domain`, + `path`, `name`, `value`, `scheme` or `expires`. +* `WHITELIST_COOKIE`: add a rule for whitelisting cookies (if any whitelist is + set then only cookies that are whitelisted cookies will be used) + - `request WHITELIST_COOKIE <component> <regexp>`: Whitelist cookies where + `<component>` matches `<regexp>`. `<component>` is one of `domain`, + `path`, `name`, `value`, `scheme` or `expires`. ### COMMAND LINE ARGUMENTS diff --git a/docs/COMMUNITY b/docs/COMMUNITY index 2817ee9..cbf3b48 100644 --- a/docs/COMMUNITY +++ b/docs/COMMUNITY @@ -4,12 +4,12 @@ COMMUNITY ### Mailing list * Address: [uzbl-dev@lists.uzbl.org](mailto:uzbl-dev@lists.uzbl.org) -* [Page](http://lists.uzbl.org/listinfo.cgi/uzbl-dev-uzbl.org) -* [Archives](http://lists.uzbl.org/pipermail/uzbl-dev-uzbl.org/) +* [Page](http://lists.uzbl.org/listinfo/uzbl-dev) +* [Archives](http://lists.uzbl.org/pipermail/uzbl-dev/) ### IRC -* `#uzbl` on irc.freenode.net +* `#uzbl` on irc.freenode.net ([webchat](http://webchat.freenode.net/?channels=uzbl)) * [Archive](http://www.uzbl.org/irc-log) ### Website @@ -23,10 +23,6 @@ COMMUNITY ### Code repositories * [github.com/Dieterbe/uzbl](http://github.com/Dieterbe/uzbl/) -* [github.com/anydot/uzbl](http://github.com/anydot/uzbl/) -* [github.com/Barrucadu/uzbl](http://github.com/Barrucadu/uzbl/) -* [github.com/dusanx/uzbl](http://github.com/dusanx/uzbl/) -* [github.com/robm/uzbl](http://github.com/robm/uzbl/) There are more contributors who have forks. See: diff --git a/docs/README.uzbl-event-manager b/docs/README.uzbl-event-manager new file mode 100644 index 0000000..074811e --- /dev/null +++ b/docs/README.uzbl-event-manager @@ -0,0 +1,99 @@ +# The uzbl event manager # + +## Core ## + +## Plugins ## + +### mode.py ### +- Named modes with different settings +- Connects To: (MODE_CONFIG, MODE_CONFIRM) +- Watches: mode, default_mode +- Emits: MODE_CHANGED, MODE_CONFIRM + +Changes between modes configured with MODE_CONFIG when the mode variable changes. + +MODE_CONFIG <mode> <var> = <value> + configures `mode` to have `var` set to `value` + +MODE_CONFIRM <mode> + Emitted when the mode has changed with a round trip to uzbl-core to allow + the settings to take effect. + emits MODE_CHANGE if `mode` matches the current mode + +MODE_CHANGE <mode> + Emitted when the mode has changed + + +### keycmd.py ### +- Tracks the currently entered command +- Connects To: FOCUS_GAINED, FOCUS_LOST, KEY_PRESS, KEY_RELEASE, (APPEND_KEYCMD, + IGNORE_KEY, INJECT_KEYCMD, KEYCMD_BACKSPACE, KEYCMD_DELETE, + KEYCMD_EXEC_CURRENT, KEYCMD_STRIP_WORD, MODKEY_ADDITION, MODMAP, + SET_CURSOR_POS, SET_KEYCMD) +- Emits: KEYCMD_UPDATE, KEYCMD_EXEC, MODCMD_UPDATE, MODCMD_EXEC + +Maintains a command line that is manipulated by simple keypresses and a number +of events. + + +### bind.py ### +- Provides support for key bindings +- Connects To: (BIND, MODE_BIND, MODE_CHANGED, KEYCMD_UPDATE, KEYCMD_EXEC, + MODCMD_UPDATE, MODCMD_EXEC) +- Emits: EXEC_BIND + +Listens for changes in keycmd and modcmd and executes bindings configured by +BIND and MODE_BIND. + +BIND <bind> = <command> + short hand for MODE_BIND global <bind> = <command> + +MODE_BIND <mode> <bind> = <command> + Makes <bind> execute <command> while the current mode is matched by `mode`. + `mode` is a comma separated list of modes in which this binding should + apply. The special mode 'global' will match all modes except any modes + excluded by prefixing them with '-'. + + e.g + MODE_BIND global,-insert <Up> = scroll vertical -20 + will make the Up-key scroll up in all modes except insert + +EXEC_BIND <bind> <args> <kwargs> + Emitted before executing `bind` with `args` as arguments and `kwargs` as + keyword arguments. `bind` is a Bind instance, <args> a sequence and <kwargs> + a dictionary. + + +### cookies.py ### +- Cookie synchronization and persistence +- Connects To: ADD_COOKIE, DELETE_COOKIE, (BLACKLIST_COOKIE, WHITELIST_COOKIE) + +This plugin acts on the (ADD|DELETE)_COOKIE events by issuing add_cookie or +delete_cookie commands as appropriate to other connected uzbl instances. +However if the cookie is blacklisted (see below) the cookie will not be +forwarded and instead delete_cookie will be sent to the source so that the +cookie will not be included in future HTTP requests. + +This plugin also maintains a mozilla cookies.txt compatible file with all your +persistent cookies in $XDG_DATA_HOME/uzbl/cookies.txt and all your session +cookies in $XDG_DATA_HOME/uzbl/session-cookies.txt. + +The blacklist is configured using the BLACKLIST_COOKIE and WHITELIST_COOKIE +events. If any whitelist is set, then any cookie that is not whitelisted will +be rejected. Otherwise, only cookies that have been blacklisted will be +rejected. + +BLACKLIST_COOKIE <part> <re> + Adds a new blacklist filter. cookies where the component specified by + `part` matches the regular expression `re` will be filtered. part can be + either 0-5 or any of the symbolic names domain, path, name, value, scheme, + expires + + for example to block all cookies which name is "__utm" followed by a single + character (google analytics cookies) do. + request BLACKLIST_COOKIE name '^__utm.$' + +WHITELIST_COOKIE <part> <re> + Adds a new whitelist filter. cookies where the component specified by + `part` matches the regular expression `re` will be allowed. part can be any + of the parts allowed for the BLACKLIST_COOKIE event diff --git a/examples/config/config b/examples/config/config index 855f7c2..96b2034 100644 --- a/examples/config/config +++ b/examples/config/config @@ -3,8 +3,10 @@ # === Core settings ========================================================== -# Install location prefix. -set prefix = /usr/local +# common directory locations +set prefix = @(echo $PREFIX)@ +set data_home = @(echo $XDG_DATA_HOME)@ +set cache_home = @(echo $XDG_CACHE_HOME)@ # Interface paths. set fifo_dir = /tmp @@ -34,15 +36,15 @@ set set_mode = set mode = set set_status = set status_message = # Spawn path shortcuts. In spawn the first dir+path match is used in "dir1:dir2:dir3:executable" -set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data:scripts +set scripts_dir = @data_home/uzbl:@prefix/share/uzbl/examples/data:scripts # === Hardcoded handlers ===================================================== # These handlers can't be moved to the new event system yet as we don't # support events that can wait for a response from a script. -set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket set scheme_handler = sync_spawn @scripts_dir/scheme.py set authentication_handler = sync_spawn @scripts_dir/auth.py +set download_handler = sync_spawn @scripts_dir/download.sh # === Dynamic event handlers ================================================= @@ -53,9 +55,6 @@ set authentication_handler = sync_spawn @scripts_dir/auth.py # Open in new tab #@on_event NEW_WINDOW event NEW_TAB %s -# Download handler -@on_event DOWNLOAD_REQUEST spawn @scripts_dir/download.sh %s \@proxy_url - # Load start handler @on_event LOAD_START @set_status <span foreground="khaki">wait</span> # Reset the keycmd on navigation @@ -64,8 +63,8 @@ set authentication_handler = sync_spawn @scripts_dir/auth.py # Load commit handlers @on_event LOAD_COMMIT @set_status <span foreground="green">recv</span> -# Userscript support. Add all scripts to $XDG_DATA_HOME/uzbl/userscripts -#@on_event LOAD_COMMIT spawn @scripts_dir/userscripts.sh +# Userscripts/per-site-settings. See the script and the example configuration for details +#@on_event LOAD_COMMIT spawn @scripts_dir/per-site-settings.py @data_home/uzbl/per-site-settings # Load finish handlers @on_event LOAD_FINISH @set_status <span foreground="gold">done</span> @@ -104,7 +103,9 @@ set name_section = <span foreground="khaki">\@[\@NAME]\@</span> set status_section = <span foreground="orange">\@status_message</span> set selected_section = <span foreground="#606060">\@[\@SELECTED_URI]\@</span> -set status_format = <span font_family="monospace">@mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section</span> +set download_section = <span foreground="white">\@downloads</span> + +set status_format = <span font_family="monospace">@mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section @download_section</span> set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI @@ -120,6 +121,10 @@ set progress.pending = set useragent = Uzbl (Webkit @{WEBKIT_MAJOR}.@{WEBKIT_MINOR}.@{WEBKIT_MICRO}) (@(+uname -sm)@ [@ARCH_UZBL]) (Commit @COMMIT) +# === Configure cookie blacklist ======================================================== +# Drop google analytics tracking cookies +#request BLACKLIST_COOKIE name '^__utm.$' + # === Key binding configuration ============================================== # --- Internal modmapping and ignoring --------------------------------------- @@ -181,7 +186,7 @@ set ebind = @mode_bind global,-insert # --- Mouse bindings --------------------------------------------------------- # Middle click open in new window -@bind <Button2> = sh 'if [ "$8" ]; then uzbl-browser -u "$8"; else echo "uri $(xclip -o | sed s/\\\@/%40/g)" > $4; fi' \@SELECTED_URI +@bind <Button2> = sh 'if [ "$8" ]; then uzbl-browser -u "$8"; else echo "uri $(xclip -o | sed s/\\\@/%40/g)" > "$UZBL_FIFO"; fi' \@SELECTED_URI # --- Keyboard bindings ------------------------------------------------------ @@ -245,14 +250,14 @@ set ebind = @mode_bind global,-insert # Exit binding @cbind ZZ = exit # Dump config to stdout -@cbind !dump = sh "echo dump_config > $4" +@cbind !dump = sh 'echo dump_config > "$UZBL_FIFO"' # Reload all variables in the config -@cbind !reload = sh "sed '/^# === Post-load misc commands/,$d' $1 | grep '^set ' > $4" +@cbind !reload = sh "sed '/^# === Post-load misc commands/,$d' \"$UZBL_CONFIG\" | grep '^set ' > \"$UZBL_FIFO\"" # 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:$5 -"' -#@cbind <Ctrl><Alt>t = sh 'urxvt -e socat unix-connect:$5 -' +@cbind <Ctrl><Alt>t = sh 'xterm -e "socat unix-connect:\"$UZBL_SOCKET\" -"' +#@cbind <Ctrl><Alt>t = sh 'urxvt -e socat unix-connect:"$UZBL_SOCKET" -' # Uri opening prompts @cbind o<uri:>_ = uri %s @@ -267,24 +272,24 @@ set ebind = @mode_bind global,-insert @cbind gh = uri http://www.uzbl.org # Yanking & pasting binds -@cbind yu = sh 'echo -n $6 | xclip' +@cbind yu = sh 'echo -n "$UZBL_URI" | xclip' @cbind yU = sh 'echo -n $8 | xclip' \@SELECTED_URI -@cbind yy = sh 'echo -n $7 | xclip' +@cbind yy = sh 'echo -n "$UZBL_TITLE" | xclip' @cbind yY = sh 'echo -n $8 | xclip' \@SELECTED_URI # Clone current window -@cbind c = sh 'uzbl-browser -u $6' +@cbind c = sh 'uzbl-browser -u "$UZBL_URI"' # Go the page from primary selection -@cbind p = sh 'echo "uri `xclip -selection primary -o | sed s/\\\@/%40/g`" > $4' +@cbind p = sh 'echo "uri `xclip -selection primary -o | sed s/\\\@/%40/g`" > "$UZBL_FIFO"' # Go to the page in clipboard -@cbind P = sh 'echo "uri `xclip -selection clipboard -o | sed s/\\\@/%40/g`" > $4' +@cbind P = sh 'echo "uri `xclip -selection clipboard -o | sed s/\\\@/%40/g`" > "$UZBL_FIFO"' # Start a new uzbl instance from the page in primary selection -@cbind 'p = sh 'exec uzbl-browser --uri $(xclip -o)' +@cbind 'p = sh 'exec uzbl-browser --uri "$(xclip -o)"' # paste primary selection into keycmd at the cursor position -@bind <Shift-Insert> = sh 'echo "event INJECT_KEYCMD `xclip -o | sed s/\\\@/%40/g`" > $4' +@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 "$6 %s"` >> $XDG_DATA_HOME/uzbl/bookmarks' +@cbind <Ctrl>b<tags:>_ = sh 'echo `printf "$UZBL_URI %s"` >> "$XDG_DATA_HOME"/uzbl/bookmarks' # Or use a script to insert a bookmark. @cbind B = spawn @scripts_dir/insert_bookmark.sh @@ -319,12 +324,12 @@ set formfiller = spawn @scripts_dir/formfiller.sh @cbind gN = event NEW_TAB_NEXT @cbind go<uri:>_ = event NEW_TAB %s @cbind gO<uri:>_ = event NEW_TAB_NEXT %s -@cbind gy = sh 'echo "event NEW_TAB `xclip -selection primary -o | sed s/\\\@/%40/g`" > $4' -@cbind gY = sh 'echo "event NEW_TAB_NEXT `xclip -selection primary -o | sed s/\\\@/%40/g`" > $4' +@cbind gy = sh 'echo "event NEW_TAB `xclip -selection primary -o | sed s/\\\@/%40/g`" > "$UZBL_FIFO"' +@cbind gY = sh 'echo "event NEW_TAB_NEXT `xclip -selection primary -o | sed s/\\\@/%40/g`" > "$UZBL_FIFO"' # Clone current tab -@cbind gd = sh 'echo "event NEW_TAB $6" > $4' -@cbind gD = sh 'echo "event NEW_TAB_NEXT $6" > $4' +@cbind gd = sh 'echo "event NEW_TAB $UZBL_URI" > "$UZBL_FIFO"' +@cbind gD = sh 'echo "event NEW_TAB_NEXT $UZBL_URI" > "$UZBL_FIFO"' # Closing / resting @cbind gC = exit @@ -389,6 +394,8 @@ set stack = @mode_config stack set default_mode = command # === Post-load misc commands ================================================ +sync_spawn_exec @scripts_dir/load_cookies.sh +sync_spawn_exec @scripts_dir/load_cookies.sh @data_home/uzbl/session-cookies.txt # Set the "home" page. set uri = uzbl.org/doesitwork/@COMMIT diff --git a/examples/data/per-site-settings b/examples/data/per-site-settings new file mode 100644 index 0000000..78bade4 --- /dev/null +++ b/examples/data/per-site-settings @@ -0,0 +1,3 @@ +.* + .*/\d+-\w+/(thread|subject|author|date).html + script @data_home/uzbl/scripts/pipermail.js diff --git a/examples/data/plugins/cookies.py b/examples/data/plugins/cookies.py new file mode 100644 index 0000000..c507fd6 --- /dev/null +++ b/examples/data/plugins/cookies.py @@ -0,0 +1,176 @@ +""" Basic cookie manager + forwards cookies to all other instances connected to the event manager""" + +from collections import defaultdict +import os, re + +# these are symbolic names for the components of the cookie tuple +symbolic = {'domain': 0, 'path':1, 'name':2, 'value':3, 'scheme':4, 'expires':5} + +_splitquoted = re.compile("( |\\\".*?\\\"|'.*?')") +def splitquoted(text): + return [str(p.strip('\'"')) for p in _splitquoted.split(text) if p.strip()] + +# allows for partial cookies +# ? allow wildcard in key +def match(key, cookie): + for k,c in zip(key,cookie): + if k != c: + return False + return True + +class NullStore(object): + def add_cookie(self, rawcookie, cookie): + pass + + def delete_cookie(self, rkey, key): + pass + +class ListStore(list): + def add_cookie(self, rawcookie, cookie): + self.append(rawcookie) + + def delete_cookie(self, rkey, key): + self[:] = [x for x in self if not match(key, splitquoted(x))] + +class TextStore(object): + def __init__(self, filename): + self.filename = filename + + def as_event(self, cookie): + if cookie[0].startswith("#HttpOnly_"): + domain = cookie[0][len("#HttpOnly_"):] + elif cookie[0].startswith('#'): + return None + else: + domain = cookie[0] + return (domain, + cookie[2], + cookie[5], + cookie[6], + 'https' if cookie[3] == 'TRUE' else 'http', + cookie[4]) + + def as_file(self, cookie): + return (cookie[0], + 'TRUE' if cookie[0].startswith('.') else 'FALSE', + cookie[1], + 'TRUE' if cookie[4] == 'https' else 'FALSE', + cookie[5], + cookie[2], + cookie[3]) + + def add_cookie(self, rawcookie, cookie): + assert len(cookie) == 6 + + # delete equal cookies (ignoring expire time, value and secure flag) + self.delete_cookie(None, cookie[:-3]) + + first = not os.path.exists(self.filename) + with open(self.filename, 'a+') as f: + if first: + print >> f, "# HTTP Cookie File" + print >> f, '\t'.join(self.as_file(cookie)) + + def delete_cookie(self, rkey, key): + if not os.path.exists(self.filename): + return + + # read all cookies + with open(self.filename, 'r') as f: + cookies = f.readlines() + + # write those that don't match the cookie to delete + with open(self.filename, 'w') as f: + for l in cookies: + c = self.as_event(l.split('\t')) + if c is None or not match(key, c): + print >> f, l, + +xdg_data_home = os.environ.get('XDG_DATA_HOME', os.path.join(os.environ['HOME'], '.local/share')) +DefaultStore = TextStore(os.path.join(xdg_data_home, 'uzbl/cookies.txt')) +SessionStore = TextStore(os.path.join(xdg_data_home, 'uzbl/session-cookies.txt')) + +def match_list(_list, cookie): + for component, match in _list: + if match(cookie[component]) is not None: + return True + return False + +# accept a cookie only when: +# a. there is no whitelist and the cookie is in the blacklist +# b. the cookie is in the whitelist and not in the blacklist +def accept_cookie(uzbl, cookie): + if uzbl.cookie_whitelist: + if match_list(uzbl.cookie_whitelist, cookie): + return not match_list(uzbl.cookie_blacklist, cookie) + return False + + return not match_list(uzbl.cookie_blacklist, cookie) + +def expires_with_session(uzbl, cookie): + return cookie[5] == '' + +def get_recipents(uzbl): + """ get a list of Uzbl instances to send the cookie too. """ + # This could be a lot more interesting + return [u for u in uzbl.parent.uzbls.values() if u is not uzbl] + +def get_store(uzbl, session=False): + if session: + return SessionStore + return DefaultStore + +def add_cookie(uzbl, cookie): + splitted = splitquoted(cookie) + if accept_cookie(uzbl, splitted): + for u in get_recipents(uzbl): + u.send('add_cookie %s' % cookie) + + get_store(uzbl, expires_with_session(uzbl, splitted)).add_cookie(cookie, splitted) + else: + logger.debug('cookie %r is blacklisted' % splitted) + uzbl.send('delete_cookie %s' % cookie) + +def delete_cookie(uzbl, cookie): + for u in get_recipents(uzbl): + u.send('delete_cookie %s' % cookie) + + splitted = splitquoted(cookie) + if len(splitted) == 6: + get_store(uzbl, expires_with_session(uzbl, splitted)).delete_cookie(cookie, splitted) + else: + for store in set([get_store(uzbl, session) for session in (True, False)]): + store.delete_cookie(cookie, splitted) + +# add a cookie matcher to a whitelist or a blacklist. +# a matcher is a (component, re) tuple that matches a cookie when the +# "component" part of the cookie matches the regular expression "re". +# "component" is one of the keys defined in the variable "symbolic" above, +# or the index of a component of a cookie tuple. +def add_cookie_matcher(_list, arg): + component, regexp = splitquoted(arg) + try: + component = symbolic[component] + except KeyError: + component = int(component) + assert component <= 5 + _list.append((component, re.compile(regexp).search)) + +def blacklist(uzbl, arg): + add_cookie_matcher(uzbl.cookie_blacklist, arg) + +def whitelist(uzbl, arg): + add_cookie_matcher(uzbl.cookie_whitelist, arg) + +def init(uzbl): + connect_dict(uzbl, { + 'ADD_COOKIE': add_cookie, + 'DELETE_COOKIE': delete_cookie, + 'BLACKLIST_COOKIE': blacklist, + 'WHITELIST_COOKIE': whitelist + }) + export_dict(uzbl, { + 'cookie_blacklist' : [], + 'cookie_whitelist' : [] + }) diff --git a/examples/data/plugins/downloads.py b/examples/data/plugins/downloads.py new file mode 100644 index 0000000..7bf32d7 --- /dev/null +++ b/examples/data/plugins/downloads.py @@ -0,0 +1,69 @@ +# this plugin does a very simple display of download progress. to use it, add +# @downloads to your status_format. + +import os +ACTIVE_DOWNLOADS = {} + +# after a download's status has changed this is called to update the status bar +def update_download_section(uzbl): + global ACTIVE_DOWNLOADS + + if len(ACTIVE_DOWNLOADS): + # add a newline before we list downloads + result = ' downloads:' + for path in ACTIVE_DOWNLOADS: + # add each download + fn = os.path.basename(path) + progress, = ACTIVE_DOWNLOADS[path] + + dl = " %s (%d%%)" % (fn, progress * 100) + + # replace entities to make sure we don't break our markup + # (this could be done with an @[]@ expansion in uzbl, but then we + # can't use the above to make a new line) + dl = dl.replace("&", "&").replace("<", "<") + result += dl + else: + result = '' + + # and the result gets saved to an uzbl variable that can be used in + # status_format + if uzbl.config.get('downloads', '') != result: + uzbl.config['downloads'] = result + +def download_started(uzbl, destination_path): + # add to the list of active downloads + global ACTIVE_DOWNLOADS + ACTIVE_DOWNLOADS[destination_path] = (0.0,) + + # update the progress + update_download_section(uzbl) + +def download_progress(uzbl, args): + # parse the arguments + s = args.rindex(' ') + destination_path = args[:s] + progress = float(args[s+1:]) + + # update the progress + global ACTIVE_DOWNLOADS + ACTIVE_DOWNLOADS[destination_path] = (progress,) + + # update the status bar variable + update_download_section(uzbl) + +def download_complete(uzbl, destination_path): + # remove from the list of active downloads + global ACTIVE_DOWNLOADS + del ACTIVE_DOWNLOADS[destination_path] + + # update the status bar variable + update_download_section(uzbl) + +# plugin init hook +def init(uzbl): + connect_dict(uzbl, { + 'DOWNLOAD_STARTED': download_started, + 'DOWNLOAD_PROGRESS': download_progress, + 'DOWNLOAD_COMPLETE': download_complete, + }) diff --git a/examples/data/scripts/download.sh b/examples/data/scripts/download.sh index 606aa62..df7a571 100755 --- a/examples/data/scripts/download.sh +++ b/examples/data/scripts/download.sh @@ -1,26 +1,26 @@ #!/bin/sh -# just an example of how you could handle your downloads -# try some pattern matching on the uri to determine what we should do +# +# uzbl's example configuration sets this script up as its download_handler. +# when uzbl starts a download it runs this script. +# 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. -. $UZBL_UTIL_DIR/uzbl-args.sh +shift 7 . $UZBL_UTIL_DIR/uzbl-dir.sh -# Some sites block the default wget --user-agent.. -GET="wget --user-agent=Firefox --content-disposition --load-cookies=$UZBL_COOKIE_JAR" +# the URL that is being downloaded +uri=$1 -url="$1" +# a filename suggested by the server or based on the URL +suggested_filename=${2:-$(echo "$uri" | sed 's/\W/-/g')} -http_proxy="$2" -export http_proxy +# the mimetype of the file being downloaded +content_type=$3 -if [ -z "$url" ]; then - echo "you must supply a url! ($url)" - exit 1 -fi +# 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 -# only changes the dir for the $get sub process -if echo "$url" | grep -E '.*\.torrent' >/dev/null; then - ( cd "$UZBL_DOWNLOAD_DIR"; $GET "$url") -else - ( cd "$UZBL_DOWNLOAD_DIR"; $GET "$url") -fi +# just save the file to the default directory with the suggested name +echo $UZBL_DOWNLOAD_DIR/$suggested_filename diff --git a/examples/data/scripts/follow.sh b/examples/data/scripts/follow.sh index c8ded84..2d666a2 100755 --- a/examples/data/scripts/follow.sh +++ b/examples/data/scripts/follow.sh @@ -3,7 +3,7 @@ # This script is just a wrapper around follow.js that lets us change uzbl's mode # after a link is selected. -. "$UZBL_UTIL_DIR"/uzbl-args.sh +shift 7 # if socat is installed then we can change Uzbl's input mode once a link is # selected; otherwise we just select a link. diff --git a/examples/data/scripts/formfiller.sh b/examples/data/scripts/formfiller.sh index b2e61ec..6e04573 100755 --- a/examples/data/scripts/formfiller.sh +++ b/examples/data/scripts/formfiller.sh @@ -45,7 +45,6 @@ DMENU_OPTIONS="vertical resize" . $UZBL_UTIL_DIR/dmenu.sh . $UZBL_UTIL_DIR/editor.sh -. $UZBL_UTIL_DIR/uzbl-args.sh . $UZBL_UTIL_DIR/uzbl-dir.sh RAND=$(dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -c 1-5) @@ -54,9 +53,11 @@ MODELINE="> vim:ft=formfiller" [ -d "$(dirname $UZBL_FORMS_DIR)" ] || exit 1 [ -d $UZBL_FORMS_DIR ] || mkdir $UZBL_FORMS_DIR || exit 1 +shift 7 + action=$1 -domain=$(echo $UZBL_URL | sed 's/\(http\|https\):\/\/\([^\/]\+\)\/.*/\2/') +domain=$(echo $UZBL_URI | sed 's/\(http\|https\):\/\/\([^\/]\+\)\/.*/\2/') if [ "$action" != 'edit' -a "$action" != 'new' -a "$action" != 'load' -a "$action" != 'add' -a "$action" != 'once' ]; then action="new" diff --git a/examples/data/scripts/go_input.sh b/examples/data/scripts/go_input.sh index c873dd8..ace0e79 100755 --- a/examples/data/scripts/go_input.sh +++ b/examples/data/scripts/go_input.sh @@ -1,20 +1,5 @@ #!/bin/sh -config=$1; -shift -pid=$1; -shift -xid=$1; -shift -fifo=$1; -shift -socket=$1; -shift -url=$1; -shift -title=$1; -shift - -case $(echo 'script @scripts_dir/go_input.js' | socat - unix-connect:$socket) in - *XXXEMIT_FORM_ACTIVEXXX*) echo 'event FORM_ACTIVE' | socat - unix-connect:$socket ;; +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 0561fe9..266d65d 100755 --- a/examples/data/scripts/history.sh +++ b/examples/data/scripts/history.sh @@ -4,4 +4,4 @@ [ -w "$UZBL_HISTORY_FILE" ] || [ ! -a "$UZBL_HISTORY_FILE" ] || exit 1 -echo $(date +'%Y-%m-%d %H:%M:%S')" $6 $7" >> $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 24f7241..f67e67a 100755 --- a/examples/data/scripts/insert_bookmark.sh +++ b/examples/data/scripts/insert_bookmark.sh @@ -1,6 +1,5 @@ #!/bin/sh -. "$UZBL_UTIL_DIR"/uzbl-args.sh . "$UZBL_UTIL_DIR"/uzbl-dir.sh [ -d "$UZBL_DATA_DIR" ] || exit 1 @@ -8,9 +7,9 @@ which zenity >/dev/null 2>&1 || exit 2 -tags=$(zenity --entry --text="Enter space-separated tags for bookmark $UZBL_URL:") +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_URL $tags" >> "$UZBL_BOOKMARKS_FILE" +echo "$UZBL_URI $tags" >> "$UZBL_BOOKMARKS_FILE" diff --git a/examples/data/scripts/load_cookies.sh b/examples/data/scripts/load_cookies.sh new file mode 100755 index 0000000..f4c6886 --- /dev/null +++ b/examples/data/scripts/load_cookies.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +if [ "$8" != "" ]; then + cookie_file=$8 +else + cookie_file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/cookies.txt +fi + +awk -F \\t ' +BEGIN { + 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) +} +$0 !~ /^#/ { +printf("add_cookie \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"\n", $1, $3, $6, $7, scheme[$4], $5) +} +' $cookie_file diff --git a/examples/data/scripts/load_url_from_bookmarks.sh b/examples/data/scripts/load_url_from_bookmarks.sh index 9346526..a5d9586 100755 --- a/examples/data/scripts/load_url_from_bookmarks.sh +++ b/examples/data/scripts/load_url_from_bookmarks.sh @@ -6,7 +6,6 @@ DMENU_SCHEME="bookmarks" DMENU_OPTIONS="xmms vertical resize" . "$UZBL_UTIL_DIR"/dmenu.sh -. "$UZBL_UTIL_DIR"/uzbl-args.sh . "$UZBL_UTIL_DIR"/uzbl-dir.sh [ -r "$UZBL_BOOKMARKS_FILE" ] || exit 1 diff --git a/examples/data/scripts/load_url_from_history.sh b/examples/data/scripts/load_url_from_history.sh index 4499e7f..59ad492 100755 --- a/examples/data/scripts/load_url_from_history.sh +++ b/examples/data/scripts/load_url_from_history.sh @@ -4,7 +4,6 @@ DMENU_SCHEME="history" DMENU_OPTIONS="xmms vertical resize" . "$UZBL_UTIL_DIR"/dmenu.sh -. "$UZBL_UTIL_DIR"/uzbl-args.sh . "$UZBL_UTIL_DIR"/uzbl-dir.sh [ -r "$UZBL_HISTORY_FILE" ] || exit 1 @@ -13,7 +12,7 @@ DMENU_OPTIONS="xmms vertical resize" # 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) + 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. diff --git a/examples/data/scripts/per-site-settings.py b/examples/data/scripts/per-site-settings.py new file mode 100755 index 0000000..febfd0f --- /dev/null +++ b/examples/data/scripts/per-site-settings.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# Per-site settings plugin + +# Example configuration usage: +# +# @on_event LOAD_COMMIT spawn @scripts_dir/per-site-settings.py @data_home/uzbl/per-site-settings + +# Format of the settings file: +# +# <url> +# <path> +# <command> +# +# - url +# May either be a regex, or literal. If literal, it will block any +# subdomains as well. +# - path +# May either be a regex, or literal. If literal, it will block any +# decendent paths as well. +# - command +# Given to uzbl verbatim. +# +# Matches are attempted on a literal match first. +# +# Any of the specifications can be repeated and acts as a fall-through to the +# next level. Make sure indentation lines up locally. Any indentation addition +# is considered as a fall through to the next level and any decrease is +# considered a pop back (zero is always urls). This works because it's only 3 +# deep. Four and we'd have to keep track of things. + +import os +import re +import socket +import stat +import subprocess +import tempfile +import urlparse +import sys + + +def match_url(url, patt): + return url.endswith(patt) or re.match(patt, url) + + +def match_path(path, patt): + return path.startswith(patt) or re.match(patt, path) + + +def grep_url(url, path, fin): + entries = [] + cur_indent = 0 + passing = [False, False] + # 0 == url + # 1 == path + # 2 == command + state = 0 + for line in fin: + raw = line.strip() + + indent = len(line) - len(raw) - 1 + if not indent: + # Reset state + passing = [False, False] + state = 0 + else: + # previous level + if indent < cur_indent: + if state == 1: + passing[0] = False + elif state == 2: + passing[1] = False + state -= 1 + # next level + elif cur_indent < indent: + state += 1 + + # parse the line + if state == 0: + if not passing[0] and match_url(url, raw): + passing[0] = True + elif state == 1 and passing[0]: + if not passing[1] and match_path(path, raw): + passing[1] = True + elif state == 2 and passing[1]: + entries.append(raw) + + cur_indent = indent + + return entries + + +def write_to_socket(commands, sockpath): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(sockpath) + for command in commands: + sock.send(command) + sock.close() + + +if __name__ == '__main__': + sockpath = os.environ['UZBL_SOCKET'] + url = urlparse.urlparse(os.environ['UZBL_URI']) + filepath = sys.argv[8] + + mode = os.stat(filepath)[stat.ST_MODE] + + if mode & stat.S_IEXEC: + fin = tempfile.TemporaryFile() + subprocess.Popen([filepath], stdout=fin).wait() + else: + fin = open(filepath, 'r') + + commands = grep_url(url.hostname, url.path, fin) + + fin.close() + + write_to_socket(commands, sockpath) diff --git a/examples/data/scripts/pipermail.js b/examples/data/scripts/pipermail.js new file mode 100644 index 0000000..5ec4aa4 --- /dev/null +++ b/examples/data/scripts/pipermail.js @@ -0,0 +1,71 @@ +// this is a userscript inspired by "Pipermail Navigation Links" by Michael +// Stone <http://userscripts.org/scripts/show/3174>. + +// it adds previous month/next month navigation links in pipermail mailing +// list archives. + +// we wrap the whole thing in a function (that gets called immediately) so +// that this script doesn't interfere with any javascript in the page. +(function() { + +// figure out what page we're looking at right now +var urlparts = document.location.toString().split("/"); +var currView = urlparts[urlparts.length-1].split("#")[0]; +var currDate = urlparts[urlparts.length-2].split("-"); + +// figure out the URLs to the next month and previous month +var months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', + 'August', 'September', 'October', 'November', 'December' ]; + +var thisMonth = currDate[1]; +var prevMonth; +var nextMonth; + +var thisYear = currDate[0]; +var prevYear = thisYear; +var nextYear = thisYear; + +if(thisMonth == 'January') { + prevMonth = "December"; + nextMonth = "February"; + prevYear = parseInt(thisYear) - 1; +} else if(thisMonth == 'December') { + prevMonth = "November"; + nextMonth = "January"; + nextYear = parseInt(thisYear) + 1; +} else { + var monthNum = months.indexOf(thisMonth); + prevMonth = months[monthNum - 1]; + nextMonth = months[monthNum + 1]; +} + +var prevHref = "../" + prevYear + "-" + prevMonth + "/" + currView; +var nextHref = "../" + nextYear + "-" + nextMonth + "/" + currView; + +// find the navigation header and footer +var selector = "a[href='date.html#start']"; + +// if we're on a "date" page then the date link isn't displayed +if(currView == "date.html") + selector = "a[href='author.html#start']"; + +var navLinks = document.querySelectorAll(selector); + +// append the prev/next links to the navigation header and footer +for(var i = 0; i < navLinks.length; i++) { + var victim = navLinks[i].parentNode; + + var prevEl = document.createElement("a"); + prevEl.textContent = "[ prev month ]"; + prevEl.href = prevHref; + + var nextEl = document.createElement("a"); + nextEl.textContent = "[ next month ]"; + nextEl.href = nextHref; + + victim.appendChild(prevEl); + victim.appendChild(document.createTextNode(" ")); + victim.appendChild(nextEl); +} + +})(); diff --git a/examples/data/scripts/session.sh b/examples/data/scripts/session.sh index 203cd52..36e0c19 100755 --- a/examples/data/scripts/session.sh +++ b/examples/data/scripts/session.sh @@ -1,14 +1,17 @@ #!/bin/sh # # Very simple session manager for uzbl-browser. -# To use, add a line like 'bind quit = spawn @scripts_dir/session.sh endsession' -# to your config. -# To restore the session, run this script with the argument "launch". An -# instance of uzbl-browser will be launched for each stored url. +# To use, add a line like 'bind quit = spawn @scripts_dir/session.sh' to your +# config. This binding will exit every instance of uzbl and store the URLs they +# had open in $UZBL_SESSION_FILE. # -# When called with "endsession" as the argument, it will backup -# $UZBL_SESSION_FILE, look for fifos in $UZBL_FIFO_DIR and instruct each of them -# to store its current url in $UZBL_SESSION_FILE and terminate. +# When a session file exists this script can be run with no arguments (or the +# argument "launch") to start an instance of uzbl-browser for every stored url. +# +# If no session file exists (or if called with "endsession" as the first +# argument), this script looks for instances of uzbl that have fifos in +# $UZBL_FIFO_DIR and instructs each of them to store its current url in +# $UZBL_SESSION_FILE and terminate. # # "endinstance" is used internally and doesn't need to be called manually. @@ -29,12 +32,17 @@ UZBL="uzbl-browser -c $UZBL_CONFIG_FILE" # add custom flags and whatever here. if [ $# -gt 1 ]; then # this script is being run from uzbl, rather than standalone - . "$UZBL_UTIL_DIR"/uzbl-args.sh + # discard the uzbl arguments + shift 7 fi -scriptfile=$0 # this script +scriptfile=$(readlink -f $0) # this script act="$1" +if [ -z "$act" ]; then + [ -f "$UZBL_SESSION_FILE" ] && act="launch" || act="endsession" +fi + case $act in "launch" ) urls=$(cat "$UZBL_SESSION_FILE") @@ -43,8 +51,8 @@ case $act in else for url in $urls; do $UZBL --uri "$url" & - disown done + mv "$UZBL_SESSION_FILE" "$UZBL_SESSION_FILE~" fi ;; @@ -53,18 +61,17 @@ case $act in echo "session manager: endinstance must be called from uzbl" exit 1 fi - [ "$UZBL_URL" != "(null)" ] && echo "$UZBL_URL" >> "$UZBL_SESSION_FILE" + [ "$UZBL_URI" != "(null)" ] && echo "$UZBL_URI" >> "$UZBL_SESSION_FILE" echo exit > "$UZBL_FIFO" ;; "endsession" ) - mv "$UZBL_SESSION_FILE" "$UZBL_SESSION_FILE~" for fifo in "$UZBL_FIFO_DIR"/uzbl_fifo_*; do if [ "$fifo" != "$UZBL_FIFO" ]; then echo "spawn $scriptfile endinstance" > "$fifo" fi done - echo "spawn $scriptfile endinstance" > "$UZBL_FIFO" + [ -z "$UZBL_FIFO" ] || echo "spawn $scriptfile endinstance" > "$UZBL_FIFO" ;; * ) diff --git a/examples/data/scripts/userscript.sh b/examples/data/scripts/userscript.sh deleted file mode 100755 index 33a24ae..0000000 --- a/examples/data/scripts/userscript.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh - -if [ $# = "3" ] -then - fifo="$1" - url="$2" - SCRIPT="$3" -else - fifo="$4" - url="$6" - SCRIPT="$8" -fi - -# Extract metadata chunk -META="`sed -ne '/^\s*\/\/\s*==UserScript==\s*$/,/^\s*\/\/\s*==\/UserScript==\s*$/p' "$SCRIPT"`" -SHOULD_RUN=false # Assume this script will not be included -# Loop over all include rules -for INCLUDE in `echo "$META" | grep "^\s*\/\/\s*@include"`; do - # Munge into grep pattern - INCLUDE="`echo "$INCLUDE" | sed -e 's/^\s*\/\/\s*@include\s*//' -e 's/\./\\\\./g' -e 's/\*/.*/g' -e 's/[\r\n]//g'`" - if echo "$url" | grep -x "$INCLUDE"; then - SHOULD_RUN=true - break - fi -done - -# Loop over all exclude rules -for EXCLUDE in `echo "$META" | grep "^\s*\/\/\s*@exclude"`; do - # Munge into grep pattern - EXCLUDE="`echo "$EXCLUDE" | sed -e 's/^\s*\/\/\s*@exclude\s*//' -e 's/\./\\\\./g' -e 's/\*/.*/g' -e 's/[\r\n]//g'`" - if echo "$url" | grep -x "$EXCLUDE"; then - SHOULD_RUN=false - break - fi -done - -# Run the script -if [ $SHOULD_RUN = true ]; then - echo "script '$SCRIPT'" > "$fifo" -fi diff --git a/examples/data/scripts/userscripts.sh b/examples/data/scripts/userscripts.sh deleted file mode 100755 index 8896224..0000000 --- a/examples/data/scripts/userscripts.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -scripts_dir="$XDG_DATA_HOME/uzbl/userscripts" - -for SCRIPT in $(grep -rlx "\s*//\s*==UserScript==\s*" "$scripts_dir") -do - $XDG_DATA_HOME/uzbl/scripts/userscript.sh "$4" "$6" "$SCRIPT" -done diff --git a/examples/data/scripts/util/dmenu.sh b/examples/data/scripts/util/dmenu.sh index f789178..da61cae 100644 --- a/examples/data/scripts/util/dmenu.sh +++ b/examples/data/scripts/util/dmenu.sh @@ -67,7 +67,7 @@ if dmenu --help 2>&1 | grep -q '\[-xs\]'; then fi # Detect the vertical patch -if dmenu --help 2>&1 | grep -q '\[-l <lines>\]'; then +if dmenu --help 2>&1 | grep -q '\[-l <\?lines>\?\]'; then # Default to 10 lines if [ -z "$DMENU_LINES" ]; then DMENU_LINES=10 diff --git a/examples/data/scripts/util/uzbl-args.sh b/examples/data/scripts/util/uzbl-args.sh deleted file mode 100644 index 7a3dbe5..0000000 --- a/examples/data/scripts/util/uzbl-args.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -# Arguments from uzbl - -UZBL_CONFIG=$1 -shift -UZBL_PID=$1 -shift -UZBL_XID=$1 -shift -UZBL_FIFO=$1 -shift -UZBL_SOCKET=$1 -shift -UZBL_URL=$1 -shift -UZBL_TITLE=$1 -shift diff --git a/examples/data/scripts/util/uzbl-window.sh b/examples/data/scripts/util/uzbl-window.sh index b2771e4..a7e92eb 100644 --- a/examples/data/scripts/util/uzbl-window.sh +++ b/examples/data/scripts/util/uzbl-window.sh @@ -1,12 +1,6 @@ #!/bin/sh # uzbl window detection -if [ -z "$UZBL_XID" ]; then - echo "Error: UZBL_XID not set" - echo "Please source uzbl-args.sh first" - exit 1 -fi - 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 | \ diff --git a/examples/data/scripts/uzbl-tabbed b/examples/data/scripts/uzbl-tabbed index 9e5d715..0086c04 100755 --- a/examples/data/scripts/uzbl-tabbed +++ b/examples/data/scripts/uzbl-tabbed @@ -471,8 +471,9 @@ class UzblInstance: elif var == "gtk_tab_pos": self.parent.update_gtk_tab_pos() elif var == "status_background": - col = gtk.gdk.color_parse(config['status_background']) - self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) + if config['status_background'].strip(): + col = gtk.gdk.color_parse(config['status_background']) + self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) elif var == "tab_titles" or var == "tab_indexes": for tab in self.parent.notebook: self.parent.tabs[tab].title_changed(True) diff --git a/src/callbacks.c b/src/callbacks.c index fcd7a8c..ee6cf58 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -412,6 +412,7 @@ title_change_cb (WebKitWebView* web_view, GParamSpec param_spec) { uzbl.gui.main_title = title ? g_strdup (title) : g_strdup ("(no title)"); update_title(); send_event(TITLE_CHANGED, uzbl.gui.main_title, NULL); + g_setenv("UZBL_TITLE", uzbl.gui.main_title, TRUE); } void @@ -437,6 +438,7 @@ load_status_change_cb (WebKitWebView* web_view, GParamSpec param_spec) { g_free (uzbl.state.uri); GString* newuri = g_string_new (webkit_web_frame_get_uri (frame)); uzbl.state.uri = g_string_free (newuri, FALSE); + g_setenv("UZBL_URI", uzbl.state.uri, TRUE); send_event(LOAD_COMMIT, webkit_web_frame_get_uri (frame), NULL); break; @@ -744,41 +746,139 @@ create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer us (void) web_view; (void) frame; (void) user_data; - if (uzbl.state.selected_url != NULL) { - if (uzbl.state.verbose) - printf("\nNew web view -> %s\n", uzbl.state.selected_url); - if (strncmp(uzbl.state.selected_url, "javascript:", strlen("javascript:")) == 0) { - WebKitWebView* new_view = WEBKIT_WEB_VIEW(webkit_web_view_new()); + if (uzbl.state.verbose) + printf("New web view -> javascript link...\n"); - g_signal_connect (new_view, "web-view-ready", - G_CALLBACK(create_web_view_js_cb), NULL); + WebKitWebView* new_view = WEBKIT_WEB_VIEW(webkit_web_view_new()); - return new_view; - } - else - send_event(NEW_WINDOW, uzbl.state.selected_url, NULL); + g_signal_connect (new_view, "web-view-ready", + G_CALLBACK(create_web_view_js_cb), NULL); + return new_view; +} - } else { - if (uzbl.state.verbose) - printf("New web view -> javascript link...\n"); +void +download_progress_cb(WebKitDownload *download, GParamSpec *pspec, gpointer user_data) { + (void) pspec; (void) user_data; + + gdouble progress; + g_object_get(download, "progress", &progress, NULL); + + const gchar *dest_uri = webkit_download_get_destination_uri(download); + const gchar *dest_path = dest_uri + strlen("file://"); + + gchar *details = g_strdup_printf("%s %.2lf", dest_path, progress); + send_event(DOWNLOAD_PROGRESS, details, NULL); + g_free(details); +} + +void +download_status_cb(WebKitDownload *download, GParamSpec *pspec, gpointer user_data) { + (void) pspec; (void) user_data; - WebKitWebView* new_view = WEBKIT_WEB_VIEW(webkit_web_view_new()); + WebKitDownloadStatus status; + g_object_get(download, "status", &status, NULL); - g_signal_connect (new_view, "web-view-ready", - G_CALLBACK(create_web_view_js_cb), NULL); - return new_view; + switch(status) { + case WEBKIT_DOWNLOAD_STATUS_CREATED: + case WEBKIT_DOWNLOAD_STATUS_STARTED: + case WEBKIT_DOWNLOAD_STATUS_ERROR: + case WEBKIT_DOWNLOAD_STATUS_CANCELLED: + return; /* these are irrelevant */ + case WEBKIT_DOWNLOAD_STATUS_FINISHED: + { + const gchar *dest_uri = webkit_download_get_destination_uri(download); + const gchar *dest_path = dest_uri + strlen("file://"); + send_event(DOWNLOAD_COMPLETE, dest_path, NULL); + } } - return NULL; } gboolean -download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) { - (void) web_view; - (void) user_data; +download_cb(WebKitWebView *web_view, WebKitDownload *download, gpointer user_data) { + (void) web_view; (void) user_data; - send_event(DOWNLOAD_REQ, webkit_download_get_uri ((WebKitDownload*)download), NULL); - return (FALSE); + /* get the URI being downloaded */ + const gchar *uri = webkit_download_get_uri(download); + + if (uzbl.state.verbose) + printf("Download requested -> %s\n", uri); + + if (!uzbl.behave.download_handler) { + webkit_download_cancel(download); + return FALSE; /* reject downloads when there's no download handler */ + } + + /* get a reasonable suggestion for a filename */ + const gchar *suggested_filename; + g_object_get(download, "suggested-filename", &suggested_filename, NULL); + + /* get the mimetype of the download */ + const gchar *content_type; + WebKitNetworkResponse *r = webkit_download_get_network_response(download); + /* downloads can be initiated from the context menu, in that case there is + no network response yet and trying to get one would crash. */ + if(WEBKIT_IS_NETWORK_RESPONSE(r)) { + SoupMessage *m = webkit_network_response_get_message(r); + SoupMessageHeaders *h; + g_object_get(m, "response-headers", &h, NULL); + content_type = soup_message_headers_get_one(h, "Content-Type"); + } else + content_type = "application/octet-stream"; + + /* get the filesize of the download, as given by the server. + (this may be inaccurate, there's nothing we can do about that.) */ + unsigned int total_size = webkit_download_get_total_size(download); + + gchar *ev = g_strdup_printf("'%s' '%s' '%s' %d", uri, suggested_filename, + content_type, total_size); + run_handler(uzbl.behave.download_handler, ev); + g_free(ev); + + /* no response, cancel the download */ + if(!uzbl.comm.sync_stdout) { + webkit_download_cancel(download); + return FALSE; + } + + /* no response, cancel the download */ + if(uzbl.comm.sync_stdout[0] == 0) { + webkit_download_cancel(download); + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + return FALSE; + } + + /* we got a response, it's the path we should download the file to */ + gchar *destination_path = uzbl.comm.sync_stdout; + uzbl.comm.sync_stdout = NULL; + + /* presumably people don't need newlines in their filenames. */ + char *p = strchr(destination_path, '\n'); + if ( p != NULL ) *p = '\0'; + + /* set up progress callbacks */ + g_signal_connect(download, "notify::status", G_CALLBACK(download_status_cb), NULL); + g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress_cb), NULL); + + /* convert relative path to absolute path */ + if(destination_path[0] != '/') { + gchar *rel_path = destination_path; + gchar *cwd = g_get_current_dir(); + destination_path = g_strconcat(cwd, "/", destination_path, NULL); + g_free(cwd); + g_free(rel_path); + } + + send_event(DOWNLOAD_STARTED, destination_path, NULL); + + /* convert absolute path to file:// URI */ + gchar *destination_uri = g_strconcat("file://", destination_path, NULL); + g_free(destination_path); + + webkit_download_set_destination_uri(download, destination_uri); + g_free(destination_uri); + + return TRUE; } gboolean diff --git a/src/callbacks.h b/src/callbacks.h index a4258f2..40fa80d 100644 --- a/src/callbacks.h +++ b/src/callbacks.h @@ -133,9 +133,6 @@ cmd_load_start(); WebKitWebSettings* view_settings(); -gboolean -download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data); - void toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result); @@ -197,7 +194,7 @@ request_starting_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitWebRes create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data); gboolean -download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data); +download_cb (WebKitWebView *web_view, WebKitDownload *download, gpointer user_data); void populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c); diff --git a/src/cookie-jar.c b/src/cookie-jar.c index f2e340b..626e454 100644 --- a/src/cookie-jar.c +++ b/src/cookie-jar.c @@ -12,6 +12,7 @@ #include "cookie-jar.h" #include "uzbl-core.h" +#include "events.h" static void uzbl_cookie_jar_session_feature_init(SoupSessionFeatureInterface *iface, gpointer user_data); @@ -38,6 +39,8 @@ soup_cookie_jar_socket_init(UzblCookieJar *jar) { jar->handler = NULL; jar->socket_path = NULL; jar->connection_fd = -1; + jar->in_get_callback = 0; + jar->in_manual_add = 0; } static void @@ -141,7 +144,7 @@ request_started(SoupSessionFeature *feature, SoupSession *session, static void changed(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie) { - (void) old_cookie; + SoupCookie * cookie = new_cookie ? new_cookie : old_cookie; UzblCookieJar *uzbl_jar = UZBL_COOKIE_JAR(jar); @@ -155,6 +158,25 @@ changed(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie) { if(uzbl_jar->in_get_callback) return; + gchar *scheme = cookie->secure ? "https" : "http"; + + /* send a ADD or DELETE -_COOKIE event depending on what have changed */ + if(!uzbl_jar->in_manual_add) { + gchar *expires = NULL; + if(cookie->expires) + expires = g_strdup_printf ("%d", soup_date_to_time_t (cookie->expires)); + + gchar * eventstr = g_strdup_printf ("'%s' '%s' '%s' '%s' '%s' '%s'", + cookie->domain, cookie->path, cookie->name, cookie->value, scheme, expires?expires:""); + if(new_cookie) + send_event(ADD_COOKIE, eventstr, NULL); + else + send_event(DELETE_COOKIE, eventstr, NULL); + g_free(eventstr); + if(expires) + g_free(expires); + } + /* the cookie daemon is only interested in new cookies and changed ones, it can take care of deleting expired cookies on its own. */ if(!new_cookie) @@ -162,8 +184,6 @@ changed(SoupCookieJar *jar, SoupCookie *old_cookie, SoupCookie *new_cookie) { GString *s = g_string_new ("PUT"); - gchar *scheme = new_cookie->secure ? "https" : "http"; - if(has_socket_handler(uzbl_jar)) { g_string_append_c(s, 0); /* null-terminate the PUT */ g_string_append_len(s, scheme, strlen(scheme)+1); diff --git a/src/cookie-jar.h b/src/cookie-jar.h index 80af00e..f3e3733 100644 --- a/src/cookie-jar.h +++ b/src/cookie-jar.h @@ -16,6 +16,7 @@ typedef struct { int connection_fd; gboolean in_get_callback; + gboolean in_manual_add; } UzblCookieJar; typedef struct { diff --git a/src/events.c b/src/events.c index 20e3675..baaf8f3 100644 --- a/src/events.c +++ b/src/events.c @@ -22,7 +22,6 @@ const char *event_table[LAST_EVENT] = { "REQUEST_STARTING" , "KEY_PRESS" , "KEY_RELEASE" , - "DOWNLOAD_REQUEST" , "COMMAND_EXECUTED" , "LINK_HOVER" , "TITLE_CHANGED" , @@ -45,10 +44,14 @@ const char *event_table[LAST_EVENT] = { "PLUG_CREATED" , "COMMAND_ERROR" , "BUILTINS" , - "PTR_MOVE" "PTR_MOVE" , "SCROLL_VERT" , - "SCROLL_HORIZ" + "SCROLL_HORIZ" , + "DOWNLOAD_STARTED" , + "DOWNLOAD_PROGRESS", + "DOWNLOAD_COMPLETE", + "ADD_COOKIE" , + "DELETE_COOKIE" }; void @@ -136,24 +139,16 @@ send_event_stdout(GString *msg) { void send_event(int type, const gchar *details, const gchar *custom_event) { GString *event_message = g_string_new(""); - gchar *buf, *p_val = NULL; - - /* expand shell vars */ - if(details) { - buf = g_strdup(details); - p_val = parseenv(buf ? g_strchug(buf) : " "); - g_free(buf); - } /* check for custom events */ if(custom_event) { g_string_printf(event_message, "EVENT [%s] %s %s\n", - uzbl.state.instance_name, custom_event, p_val); + uzbl.state.instance_name, custom_event, details); } /* check wether we support the internal event */ else if(type < LAST_EVENT) { g_string_printf(event_message, "EVENT [%s] %s %s\n", - uzbl.state.instance_name, event_table[type], p_val); + uzbl.state.instance_name, event_table[type], details); } if(event_message->str) { @@ -163,7 +158,6 @@ send_event(int type, const gchar *details, const gchar *custom_event) { g_string_free(event_message, TRUE); } - g_free(p_val); } /* Transform gdk key events to our own events */ diff --git a/src/events.h b/src/events.h index bc7960d..3c7b933 100644 --- a/src/events.h +++ b/src/events.h @@ -7,7 +7,7 @@ enum event_type { LOAD_START, LOAD_COMMIT, LOAD_FINISH, LOAD_ERROR, REQUEST_STARTING, - KEY_PRESS, KEY_RELEASE, DOWNLOAD_REQ, COMMAND_EXECUTED, + KEY_PRESS, KEY_RELEASE, COMMAND_EXECUTED, LINK_HOVER, TITLE_CHANGED, GEOMETRY_CHANGED, WEBINSPECTOR, NEW_WINDOW, SELECTION_CHANGED, VARIABLE_SET, FIFO_SET, SOCKET_SET, @@ -16,6 +16,8 @@ enum event_type { FOCUS_LOST, FOCUS_GAINED, FILE_INCLUDED, PLUG_CREATED, COMMAND_ERROR, BUILTINS, PTR_MOVE, SCROLL_VERT, SCROLL_HORIZ, + DOWNLOAD_STARTED, DOWNLOAD_PROGRESS, DOWNLOAD_COMPLETE, + ADD_COOKIE, DELETE_COOKIE, /* must be last entry */ LAST_EVENT diff --git a/src/uzbl-browser b/src/uzbl-browser index 88d3742..faa2829 100755 --- a/src/uzbl-browser +++ b/src/uzbl-browser @@ -8,6 +8,8 @@ # to your $XDG_DATA_HOME/uzbl/scripts/ and edit them PREFIX=/usr/local +export PREFIX + EXAMPLES=$PREFIX/share/uzbl/examples XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} @@ -66,7 +68,7 @@ fi # we could also check if its pid file exists to avoid having to spawn it. #if [ ! -f "$XDG_CACHE_HOME"/uzbl/cookie_daemon_socket.pid ] #then - ${UZBL_COOKIE_DAEMON:-uzbl-cookie-manager} +# ${UZBL_COOKIE_DAEMON:-uzbl-cookie-manager} #fi # uzbl-event-manager will exit if one is already running. diff --git a/src/uzbl-core.c b/src/uzbl-core.c index 9a9af23..cc680c2 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -93,6 +93,7 @@ const struct var_name_to_ptr_t { { "cookie_handler", PTR_V_STR(uzbl.behave.cookie_handler, 1, cmd_set_cookie_handler)}, { "authentication_handler", PTR_V_STR(uzbl.behave.authentication_handler, 1, set_authentication_handler)}, { "scheme_handler", PTR_V_STR(uzbl.behave.scheme_handler, 1, NULL)}, + { "download_handler", PTR_V_STR(uzbl.behave.download_handler, 1, NULL)}, { "fifo_dir", PTR_V_STR(uzbl.behave.fifo_dir, 1, cmd_fifo_dir)}, { "socket_dir", PTR_V_STR(uzbl.behave.socket_dir, 1, cmd_socket_dir)}, { "http_debug", PTR_V_INT(uzbl.behave.http_debug, 1, cmd_http_debug)}, @@ -414,36 +415,6 @@ find_existing_file(gchar* path_list) { return NULL; } - -/* Returns a new string with environment $variables expanded */ -gchar* -parseenv (gchar* string) { - extern char** environ; - gchar* tmpstr = NULL, * out; - int i = 0; - - if(!string) - return NULL; - - out = g_strdup(string); - while (environ[i] != NULL) { - gchar** env = g_strsplit (environ[i], "=", 2); - gchar* envname = g_strconcat ("$", env[0], NULL); - - if (g_strrstr (string, envname) != NULL) { - tmpstr = out; - out = str_replace(envname, env[1], out); - g_free (tmpstr); - } - - g_free (envname); - g_strfreev (env); // somebody said this breaks uzbl - i++; - } - - return out; -} - void clean_up(void) { if(uzbl.info.pid_str) { @@ -496,8 +467,6 @@ get_click_context() { } /* --- SIGNALS --- */ -int sigs[] = {SIGTERM, SIGINT, SIGSEGV, SIGILL, SIGFPE, SIGQUIT, SIGALRM, 0}; - sigfunc* setup_signal(int signr, sigfunc *shandler) { struct sigaction nh, oh; @@ -513,21 +482,9 @@ setup_signal(int signr, sigfunc *shandler) { } void -catch_signal(int s) { - if(s == SIGTERM || - s == SIGINT || - s == SIGILL || - s == SIGFPE || - s == SIGQUIT) { - clean_up(); - exit(EXIT_SUCCESS); - } - else if(s == SIGSEGV) { - clean_up(); - fprintf(stderr, "Program aborted, segmentation fault!\nAttempting to clean up...\n"); - exit(EXIT_FAILURE); - } - else if(s == SIGALRM && uzbl.state.event_buffer) { +empty_event_buffer(int s) { + (void) s; + if(uzbl.state.event_buffer) { g_ptr_array_free(uzbl.state.event_buffer, TRUE); uzbl.state.event_buffer = NULL; } @@ -626,9 +583,10 @@ struct {const char *key; CommandInfo value;} cmdlist[] = { "js", {run_js, TRUE} }, { "script", {run_external_js, 0} }, { "toggle_status", {toggle_status_cb, 0} }, - { "spawn", {spawn, 0} }, + { "spawn", {spawn_async, 0} }, { "sync_spawn", {spawn_sync, 0} }, // needed for cookie handler - { "sh", {spawn_sh, 0} }, + { "sync_spawn_exec", {spawn_sync_exec, 0} }, // needed for load_cookies.sh :( + { "sh", {spawn_sh_async, 0} }, { "sync_sh", {spawn_sh_sync, 0} }, // needed for cookie handler { "exit", {close_uzbl, 0} }, { "search", {search_forward_text, TRUE} }, @@ -656,7 +614,9 @@ struct {const char *key; CommandInfo value;} cmdlist[] = { "menu_editable_remove", {menu_remove_edit, TRUE} }, { "hardcopy", {hardcopy, TRUE} }, { "include", {include, TRUE} }, - { "show_inspector", {show_inspector, 0} } + { "show_inspector", {show_inspector, 0} }, + { "add_cookie", {add_cookie, 0} }, + { "delete_cookie", {delete_cookie, 0} } }; void @@ -695,9 +655,8 @@ set_var(WebKitWebView *page, GArray *argv, GString *result) { gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2); if (split[0] != NULL) { - gchar *value = parseenv(split[1] ? g_strchug(split[1]) : " "); + gchar *value = split[1] ? g_strchug(split[1]) : " "; set_var_value(g_strstrip(split[0]), value); - g_free(value); } g_strfreev(split); } @@ -927,14 +886,12 @@ void include(WebKitWebView *page, GArray *argv, GString *result) { (void) page; (void) result; - gchar *pe = NULL, - *path = NULL; + gchar *path = argv_idx(argv, 0); - if(!argv_idx(argv, 0)) + if(!path) return; - pe = parseenv(argv_idx(argv, 0)); - if((path = find_existing_file(pe))) { + if((path = find_existing_file(path))) { if(!for_each_line_in_file(path, parse_cmd_line_cb, NULL)) { gchar *tmp = g_strdup_printf("File %s can not be read.", path); send_event(COMMAND_ERROR, tmp, NULL); @@ -944,7 +901,6 @@ include(WebKitWebView *page, GArray *argv, GString *result) { send_event(FILE_INCLUDED, path, NULL); g_free(path); } - g_free(pe); } void @@ -955,6 +911,57 @@ show_inspector(WebKitWebView *page, GArray *argv, GString *result) { } void +add_cookie(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + gchar *host, *path, *name, *value; + gboolean secure = 0; + SoupDate *expires = NULL; + + if(argv->len != 6) + return; + + // Parse with same syntax as ADD_COOKIE event + host = argv_idx (argv, 0); + path = argv_idx (argv, 1); + name = argv_idx (argv, 2); + value = argv_idx (argv, 3); + secure = strcmp (argv_idx (argv, 4), "https") == 0; + if (strlen (argv_idx (argv, 5)) != 0) + expires = soup_date_new_from_time_t ( + strtoul (argv_idx (argv, 5), NULL, 10)); + + // Create new cookie + SoupCookie * cookie = soup_cookie_new (name, value, host, path, -1); + soup_cookie_set_secure (cookie, secure); + if (expires) + soup_cookie_set_expires (cookie, expires); + + // Add cookie to jar + uzbl.net.soup_cookie_jar->in_manual_add = 1; + soup_cookie_jar_add_cookie (SOUP_COOKIE_JAR (uzbl.net.soup_cookie_jar), cookie); + uzbl.net.soup_cookie_jar->in_manual_add = 0; +} + +void +delete_cookie(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + + if(argv->len < 4) + return; + + SoupCookie * cookie = soup_cookie_new ( + argv_idx (argv, 2), + argv_idx (argv, 3), + argv_idx (argv, 0), + argv_idx (argv, 1), + 0); + + uzbl.net.soup_cookie_jar->in_manual_add = 1; + soup_cookie_jar_delete_cookie (SOUP_COOKIE_JAR (uzbl.net.soup_cookie_jar), cookie); + uzbl.net.soup_cookie_jar->in_manual_add = 0; +} + +void act_dump_config() { dump_config(); } @@ -1271,7 +1278,7 @@ run_command (const gchar *command, const guint npre, const gchar **args, /*@null@*/ gchar** split_quoted(const gchar* src, const gboolean unquote) { - /* split on unquoted space, return array of strings; + /* split on unquoted space or tab, return array of strings; remove a layer of quotes and backslashes if unquote */ if (!src) return NULL; @@ -1292,7 +1299,7 @@ split_quoted(const gchar* src, const gboolean unquote) { else if ((*p == '\'') && unquote && !dq) sq = !sq; else if (*p == '\'' && !dq) { g_string_append_c(s, *p); sq = ! sq; } - else if ((*p == ' ') && !dq && !sq) { + else if ((*p == ' ' || *p == '\t') && !dq && !sq) { dup = g_strdup(s->str); g_array_append_val(a, dup); g_string_truncate(s, 0); @@ -1307,37 +1314,51 @@ split_quoted(const gchar* src, const gboolean unquote) { } void -spawn(WebKitWebView *web_view, GArray *argv, GString *result) { - (void)web_view; (void)result; +spawn(GArray *argv, gboolean sync, gboolean exec) { gchar *path = NULL; //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after if (argv_idx(argv, 0) && ((path = find_existing_file(argv_idx(argv, 0)))) ) { + if (uzbl.comm.sync_stdout) + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); run_command(path, 0, - ((const gchar **) (argv->data + sizeof(gchar*))), - FALSE, NULL); + ((const gchar **) (argv->data + sizeof(gchar*))), + sync, sync?&uzbl.comm.sync_stdout:NULL); + // run each line of output from the program as a command + if (sync && exec && uzbl.comm.sync_stdout) { + gchar *head = uzbl.comm.sync_stdout; + gchar *tail; + while ((tail = strchr (head, '\n'))) { + *tail = '\0'; + parse_cmd_line(head, NULL); + head = tail + 1; + } + } g_free(path); } } void -spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result) { +spawn_async(WebKitWebView *web_view, GArray *argv, GString *result) { (void)web_view; (void)result; - gchar *path = NULL; + spawn(argv, FALSE, FALSE); +} - if (argv_idx(argv, 0) && - ((path = find_existing_file(argv_idx(argv, 0)))) ) { - run_command(path, 0, - ((const gchar **) (argv->data + sizeof(gchar*))), - TRUE, &uzbl.comm.sync_stdout); - g_free(path); - } +void +spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + spawn(argv, TRUE, FALSE); } void -spawn_sh(WebKitWebView *web_view, GArray *argv, GString *result) { +spawn_sync_exec(WebKitWebView *web_view, GArray *argv, GString *result) { (void)web_view; (void)result; + spawn(argv, TRUE, TRUE); +} + +void +spawn_sh(GArray *argv, gboolean sync) { if (!uzbl.behave.shell_cmd) { g_printerr ("spawn_sh: shell_cmd is not set!\n"); return; @@ -1351,31 +1372,28 @@ spawn_sh(WebKitWebView *web_view, GArray *argv, GString *result) { for (i = 1; i < g_strv_length(cmd); i++) g_array_prepend_val(argv, cmd[i]); - if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL); + if (cmd) { + if (uzbl.comm.sync_stdout) + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + run_command(cmd[0], g_strv_length(cmd) + 1, + (const gchar **) argv->data, + sync, sync?&uzbl.comm.sync_stdout:NULL); + } g_free (spacer); g_strfreev (cmd); } void -spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result) { +spawn_sh_async(WebKitWebView *web_view, GArray *argv, GString *result) { (void)web_view; (void)result; - if (!uzbl.behave.shell_cmd) { - g_printerr ("spawn_sh_sync: shell_cmd is not set!\n"); - return; - } - - guint i; - gchar *spacer = g_strdup(""); - g_array_insert_val(argv, 1, spacer); - gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE); - - for (i = 1; i < g_strv_length(cmd); i++) - g_array_prepend_val(argv, cmd[i]); + spawn_sh(argv, FALSE); +} - if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, - TRUE, &uzbl.comm.sync_stdout); - g_free (spacer); - g_strfreev (cmd); +void +spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + spawn_sh(argv, TRUE); } void @@ -1592,6 +1610,28 @@ control_fifo(GIOChannel *gio, GIOCondition condition) { return TRUE; } +gboolean +attach_fifo(gchar *path) { + GError *error = NULL; + /* we don't really need to write to the file, but if we open the + * file as 'r' we will block here, waiting for a writer to open + * the file. */ + GIOChannel *chan = g_io_channel_new_file(path, "r+", &error); + if (chan) { + if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) { + if (uzbl.state.verbose) + printf ("attach_fifo: created successfully as %s\n", path); + send_event(FIFO_SET, path, NULL); + uzbl.comm.fifo_path = path; + g_setenv("UZBL_FIFO", uzbl.comm.fifo_path, TRUE); + return TRUE; + } else g_warning ("attach_fifo: could not add watch on %s\n", path); + } else g_warning ("attach_fifo: can't open: %s\n", error->message); + + if (error) g_error_free (error); + return FALSE; +} + /*@null@*/ gchar* init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */ if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */ @@ -1601,28 +1641,31 @@ init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */ uzbl.comm.fifo_path = NULL; } - GIOChannel *chan = NULL; - GError *error = NULL; gchar *path = build_stream_name(FIFO, dir); if (!file_exists(path)) { - if (mkfifo (path, 0666) == 0) { - // we don't really need to write to the file, but if we open the file as 'r' we will block here, waiting for a writer to open the file. - chan = g_io_channel_new_file(path, "r+", &error); - if (chan) { - if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) { - if (uzbl.state.verbose) - printf ("init_fifo: created successfully as %s\n", path); - send_event(FIFO_SET, path, NULL); - uzbl.comm.fifo_path = path; - return dir; - } else g_warning ("init_fifo: could not add watch on %s\n", path); - } else g_warning ("init_fifo: can't open: %s\n", error->message); + if (mkfifo (path, 0666) == 0 && attach_fifo(path)) { + return dir; } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno)); - } else g_warning ("init_fifo: can't create %s: file exists\n", path); + } else { + /* the fifo exists. but is anybody home? */ + int fd = open(path, O_WRONLY|O_NONBLOCK); + if(fd < 0) { + /* some error occurred, presumably nobody's on the read end. + * we can attach ourselves to it. */ + if(attach_fifo(path)) + return dir; + else + g_warning("init_fifo: can't attach to %s: %s\n", path, strerror(errno)); + } else { + /* somebody's there, we can't use that fifo. */ + close(fd); + /* whatever, this instance can live without a fifo. */ + g_warning ("init_fifo: can't create %s: file exists and is occupied\n", path); + } + } /* if we got this far, there was an error; cleanup */ - if (error) g_error_free (error); g_free(dir); g_free(path); return NULL; @@ -1773,6 +1816,32 @@ control_client_socket(GIOChannel *clientchan) { return TRUE; } + +gboolean +attach_socket(gchar *path, struct sockaddr_un *local) { + GIOChannel *chan = NULL; + int sock = socket (AF_UNIX, SOCK_STREAM, 0); + + if (bind (sock, (struct sockaddr *) local, sizeof(*local)) != -1) { + if (uzbl.state.verbose) + printf ("init_socket: opened in %s\n", path); + + if(listen (sock, 5) < 0) + g_warning ("attach_socket: could not listen on %s: %s\n", path, strerror(errno)); + + if( (chan = g_io_channel_unix_new(sock)) ) { + g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan); + uzbl.comm.socket_path = path; + send_event(SOCKET_SET, path, NULL); + g_setenv("UZBL_SOCKET", uzbl.comm.socket_path, TRUE); + return TRUE; + } + } else g_warning ("attach_socket: could not bind to %s: %s\n", path, strerror(errno)); + + return FALSE; +} + + /*@null@*/ gchar* init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */ if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */ @@ -1787,30 +1856,33 @@ init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL * return NULL; } - GIOChannel *chan = NULL; - int sock, len; struct sockaddr_un local; gchar *path = build_stream_name(SOCKET, dir); - sock = socket (AF_UNIX, SOCK_STREAM, 0); - local.sun_family = AF_UNIX; strcpy (local.sun_path, path); - unlink (local.sun_path); - len = strlen (local.sun_path) + sizeof (local.sun_family); - if (bind (sock, (struct sockaddr *) &local, len) != -1) { - if (uzbl.state.verbose) - printf ("init_socket: opened in %s\n", path); - listen (sock, 5); - - if( (chan = g_io_channel_unix_new(sock)) ) { - g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan); - uzbl.comm.socket_path = path; - send_event(SOCKET_SET, path, NULL); - return dir; + if(!file_exists(path) && attach_socket(path, &local)) { + /* it's free for the taking. */ + return dir; + } else { + /* see if anybody's listening on the socket path we want. */ + int sock = socket (AF_UNIX, SOCK_STREAM, 0); + if(connect(sock, (struct sockaddr *) &local, sizeof(local)) < 0) { + /* some error occurred, presumably nobody's listening. + * we can attach ourselves to it. */ + unlink(path); + if(attach_socket(path, &local)) + return dir; + else + g_warning("init_socket: can't attach to existing socket %s: %s\n", path, strerror(errno)); + } else { + /* somebody's there, we can't use that socket path. */ + close(sock); + /* whatever, this instance can live without a socket. */ + g_warning ("init_socket: can't create %s: socket exists and is occupied\n", path); } - } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno)); + } /* if we got this far, there was an error; cleanup */ g_free(path); @@ -2075,6 +2147,7 @@ settings_init () { send_event(COMMAND_ERROR, tmp, NULL); g_free(tmp); } + g_setenv("UZBL_CONFIG", s->config_file, TRUE); } else if (uzbl.state.verbose) printf ("No configuration file loaded.\n"); @@ -2241,10 +2314,10 @@ initialize(int argc, char *argv[]) { uzbl.net.soup_cookie_jar = uzbl_cookie_jar_new(); soup_session_add_feature(uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_cookie_jar)); - for(i=0; sigs[i]; i++) { - if(setup_signal(sigs[i], catch_signal) == SIG_ERR) - fprintf(stderr, "uzbl: error hooking %d: %s\n", sigs[i], strerror(errno)); - } + /* TODO: move the handler setup to event_buffer_timeout and disarm the + * handler in empty_event_buffer? */ + if(setup_signal(SIGALRM, empty_event_buffer) == SIG_ERR) + fprintf(stderr, "uzbl: error hooking %d: %s\n", SIGALRM, strerror(errno)); event_buffer_timeout(10); uzbl.info.webkit_major = webkit_major_version(); @@ -2358,12 +2431,16 @@ main (int argc, char* argv[]) { "signal::changed", (GCallback)scroll_horiz_cb, NULL, NULL); + gchar *xwin = g_strdup_printf("%d", (int)uzbl.xwin); + g_setenv("UZBL_XID", xwin, TRUE); + if(!uzbl.state.instance_name) - uzbl.state.instance_name = itos((int)uzbl.xwin); + uzbl.state.instance_name = g_strdup(xwin); - GString *tmp = g_string_new(""); - g_string_printf(tmp, "%d", getpid()); - uzbl.info.pid_str = g_string_free(tmp, FALSE); + g_free(xwin); + + uzbl.info.pid_str = g_strdup_printf("%d", getpid()); + g_setenv("UZBL_PID", uzbl.info.pid_str, TRUE); send_event(INSTANCE_START, uzbl.info.pid_str, NULL); if(uzbl.state.plug_mode) { diff --git a/src/uzbl-core.h b/src/uzbl-core.h index b5a502e..98ae342 100644 --- a/src/uzbl-core.h +++ b/src/uzbl-core.h @@ -133,6 +133,7 @@ typedef struct { gchar* fantasy_font_family; gchar* cursive_font_family; gchar* scheme_handler; + gchar* download_handler; gboolean show_status; gboolean forward_keys; gboolean status_top; @@ -228,9 +229,6 @@ itos(int val); gchar* strfree(gchar *str); -gchar* -parseenv (gchar* string); - void clean_up(void); @@ -266,10 +264,10 @@ run_command(const gchar *command, const guint npre, const gchar **args, const gboolean sync, char **output_stdout); void -spawn(WebKitWebView *web_view, GArray *argv, GString *result); +spawn_async(WebKitWebView *web_view, GArray *argv, GString *result); void -spawn_sh(WebKitWebView *web_view, GArray *argv, GString *result); +spawn_sh_async(WebKitWebView *web_view, GArray *argv, GString *result); void spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result); @@ -278,6 +276,9 @@ void spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result); void +spawn_sync_exec(WebKitWebView *web_view, GArray *argv, GString *result); + +void parse_command(const char *cmd, const char *param, GString *result); void @@ -454,6 +455,12 @@ void show_inspector(WebKitWebView *page, GArray *argv, GString *result); void +add_cookie(WebKitWebView *page, GArray *argv, GString *result); + +void +delete_cookie(WebKitWebView *page, GArray *argv, GString *result); + +void builtins(); typedef void (*Command)(WebKitWebView*, GArray *argv, GString *result); |