diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | README | 1 | ||||
-rw-r--r-- | docs/formfiller-data-format | 35 | ||||
-rw-r--r-- | examples/config/config | 11 | ||||
-rw-r--r-- | examples/data/scripts/follow.js | 416 | ||||
-rwxr-xr-x | examples/data/scripts/follow.sh | 20 | ||||
-rw-r--r-- | examples/data/scripts/formfiller.js | 67 | ||||
-rwxr-xr-x | examples/data/scripts/formfiller.sh | 324 | ||||
-rw-r--r-- | examples/data/style.css | 38 | ||||
-rw-r--r-- | extras/vim/syntax/uzbl.vim | 13 | ||||
-rw-r--r-- | src/callbacks.c | 85 | ||||
-rw-r--r-- | src/callbacks.h | 6 | ||||
-rw-r--r-- | src/events.c | 7 | ||||
-rw-r--r-- | src/events.h | 5 | ||||
-rw-r--r-- | src/io.c | 11 | ||||
-rw-r--r-- | src/util.c | 176 | ||||
-rw-r--r-- | src/util.h | 6 | ||||
-rw-r--r-- | src/uzbl-core.c | 34 | ||||
-rw-r--r-- | src/uzbl-core.h | 14 | ||||
-rw-r--r-- | tests/test-command.c | 8 |
20 files changed, 667 insertions, 611 deletions
@@ -75,6 +75,7 @@ In alphabetical order: Simon Lipp (sloonz) - various patches, EM contributions Sylvester Johansson (scj) - form filler script & different take on link follower Tassilo Horn (tsdh) - $VISUAL patch + Taylan Ulrich Bayırlı (taylanub) - updated form filler Thorsten Wilms - logo design Tom Adams (holizz) - few patches, cookies.py, gtkplug/socket & proof of concept uzbl_tabbed.py, scheme_handler Uli Schlachter (psychon) - basic mime_policy_cb & Makefile patch @@ -356,6 +356,7 @@ file). stylesheet. * `resizable_text_areas`: Whether text areas can be resized (default 0). * `default_encoding`: The default text encoding (default "iso-8859-1"). +* `current_encoding`: This can be set to force a text encoding. * `enforce_96_dpi`: Enforce a resolution of 96 DPI (default 1). * `caret_browsing`: Whether the caret is enabled in the text portion of pages (default 0). diff --git a/docs/formfiller-data-format b/docs/formfiller-data-format new file mode 100644 index 0000000..f42114c --- /dev/null +++ b/docs/formfiller-data-format @@ -0,0 +1,35 @@ +FORMFILLER FILE FORMAT + +lines starting with '>' are ignored + +a file consists of profile definitions +lines between profile definitions are ignored + +a line starting with '!profile=' introduces a profile definition +the rest of that line is taken as the profile name +profile names must match the RE /^[a-zA-Z0-9_-]*$/ +a line starting with '!' terminates the profile definition +the rest of that line is ignored + +a profile definition consists of field definitions +lines between field definitions are ignored + +a line starting with '%' introduces a field definition +(details depend on the field type) +the rest of that line shall be in the format ... + 'name(type){value}:checked' for checkbox/radio + 'name(type):value' for text/password/search + 'name(type):' for textarea +where name/type/value/checked are the input field's HTML attribute values, + 'name' always being percent encoded, 'checked' being a JS boolean, + and 'value' being percent encoded if type is checkbox/radio +for all types but textarea, the field definition ends with that single line +for textarea, all lines up to (but excluding) one starting with '%' make up the 'value' +the rest of that line starting with '%' is ignored + +for all lines, a leading '\', if present, is removed + +-- + +a once-edit temporary file consists only of field definitions, + and lines starting with '!' or '>' are not special diff --git a/examples/config/config b/examples/config/config index d5555e0..bcd6d3e 100644 --- a/examples/config/config +++ b/examples/config/config @@ -69,6 +69,11 @@ set download_handler = sync_spawn @scripts_dir/download.sh # Load commit handlers @on_event LOAD_COMMIT @set_status <span foreground="green">recv</span> + # add some javascript to the page for other 'js' and 'script' commands to access later. +@on_event LOAD_COMMIT js uzbl = {}; +@on_event LOAD_COMMIT script @scripts_dir/formfiller.js +@on_event LOAD_COMMIT script @scripts_dir/follow.js + # 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 @@ -89,6 +94,9 @@ set download_handler = sync_spawn @scripts_dir/download.sh # === Behaviour and appearance =============================================== +# Custom CSS can be defined here, including link follower hint styles +set stylesheet_uri = file://@data_home/uzbl/style.css + set show_status = 1 set status_top = 0 set status_background = #303030 @@ -323,7 +331,8 @@ set follow_hint_keys = 0123456789 #set follow_hint_keys = qwerty #set follow_hint_keys = asdfghjkl; #set follow_hint_keys = thsnd-rcgmvwb/;789aefijkopquxyz234 -@cbind fl* = spawn @scripts_dir/follow.sh "%s" +@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 # Form filler binds diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js index d995696..536256b 100644 --- a/examples/data/scripts/follow.js +++ b/examples/data/scripts/follow.js @@ -1,219 +1,192 @@ /* This is the basic linkfollowing script. - * Its pretty stable, and you configure which keys to use for hinting * - * TODO: Some pages mess around a lot with the zIndex which - * lets some hints in the background. - * TODO: Some positions are not calculated correctly (mostly - * because of uber-fancy-designed-webpages. Basic HTML and CSS - * works good - * TODO: Still some links can't be followed/unexpected things - * happen. Blame some freaky webdesigners ;) + * TODO: + * Some pages mess around a lot with the zIndex which + * lets some hints in the background. + * Some positions are not calculated correctly (mostly + * because of uber-fancy-designed-webpages. Basic HTML and CSS + * works good + * Still some links can't be followed/unexpected things + * happen. Blame some freaky webdesigners ;) */ -//Just some shortcuts and globals -var uzblid = 'uzbl_link_hint'; -var uzbldivid = uzblid + '_div_container'; -var doc = document; -var win = window; -var links = document.links; -var forms = document.forms; -//Make onlick-links "clickable" -try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ - type: 'click' - }); - } - }; -} catch(e) {} -//Catch the ESC keypress to stop linkfollowing -function keyPressHandler(e) { - var kC = window.event ? event.keyCode: e.keyCode; - var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; - if (kC == Esc) { - removeAllHints(); - } +// Globals +uzbldivid = 'uzbl_link_hints'; + +uzbl.follow = function() { + // Export + charset = arguments[0]; + newwindow = arguments[2]; + + var keypress = arguments[1]; + return arguments.callee.followLinks(keypress); +} + +uzbl.follow.isFrame = function(el) { + return (el.tagName == "FRAME" || el.tagName == "IFRAME"); +} + +// find the document that the given element belongs to +uzbl.follow.getDocument = function(el) { + if (this.isFrame(el)) + return el.contentDocument; + + var doc = el; + while (doc.parentNode !== null) + doc = doc.parentNode; + return doc; +} + +// find all documents in the display, searching frames recursively +uzbl.follow.documents = function() { + return this.windows().map(function(w) { return w.document; }).filter(function(d) { return d != null; }); +} + +// find all windows in the display, searching for frames recursively +uzbl.follow.windows = function(w) { + w = (typeof w == 'undefined') ? window.top : w; + + var wins = [w]; + var frames = w.frames; + for(var i = 0; i < frames.length; i++) + wins = wins.concat(uzbl.follow.windows(frames[i])); + return wins; +} + +// search all frames for elements matching the given CSS selector +uzbl.follow.query = function(selector) { + var res = []; + this.documents().forEach(function (doc) { + var set = doc.body.querySelectorAll(selector); + // convert the NodeList to an Array + set = Array.prototype.slice.call(set); + res = res.concat(set); + }); + return res; } -//Calculate element position to draw the hint -//Pretty accurate but on fails in some very fancy cases -function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; + +// Calculate element position to draw the hint +uzbl.follow.elementPosition = function(el) { + // el.getBoundingClientRect is another way to do this, but when a link is + // line-wrapped we want our hint at the left end of the link, not its + // bounding rectangle + var up = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; var height = el.offsetHeight; + while (el.offsetParent) { el = el.offsetParent; up += el.offsetTop; left += el.offsetLeft; } + return [up, left, width, height]; } -//Calculate if an element is visible -function isVisible(el) { - if (el == doc) { - return true; - } - if (!el) { - return false; - } - if (!el.parentNode) { - return false; - } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); -} -//Calculate if an element is on the viewport. -function elementInViewport(el) { - offset = elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; + +// Calculate if an element is on the viewport. +uzbl.follow.elementInViewport = function(el) { + offset = uzbl.follow.elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; + return up < window.pageYOffset + window.innerHeight && + left < window.pageXOffset + window.innerWidth && + (up + height) > window.pageYOffset && + (left + width) > window.pageXOffset; } -//Removes all hints/leftovers that might be generated -//by this script. -function removeAllHints() { + +// Removes all hints/leftovers that might be generated +// by this script. +uzbl.follow.removeAllHints = function(doc) { var elements = doc.getElementById(uzbldivid); - if (elements) { - elements.parentNode.removeChild(elements); - } + if (elements) elements.parentNode.removeChild(elements); } -//Generate a hint for an element with the given label -//Here you can play around with the style of the hints! -function generateHint(el, label) { - var pos = elementPosition(el); - var hint = doc.createElement('div'); - hint.setAttribute('name', uzblid); + +// 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) { + var hint = doc.createElement('span'); hint.innerText = label; - hint.style.display = 'inline'; - hint.style.backgroundColor = '#B9FF00'; - hint.style.border = '2px solid #4A6600'; - hint.style.color = 'black'; - hint.style.fontSize = '9px'; - hint.style.fontWeight = 'bold'; - hint.style.lineHeight = '9px'; - hint.style.margin = '0px'; - hint.style.width = 'auto'; // fix broken rendering on w3schools.com - hint.style.padding = '1px'; hint.style.position = 'absolute'; - hint.style.zIndex = '1000'; - // hint.style.textTransform = 'uppercase'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; - // var img = el.getElementsByTagName('img'); - // if (img.length > 0) { - // hint.style.top = pos[1] + img[0].height / 2 - 6 + 'px'; - // } - hint.style.textDecoration = 'none'; - // hint.style.webkitBorderRadius = '6px'; // slow - // Play around with this, pretty funny things to do :) - // hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; + hint.style.top = top + 'px'; + hint.style.left = left + 'px'; return hint; } -// Here we choose what to do with an element that the user has selected. -// Form elements get selected and/or focussed, and links and buttons are -// clicked. This function returns "XXXRESET_MODEXXX" to indicate that uzbl -// should be reset to command mode with an empty keycmd, or -// "XXX_EMIT_FORM_ACTIVEXXX" to indicate that uzbl should be set to insert mode. -function clickElem(item) { - removeAllHints(); - if (item) { - var name = item.tagName; - if (name == 'BUTTON') { - item.click(); - return "XXXRESET_MODEXXX"; - } else if (name == 'INPUT') { - var type = item.type.toUpperCase(); - if (type == 'TEXT' || type == 'SEARCH' || type == 'PASSWORD') { - item.focus(); - item.select(); - return "XXXEMIT_FORM_ACTIVEXXX"; - } else { - item.click(); - return "XXXRESET_MODEXXX"; - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { +// Here we choose what to do with an element if we +// want to "follow" it. On form elements we "select" +// or pass the focus, on links we try to perform a click, +// but at least set the href of the link. (needs some improvements) +uzbl.follow.clickElem = function(item) { + if(!item) return; + var name = item.tagName; + + if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { item.focus(); item.select(); return "XXXEMIT_FORM_ACTIVEXXX"; - } else { - item.click(); - window.location = item.href; - return "XXXRESET_MODEXXX"; } + // otherwise fall through to a simulated mouseclick. + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + return "XXXEMIT_FORM_ACTIVEXXX"; } + + // simulate a mouseclick to activate the element + var mouseEvent = document.createEvent("MouseEvent"); + mouseEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + item.dispatchEvent(mouseEvent); + return "XXXRESET_MODEXXX"; } -//Returns a list of all links (in this version -//just the elements itself, but in other versions, we -//add the label here. -function addLinks() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (isVisible(li) && elementInViewport(li)) { - res[0].push(li); - } - } - return res; -} -//Same as above, just for the form elements -function addFormElems() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { - res[0].push(el); - } - } - } - return res; -} -//Draw all hints for all elements passed. "len" is for -//the number of chars we should use to avoid collisions -function reDrawHints(elems, chars) { - removeAllHints(); - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - for (var i = 0; i < elems[0].length; i++) { - if (elems[0][i]) { - var label = elems[1][i].substring(chars); - var h = generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) { - document.body.appendChild(hintdiv); - } + +// Draw all hints for all elements passed. +uzbl.follow.reDrawHints = function(elems, chars) { + var elements = elems.map(function(pair) { return pair[0] }); + var labels = elems.map(function(pair) { return pair[1].substring(chars) }); + // we have to calculate element positions before we modify the DOM + // otherwise the elementPosition call slows way down. + var positions = elements.map(uzbl.follow.elementPosition); + + this.documents().forEach(function(doc) { + uzbl.follow.removeAllHints(doc); + if (!doc.body) return; + doc.hintdiv = doc.createElement('div'); + doc.hintdiv.id = uzbldivid; + if(newwindow) doc.hintdiv.className = "new-window"; + doc.body.appendChild(doc.hintdiv); + }); + + elements.forEach(function(el, i) { + var label = labels[i]; + var pos = positions[i]; + var doc = uzbl.follow.getDocument(el); + var h = uzbl.follow.generateHint(doc, el, label, pos[0], pos[1]); + doc.hintdiv.appendChild(h); + }); } + // pass: number of keys // returns: key length -function labelLength(n) { +uzbl.follow.labelLength = function(n) { var oldn = n; var keylen = 0; - if(n < 2) { - return 1; - } - n -= 1; // our highest key will be n-1 + if(n < 2) return 1; + n -= 1; // Our highest key will be n-1 while(n) { keylen += 1; n = Math.floor(n / charset.length); } return keylen; } + // pass: number // returns: label -function intToLabel(n) { +uzbl.follow.intToLabel = function(n) { var label = ''; do { label = charset.charAt(n % charset.length) + label; @@ -221,55 +194,68 @@ function intToLabel(n) { } while(n); return label; } + // pass: label // returns: number -function labelToInt(label) { +uzbl.follow.labelToInt = function(label) { var n = 0; - var i; - for(i = 0; i < label.length; ++i) { + for(var i = 0; i < label.length; ++i) { n *= charset.length; n += charset.indexOf(label[i]); } return n; } -//Put it all together -function followLinks(follow) { - // if(follow.charAt(0) == 'l') { - // follow = follow.substr(1); - // charset = 'thsnlrcgfdbmwvz-/'; - // } + +// Put it all together +uzbl.follow.followLinks = function(follow) { var s = follow.split(''); - var linknr = labelToInt(follow); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = addLinks(); - var formelems = addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = labelLength(elems[0].length); - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (s.length == len && linknr < elems[0].length && linknr >= 0) { - return clickElem(elems[0][linknr]); - } else { - for (var j = 0; j < elems[0].length; j++) { - var b = true; - var label = intToLabel(j); - var n = label.length; - for (n; n < len; n++) { - label = charset.charAt(0) + label; - } - for (var k = 0; k < s.length; k++) { - b = b && label.charAt(k) == s[k]; - } - if (b) { - leftover[0].push(elems[0][j]); - leftover[1].push(label); - } + var linknr = this.labelToInt(follow); + + var followable = 'a, area, textarea, select, input:not([type=hidden]), button'; + 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]'; + //var image = 'img, input[type=image]'; + + if(newwindow) + var res = this.query(uri); + else + var res = this.query(followable); + + var elems = res.filter(uzbl.follow.elementInViewport); + var len = this.labelLength(elems.length); + + if (s.length == len && linknr < elems.length && linknr >= 0) { + // an element has been selected! + var el = elems[linknr]; + + // clear all of our hints + this.documents().forEach(uzbl.follow.removeAllHints); + + if (newwindow) { + // we're opening a new window using the URL attached to this element + var uri = el.src || el.href; + if(uri.match(/javascript:/)) return; + window.open(uri); + return "XXXRESET_MODEXXX" } - reDrawHints(leftover, s.length); + + // we're just going to click the element + return this.clickElem(el); } -} -//Parse input: first argument is follow keys, second is user input. -var args = '%s'.split(' '); -var charset = args[0]; -followLinks(args[1]); + var leftover = []; + for (var j = 0; j < elems.length; j++) { + var b = true; + var label = this.intToLabel(j); + var n = label.length; + for (n; n < len; n++) + label = charset.charAt(0) + label; + for (var k = 0; k < s.length; k++) + b = b && label.charAt(k) == s[k]; + if (b) + leftover.push([elems[j], label]); + } + + this.reDrawHints(leftover, s.length); +} diff --git a/examples/data/scripts/follow.sh b/examples/data/scripts/follow.sh index d7fe117..1f8947d 100755 --- a/examples/data/scripts/follow.sh +++ b/examples/data/scripts/follow.sh @@ -1,21 +1,11 @@ #!/bin/sh +# This scripts acts on the return value of followLinks in follow.js -# This script is just a wrapper around follow.js that lets us change uzbl's mode -# after a link is selected. - -# if socat is installed then we can change Uzbl's input mode once a link is -# selected; otherwise we just select a link. -if ! which socat >/dev/null 2>&1; then - echo 'script @scripts_dir/follow.js "@{follow_hint_keys} '$1'"' > "$UZBL_FIFO" - exit -fi - -result=$(echo 'script @scripts_dir/follow.js "@{follow_hint_keys} '$1'"' | socat - unix-connect:"$UZBL_SOCKET") -case $result in - *XXXEMIT_FORM_ACTIVEXXX*) +case "$1" in + XXXEMIT_FORM_ACTIVEXXX) # a form element was selected - echo 'event FORM_ACTIVE' > "$UZBL_FIFO" ;; - *XXXRESET_MODEXXX*) + 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.js b/examples/data/scripts/formfiller.js new file mode 100644 index 0000000..abf0162 --- /dev/null +++ b/examples/data/scripts/formfiller.js @@ -0,0 +1,67 @@ +uzbl.formfiller = { + + dump: function() { + var rv = ''; + var allFrames = new Array(window); + for ( f=0; f<window.frames.length; ++f ) { + allFrames.push(window.frames[f]); + } + for ( j=0; j<allFrames.length; ++j ) { + try { + var xp_res = allFrames[j].document.evaluate( + '//input', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null + ); + var input; + while ( input = xp_res.iterateNext() ) { + var type = (input.type?input.type:text); + if ( type == 'text' || type == 'password' || type == 'search' ) { + rv += '%' + escape(input.name) + '(' + type + '):' + input.value + '\n'; + } + else if ( type == 'checkbox' || type == 'radio' ) { + rv += '%' + escape(input.name) + '(' + type + '){' + escape(input.value) + '}:' + (input.checked?'1':'0') + '\n'; + } + } + xp_res = allFrames[j].document.evaluate( + '//textarea', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null + ); + var input; + while ( input = xp_res.iterateNext() ) { + rv += '%' + escape(input.name) + '(textarea):\n' + input.value.replace(/\n%/g,"\n\\%") + '\n%\n'; + } + } + catch (err) { } + } + return 'formfillerstart\n' + rv + '%!end'; + } + + , + + insert: function(fname, ftype, fvalue, fchecked) { + fname = unescape(fname); + var allFrames = new Array(window); + for ( f=0; f<window.frames.length; ++f ) { + allFrames.push(window.frames[f]); + } + for ( j=0; j<allFrames.length; ++j ) { + try { + if ( ftype == 'text' || ftype == 'password' || ftype == 'search' || ftype == 'textarea' ) { + allFrames[j].document.getElementsByName(fname)[0].value = fvalue; + } + else if ( ftype == 'checkbox' ) { + allFrames[j].document.getElementsByName(fname)[0].checked = fchecked; + } + else if ( ftype == 'radio' ) { + fvalue = unescape(fvalue); + var radios = allFrames[j].document.getElementsByName(fname); + for ( r=0; r<radios.length; ++r ) { + if ( radios[r].value == fvalue ) { + radios[r].checked = fchecked; + } + } + } + } + catch (err) { } + } + } + +} diff --git a/examples/data/scripts/formfiller.sh b/examples/data/scripts/formfiller.sh index c6822e6..3dc9dc4 100755 --- a/examples/data/scripts/formfiller.sh +++ b/examples/data/scripts/formfiller.sh @@ -1,202 +1,146 @@ #!/bin/sh # -# Enhanced html form (eg for logins) filler (and manager) for uzbl. -# -# uses settings files like: $UZBL_FORMS_DIR/<domain> -# files contain lines like: !profile=<profile_name> -# <fieldname>(fieldtype): <value> -# profile_name should be replaced with a name that will tell sth about that -# profile -# fieldtype can be checkbox, text or password, textarea - only for information -# pupropse (auto-generated) - don't change that -# -# Texteares: for textareas edited text can be now splitted into more lines. -# If there will be text, that doesn't match key line: -# <fieldname>(fieldtype):<value> -# then it will be considered as a multiline for the first field above it -# Keep in mind, that if you make more than one line for fileds like input -# text fields, then all lines will be inserted into as one line -# -# Checkboxes/radio-buttons: to uncheck it type on of the following after the -# colon: -# no -# off -# 0 -# unchecked -# false -# or leave it blank, even without spaces -# otherwise it will be considered as checked -# -# user arg 1: -# edit: force editing the file (falls back to new if not found) -# new: start with a new file. -# load: try to load from file into form -# add: try to add another profile to an existing file -# once: edit form using external editor -# -# something else (or empty): if file not available: new, otherwise load. +# action +# new: add new profile template (creates file if not found), then edit +# edit: edit file (fall back to 'new' if file not found) +# load: load from file +# once: use temporary file to edit form once +# (empty): if file not available, new; otherwise, load # -DMENU_ARGS="-i" -DMENU_SCHEMA="formfiller" -DMENU_LINES="3" -DMENU_PROMPT="Choose profile" -DMENU_OPTIONS="vertical resize" +action=$1 -. $UZBL_UTIL_DIR/dmenu.sh -. $UZBL_UTIL_DIR/editor.sh -. $UZBL_UTIL_DIR/uzbl-dir.sh +. "$UZBL_UTIL_DIR/uzbl-dir.sh" +. "$UZBL_UTIL_DIR/editor.sh" -RAND=$(dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -c 1-5) -MODELINE="> vim:ft=formfiller" +mkdir -p "$UZBL_FORMS_DIR" || exit -[ -d "$(dirname $UZBL_FORMS_DIR)" ] || exit 1 -[ -d $UZBL_FORMS_DIR ] || mkdir $UZBL_FORMS_DIR || exit 1 +domain=${UZBL_URI#*://} +domain=${domain%%/*} -action=$1 +test "$domain" || exit -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" - [ -e "$UZBL_FORMS_DIR/$domain" ] && action="load" -elif [ "$action" = 'edit' ] && [ ! -e "$UZBL_FORMS_DIR/$domain" ]; then - action="new" -fi - -dumpFunction="function dump() { \ - var rv=''; \ - var allFrames = new Array(window); \ - for(f=0;f<window.frames.length;f=f+1) { \ - allFrames.push(window.frames[f]); \ - } \ - for(j=0;j<allFrames.length;j=j+1) { \ - try { \ - var xp_res=allFrames[j].document.evaluate('//input', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null); \ - var input; \ - while(input=xp_res.iterateNext()) { \ - var type=(input.type?input.type:text); \ - if(type == 'text' || type == 'password' || type == 'search') { \ - rv += input.name + '(' + type + '):' + input.value + '\\\\n'; \ - } \ - else if(type == 'checkbox' || type == 'radio') { \ - rv += input.name + '{' + input.value + '}(' + type + '):' + (input.checked?'ON':'OFF') + '\\\\n'; \ - } \ - } \ - xp_res=allFrames[j].document.evaluate('//textarea', allFrames[j].document.documentElement, null, XPathResult.ANY_TYPE,null); \ - var input; \ - while(input=xp_res.iterateNext()) { \ - rv += input.name + '(textarea):' + input.value + '\\\\n'; \ - } \ - } \ - catch(err) { } \ - } \ - return rv; \ -}; " - -insertFunction="function insert(fname, ftype, fvalue, fchecked) { \ - var allFrames = new Array(window); \ - for(f=0;f<window.frames.length;f=f+1) { \ - allFrames.push(window.frames[f]); \ - } \ - for(j=0;j<allFrames.length;j=j+1) { \ - try { \ - if(ftype == 'text' || ftype == 'password' || ftype == 'search' || ftype == 'textarea') { \ - allFrames[j].document.getElementsByName(fname)[0].value = fvalue; \ - } \ - else if(ftype == 'checkbox') { \ - allFrames[j].document.getElementsByName(fname)[0].checked = fchecked;\ - } \ - else if(ftype == 'radio') { \ - var radios = allFrames[j].document.getElementsByName(fname); \ - for(r=0;r<radios.length;r+=1) { \ - if(radios[r].value == fvalue) { \ - radios[r].checked = fchecked; \ - } \ - } \ - } \ - } \ - catch(err) { } \ - } \ -}; " - -if [ "$action" = 'load' ]; then - [ -e $UZBL_FORMS_DIR/$domain ] || exit 2 - if [ $(cat $UZBL_FORMS_DIR/$domain | grep "!profile" | wc -l) -gt 1 ]; then - menu=$(cat $UZBL_FORMS_DIR/$domain | \ - sed -n 's/^!profile=\([^[:blank:]]\+\)/\1/p') - option=$(printf "$menu" | $DMENU) - fi +file=$UZBL_FORMS_DIR/$domain + +GenForm () +{ + echo 'js uzbl.formfiller.dump();' \ + | socat - unix-connect:"$UZBL_SOCKET" \ + | awk ' + /^formfillerstart$/ { + while (getline) { + if ( /^%!end/ ) exit + print + } + } + ' +} + +GetOption () +{ + DMENU_SCHEME=formfiller + DMENU_PROMPT="choose profile" + DMENU_LINES=4 - # Remove comments - sed '/^>/d' -i $tmpfile - - sed 's/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):\(off\|no\|false\|unchecked\|0\|$\)/\1{\2}(\3):0/I;s/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[^0]\+/\1{\2}(\3):1/I' -i $UZBL_FORMS_DIR/$domain - fields=$(cat $UZBL_FORMS_DIR/$domain | \ - sed -n "/^!profile=${option}/,/^!profile=/p" | \ - sed '/^!profile=/d' | \ - sed 's/^\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):/%{>\1\2):<}%/' | \ - sed 's/^\(.\+\)$/<{br}>\1/' | \ - tr -d '\n' | \ - sed 's/<{br}>%{>\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):<}%/\\n\1\2):/g') - printf '%s\n' "${fields}" | \ - sed -n -e "s/\([^(]\+\)(\(password\|text\|search\|textarea\)\+):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\2', '\3', 0);/p" | \ - sed -e 's/@/\\@/g;s/<{br}>/\\\\n/g' | socat - unix-connect:$UZBL_SOCKET - printf '%s\n' "${fields}" | \ - sed -n -e "s/\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\3', '\2', \4);/p" | \ - sed -e 's/@/\\@/g' | socat - unix-connect:$UZBL_SOCKET -elif [ "$action" = "once" ]; then - tmpfile=$(mktemp) - printf 'js %s dump(); \n' "$dumpFunction" | \ - socat - unix-connect:$UZBL_SOCKET | \ - sed -n '/^[^(]\+([^)]\+):/p' > $tmpfile - echo "$MODELINE" >> $tmpfile - $UZBL_EDITOR $tmpfile - - [ -e $tmpfile ] || exit 2 - - # Remove comments - sed '/^>/d' -i $tmpfile - - sed 's/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):\(off\|no\|false\|unchecked\|0\|$\)/\1{\2}(\3):0/I;s/^\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[^0]\+/\1{\2}(\3):1/I' -i $tmpfile - fields=$(cat $tmpfile | \ - sed 's/^\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):/%{>\1\2):<}%/' | \ - sed 's/^\(.\+\)$/<{br}>\1/' | \ - tr -d '\n' | \ - sed 's/<{br}>%{>\([^(]\+(\)\(radio\|checkbox\|text\|search\|textarea\|password\)):<}%/\\n\1\2):/g') - printf '%s\n' "${fields}" | \ - sed -n -e "s/\([^(]\+\)(\(password\|text\|search\|textarea\)\+):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\2', '\3', 0);/p" | \ - sed -e 's/@/\\@/g;s/<{br}>/\\\\n/g' | socat - unix-connect:$UZBL_SOCKET - printf '%s\n' "${fields}" | \ - sed -n -e "s/\([^{]\+\){\([^}]*\)}(\(radio\|checkbox\)):[ ]*\(.\+\)/js $insertFunction; insert('\1', '\3', '\2', \4);/p" | \ - sed -e 's/@/\\@/g' | socat - unix-connect:$UZBL_SOCKET - rm -f $tmpfile -else - if [ "$action" = 'new' -o "$action" = 'add' ]; then - [ "$action" = 'new' ] && echo "$MODELINE" > $UZBL_FORMS_DIR/$domain - echo "!profile=NAME_THIS_PROFILE$RAND" >> $UZBL_FORMS_DIR/$domain - # - # 2. and 3. line (tr -d and sed) are because, on gmail login for example, - # <input > tag is splited into lines - # ex: - # <input name="Email" - # type="text" - # value=""> - # So, tr removes all new lines, and sed inserts new line after each > - # Next sed selects only <input> tags and only with type = "text" or = "password" - # If type is first and name is second, then another sed will change their order - # so the last sed will make output - # text_from_the_name_attr(text or password): - # - # login(text): - # passwd(password): - # - printf 'js %s dump(); \n' "$dumpFunction" | \ - socat - unix-connect:$UZBL_SOCKET | \ - sed -n '/^[^(]\+([^)]\+):/p' >> $UZBL_FORMS_DIR/$domain + . "$UZBL_UTIL_DIR/dmenu.sh" + + if [ $(grep -c '^!profile' "$1") -gt 1 ] + then sed -n 's/^!profile=//p' "$1" | $DMENU + else sed -n 's/^!profile=//p' "$1" + fi +} + +ParseProfile () +{ + sed "/^>/d; /^!profile=$1$/,/^!/!d; /^!/d" +} + +ParseFields () +{ + awk '/^%/ { + + sub ( /%/, "" ) + + split( $0, parts, /\(|\)|\{|\}/ ) + + field = $0 + sub ( /[^:]*:/, "", field ) + + 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)/ ) + printf( "js uzbl.formfiller.insert(\"%s\",\"%s\",\"%s\",%s);\n", + parts[1], parts[2], parts[3], field ) + + else if ( parts[2] == "textarea" ) { + field = "" + while (getline) { + if ( /^%/ ) break + sub ( /^\\/, "" ) + gsub ( /"/, "\\\"" ) + gsub ( /\\/, "\\\\" ) + field = field $0 "\\n" + } + printf( "js uzbl.formfiller.insert(\"%s\",\"%s\",\"%s\",0);\n", + parts[1], parts[2], field ) + } + + }' +} + +New () +{ + { echo '!profile=NAME_THIS_PROFILE' + GenForm | sed 's/^!/\\!/' + echo '!' + } >> "$file" + chmod 600 "$file" + $UZBL_EDITOR "$file" +} + +Edit () + if [ -e "$file" ] + then $UZBL_EDITOR "$file" + else New fi - [ -e "$UZBL_FORMS_DIR/$domain" ] || exit 3 #this should never happen, but you never know. - $UZBL_EDITOR "$UZBL_FORMS_DIR/$domain" #TODO: if user aborts save in editor, the file is already overwritten -fi -# vim:fileencoding=utf-8:sw=4 +Load () +{ + test -e "$file" || exit + + option=$(GetOption "$file") + + case $option in *[!a-zA-Z0-9_-]*) exit 1; esac + + ParseProfile $option < "$file" \ + | ParseFields \ + | sed 's/@/\\@/' \ + > "$UZBL_FIFO" +} + +Once () +{ + tmpfile=/tmp/${0##*/}-$$-tmpfile + trap 'rm -f "$tmpfile"' EXIT + + GenForm > "$tmpfile" + chmod 600 "$tmpfile" + + $UZBL_EDITOR "$tmpfile" + + test -e "$tmpfile" && + ParseFields < "$tmpfile" \ + | sed 's/@/\\@' \ + > "$UZBL_FIFO" +} + +case $action in + new) New; Load ;; + edit) Edit; Load ;; + load) Load ;; + once) Once ;; + '') if [ -e "$file" ]; then Load; else New; Load; fi ;; + *) exit 1 +esac diff --git a/examples/data/style.css b/examples/data/style.css index f9b111e..ff055d1 100644 --- a/examples/data/style.css +++ b/examples/data/style.css @@ -1,25 +1,25 @@ -.uzbl_highlight { background-color: yellow;} -.uzbl_h_first { background-color: lightgreen;} +#uzbl_link_hints > span { + z-index: 1000 !important; -.uzbl_follow { border-style: dotted; - border-width: thin; + background-color: #aaff00 !important; + border: 2px solid #556600 !important; + margin: 0 !important; + padding: 1px !important; + + color: black !important; + font-size: 9px !important; + line-height: 9px !important; + font-weight: bold !important; + font-variant: normal !important; + text-decoration: none !important; + + -webkit-transform: translate(-5px,-5px); + /* opacity: 0.7; */ } -#uzbl_hint > div { - display: inline; - border: 2px solid #4a6600; - background-color: #b9ff00; - color: black; - font-size: 9px; - font-weight: bold; - line-height: 9px; - margin: 0px; - padding: 0px; - position: absolute; - z-index: 1000; - -webkit-border-radius: 6px; - text-decoration: none; - -wekit-transform: scale(1) rotate(0deg) translate(-6px,-5px); +/* we can have different colours for different types of hints! */ +#uzbl_link_hints.new-window > span { + background-color: #ffff00 !important; } /* vim:set et ts=4: */ diff --git a/extras/vim/syntax/uzbl.vim b/extras/vim/syntax/uzbl.vim index bf7108c..1a4172b 100644 --- a/extras/vim/syntax/uzbl.vim +++ b/extras/vim/syntax/uzbl.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Uzbl config syntax -" Maintainer: Mason Larobina <mason.larobina@gmail.com> -" Contributors: Gregor Uhlenheuer (kongo2002) +" Maintainer: Gregor Uhlenheuer (kongo2002) <kongo2002@gmail.com> +" Contributors: Mason Larobina <mason.larobina@gmail.com> " Pawel Tomak (grodzik) <pawel.tomak@gmail.com> " Version: 0.1 " @@ -25,9 +25,6 @@ elseif exists("b:current_syntax") finish endif -" Don't match keywords inside strings -setl iskeyword=!-~,192-255 - syn keyword uzblKeyword back forward scroll reload reload_ign_cache stop syn keyword uzblKeyword zoom_in zoom_out toggle_zoom_type uri script syn keyword uzblKeyword toggle_status spawn sync_spawn sync_sh sync_spawn_exec @@ -37,7 +34,11 @@ syn keyword uzblKeyword request menu_add menu_link_add menu_image_add syn keyword uzblKeyword menu_editable_add menu_separator menu_link_separator syn keyword uzblKeyword menu_image_separator menu_editable_separator syn keyword uzblKeyword menu_remove menu_link_remove menu_image_remove -syn keyword uzblKeyword menu_editable_remove hardcopy include js sh +syn keyword uzblKeyword menu_editable_remove hardcopy include + +" Match 'js' and 'sh' only without a dot in front +syn match uzblKeyword /\.\@<!sh\s\+/ +syn match uzblKeyword /\.\@<!js\s\+/ " Comments syn match uzblTodo /TODO:/ contained diff --git a/src/callbacks.c b/src/callbacks.c index a40057c..deda426 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -291,6 +291,13 @@ cmd_caret_browsing() { } void +set_current_encoding() { + webkit_web_view_set_custom_encoding(uzbl.gui.web_view, + uzbl.behave.current_encoding); +} + + +void cmd_fifo_dir() { uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir); } @@ -448,29 +455,45 @@ void load_status_change_cb (WebKitWebView* web_view, GParamSpec param_spec) { (void) param_spec; - WebKitWebFrame *frame = webkit_web_view_get_main_frame(web_view); + WebKitWebFrame *frame; WebKitLoadStatus status = webkit_web_view_get_load_status(web_view); switch(status) { case WEBKIT_LOAD_PROVISIONAL: - send_event(LOAD_START, NULL, TYPE_STR, uzbl.state.uri ? uzbl.state.uri : "", NULL); + send_event(LOAD_START, NULL, TYPE_STR, uzbl.state.uri ? uzbl.state.uri : "", NULL); break; case WEBKIT_LOAD_COMMITTED: - 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); - + frame = webkit_web_view_get_main_frame(web_view); send_event(LOAD_COMMIT, NULL, TYPE_STR, webkit_web_frame_get_uri (frame), NULL); break; case WEBKIT_LOAD_FINISHED: - send_event(LOAD_FINISH, NULL, TYPE_STR, webkit_web_frame_get_uri(frame), NULL); + send_event(LOAD_FINISH, NULL, TYPE_STR, uzbl.state.uri, NULL); break; case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT: break; /* we don't do anything with this (yet) */ case WEBKIT_LOAD_FAILED: break; /* load_error_cb will handle this case */ } +} +void +load_error_cb (WebKitWebView* page, WebKitWebFrame* frame, gchar *uri, gpointer web_err, gpointer ud) { + (void) page; (void) frame; (void) ud; + GError *err = web_err; + + send_event (LOAD_ERROR, NULL, + TYPE_STR, uri, + TYPE_INT, err->code, + TYPE_STR, err->message, + NULL); +} + +void +uri_change_cb (WebKitWebView *web_view, GParamSpec param_spec) { + (void) param_spec; + + g_free (uzbl.state.uri); + g_object_get (web_view, "uri", &uzbl.state.uri, NULL); + g_setenv("UZBL_URI", uzbl.state.uri, TRUE); } void @@ -485,20 +508,6 @@ selection_changed_cb(WebKitWebView *webkitwebview, gpointer ud) { } void -load_error_cb (WebKitWebView* page, WebKitWebFrame* frame, gchar *uri, gpointer web_err, gpointer ud) { - (void) page; - (void) frame; - (void) ud; - GError *err = web_err; - - send_event (LOAD_ERROR, NULL, - TYPE_STR, uri, - TYPE_INT, err->code, - TYPE_STR, err->message, - NULL); -} - -void destroy_cb (GtkWidget* widget, gpointer data) { (void) widget; (void) data; @@ -957,16 +966,28 @@ scroll_horiz_cb(GtkAdjustment *adjust, void *w) } void -run_menu_command(GtkWidget *menu, const char *line) { +run_menu_command(GtkWidget *menu, MenuItem *mi) { (void) menu; - parse_cmd_line(line, NULL); + if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) { + gchar* uri; + g_object_get(mi->hittest, "image-uri", &uri, NULL); + gchar* cmd = g_strdup_printf("%s %s", mi->cmd, uri); + + parse_cmd_line(cmd, NULL); + + g_free(cmd); + g_free(uri); + g_object_unref(mi->hittest); + } + else { + parse_cmd_line(mi->cmd, NULL); + } } void populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) { - (void) v; (void) c; GUI *g = &uzbl.gui; GtkWidget *item; @@ -981,11 +1002,19 @@ populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) { if((context = get_click_context(NULL)) == -1) return; - for(i=0; i < uzbl.gui.menu_items->len; i++) { hit = 0; mi = g_ptr_array_index(uzbl.gui.menu_items, i); + if (mi->context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) { + GdkEventButton ev; + gint x, y; + gdk_window_get_pointer(gtk_widget_get_window(GTK_WIDGET(v)), &x, &y, NULL); + ev.x = x; + ev.y = y; + mi->hittest = webkit_web_view_get_hit_test_result(v, &ev); + } + if((mi->context > WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) && (context & mi->context)) { if(mi->issep) { @@ -996,7 +1025,7 @@ populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) { else { item = gtk_menu_item_new_with_label(mi->name); g_signal_connect(item, "activate", - G_CALLBACK(run_menu_command), mi->cmd); + G_CALLBACK(run_menu_command), mi); gtk_menu_shell_append(GTK_MENU_SHELL(m), item); gtk_widget_show(item); } @@ -1014,7 +1043,7 @@ populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) { else { item = gtk_menu_item_new_with_label(mi->name); g_signal_connect(item, "activate", - G_CALLBACK(run_menu_command), mi->cmd); + G_CALLBACK(run_menu_command), mi); gtk_menu_shell_append(GTK_MENU_SHELL(m), item); gtk_widget_show(item); } diff --git a/src/callbacks.h b/src/callbacks.h index 13cb83d..d34b9fa 100644 --- a/src/callbacks.h +++ b/src/callbacks.h @@ -110,6 +110,9 @@ void cmd_default_encoding(); void +set_current_encoding(); + +void cmd_enforce_96dpi(); void @@ -158,6 +161,9 @@ void load_error_cb (WebKitWebView* page, WebKitWebFrame* frame, gchar *uri, gpointer web_err, gpointer ud); void +uri_change_cb (WebKitWebView *web_view, GParamSpec param_spec); + +void selection_changed_cb(WebKitWebView *webkitwebview, gpointer ud); void diff --git a/src/events.c b/src/events.c index 174ff75..58dddfc 100644 --- a/src/events.c +++ b/src/events.c @@ -92,7 +92,7 @@ send_event_sockets(GPtrArray *sockets, GString *msg) { } } -static void +void replay_buffered_events() { guint i = 0; @@ -205,6 +205,7 @@ void key_to_event(guint keyval, gint mode) { gchar ucs[7]; gint ulen; + gchar *keyname; guint32 ukval = gdk_keyval_to_unicode(keyval); /* check for printable unicode char */ @@ -219,9 +220,9 @@ key_to_event(guint keyval, gint mode) { NULL, TYPE_FORMATTEDSTR, ucs, NULL); } /* send keysym for non-printable chars */ - else { + else if((keyname = gdk_keyval_name(keyval))){ send_event(mode == GDK_KEY_PRESS ? KEY_PRESS : KEY_RELEASE, - NULL, TYPE_NAME, gdk_keyval_name(keyval), NULL); + NULL, TYPE_NAME, keyname , NULL); } } diff --git a/src/events.h b/src/events.h index 8e75097..bd519a6 100644 --- a/src/events.h +++ b/src/events.h @@ -33,10 +33,7 @@ void event_buffer_timeout(guint sec); void -send_event_socket(GString *msg); - -void -send_event_stdout(GString *msg); +replay_buffered_events(); void vsend_event(int type, const gchar *custom_event, va_list vargs); @@ -123,9 +123,14 @@ control_stdin(GIOChannel *gio, GIOCondition condition) { if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) ) return FALSE; - parse_cmd_line(ctl_line, NULL); + GString *result = g_string_new(""); + + parse_cmd_line(ctl_line, result); g_free(ctl_line); + puts(result->str); + g_string_free(result, TRUE); + return TRUE; } @@ -211,8 +216,8 @@ init_connect_socket() { } /* replay buffered events */ - if(replay) - send_event_socket(NULL); + if(replay && uzbl.state.event_buffer) + replay_buffered_events(); } @@ -6,6 +6,8 @@ #include "util.h" +gchar* find_existing_file2(gchar *, const gchar *); + const XDG_Var XDG[] = { { "XDG_CONFIG_HOME", "~/.config" }, { "XDG_DATA_HOME", "~/.local/share" }, @@ -16,56 +18,41 @@ const XDG_Var XDG[] = { /*@null@*/ gchar* get_xdg_var (XDG_Var xdg) { - const gchar* actual_value = getenv (xdg.environmental); - const gchar* home = getenv ("HOME"); - gchar* return_value; - - if (! actual_value || strcmp (actual_value, "") == 0) { - if (xdg.default_value) { - return_value = str_replace ("~", home, xdg.default_value); - } else { - return_value = NULL; - } - } else { - return_value = str_replace("~", home, actual_value); - } + const gchar *actual_value = getenv(xdg.environmental); + const gchar *home = getenv("HOME"); + + if (!actual_value || !actual_value[0]) + actual_value = xdg.default_value; - return return_value; + if (!actual_value) + return NULL; + + return str_replace("~", home, actual_value); } /*@null@*/ gchar* -find_xdg_file (int xdg_type, const char* filename) { +find_xdg_file (int xdg_type, const char* basename) { /* xdg_type = 0 => config xdg_type = 1 => data - xdg_type = 2 => cache*/ + xdg_type = 2 => cache */ - gchar* xdgv = get_xdg_var (XDG[xdg_type]); - gchar* temporary_file = g_strconcat (xdgv, filename, NULL); + gchar *xdgv = get_xdg_var(XDG[xdg_type]); + gchar *path = g_strconcat (xdgv, basename, NULL); g_free (xdgv); - gchar* temporary_string; - char* saveptr; - char* buf; - - if (! file_exists (temporary_file) && xdg_type != 2) { - buf = get_xdg_var (XDG[3 + xdg_type]); - temporary_string = (char *) strtok_r (buf, ":", &saveptr); - g_free(buf); + if (file_exists(path)) + return path; - while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) { - g_free (temporary_file); - temporary_file = g_strconcat (temporary_string, filename, NULL); - } - } + if (xdg_type == 2) + return NULL; - //g_free (temporary_string); - segfaults. + /* the file doesn't exist in the expected directory. + * check if it exists in one of the system-wide directories. */ + char *system_dirs = get_xdg_var(XDG[3 + xdg_type]); + path = find_existing_file2(system_dirs, basename); + g_free(system_dirs); - if (file_exists (temporary_file)) { - return temporary_file; - } else { - g_free(temporary_file); - return NULL; - } + return path; } gboolean @@ -95,85 +82,66 @@ for_each_line_in_file(const gchar *path, void (*callback)(const gchar *l, void * GIOChannel *chan = g_io_channel_new_file(path, "r", NULL); - if (chan) { - while (g_io_channel_read_line(chan, &line, &len, NULL, NULL) == G_IO_STATUS_NORMAL) { - callback(line, user_data); - g_free(line); - } - g_io_channel_unref (chan); + if (!chan) + return FALSE; - return TRUE; + while (g_io_channel_read_line(chan, &line, &len, NULL, NULL) == G_IO_STATUS_NORMAL) { + callback(line, user_data); + g_free(line); } - return FALSE; -} + g_io_channel_unref (chan); -enum exp_type -get_exp_type(const gchar *s) { - /* variables */ - if(*(s+1) == '(') - return EXP_EXPR; - else if(*(s+1) == '{') - return EXP_BRACED_VAR; - else if(*(s+1) == '<') - return EXP_JS; - else if(*(s+1) == '[') - return EXP_ESCAPE; - else - return EXP_SIMPLE_VAR; - - /*@notreached@*/ -return EXP_ERR; + return TRUE; } - -/* search a PATH style string for an existing file+path combination */ +/* This function searches the directories in the : separated ($PATH style) + * string 'dirs' for a file named 'basename'. It returns the path of the first + * file found, or NULL if none could be found. + * NOTE: this function modifies the 'dirs' argument. */ gchar* -find_existing_file(gchar* path_list) { - int i=0; - int cnt; - gchar **split; - gchar *tmp = NULL; - gchar *executable; +find_existing_file2(gchar *dirs, const gchar *basename) { + char *saveptr = NULL; + + /* iterate through the : separated elements until we find our file. */ + char *tok = strtok_r(dirs, ":", &saveptr); + char *path = g_strconcat (tok, "/", basename, NULL); + + while (!file_exists(path)) { + g_free(path); + + tok = strtok_r(NULL, ":", &saveptr); + if (!tok) + return NULL; /* we've hit the end of the string */ + path = g_strconcat (tok, "/", basename, NULL); + } + + return path; +} + +/* search a PATH style string for an existing file+path combination. + * everything after the last ':' is assumed to be the name of the file. + * e.g. "/tmp:/home:a/file" will look for /tmp/a/file and /home/a/file. + * + * if there are no :s then the entire thing is taken to be the path. */ +gchar* +find_existing_file(const gchar* path_list) { if(!path_list) return NULL; - split = g_strsplit(path_list, ":", 0); - while(split[i]) - i++; + char *path_list_dup = g_strdup(path_list); - if(i<=1) { - tmp = g_strdup(split[0]); - g_strfreev(split); - return tmp; - } - else - cnt = i-1; - - i=0; - tmp = g_strdup(split[cnt]); - g_strstrip(tmp); - if(tmp[0] == '/') - executable = g_strdup_printf("%s", tmp+1); - else - executable = g_strdup(tmp); - g_free(tmp); - - while(i<cnt) { - tmp = g_strconcat(g_strstrip(split[i]), "/", executable, NULL); - if(g_file_test(tmp, G_FILE_TEST_EXISTS)) { - g_strfreev(split); - return tmp; - } - else - g_free(tmp); - i++; - } + char *basename = strrchr(path_list_dup, ':'); + if(!basename) + return path_list_dup; + + basename[0] = '\0'; + basename++; - g_free(executable); - g_strfreev(split); - return NULL; + char *result = find_existing_file2(path_list_dup, basename); + g_free(path_list_dup); + return result; } gchar* @@ -6,16 +6,12 @@ typedef struct { gchar* default_value; } XDG_Var; -enum exp_type {EXP_ERR, EXP_SIMPLE_VAR, EXP_BRACED_VAR, EXP_EXPR, EXP_JS, EXP_ESCAPE}; - - gchar* get_xdg_var(XDG_Var xdg); gchar* find_xdg_file(int xdg_type, const char* filename); gboolean file_exists(const char* filename); char* str_replace(const char* search, const char* replace, const char* string); gboolean for_each_line_in_file(const gchar *path, void (*callback)(const gchar *l, void *c), void *user_data); -enum exp_type get_exp_type(const gchar*); -gchar* find_existing_file(gchar*); +gchar* find_existing_file(const gchar*); gchar* argv_idx(const GArray*, const guint); /** * appends `src' to `dest' with backslash, single-quotes and newlines in diff --git a/src/uzbl-core.c b/src/uzbl-core.c index 26b3dba..d73fbdf 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -131,6 +131,7 @@ const struct var_name_to_ptr_t { { "stylesheet_uri", PTR_V_STR(uzbl.behave.style_uri, 1, cmd_style_uri)}, { "resizable_text_areas", PTR_V_INT(uzbl.behave.resizable_txt, 1, cmd_resizable_txt)}, { "default_encoding", PTR_V_STR(uzbl.behave.default_encoding, 1, cmd_default_encoding)}, + { "current_encoding", PTR_V_STR(uzbl.behave.current_encoding, 1, set_current_encoding)}, { "enforce_96_dpi", PTR_V_INT(uzbl.behave.enforce_96dpi, 1, cmd_enforce_96dpi)}, { "caret_browsing", PTR_V_INT(uzbl.behave.caret_browsing, 1, cmd_caret_browsing)}, { "scrollbars_visible", PTR_V_INT(uzbl.gui.scrollbars_visible, 1, cmd_scrollbars_visibility)}, @@ -165,6 +166,20 @@ create_var_to_name_hash() { /* --- UTILITY FUNCTIONS --- */ +enum exp_type { + EXP_ERR, EXP_SIMPLE_VAR, EXP_BRACED_VAR, EXP_EXPR, EXP_JS, EXP_ESCAPE +}; + +enum exp_type +get_exp_type(const gchar *s) { + switch(*(s+1)) { + case '(': return EXP_EXPR; + case '{': return EXP_BRACED_VAR; + case '<': return EXP_JS; + case '[': return EXP_ESCAPE; + default: return EXP_SIMPLE_VAR; + } +} /* * recurse == 1: don't expand '@(command)@' @@ -608,6 +623,9 @@ print(WebKitWebView *page, GArray *argv, GString *result) { (void) page; (void) result; gchar* buf; + if(!result) + return; + buf = expand(argv_idx(argv, 0), 0); g_string_assign(result, buf); g_free(buf); @@ -720,7 +738,8 @@ act_dump_config_as_events() { void load_uri(WebKitWebView *web_view, GArray *argv, GString *result) { (void) web_view; (void) result; - set_var_value("uri", argv_idx(argv, 0)); + gchar * uri = argv_idx(argv, 0); + set_var_value("uri", uri ? uri : ""); } /* Javascript*/ @@ -744,7 +763,7 @@ eval_js(WebKitWebView * web_view, gchar *script, GString *result, const char *fi js_script = JSStringCreateWithUTF8CString(script); js_file = JSStringCreateWithUTF8CString(file); js_result = JSEvaluateScript(context, js_script, globalobject, js_file, 0, &js_exc); - if (js_result && !JSValueIsUndefined(context, js_result)) { + if (result && js_result && !JSValueIsUndefined(context, js_result)) { js_result_string = JSValueToStringCopy(context, js_result, NULL); js_result_size = JSStringGetMaximumUTF8CStringSize(js_result_string); @@ -1143,8 +1162,10 @@ parse_command_parts(const gchar *line, GArray *a) { CommandInfo *c = NULL; gchar *exp_line = expand(line, 0); - if(exp_line[0] == '\0') + if(exp_line[0] == '\0') { + g_free(exp_line); return NULL; + } /* separate the line into the command and its parameters */ gchar **tokens = g_strsplit(exp_line, " ", 2); @@ -1212,7 +1233,8 @@ move_statusbar() { } g_object_unref(uzbl.gui.scrolled_win); g_object_unref(uzbl.gui.mainbar); - gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); + if (!uzbl.state.plug_mode) + gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); return; } @@ -1258,6 +1280,8 @@ set_var_value(const gchar *name, gchar *val) { char *endp = NULL; char *buf = NULL; + g_assert(val != NULL); + if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) { if(!c->writeable) return FALSE; @@ -1370,7 +1394,6 @@ update_title(void) { } } - void create_scrolled_win() { GUI* g = &uzbl.gui; @@ -1399,6 +1422,7 @@ create_scrolled_win() { "signal::selection-changed", (GCallback)selection_changed_cb, NULL, "signal::notify::progress", (GCallback)progress_change_cb, NULL, "signal::notify::load-status", (GCallback)load_status_change_cb, NULL, + "signal::notify::uri", (GCallback)uri_change_cb, NULL, "signal::load-error", (GCallback)load_error_cb, NULL, "signal::hovering-over-link", (GCallback)link_hover_cb, NULL, "signal::navigation-policy-decision-requested", (GCallback)navigation_decision_cb, NULL, diff --git a/src/uzbl-core.h b/src/uzbl-core.h index 3240fc6..766fe56 100644 --- a/src/uzbl-core.h +++ b/src/uzbl-core.h @@ -165,7 +165,6 @@ typedef struct { gchar* cursive_font_family; gboolean forward_keys; - guint modmask; guint http_debug; gchar* shell_cmd; guint view_source; @@ -187,12 +186,11 @@ typedef struct { gchar* style_uri; guint resizable_txt; gchar* default_encoding; + gchar* current_encoding; guint enforce_96dpi; gchar *inject_html; guint caret_browsing; guint javascript_windows; - guint mode; - gchar* base_url; gboolean print_version; /* command list: (key)name -> (value)Command */ @@ -203,14 +201,6 @@ typedef struct { } Behaviour; -/* Javascript */ -typedef struct { - gboolean initialized; - JSClassDefinition classdef; - JSClassRef classref; -} Javascript; - - /* Static information */ typedef struct { int webkit_major; @@ -229,7 +219,6 @@ typedef struct { Network net; Behaviour behave; Communication comm; - Javascript js; Info info; Window xwin; @@ -362,6 +351,7 @@ typedef struct { gchar* cmd; gboolean issep; guint context; + WebKitHitTestResult* hittest; } MenuItem; #endif diff --git a/tests/test-command.c b/tests/test-command.c index 55bf316..6b55fb3 100644 --- a/tests/test-command.c +++ b/tests/test-command.c @@ -282,6 +282,11 @@ test_js (void) { g_string_free(result, TRUE); } +void test_uri(void) { + /* Testing for a crash, not crashing is a pass */ + parse_cmd_line("uri", NULL); +} + void test_last_result (void) { GString *result = g_string_new(""); @@ -313,6 +318,7 @@ main (int argc, char *argv[]) { g_test_add("/test-command/event", struct EventFixture, NULL, event_fixture_setup, test_event, event_fixture_teardown); g_test_add_func("/test-command/print", test_print); + g_test_add_func("/test-command/uri", test_uri); g_test_add_func("/test-command/scroll", test_scroll); g_test_add_func("/test-command/toggle-status", test_toggle_status); g_test_add_func("/test-command/sync-sh", test_sync_sh); @@ -329,7 +335,7 @@ main (int argc, char *argv[]) { uzbl.state.config_file = "/tmp/uzbl-config"; uzbl.comm.fifo_path = "/tmp/some-nonexistant-fifo"; uzbl.comm.socket_path = "/tmp/some-nonexistant-socket"; - uzbl.state.uri = "http://example.org/"; + uzbl.state.uri = g_strdup("http://example.org/"); uzbl.gui.main_title = "Example.Org"; uzbl.state.instance_name = INSTANCE_NAME; |