From 32f028a2b49c6c4e44865ae5b9767899c9fd24ab Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Fri, 25 Dec 2009 13:53:18 +0100 Subject: specific install things should not install others, so that it doesnt fight with packaging and stuff --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cf55989..7df6195 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ install-uzbl-core: all install -m644 README $(INSTALLDIR)/share/uzbl/docs sed -i 's#^set prefix.*=.*#set prefix = $(RUN_PREFIX)#' $(INSTALLDIR)/share/uzbl/examples/config/uzbl/config -install-uzbl-browser: install-uzbl-core +install-uzbl-browser: install -d $(INSTALLDIR)/bin install -m755 uzbl-browser $(INSTALLDIR)/bin/uzbl-browser install -m755 examples/data/uzbl/scripts/uzbl-cookie-daemon $(INSTALLDIR)/bin/uzbl-cookie-daemon @@ -109,7 +109,7 @@ install-uzbl-browser: install-uzbl-core sed -i 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' $(INSTALLDIR)/bin/uzbl-browser sed -i "s#^PREFIX = .*#PREFIX = '$(RUN_PREFIX)'#" $(INSTALLDIR)/bin/uzbl-event-manager -install-uzbl-tabbed: install-uzbl-browser +install-uzbl-tabbed: install -d $(INSTALLDIR)/bin install -m755 examples/data/uzbl/scripts/uzbl-tabbed $(INSTALLDIR)/bin/uzbl-tabbed -- cgit v1.2.3 From 7bb5004a1d2cd5b0d7281a69e0bd15de61058044 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sun, 27 Dec 2009 12:17:53 +0100 Subject: since you now have to install things separately, you also need to do this in the test targets --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 7df6195..73b1b67 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,7 @@ test-uzbl-core-sandbox: uzbl-core rm -rf ./sandbox/usr test-uzbl-browser-sandbox: uzbl-browser + make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-browser source ./sandbox/env.sh && uzbl-cookie-daemon restart -nv & source ./sandbox/env.sh && uzbl-event-manager restart -nav & -- cgit v1.2.3 From d55f6c7e55a94161924e1380dd70ef72624cecd7 Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Fri, 1 Jan 2010 13:09:36 +0800 Subject: Moved xdghome function out of config section. --- examples/data/uzbl/scripts/uzbl-event-manager | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/data/uzbl/scripts/uzbl-event-manager b/examples/data/uzbl/scripts/uzbl-event-manager index 916259a..a3148a2 100755 --- a/examples/data/uzbl/scripts/uzbl-event-manager +++ b/examples/data/uzbl/scripts/uzbl-event-manager @@ -41,13 +41,6 @@ from traceback import print_exc from functools import partial -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -# `make install` will put the correct value here for your system -PREFIX = '/usr/local/' - def xdghome(key, default): '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise use $HOME and the default path.''' @@ -58,11 +51,18 @@ def xdghome(key, default): return os.path.join(os.environ['HOME'], default) + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +# `make install` will put the correct value here for your system +PREFIX = '/usr/local/' + # Setup xdg paths. DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') - # Event manager config dictionary. This is not to be confused with the config # dict that tracks variables in the uzbl instance. CONFIG = { @@ -80,7 +80,6 @@ CONFIG = { 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'), } - # ============================================================================ # ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: # ============================================================================ -- cgit v1.2.3 From 72e8c59426a4a8ac3f8612c4c34848a259882290 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Sun, 18 Oct 2009 16:17:19 +0200 Subject: print javascript errors in eval_js --- uzbl-core.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/uzbl-core.c b/uzbl-core.c index fd8ee41..9203fd0 100644 --- a/uzbl-core.c +++ b/uzbl-core.c @@ -1050,6 +1050,7 @@ eval_js(WebKitWebView * web_view, gchar *script, GString *result) { JSStringRef js_script; JSValueRef js_result; + JSValueRef js_exc = NULL; JSStringRef js_result_string; size_t js_result_size; @@ -1067,7 +1068,7 @@ eval_js(WebKitWebView * web_view, gchar *script, GString *result) { /* evaluate the script and get return value*/ js_script = JSStringCreateWithUTF8CString(script); - js_result = JSEvaluateScript(context, js_script, globalobject, NULL, 0, NULL); + js_result = JSEvaluateScript(context, js_script, globalobject, NULL, 0, &js_exc); if (js_result && !JSValueIsUndefined(context, js_result)) { js_result_string = JSValueToStringCopy(context, js_result, NULL); js_result_size = JSStringGetMaximumUTF8CStringSize(js_result_string); @@ -1080,6 +1081,37 @@ eval_js(WebKitWebView * web_view, gchar *script, GString *result) { JSStringRelease(js_result_string); } + else if (js_exc && JSValueIsObject(context, js_exc)) { + size_t size; + JSStringRef prop, val; + JSObjectRef exc = JSValueToObject(context, js_exc, NULL); + + printf("Exception occured while executing script:\n"); + + /* Print line */ + prop = JSStringCreateWithUTF8CString("line"); + val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL); + size = JSStringGetMaximumUTF8CStringSize(val); + if(size) { + char cstr[size]; + JSStringGetUTF8CString(val, cstr, size); + printf("At line %s: ", cstr); + } + JSStringRelease(prop); + JSStringRelease(val); + + /* Print message */ + prop = JSStringCreateWithUTF8CString("message"); + val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL); + size = JSStringGetMaximumUTF8CStringSize(val); + if(size) { + char cstr[size]; + JSStringGetUTF8CString(val, cstr, size); + printf("%s\n", cstr); + } + JSStringRelease(prop); + JSStringRelease(val); + } /* cleanup */ JSObjectDeleteProperty(context, globalobject, var_name, NULL); -- cgit v1.2.3 From 11cdbaa40ad3c659d3f138cd03da4359a5a888e8 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Sun, 13 Dec 2009 18:23:05 +0100 Subject: Remove examples/date from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3bdd4e9..77faa74 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ uzbl-core *.o *~ tags -examples/data examples/config/enchant -- cgit v1.2.3 From 2b254b5a026f273539d26f3d9af5a912ae1649fa Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Tue, 29 Dec 2009 15:08:53 +0100 Subject: add *.pyc to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 77faa74..803a154 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ uzbl-core *.o +*.pyc *~ tags examples/config/enchant -- cgit v1.2.3 From 9b6438a878ca2d8696484438430971507219f3ed Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Fri, 1 Jan 2010 17:09:54 +0100 Subject: authors file update --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 7d8d76e..eac03d4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -53,6 +53,7 @@ In alphabetical order: Přemysl Hrubý (anydot) - several C contributions and cleanups Robert Manea (robm) - C code all over the place Sergey Shepelev (temoto) - doc patch + Simon Lipp (sloonz) - various patches Sylvester Johansson (scj) - form filler script & different take on link follower Tassilo Horn (tsdh) - $VISUAL patch Thorsten Wilms - logo design -- cgit v1.2.3 From a2ddc36070d9478d3a75e3aa80760e4016ba59d2 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Fri, 1 Jan 2010 18:14:22 +0100 Subject: hash.sh -> misc/hash.sh --- Makefile | 4 ++-- hash.sh | 25 ------------------------- misc/hash.sh | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 27 deletions(-) delete mode 100755 hash.sh create mode 100755 misc/hash.sh diff --git a/Makefile b/Makefile index 73b1b67..3baa6c6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # first entries are for gnu make, 2nd for BSD make. see http://lists.uzbl.org/pipermail/uzbl-dev-uzbl.org/2009-July/000177.html -CFLAGS:=-std=c99 $(shell pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0) -ggdb -Wall -W -DARCH="\"$(shell uname -m)\"" -lgthread-2.0 -DCOMMIT="\"$(shell ./hash.sh)\"" $(CPPFLAGS) -fPIC -W -Wall -Wextra -pedantic -CFLAGS!=echo -std=c99 `pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0` -ggdb -Wall -W -DARCH='"\""'`uname -m`'"\""' -lgthread-2.0 -DCOMMIT='"\""'`./hash.sh`'"\""' $(CPPFLAGS) -fPIC -W -Wall -Wextra -pedantic +CFLAGS:=-std=c99 $(shell pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0) -ggdb -Wall -W -DARCH="\"$(shell uname -m)\"" -lgthread-2.0 -DCOMMIT="\"$(shell ./misc/hash.sh)\"" $(CPPFLAGS) -fPIC -W -Wall -Wextra -pedantic +CFLAGS!=echo -std=c99 `pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0` -ggdb -Wall -W -DARCH='"\""'`uname -m`'"\""' -lgthread-2.0 -DCOMMIT='"\""'`./misc/hash.sh`'"\""' $(CPPFLAGS) -fPIC -W -Wall -Wextra -pedantic LDFLAGS:=$(shell pkg-config --libs gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0) -pthread $(LDFLAGS) LDFLAGS!=echo `pkg-config --libs gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0` -pthread $(LDFLAGS) diff --git a/hash.sh b/hash.sh deleted file mode 100755 index 0c97722..0000000 --- a/hash.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -# script to determine git hash of current source tree - -# set a variable when running `git --archive ` (this is what github does) -# alternatively, you could also git get-tar-commit-id < tarball (but that's a bit dirtier) -FROM_ARCHIVE=$Format:%H$ - -# ... but try to use whatever git tells us if there is a .git folder -if [ -d .git -a -r .git ] -then - hash=$(git log 2>/dev/null | head -n1 2>/dev/null | sed "s/.* //" 2>/dev/null) -fi - -if [ x"$hash" != x ] -then - echo $hash -elif [ "$FROM_ARCHIVE" != ':%H$' ] -then - echo $FROM_ARCHIVE -else - echo "commit hash detection fail. Dear packager, please figure out what goes wrong or get in touch with us" >&2 - echo UNKNOWN - exit 2 -fi -exit 0 diff --git a/misc/hash.sh b/misc/hash.sh new file mode 100755 index 0000000..0c97722 --- /dev/null +++ b/misc/hash.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# script to determine git hash of current source tree + +# set a variable when running `git --archive ` (this is what github does) +# alternatively, you could also git get-tar-commit-id < tarball (but that's a bit dirtier) +FROM_ARCHIVE=$Format:%H$ + +# ... but try to use whatever git tells us if there is a .git folder +if [ -d .git -a -r .git ] +then + hash=$(git log 2>/dev/null | head -n1 2>/dev/null | sed "s/.* //" 2>/dev/null) +fi + +if [ x"$hash" != x ] +then + echo $hash +elif [ "$FROM_ARCHIVE" != ':%H$' ] +then + echo $FROM_ARCHIVE +else + echo "commit hash detection fail. Dear packager, please figure out what goes wrong or get in touch with us" >&2 + echo UNKNOWN + exit 2 +fi +exit 0 -- cgit v1.2.3 From 827415529f71575c9423cb120083ec874b139ac7 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Sun, 13 Dec 2009 16:18:38 +0100 Subject: Add a print_events option --- README | 1 + events.c | 4 ++-- uzbl-browser | 2 +- uzbl-core.c | 3 +++ uzbl-core.h | 1 + 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README b/README index 0d6ed04..12bd998 100644 --- a/README +++ b/README @@ -206,6 +206,7 @@ Besides the builtin variables you can also define your own ones and use them in - `scheme_handler`: handler to execute for each URI navigated to - the navigation request will be ignored if handler prints "USED\n" - `fifo_dir`: location to store fifo's - `socket_dir`: location to store sockets + - `print_events`: show events on stdout - `http_debug`: http debug mode (value 0-3) - `shell_cmd`: alias which will be expanded to use shell commands (eg sh -c) - `proxy_url`: http traffic socks proxy (eg: http://:) diff --git a/events.c b/events.c index 719dc44..acb554c 100644 --- a/events.c +++ b/events.c @@ -169,8 +169,8 @@ send_event(int type, const gchar *details, const gchar *custom_event) { } if(event_message->str) { - /* TODO: a means to select the interface to which events are sent */ - send_event_stdout(event_message); + if(uzbl.state.events_stdout) + send_event_stdout(event_message); send_event_socket(event_message); g_string_free(event_message, TRUE); diff --git a/uzbl-browser b/uzbl-browser index eebf9e3..57c5087 100755 --- a/uzbl-browser +++ b/uzbl-browser @@ -63,4 +63,4 @@ DAEMON_PID=${DAEMON_SOCKET}.pid uzbl-event-manager -va start #fi -uzbl-core "$@" --connect-socket $DAEMON_SOCKET | grep -v ^EVENT +uzbl-core "$@" --connect-socket $DAEMON_SOCKET diff --git a/uzbl-core.c b/uzbl-core.c index 9203fd0..9c8660e 100644 --- a/uzbl-core.c +++ b/uzbl-core.c @@ -53,6 +53,8 @@ GOptionEntry entries[] = "Socket ID", "SOCKET" }, { "connect-socket", 0, 0, G_OPTION_ARG_STRING_ARRAY, &uzbl.state.connect_socket_names, "Connect to server socket", "CSOCKET" }, + { "print-events", 'p', 0, G_OPTION_ARG_NONE, &uzbl.state.events_stdout, + "Whether to print events to stdout.", NULL }, { "geometry", 'g', 0, G_OPTION_ARG_STRING, &uzbl.gui.geometry, "Set window geometry (format: WIDTHxHEIGHT+-X+-Y or maximized)", "GEOMETRY" }, { "version", 'V', 0, G_OPTION_ARG_NONE, &uzbl.behave.print_version, @@ -85,6 +87,7 @@ const struct var_name_to_ptr_t { /* ---------------------------------------------------------------------------------------------- */ { "uri", PTR_V_STR(uzbl.state.uri, 1, cmd_load_uri)}, { "verbose", PTR_V_INT(uzbl.state.verbose, 1, NULL)}, + { "print_events", PTR_V_INT(uzbl.state.events_stdout, 1, NULL)}, { "inject_html", PTR_V_STR(uzbl.behave.inject_html, 0, cmd_inject_html)}, { "geometry", PTR_V_STR(uzbl.gui.geometry, 1, cmd_set_geometry)}, { "keycmd", PTR_V_STR(uzbl.state.keycmd, 1, NULL)}, diff --git a/uzbl-core.h b/uzbl-core.h index 83fa4b2..df9eb1a 100644 --- a/uzbl-core.h +++ b/uzbl-core.h @@ -93,6 +93,7 @@ typedef struct { gchar* keycmd; gchar* searchtx; gboolean verbose; + gboolean events_stdout; GPtrArray *event_buffer; gchar** connect_socket_names; GdkEventButton *last_button; -- cgit v1.2.3 From fd1fce360b8ef484f48f5786081834628ba67a7d Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Fri, 1 Jan 2010 23:01:21 +0100 Subject: bring readme up to date for latest new options and stuff --- README | 6 ++++-- uzbl-browser | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README b/README index 12bd998..f46d552 100644 --- a/README +++ b/README @@ -529,12 +529,14 @@ Basically all events have this format: ### COMMAND LINE ARGUMENTS - uzbl [ uri ] + uzbl-core [OPTION...] [ uri ] -u, --uri=URI Uri to load at startup (equivalent to 'uzbl ' or 'set uri = URI' after uzbl has launched) -v, --verbose Whether to print all messages or just errors. - -n, --name=NAME Name of the current instance (defaults to Xorg window id) + -n, --name=NAME Name of the current instance (defaults to Xorg window id or random for GtkSocket mode) -c, --config=FILE Path to config file or '-' for stdin -s, --socket=SOCKET Socket ID + --connect-socket=CSOCKET Connect to server socket + -p, --print-events Whether to print events to stdout -g, --geometry=GEOMETRY Set window geometry (format: WIDTHxHEIGHT+-X+-Y or maximized) -V, --version Print the version and exit --display=DISPLAY X display to use diff --git a/uzbl-browser b/uzbl-browser index 57c5087..d9b9752 100755 --- a/uzbl-browser +++ b/uzbl-browser @@ -63,4 +63,4 @@ DAEMON_PID=${DAEMON_SOCKET}.pid uzbl-event-manager -va start #fi -uzbl-core "$@" --connect-socket $DAEMON_SOCKET +uzbl-core "$@" --connect-socket $DAEMON_SOCKET -- cgit v1.2.3 From 7008808e0defff7a43abdd64d8c9df27b4eec000 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Fri, 1 Jan 2010 00:56:51 +0100 Subject: eval_js: use JavaScriptCore internal function to convert exception message to string (more verbose) --- uzbl-core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/uzbl-core.c b/uzbl-core.c index 9c8660e..bc294b4 100644 --- a/uzbl-core.c +++ b/uzbl-core.c @@ -1104,15 +1104,13 @@ eval_js(WebKitWebView * web_view, gchar *script, GString *result) { JSStringRelease(val); /* Print message */ - prop = JSStringCreateWithUTF8CString("message"); - val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL); + val = JSValueToStringCopy(context, exc, NULL); size = JSStringGetMaximumUTF8CStringSize(val); if(size) { char cstr[size]; JSStringGetUTF8CString(val, cstr, size); printf("%s\n", cstr); } - JSStringRelease(prop); JSStringRelease(val); } -- cgit v1.2.3 From 5fab9b5f81ff0d7eeebbd1bf07f4b945e1454165 Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Wed, 23 Dec 2009 21:15:18 +0800 Subject: Added the escaped and quoted %r replace for on_event and bind args. --- examples/data/uzbl/plugins/bind.py | 10 ++++++++++ examples/data/uzbl/plugins/on_event.py | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py index 1cba7b2..668b595 100644 --- a/examples/data/uzbl/plugins/bind.py +++ b/examples/data/uzbl/plugins/bind.py @@ -306,9 +306,19 @@ class Bind(object): def expand(cmd, args): '''Replaces "%s %1 %2 %3..." with " ...".''' + # Direct string replace. if '%s' in cmd: cmd = cmd.replace('%s', ' '.join(map(unicode, args))) + # Escaped and quoted string replace. + if '%r' in cmd: + joined = ('%r' % ' '.join(map(unicode, args)))[1:] + for char in ['\\', '@']: + joined = joined.replace(char, '\\'+char) + + cmd = cmd.replace('%r', joined) + + # Arg index string replace. for (index, arg) in enumerate(args): index += 1 if '%%%d' % index in cmd: diff --git a/examples/data/uzbl/plugins/on_event.py b/examples/data/uzbl/plugins/on_event.py index afee4e6..9d2525b 100644 --- a/examples/data/uzbl/plugins/on_event.py +++ b/examples/data/uzbl/plugins/on_event.py @@ -2,6 +2,7 @@ Formatting options: %s = space separated string of the arguments + %r = escaped and quoted version of %s %1 = argument 1 %2 = argument 2 %n = argument n @@ -47,9 +48,19 @@ def get_on_events(uzbl): def expand(cmd, args): '''Replaces "%s %1 %2 %3..." with " ...".''' + # Direct string replace. if '%s' in cmd: cmd = cmd.replace('%s', ' '.join(map(unicode, args))) + # Escaped and quoted string replace. + if '%r' in cmd: + joined = ('%r' % ' '.join(map(unicode, args)))[1:] + for char in ['\\', '@']: + joined = joined.replace(char, '\\'+char) + + cmd = cmd.replace('%r', joined) + + # Arg index string replace. for (index, arg) in enumerate(args): index += 1 if '%%%d' % index in cmd: -- cgit v1.2.3 From d324593e958f89bf15478f60269688b2ca0c1b4a Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Thu, 24 Dec 2009 00:27:19 +0800 Subject: Moved expand function to external plugin to reduce code duplication. --- examples/data/uzbl/plugins/bind.py | 27 ++------------------ examples/data/uzbl/plugins/cmd_expand.py | 43 ++++++++++++++++++++++++++++++++ examples/data/uzbl/plugins/on_event.py | 27 ++------------------ 3 files changed, 47 insertions(+), 50 deletions(-) create mode 100644 examples/data/uzbl/plugins/cmd_expand.py diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py index 668b595..9614df6 100644 --- a/examples/data/uzbl/plugins/bind.py +++ b/examples/data/uzbl/plugins/bind.py @@ -303,30 +303,6 @@ class Bind(object): return self._repr_cache -def expand(cmd, args): - '''Replaces "%s %1 %2 %3..." with " ...".''' - - # Direct string replace. - if '%s' in cmd: - cmd = cmd.replace('%s', ' '.join(map(unicode, args))) - - # Escaped and quoted string replace. - if '%r' in cmd: - joined = ('%r' % ' '.join(map(unicode, args)))[1:] - for char in ['\\', '@']: - joined = joined.replace(char, '\\'+char) - - cmd = cmd.replace('%r', joined) - - # Arg index string replace. - for (index, arg) in enumerate(args): - index += 1 - if '%%%d' % index in cmd: - cmd = cmd.replace('%%%d' % index, unicode(arg)) - - return cmd - - def exec_bind(uzbl, bind, *args, **kargs): '''Execute bind objects.''' @@ -342,8 +318,9 @@ def exec_bind(uzbl, bind, *args, **kargs): raise ArgumentError('cannot supply kargs for uzbl commands') commands = [] + cmd_expand = uzbl.cmd_expand for cmd in bind.commands: - cmd = expand(cmd, args) + cmd = cmd_expand(cmd, args) uzbl.send(cmd) diff --git a/examples/data/uzbl/plugins/cmd_expand.py b/examples/data/uzbl/plugins/cmd_expand.py new file mode 100644 index 0000000..a5c279d --- /dev/null +++ b/examples/data/uzbl/plugins/cmd_expand.py @@ -0,0 +1,43 @@ +__export__ = ['cmd_expand',] + + +def escape(str): + for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]: + str = str.replace(char, (level * '\\') + char) + + return str + + +def cmd_expand(uzbl, cmd, args): + '''Exports a function that provides the following + expansions in any uzbl command string: + + %s = replace('%s', ' '.join(args)) + %r = replace('%r', "'%s'" % escaped(' '.join(args))) + %1 = replace('%1', arg[0]) + %2 = replace('%2', arg[1]) + %n = replace('%n', arg[n-1]) + ''' + + # Ensure (1) all string representable and (2) correct string encoding. + args = map(unicode, args) + + # Direct string replace. + if '%s' in cmd: + cmd = cmd.replace('%s', ' '.join(args)) + + # Escaped and quoted string replace. + if '%r' in cmd: + cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args))) + + # Arg index string replace. + for (index, arg) in enumerate(args): + index += 1 + if '%%%d' % index in cmd: + cmd = cmd.replace('%%%d' % index, unicode(arg)) + + return cmd + + +def init(*args): + pass diff --git a/examples/data/uzbl/plugins/on_event.py b/examples/data/uzbl/plugins/on_event.py index 9d2525b..f1ad0c9 100644 --- a/examples/data/uzbl/plugins/on_event.py +++ b/examples/data/uzbl/plugins/on_event.py @@ -45,30 +45,6 @@ def get_on_events(uzbl): return UZBLS[uzbl] -def expand(cmd, args): - '''Replaces "%s %1 %2 %3..." with " ...".''' - - # Direct string replace. - if '%s' in cmd: - cmd = cmd.replace('%s', ' '.join(map(unicode, args))) - - # Escaped and quoted string replace. - if '%r' in cmd: - joined = ('%r' % ' '.join(map(unicode, args)))[1:] - for char in ['\\', '@']: - joined = joined.replace(char, '\\'+char) - - cmd = cmd.replace('%r', joined) - - # Arg index string replace. - for (index, arg) in enumerate(args): - index += 1 - if '%%%d' % index in cmd: - cmd = cmd.replace('%%%d' % index, unicode(arg)) - - return cmd - - def event_handler(uzbl, *args, **kargs): '''This function handles all the events being watched by various on_event definitions and responds accordingly.''' @@ -79,8 +55,9 @@ def event_handler(uzbl, *args, **kargs): return commands = events[event] + cmd_expand = uzbl.cmd_expand for cmd in commands: - cmd = expand(cmd, args) + cmd = cmd_expand(cmd, args) uzbl.send(cmd) -- cgit v1.2.3 From cf98a9a7f1713c81ba855f79de2a48aa1f994714 Mon Sep 17 00:00:00 2001 From: Mason Larobina Date: Sat, 26 Dec 2009 19:27:56 +0800 Subject: URIEncode search string component of web searching binds. --- examples/config/uzbl/config | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/config/uzbl/config b/examples/config/uzbl/config index 9dc4a35..a6be6d6 100644 --- a/examples/config/uzbl/config +++ b/examples/config/uzbl/config @@ -230,9 +230,9 @@ set open_new_window = sh 'uzbl-browser -u \@SELECTED_URI' @cbind N = search_reverse # --- Web searching binds --- -@cbind gg_ = uri http://www.google.com/search?q=%s -@cbind \\awiki_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=%s&go=Go -@cbind \\wiki_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=%s&go=Go +@cbind gg_ = uri http://www.google.com/search?q=\@\@ +@cbind \\awiki_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=\@\@&go=Go +@cbind \\wiki_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=\@\@&go=Go # --- Handy binds --- # Set function shortcut -- cgit v1.2.3 From 7f8ef03cfd55c266de8b78bfa19e154e1e9047b6 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 11:50:21 +0100 Subject: move source files from project root into src directory. Makefile is a bit broken though --- Makefile | 12 +- callbacks.c | 724 --------------- callbacks.h | 197 ---- config.h | 10 - events.c | 206 ----- events.h | 35 - inspector.c | 103 --- inspector.h | 7 - src/callbacks.c | 724 +++++++++++++++ src/callbacks.h | 197 ++++ src/config.h | 10 + src/events.c | 206 +++++ src/events.h | 35 + src/inspector.c | 103 +++ src/inspector.h | 7 + src/uzbl-browser | 66 ++ src/uzbl-core.c | 2637 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/uzbl-core.h | 494 ++++++++++ uzbl-browser | 66 -- uzbl-core.c | 2637 ------------------------------------------------------ uzbl-core.h | 494 ---------- 21 files changed, 4486 insertions(+), 4484 deletions(-) delete mode 100644 callbacks.c delete mode 100644 callbacks.h delete mode 100644 config.h delete mode 100644 events.c delete mode 100644 events.h delete mode 100644 inspector.c delete mode 100644 inspector.h create mode 100644 src/callbacks.c create mode 100644 src/callbacks.h create mode 100644 src/config.h create mode 100644 src/events.c create mode 100644 src/events.h create mode 100644 src/inspector.c create mode 100644 src/inspector.h create mode 100755 src/uzbl-browser create mode 100644 src/uzbl-core.c create mode 100644 src/uzbl-core.h delete mode 100755 uzbl-browser delete mode 100644 uzbl-core.c delete mode 100644 uzbl-core.h diff --git a/Makefile b/Makefile index 3baa6c6..15e1851 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,10 @@ CFLAGS!=echo -std=c99 `pkg-config --cflags gtk+-2.0 webkit-1.0 libsoup-2.4 gthre LDFLAGS:=$(shell pkg-config --libs gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0) -pthread $(LDFLAGS) LDFLAGS!=echo `pkg-config --libs gtk+-2.0 webkit-1.0 libsoup-2.4 gthread-2.0` -pthread $(LDFLAGS) -SRC = uzbl-core.c events.c callbacks.c inspector.c -OBJ = ${SRC:.c=.o} +SRC = $(wildcard src/*.c) +HEAD = $(wildcard src/*.h) +TOBJ = $(SRC:.c=.o) +OBJ = $(foreach obj, $(TOBJ), $(notdir $(obj))) all: uzbl-browser options @@ -25,7 +27,7 @@ options: @${CC} -c ${CFLAGS} $< @echo ... done. -${OBJ}: uzbl-core.h events.h callbacks.h inspector.h config.h +${OBJ}: ${HEAD} uzbl-core: ${OBJ} @echo @@ -59,7 +61,7 @@ test-uzbl-core: uzbl-core ./uzbl-core --uri http://www.uzbl.org --verbose test-uzbl-browser: uzbl-browser - ./uzbl-browser --uri http://www.uzbl.org --verbose + ./src/uzbl-browser --uri http://www.uzbl.org --verbose test-uzbl-core-sandbox: uzbl-core make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core @@ -104,7 +106,7 @@ install-uzbl-core: all install-uzbl-browser: install -d $(INSTALLDIR)/bin - install -m755 uzbl-browser $(INSTALLDIR)/bin/uzbl-browser + install -m755 src/uzbl-browser $(INSTALLDIR)/bin/uzbl-browser install -m755 examples/data/uzbl/scripts/uzbl-cookie-daemon $(INSTALLDIR)/bin/uzbl-cookie-daemon install -m755 examples/data/uzbl/scripts/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager sed -i 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' $(INSTALLDIR)/bin/uzbl-browser diff --git a/callbacks.c b/callbacks.c deleted file mode 100644 index dab92c1..0000000 --- a/callbacks.c +++ /dev/null @@ -1,724 +0,0 @@ -/* - ** Callbacks - ** (c) 2009 by Robert Manea et al. -*/ - -#include "uzbl-core.h" -#include "callbacks.h" -#include "events.h" - - -void -set_proxy_url() { - SoupURI *suri; - - if(uzbl.net.proxy_url == NULL || *uzbl.net.proxy_url == ' ') { - soup_session_remove_feature_by_type(uzbl.net.soup_session, - (GType) SOUP_SESSION_PROXY_URI); - } - else { - suri = soup_uri_new(uzbl.net.proxy_url); - g_object_set(G_OBJECT(uzbl.net.soup_session), - SOUP_SESSION_PROXY_URI, - suri, NULL); - soup_uri_free(suri); - } - return; -} - -void -set_icon() { - if(file_exists(uzbl.gui.icon)) { - if (uzbl.gui.main_window) - gtk_window_set_icon_from_file (GTK_WINDOW (uzbl.gui.main_window), uzbl.gui.icon, NULL); - } else { - g_printerr ("Icon \"%s\" not found. ignoring.\n", uzbl.gui.icon); - } -} - -void -cmd_set_geometry() { - int ret=0, x=0, y=0; - unsigned int w=0, h=0; - if(uzbl.gui.geometry) { - if(uzbl.gui.geometry[0] == 'm') { /* m/maximize/maximized */ - gtk_window_maximize((GtkWindow *)(uzbl.gui.main_window)); - } else { - /* we used to use gtk_window_parse_geometry() but that didn't work how it was supposed to */ - ret = XParseGeometry(uzbl.gui.geometry, &x, &y, &w, &h); - if(ret & XValue) - gtk_window_move((GtkWindow *)uzbl.gui.main_window, x, y); - if(ret & WidthValue) - gtk_window_resize((GtkWindow *)uzbl.gui.main_window, w, h); - } - } - - /* update geometry var with the actual geometry - this is necessary as some WMs don't seem to honour - the above setting and we don't want to end up with - wrong geometry information - */ - retrieve_geometry(); -} - -void -cmd_set_status() { - if (!uzbl.behave.show_status) { - gtk_widget_hide(uzbl.gui.mainbar); - } else { - gtk_widget_show(uzbl.gui.mainbar); - } - update_title(); -} - -void -cmd_load_uri() { - load_uri_imp (uzbl.state.uri); -} - -void -cmd_max_conns() { - g_object_set(G_OBJECT(uzbl.net.soup_session), - SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL); -} - -void -cmd_max_conns_host() { - g_object_set(G_OBJECT(uzbl.net.soup_session), - SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL); -} - -void -cmd_http_debug() { - soup_session_remove_feature - (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger)); - /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */ - /*g_free(uzbl.net.soup_logger);*/ - - uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1); - soup_session_add_feature(uzbl.net.soup_session, - SOUP_SESSION_FEATURE(uzbl.net.soup_logger)); -} - -WebKitWebSettings* -view_settings() { - return webkit_web_view_get_settings(uzbl.gui.web_view); -} - -void -cmd_font_size() { - WebKitWebSettings *ws = view_settings(); - if (uzbl.behave.font_size > 0) { - g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.font_size, NULL); - } - - if (uzbl.behave.monospace_size > 0) { - g_object_set (G_OBJECT(ws), "default-monospace-font-size", - uzbl.behave.monospace_size, NULL); - } else { - g_object_set (G_OBJECT(ws), "default-monospace-font-size", - uzbl.behave.font_size, NULL); - } -} - -void -cmd_default_font_family() { - g_object_set (G_OBJECT(view_settings()), "default-font-family", - uzbl.behave.default_font_family, NULL); -} - -void -cmd_monospace_font_family() { - g_object_set (G_OBJECT(view_settings()), "monospace-font-family", - uzbl.behave.monospace_font_family, NULL); -} - -void -cmd_sans_serif_font_family() { - g_object_set (G_OBJECT(view_settings()), "sans_serif-font-family", - uzbl.behave.sans_serif_font_family, NULL); -} - -void -cmd_serif_font_family() { - g_object_set (G_OBJECT(view_settings()), "serif-font-family", - uzbl.behave.serif_font_family, NULL); -} - -void -cmd_cursive_font_family() { - g_object_set (G_OBJECT(view_settings()), "cursive-font-family", - uzbl.behave.cursive_font_family, NULL); -} - -void -cmd_fantasy_font_family() { - g_object_set (G_OBJECT(view_settings()), "fantasy-font-family", - uzbl.behave.fantasy_font_family, NULL); -} - -void -cmd_zoom_level() { - webkit_web_view_set_zoom_level (uzbl.gui.web_view, uzbl.behave.zoom_level); -} - -void -cmd_disable_plugins() { - g_object_set (G_OBJECT(view_settings()), "enable-plugins", - !uzbl.behave.disable_plugins, NULL); -} - -void -cmd_disable_scripts() { - g_object_set (G_OBJECT(view_settings()), "enable-scripts", - !uzbl.behave.disable_scripts, NULL); -} - -void -cmd_minimum_font_size() { - g_object_set (G_OBJECT(view_settings()), "minimum-font-size", - uzbl.behave.minimum_font_size, NULL); -} -void -cmd_autoload_img() { - g_object_set (G_OBJECT(view_settings()), "auto-load-images", - uzbl.behave.autoload_img, NULL); -} - - -void -cmd_autoshrink_img() { - g_object_set (G_OBJECT(view_settings()), "auto-shrink-images", - uzbl.behave.autoshrink_img, NULL); -} - - -void -cmd_enable_spellcheck() { - g_object_set (G_OBJECT(view_settings()), "enable-spell-checking", - uzbl.behave.enable_spellcheck, NULL); -} - -void -cmd_enable_private() { - g_object_set (G_OBJECT(view_settings()), "enable-private-browsing", - uzbl.behave.enable_private, NULL); -} - -void -cmd_print_bg() { - g_object_set (G_OBJECT(view_settings()), "print-backgrounds", - uzbl.behave.print_bg, NULL); -} - -void -cmd_style_uri() { - g_object_set (G_OBJECT(view_settings()), "user-stylesheet-uri", - uzbl.behave.style_uri, NULL); -} - -void -cmd_resizable_txt() { - g_object_set (G_OBJECT(view_settings()), "resizable-text-areas", - uzbl.behave.resizable_txt, NULL); -} - -void -cmd_default_encoding() { - g_object_set (G_OBJECT(view_settings()), "default-encoding", - uzbl.behave.default_encoding, NULL); -} - -void -cmd_enforce_96dpi() { - g_object_set (G_OBJECT(view_settings()), "enforce-96-dpi", - uzbl.behave.enforce_96dpi, NULL); -} - -void -cmd_caret_browsing() { - g_object_set (G_OBJECT(view_settings()), "enable-caret-browsing", - uzbl.behave.caret_browsing, NULL); -} - -void -cmd_fifo_dir() { - uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir); -} - -void -cmd_socket_dir() { - uzbl.behave.socket_dir = init_socket(uzbl.behave.socket_dir); -} - -void -cmd_inject_html() { - if(uzbl.behave.inject_html) { - webkit_web_view_load_html_string (uzbl.gui.web_view, - uzbl.behave.inject_html, NULL); - } -} - -void -cmd_useragent() { - if (*uzbl.net.useragent == ' ') { - g_free (uzbl.net.useragent); - uzbl.net.useragent = NULL; - } else { - g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, - uzbl.net.useragent, NULL); - } -} - -/* requires webkit >=1.1.14 */ -void -cmd_view_source() { - webkit_web_view_set_view_source_mode(uzbl.gui.web_view, - (gboolean) uzbl.behave.view_source); -} - -void -cmd_set_zoom_type () { - if(uzbl.behave.zoom_type) - webkit_web_view_set_full_content_zoom (uzbl.gui.web_view, TRUE); - else - webkit_web_view_set_full_content_zoom (uzbl.gui.web_view, FALSE); -} - -void -toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result) { - (void)argv; - (void)result; - - webkit_web_view_set_full_content_zoom (page, !webkit_web_view_get_full_content_zoom (page)); -} - -void -toggle_status_cb (WebKitWebView* page, GArray *argv, GString *result) { - (void)page; - (void)argv; - (void)result; - - if (uzbl.behave.show_status) { - gtk_widget_hide(uzbl.gui.mainbar); - } else { - gtk_widget_show(uzbl.gui.mainbar); - } - uzbl.behave.show_status = !uzbl.behave.show_status; - update_title(); -} - -void -link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) { - (void) page; - (void) title; - (void) data; - State *s = &uzbl.state; - - if(s->selected_url) { - if(s->last_selected_url) - g_free(s->last_selected_url); - s->last_selected_url = g_strdup(s->selected_url); - } - else { - if(s->last_selected_url) g_free(s->last_selected_url); - s->last_selected_url = NULL; - } - - g_free(s->selected_url); - s->selected_url = NULL; - - if (link) { - s->selected_url = g_strdup(link); - - if(s->last_selected_url && - g_strcmp0(s->selected_url, s->last_selected_url)) - send_event(LINK_UNHOVER, s->last_selected_url, NULL); - - send_event(LINK_HOVER, s->selected_url, NULL); - } - else if(s->last_selected_url) { - send_event(LINK_UNHOVER, s->last_selected_url, NULL); - } - - update_title(); -} - -void -title_change_cb (WebKitWebView* web_view, GParamSpec param_spec) { - (void) web_view; - (void) param_spec; - const gchar *title = webkit_web_view_get_title(web_view); - if (uzbl.gui.main_title) - g_free (uzbl.gui.main_title); - uzbl.gui.main_title = title ? g_strdup (title) : g_strdup ("(no title)"); - update_title(); - send_event(TITLE_CHANGED, uzbl.gui.main_title, NULL); -} - -void -progress_change_cb (WebKitWebView* page, gint progress, gpointer data) { - (void) page; - (void) data; - gchar *prg_str; - - prg_str = itos(progress); - send_event(LOAD_PROGRESS, prg_str, NULL); - g_free(prg_str); -} - -void -selection_changed_cb(WebKitWebView *webkitwebview, gpointer ud) { - (void)ud; - gchar *tmp; - - webkit_web_view_copy_clipboard(webkitwebview); - tmp = gtk_clipboard_wait_for_text(gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)); - send_event(SELECTION_CHANGED, tmp, NULL); - g_free(tmp); -} - -void -load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { - (void) page; - (void) data; - - send_event(LOAD_FINISH, webkit_web_frame_get_uri(frame), NULL); -} - -void -load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { - (void) page; - (void) frame; - (void) data; - - send_event(LOAD_START, uzbl.state.uri, NULL); -} - -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; - gchar *details; - - details = g_strdup_printf("%s %d:%s", uri, err->code, err->message); - send_event(LOAD_ERROR, details, NULL); - g_free(details); -} - -void -load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { - (void) page; - (void) data; - g_free (uzbl.state.uri); - GString* newuri = g_string_new (webkit_web_frame_get_uri (frame)); - uzbl.state.uri = g_string_free (newuri, FALSE); - - send_event(LOAD_COMMIT, webkit_web_frame_get_uri (frame), NULL); -} - -void -destroy_cb (GtkWidget* widget, gpointer data) { - (void) widget; - (void) data; - gtk_main_quit (); -} - -gboolean -configure_event_cb(GtkWidget* window, GdkEventConfigure* event) { - (void) window; - (void) event; - gchar *lastgeo = NULL; - - lastgeo = g_strdup(uzbl.gui.geometry); - retrieve_geometry(); - - if(strcmp(lastgeo, uzbl.gui.geometry)) - send_event(GEOMETRY_CHANGED, uzbl.gui.geometry, NULL); - g_free(lastgeo); - - return FALSE; -} - -gboolean -focus_cb(GtkWidget* window, GdkEventFocus* event, void *ud) { - (void) window; - (void) event; - (void) ud; - - if(event->in) - send_event(FOCUS_GAINED, "", NULL); - else - send_event(FOCUS_LOST, "", NULL); - - return FALSE; -} - -gboolean -key_press_cb (GtkWidget* window, GdkEventKey* event) { - (void) window; - - if(event->type == GDK_KEY_PRESS) - key_to_event(event->keyval, GDK_KEY_PRESS); - - return uzbl.behave.forward_keys ? FALSE : TRUE; -} - -gboolean -key_release_cb (GtkWidget* window, GdkEventKey* event) { - (void) window; - - if(event->type == GDK_KEY_RELEASE) - key_to_event(event->keyval, GDK_KEY_RELEASE); - - return uzbl.behave.forward_keys ? FALSE : TRUE; -} - -gboolean -button_press_cb (GtkWidget* window, GdkEventButton* event) { - (void) window; - gint context; - gchar *details; - gboolean propagate = FALSE, - sendev = FALSE; - - if(event->type == GDK_BUTTON_PRESS) { - if(uzbl.state.last_button) - gdk_event_free((GdkEvent *)uzbl.state.last_button); - uzbl.state.last_button = (GdkEventButton *)gdk_event_copy((GdkEvent *)event); - - context = get_click_context(NULL); - /* left click */ - if(event->button == 1) { - if((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) - send_event(FORM_ACTIVE, "button1", NULL); - else if((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)) - send_event(ROOT_ACTIVE, "button1", NULL); - } - else if(event->button == 2 && !(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) { - sendev = TRUE; - propagate = TRUE; - } - else if(event->button > 3) { - sendev = TRUE; - propagate = TRUE; - } - - if(sendev) { - details = g_strdup_printf("Button%d", event->button); - send_event(KEY_PRESS, details, NULL); - g_free(details); - } - } - - return propagate; -} - -gboolean -button_release_cb (GtkWidget* window, GdkEventButton* event) { - (void) window; - gint context; - gchar *details; - gboolean propagate = FALSE, - sendev = FALSE; - - context = get_click_context(NULL); - if(event->type == GDK_BUTTON_RELEASE) { - if(event->button == 2 && !(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) { - sendev = TRUE; - propagate = TRUE; - } - else if(event->button > 3) { - sendev = TRUE; - propagate = TRUE; - } - - if(sendev) { - details = g_strdup_printf("Button%d", event->button); - send_event(KEY_RELEASE, details, NULL); - g_free(details); - } - } - - return propagate; -} - -gboolean -navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { - (void) web_view; - (void) frame; - (void) navigation_action; - (void) user_data; - - const gchar* uri = webkit_network_request_get_uri (request); - gboolean decision_made = FALSE; - - if (uzbl.state.verbose) - printf("Navigation requested -> %s\n", uri); - - if (uzbl.behave.scheme_handler) { - GString *s = g_string_new (""); - g_string_printf(s, "'%s'", uri); - - run_handler(uzbl.behave.scheme_handler, s->str); - - if(uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) { - char *p = strchr(uzbl.comm.sync_stdout, '\n' ); - if ( p != NULL ) *p = '\0'; - if (!strcmp(uzbl.comm.sync_stdout, "USED")) { - webkit_web_policy_decision_ignore(policy_decision); - decision_made = TRUE; - } - } - if (uzbl.comm.sync_stdout) - uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); - - g_string_free(s, TRUE); - } - if (!decision_made) - webkit_web_policy_decision_use(policy_decision); - - return TRUE; -} - -gboolean -new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { - (void) web_view; - (void) frame; - (void) navigation_action; - (void) policy_decision; - (void) user_data; - const gchar* uri = webkit_network_request_get_uri (request); - if (uzbl.state.verbose) - printf("New window requested -> %s \n", uri); - webkit_web_policy_decision_use(policy_decision); - send_event(NEW_WINDOW, uri, NULL); - return TRUE; -} - -gboolean -mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, gchar *mime_type, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { - (void) frame; - (void) request; - (void) user_data; - - /* If we can display it, let's display it... */ - if (webkit_web_view_can_show_mime_type (web_view, mime_type)) { - webkit_web_policy_decision_use (policy_decision); - return TRUE; - } - - /* ...everything we can't display is downloaded */ - webkit_web_policy_decision_download (policy_decision); - return TRUE; -} - -/*@null@*/ WebKitWebView* -create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) { - (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); - new_window_load_uri(uzbl.state.selected_url); - } else { - if (uzbl.state.verbose) - printf("New web view -> %s\n","Nothing to open, exiting"); - } - return (NULL); -} - -gboolean -download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) { - (void) web_view; - (void) user_data; - if (uzbl.behave.download_handler) { - const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download); - if (uzbl.state.verbose) - printf("Download -> %s\n",uri); - - /* if urls not escaped, we may have to escape and quote uri before this call */ - GString *args = g_string_new(uri); - - if (uzbl.net.proxy_url) { - g_string_append_c(args, ' '); - g_string_append(args, uzbl.net.proxy_url); - } - - run_handler(uzbl.behave.download_handler, args->str); - - g_string_free(args, TRUE); - } - send_event(DOWNLOAD_REQ, webkit_download_get_uri ((WebKitDownload*)download), NULL); - return (FALSE); -} - -void -run_menu_command(GtkWidget *menu, const char *line) { - (void) menu; - - parse_cmd_line(line, NULL); -} - - -void -populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) { - (void) v; - (void) c; - GUI *g = &uzbl.gui; - GtkWidget *item; - MenuItem *mi; - guint i=0; - gint context, hit=0; - - if(!g->menu_items) - return; - - /* check context */ - 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_DOCUMENT) && - (context & mi->context)) { - if(mi->issep) { - item = gtk_separator_menu_item_new(); - gtk_menu_append(GTK_MENU(m), item); - gtk_widget_show(item); - } - else { - item = gtk_menu_item_new_with_label(mi->name); - g_signal_connect(item, "activate", - G_CALLBACK(run_menu_command), mi->cmd); - gtk_menu_append(GTK_MENU(m), item); - gtk_widget_show(item); - } - hit++; - } - - if((mi->context == WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) && - (context <= WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) && - !hit) { - if(mi->issep) { - item = gtk_separator_menu_item_new(); - gtk_menu_append(GTK_MENU(m), item); - gtk_widget_show(item); - } - else { - item = gtk_menu_item_new_with_label(mi->name); - g_signal_connect(item, "activate", - G_CALLBACK(run_menu_command), mi->cmd); - gtk_menu_append(GTK_MENU(m), item); - gtk_widget_show(item); - } - } - } -} - diff --git a/callbacks.h b/callbacks.h deleted file mode 100644 index 3f318f2..0000000 --- a/callbacks.h +++ /dev/null @@ -1,197 +0,0 @@ -/* - ** Callbacks - ** (c) 2009 by Robert Manea et al. -*/ - -void -cmd_load_uri(); - -void -cmd_set_status(); - -void -set_proxy_url(); - -void -set_icon(); - -void -move_statusbar(); - -void -cmd_http_debug(); - -void -cmd_max_conns(); - -void -cmd_max_conns_host(); - -/* exported WebKitWebSettings properties */ -void -cmd_font_size(); - -void -cmd_default_font_family(); - -void -cmd_monospace_font_family(); - -void -cmd_sans_serif_font_family(); - -void -cmd_serif_font_family(); - -void -cmd_cursive_font_family(); - -void -cmd_fantasy_font_family(); - -void -cmd_zoom_level(); - -void -cmd_set_zoom_type(); - -void -cmd_disable_plugins(); - -void -cmd_disable_scripts(); - -void -cmd_minimum_font_size(); - -void -cmd_fifo_dir(); - -void -cmd_socket_dir(); - -void -cmd_useragent() ; - -void -cmd_autoload_img(); - -void -cmd_autoshrink_img(); - -void -cmd_enable_spellcheck(); - -void -cmd_enable_private(); - -void -cmd_print_bg(); - -void -cmd_style_uri(); - -void -cmd_resizable_txt(); - -void -cmd_default_encoding(); - -void -cmd_enforce_96dpi(); - -void -cmd_inject_html(); - -void -cmd_caret_browsing(); - -void -cmd_set_geometry(); - -void -cmd_view_source(); - -void -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); - -void -toggle_status_cb (WebKitWebView* page, GArray *argv, GString *result); - -void -link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data); - -void -title_change_cb (WebKitWebView* web_view, GParamSpec param_spec); - -void -progress_change_cb (WebKitWebView* page, gint progress, gpointer data); - -void -load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); - -void -load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); - -void -load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); - -void -load_error_cb (WebKitWebView* page, WebKitWebFrame* frame, gchar *uri, gpointer web_err, gpointer ud); - -void -selection_changed_cb(WebKitWebView *webkitwebview, gpointer ud); - -void -destroy_cb (GtkWidget* widget, gpointer data); - -gboolean -configure_event_cb(GtkWidget* window, GdkEventConfigure* event); - -gboolean -key_press_cb (GtkWidget* window, GdkEventKey* event); - -gboolean -key_release_cb (GtkWidget* window, GdkEventKey* event); - -gboolean -navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, - WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, - WebKitWebPolicyDecision *policy_decision, gpointer user_data); - -gboolean -new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, - WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, - WebKitWebPolicyDecision *policy_decision, gpointer user_data); - -gboolean -mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, - gchar *mime_type, WebKitWebPolicyDecision *policy_decision, gpointer user_data); - -/*@null@*/ WebKitWebView* -create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data); - -gboolean -download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data); - -void -populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c); - -gboolean -button_press_cb (GtkWidget* window, GdkEventButton* event); - -gboolean -button_release_cb (GtkWidget* window, GdkEventButton* event); - -gboolean -focus_cb(GtkWidget* window, GdkEventFocus* event, void *ud); - diff --git a/config.h b/config.h deleted file mode 100644 index e9b9a8e..0000000 --- a/config.h +++ /dev/null @@ -1,10 +0,0 @@ -const struct { - /*@null@*/ char *command; -} default_config[] = { -{ "set status_format = \\@[\\@keycmd]\\@ \\@[\\@TITLE]\\@ - Uzbl browser"}, -{ "set title_format_long = \\@keycmd \\@TITLE - Uzbl browser <\\@NAME> > \\@SELECTED_URI"}, -{ "set title_format_short = \\@TITLE - Uzbl browser <\\@NAME>"}, -{ "set max_conns = 100"}, /* WebkitGTK default: 10 */ -{ "set max_conns_host = 6"}, /* WebkitGTK default: 2 */ -{ NULL } -}; diff --git a/events.c b/events.c deleted file mode 100644 index acb554c..0000000 --- a/events.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - ** Uzbl event routines - ** (c) 2009 by Robert Manea -*/ - -#include "uzbl-core.h" -#include "events.h" - -UzblCore uzbl; - -/* Event id to name mapping - * Event names must be in the same - * order as in 'enum event_type' - * - * TODO: Add more useful events -*/ -const char *event_table[LAST_EVENT] = { - "LOAD_START" , - "LOAD_COMMIT" , - "LOAD_FINISH" , - "LOAD_ERROR" , - "KEY_PRESS" , - "KEY_RELEASE" , - "DOWNLOAD_REQUEST" , - "COMMAND_EXECUTED" , - "LINK_HOVER" , - "TITLE_CHANGED" , - "GEOMETRY_CHANGED" , - "WEBINSPECTOR" , - "NEW_WINDOW" , - "SELECTION_CHANGED", - "VARIABLE_SET" , - "FIFO_SET" , - "SOCKET_SET" , - "INSTANCE_START" , - "INSTANCE_EXIT" , - "LOAD_PROGRESS" , - "LINK_UNHOVER" , - "FORM_ACTIVE" , - "ROOT_ACTIVE" , - "FOCUS_LOST" , - "FOCUS_GAINED" , - "FILE_INCLUDED" , - "PLUG_CREATED" , - "COMMAND_ERROR" , - "BUILTINS" -}; - -void -event_buffer_timeout(guint sec) { - struct itimerval t; - memset(&t, 0, sizeof t); - t.it_value.tv_sec = sec; - t.it_value.tv_usec = 0; - setitimer(ITIMER_REAL, &t, NULL); -} - - -void -send_event_socket(GString *msg) { - GError *error = NULL; - GString *tmp; - GIOChannel *gio = NULL; - GIOStatus ret; - gsize len; - guint i=0, j=0; - - /* write to all --connect-socket sockets */ - if(uzbl.comm.connect_chan) { - while(i < uzbl.comm.connect_chan->len) { - gio = g_ptr_array_index(uzbl.comm.connect_chan, i++); - j=0; - - if(gio && gio->is_writeable) { - if(uzbl.state.event_buffer) { - event_buffer_timeout(0); - - /* replay buffered events */ - while(j < uzbl.state.event_buffer->len) { - tmp = g_ptr_array_index(uzbl.state.event_buffer, j++); - ret = g_io_channel_write_chars (gio, - tmp->str, tmp->len, - &len, &error); - - if (ret == G_IO_STATUS_ERROR) - g_warning ("Error sending event to socket: %s", error->message); - else - g_io_channel_flush(gio, &error); - } - } - - if(msg) { - ret = g_io_channel_write_chars (gio, - msg->str, msg->len, - &len, &error); - - if (ret == G_IO_STATUS_ERROR) - g_warning ("Error sending event to socket: %s", error->message); - else - g_io_channel_flush(gio, &error); - } - } - } - if(uzbl.state.event_buffer) { - g_ptr_array_free(uzbl.state.event_buffer, TRUE); - uzbl.state.event_buffer = NULL; - } - } - /* buffer events until a socket is set and connected - * or a timeout is encountered - */ - else { - if(!uzbl.state.event_buffer) - uzbl.state.event_buffer = g_ptr_array_new(); - g_ptr_array_add(uzbl.state.event_buffer, (gpointer)g_string_new(msg->str)); - } - - /* write to all client sockets */ - i=0; - if(msg && uzbl.comm.client_chan) { - while(i < uzbl.comm.client_chan->len) { - gio = g_ptr_array_index(uzbl.comm.client_chan, i++); - - if(gio && gio->is_writeable && msg) { - ret = g_io_channel_write_chars (gio, - msg->str, msg->len, - &len, &error); - - if (ret == G_IO_STATUS_ERROR) - g_warning ("Error sending event to socket: %s", error->message); - else - g_io_channel_flush(gio, &error); - } - } - } -} - -void -send_event_stdout(GString *msg) { - printf("%s", msg->str); - fflush(stdout); -} - -/* - * build event string and send over the supported interfaces - * custom_event == NULL indicates an internal event -*/ -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); - } - /* 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); - } - - if(event_message->str) { - if(uzbl.state.events_stdout) - send_event_stdout(event_message); - send_event_socket(event_message); - - g_string_free(event_message, TRUE); - } - g_free(p_val); -} - -/* Transform gdk key events to our own events */ -void -key_to_event(guint keyval, gint mode) { - gchar ucs[7]; - gint ulen; - guint32 ukval = gdk_keyval_to_unicode(keyval); - - /* check for printable unicode char */ - /* TODO: Pass the keyvals through a GtkIMContext so that - * we also get combining chars right - */ - if(g_unichar_isgraph(ukval)) { - ulen = g_unichar_to_utf8(ukval, ucs); - ucs[ulen] = 0; - - send_event(mode == GDK_KEY_PRESS ? KEY_PRESS : KEY_RELEASE, - ucs, NULL); - } - /* send keysym for non-printable chars */ - else { - send_event(mode == GDK_KEY_PRESS ? KEY_PRESS : KEY_RELEASE, - gdk_keyval_name(keyval), NULL); - } - -} - diff --git a/events.h b/events.h deleted file mode 100644 index 7b8f58b..0000000 --- a/events.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - ** Uzbl event routines - ** (c) 2009 by Robert Manea -*/ - -/* Event system */ -enum event_type { - LOAD_START, LOAD_COMMIT, LOAD_FINISH, LOAD_ERROR, - KEY_PRESS, KEY_RELEASE, DOWNLOAD_REQ, COMMAND_EXECUTED, - LINK_HOVER, TITLE_CHANGED, GEOMETRY_CHANGED, - WEBINSPECTOR, NEW_WINDOW, SELECTION_CHANGED, - VARIABLE_SET, FIFO_SET, SOCKET_SET, - INSTANCE_START, INSTANCE_EXIT, LOAD_PROGRESS, - LINK_UNHOVER, FORM_ACTIVE, ROOT_ACTIVE, - FOCUS_LOST, FOCUS_GAINED, FILE_INCLUDED, - PLUG_CREATED, COMMAND_ERROR, BUILTINS, - - /* must be last entry */ - LAST_EVENT -}; - -void -event_buffer_timeout(guint sec); - -void -send_event_socket(GString *msg); - -void -send_event_stdout(GString *msg); - -void -send_event(int type, const gchar *details, const gchar *custom_event); - -void -key_to_event(guint keyval, gint mode); diff --git a/inspector.c b/inspector.c deleted file mode 100644 index de3dbcd..0000000 --- a/inspector.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - ** WebInspector - ** (c) 2009 by Robert Manea -*/ - -#include "uzbl-core.h" -#include "events.h" -#include "callbacks.h" - - -void -hide_window_cb(GtkWidget *widget, gpointer data) { - (void) data; - - gtk_widget_hide(widget); -} - -WebKitWebView* -create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){ - (void) data; - (void) page; - (void) web_inspector; - GtkWidget* scrolled_window; - GtkWidget* new_web_view; - GUI *g = &uzbl.gui; - - g->inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - g_signal_connect(G_OBJECT(g->inspector_window), "delete-event", - G_CALLBACK(hide_window_cb), NULL); - - gtk_window_set_title(GTK_WINDOW(g->inspector_window), "Uzbl WebInspector"); - gtk_window_set_default_size(GTK_WINDOW(g->inspector_window), 400, 300); - gtk_widget_show(g->inspector_window); - - scrolled_window = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_container_add(GTK_CONTAINER(g->inspector_window), scrolled_window); - gtk_widget_show(scrolled_window); - - new_web_view = webkit_web_view_new(); - gtk_container_add(GTK_CONTAINER(scrolled_window), new_web_view); - - return WEBKIT_WEB_VIEW(new_web_view); -} - -gboolean -inspector_show_window_cb (WebKitWebInspector* inspector){ - (void) inspector; - gtk_widget_show(uzbl.gui.inspector_window); - - send_event(WEBINSPECTOR, "open", NULL); - return TRUE; -} - -/* TODO: Add variables and code to make use of these functions */ -gboolean -inspector_close_window_cb (WebKitWebInspector* inspector){ - (void) inspector; - send_event(WEBINSPECTOR, "close", NULL); - return TRUE; -} - -gboolean -inspector_attach_window_cb (WebKitWebInspector* inspector){ - (void) inspector; - return FALSE; -} - -gboolean -inspector_detach_window_cb (WebKitWebInspector* inspector){ - (void) inspector; - return FALSE; -} - -gboolean -inspector_uri_changed_cb (WebKitWebInspector* inspector){ - (void) inspector; - return FALSE; -} - -gboolean -inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){ - (void) inspector; - return FALSE; -} - -void -set_up_inspector() { - GUI *g = &uzbl.gui; - WebKitWebSettings *settings = view_settings(); - g_object_set(G_OBJECT(settings), "enable-developer-extras", TRUE, NULL); - - uzbl.gui.inspector = webkit_web_view_get_inspector(uzbl.gui.web_view); - g_signal_connect (G_OBJECT (g->inspector), "inspect-web-view", G_CALLBACK (create_inspector_cb), NULL); - g_signal_connect (G_OBJECT (g->inspector), "show-window", G_CALLBACK (inspector_show_window_cb), NULL); - g_signal_connect (G_OBJECT (g->inspector), "close-window", G_CALLBACK (inspector_close_window_cb), NULL); - g_signal_connect (G_OBJECT (g->inspector), "attach-window", G_CALLBACK (inspector_attach_window_cb), NULL); - g_signal_connect (G_OBJECT (g->inspector), "detach-window", G_CALLBACK (inspector_detach_window_cb), NULL); - g_signal_connect (G_OBJECT (g->inspector), "finished", G_CALLBACK (inspector_inspector_destroyed_cb), NULL); - - g_signal_connect (G_OBJECT (g->inspector), "notify::inspected-uri", G_CALLBACK (inspector_uri_changed_cb), NULL); -} diff --git a/inspector.h b/inspector.h deleted file mode 100644 index 57d0ca9..0000000 --- a/inspector.h +++ /dev/null @@ -1,7 +0,0 @@ -/* - ** WebInspector - ** (c) 2009 by Robert Manea -*/ - -void -set_up_inspector(); diff --git a/src/callbacks.c b/src/callbacks.c new file mode 100644 index 0000000..dab92c1 --- /dev/null +++ b/src/callbacks.c @@ -0,0 +1,724 @@ +/* + ** Callbacks + ** (c) 2009 by Robert Manea et al. +*/ + +#include "uzbl-core.h" +#include "callbacks.h" +#include "events.h" + + +void +set_proxy_url() { + SoupURI *suri; + + if(uzbl.net.proxy_url == NULL || *uzbl.net.proxy_url == ' ') { + soup_session_remove_feature_by_type(uzbl.net.soup_session, + (GType) SOUP_SESSION_PROXY_URI); + } + else { + suri = soup_uri_new(uzbl.net.proxy_url); + g_object_set(G_OBJECT(uzbl.net.soup_session), + SOUP_SESSION_PROXY_URI, + suri, NULL); + soup_uri_free(suri); + } + return; +} + +void +set_icon() { + if(file_exists(uzbl.gui.icon)) { + if (uzbl.gui.main_window) + gtk_window_set_icon_from_file (GTK_WINDOW (uzbl.gui.main_window), uzbl.gui.icon, NULL); + } else { + g_printerr ("Icon \"%s\" not found. ignoring.\n", uzbl.gui.icon); + } +} + +void +cmd_set_geometry() { + int ret=0, x=0, y=0; + unsigned int w=0, h=0; + if(uzbl.gui.geometry) { + if(uzbl.gui.geometry[0] == 'm') { /* m/maximize/maximized */ + gtk_window_maximize((GtkWindow *)(uzbl.gui.main_window)); + } else { + /* we used to use gtk_window_parse_geometry() but that didn't work how it was supposed to */ + ret = XParseGeometry(uzbl.gui.geometry, &x, &y, &w, &h); + if(ret & XValue) + gtk_window_move((GtkWindow *)uzbl.gui.main_window, x, y); + if(ret & WidthValue) + gtk_window_resize((GtkWindow *)uzbl.gui.main_window, w, h); + } + } + + /* update geometry var with the actual geometry + this is necessary as some WMs don't seem to honour + the above setting and we don't want to end up with + wrong geometry information + */ + retrieve_geometry(); +} + +void +cmd_set_status() { + if (!uzbl.behave.show_status) { + gtk_widget_hide(uzbl.gui.mainbar); + } else { + gtk_widget_show(uzbl.gui.mainbar); + } + update_title(); +} + +void +cmd_load_uri() { + load_uri_imp (uzbl.state.uri); +} + +void +cmd_max_conns() { + g_object_set(G_OBJECT(uzbl.net.soup_session), + SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL); +} + +void +cmd_max_conns_host() { + g_object_set(G_OBJECT(uzbl.net.soup_session), + SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL); +} + +void +cmd_http_debug() { + soup_session_remove_feature + (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger)); + /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */ + /*g_free(uzbl.net.soup_logger);*/ + + uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1); + soup_session_add_feature(uzbl.net.soup_session, + SOUP_SESSION_FEATURE(uzbl.net.soup_logger)); +} + +WebKitWebSettings* +view_settings() { + return webkit_web_view_get_settings(uzbl.gui.web_view); +} + +void +cmd_font_size() { + WebKitWebSettings *ws = view_settings(); + if (uzbl.behave.font_size > 0) { + g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.font_size, NULL); + } + + if (uzbl.behave.monospace_size > 0) { + g_object_set (G_OBJECT(ws), "default-monospace-font-size", + uzbl.behave.monospace_size, NULL); + } else { + g_object_set (G_OBJECT(ws), "default-monospace-font-size", + uzbl.behave.font_size, NULL); + } +} + +void +cmd_default_font_family() { + g_object_set (G_OBJECT(view_settings()), "default-font-family", + uzbl.behave.default_font_family, NULL); +} + +void +cmd_monospace_font_family() { + g_object_set (G_OBJECT(view_settings()), "monospace-font-family", + uzbl.behave.monospace_font_family, NULL); +} + +void +cmd_sans_serif_font_family() { + g_object_set (G_OBJECT(view_settings()), "sans_serif-font-family", + uzbl.behave.sans_serif_font_family, NULL); +} + +void +cmd_serif_font_family() { + g_object_set (G_OBJECT(view_settings()), "serif-font-family", + uzbl.behave.serif_font_family, NULL); +} + +void +cmd_cursive_font_family() { + g_object_set (G_OBJECT(view_settings()), "cursive-font-family", + uzbl.behave.cursive_font_family, NULL); +} + +void +cmd_fantasy_font_family() { + g_object_set (G_OBJECT(view_settings()), "fantasy-font-family", + uzbl.behave.fantasy_font_family, NULL); +} + +void +cmd_zoom_level() { + webkit_web_view_set_zoom_level (uzbl.gui.web_view, uzbl.behave.zoom_level); +} + +void +cmd_disable_plugins() { + g_object_set (G_OBJECT(view_settings()), "enable-plugins", + !uzbl.behave.disable_plugins, NULL); +} + +void +cmd_disable_scripts() { + g_object_set (G_OBJECT(view_settings()), "enable-scripts", + !uzbl.behave.disable_scripts, NULL); +} + +void +cmd_minimum_font_size() { + g_object_set (G_OBJECT(view_settings()), "minimum-font-size", + uzbl.behave.minimum_font_size, NULL); +} +void +cmd_autoload_img() { + g_object_set (G_OBJECT(view_settings()), "auto-load-images", + uzbl.behave.autoload_img, NULL); +} + + +void +cmd_autoshrink_img() { + g_object_set (G_OBJECT(view_settings()), "auto-shrink-images", + uzbl.behave.autoshrink_img, NULL); +} + + +void +cmd_enable_spellcheck() { + g_object_set (G_OBJECT(view_settings()), "enable-spell-checking", + uzbl.behave.enable_spellcheck, NULL); +} + +void +cmd_enable_private() { + g_object_set (G_OBJECT(view_settings()), "enable-private-browsing", + uzbl.behave.enable_private, NULL); +} + +void +cmd_print_bg() { + g_object_set (G_OBJECT(view_settings()), "print-backgrounds", + uzbl.behave.print_bg, NULL); +} + +void +cmd_style_uri() { + g_object_set (G_OBJECT(view_settings()), "user-stylesheet-uri", + uzbl.behave.style_uri, NULL); +} + +void +cmd_resizable_txt() { + g_object_set (G_OBJECT(view_settings()), "resizable-text-areas", + uzbl.behave.resizable_txt, NULL); +} + +void +cmd_default_encoding() { + g_object_set (G_OBJECT(view_settings()), "default-encoding", + uzbl.behave.default_encoding, NULL); +} + +void +cmd_enforce_96dpi() { + g_object_set (G_OBJECT(view_settings()), "enforce-96-dpi", + uzbl.behave.enforce_96dpi, NULL); +} + +void +cmd_caret_browsing() { + g_object_set (G_OBJECT(view_settings()), "enable-caret-browsing", + uzbl.behave.caret_browsing, NULL); +} + +void +cmd_fifo_dir() { + uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir); +} + +void +cmd_socket_dir() { + uzbl.behave.socket_dir = init_socket(uzbl.behave.socket_dir); +} + +void +cmd_inject_html() { + if(uzbl.behave.inject_html) { + webkit_web_view_load_html_string (uzbl.gui.web_view, + uzbl.behave.inject_html, NULL); + } +} + +void +cmd_useragent() { + if (*uzbl.net.useragent == ' ') { + g_free (uzbl.net.useragent); + uzbl.net.useragent = NULL; + } else { + g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, + uzbl.net.useragent, NULL); + } +} + +/* requires webkit >=1.1.14 */ +void +cmd_view_source() { + webkit_web_view_set_view_source_mode(uzbl.gui.web_view, + (gboolean) uzbl.behave.view_source); +} + +void +cmd_set_zoom_type () { + if(uzbl.behave.zoom_type) + webkit_web_view_set_full_content_zoom (uzbl.gui.web_view, TRUE); + else + webkit_web_view_set_full_content_zoom (uzbl.gui.web_view, FALSE); +} + +void +toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result) { + (void)argv; + (void)result; + + webkit_web_view_set_full_content_zoom (page, !webkit_web_view_get_full_content_zoom (page)); +} + +void +toggle_status_cb (WebKitWebView* page, GArray *argv, GString *result) { + (void)page; + (void)argv; + (void)result; + + if (uzbl.behave.show_status) { + gtk_widget_hide(uzbl.gui.mainbar); + } else { + gtk_widget_show(uzbl.gui.mainbar); + } + uzbl.behave.show_status = !uzbl.behave.show_status; + update_title(); +} + +void +link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) { + (void) page; + (void) title; + (void) data; + State *s = &uzbl.state; + + if(s->selected_url) { + if(s->last_selected_url) + g_free(s->last_selected_url); + s->last_selected_url = g_strdup(s->selected_url); + } + else { + if(s->last_selected_url) g_free(s->last_selected_url); + s->last_selected_url = NULL; + } + + g_free(s->selected_url); + s->selected_url = NULL; + + if (link) { + s->selected_url = g_strdup(link); + + if(s->last_selected_url && + g_strcmp0(s->selected_url, s->last_selected_url)) + send_event(LINK_UNHOVER, s->last_selected_url, NULL); + + send_event(LINK_HOVER, s->selected_url, NULL); + } + else if(s->last_selected_url) { + send_event(LINK_UNHOVER, s->last_selected_url, NULL); + } + + update_title(); +} + +void +title_change_cb (WebKitWebView* web_view, GParamSpec param_spec) { + (void) web_view; + (void) param_spec; + const gchar *title = webkit_web_view_get_title(web_view); + if (uzbl.gui.main_title) + g_free (uzbl.gui.main_title); + uzbl.gui.main_title = title ? g_strdup (title) : g_strdup ("(no title)"); + update_title(); + send_event(TITLE_CHANGED, uzbl.gui.main_title, NULL); +} + +void +progress_change_cb (WebKitWebView* page, gint progress, gpointer data) { + (void) page; + (void) data; + gchar *prg_str; + + prg_str = itos(progress); + send_event(LOAD_PROGRESS, prg_str, NULL); + g_free(prg_str); +} + +void +selection_changed_cb(WebKitWebView *webkitwebview, gpointer ud) { + (void)ud; + gchar *tmp; + + webkit_web_view_copy_clipboard(webkitwebview); + tmp = gtk_clipboard_wait_for_text(gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)); + send_event(SELECTION_CHANGED, tmp, NULL); + g_free(tmp); +} + +void +load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { + (void) page; + (void) data; + + send_event(LOAD_FINISH, webkit_web_frame_get_uri(frame), NULL); +} + +void +load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { + (void) page; + (void) frame; + (void) data; + + send_event(LOAD_START, uzbl.state.uri, NULL); +} + +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; + gchar *details; + + details = g_strdup_printf("%s %d:%s", uri, err->code, err->message); + send_event(LOAD_ERROR, details, NULL); + g_free(details); +} + +void +load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { + (void) page; + (void) data; + g_free (uzbl.state.uri); + GString* newuri = g_string_new (webkit_web_frame_get_uri (frame)); + uzbl.state.uri = g_string_free (newuri, FALSE); + + send_event(LOAD_COMMIT, webkit_web_frame_get_uri (frame), NULL); +} + +void +destroy_cb (GtkWidget* widget, gpointer data) { + (void) widget; + (void) data; + gtk_main_quit (); +} + +gboolean +configure_event_cb(GtkWidget* window, GdkEventConfigure* event) { + (void) window; + (void) event; + gchar *lastgeo = NULL; + + lastgeo = g_strdup(uzbl.gui.geometry); + retrieve_geometry(); + + if(strcmp(lastgeo, uzbl.gui.geometry)) + send_event(GEOMETRY_CHANGED, uzbl.gui.geometry, NULL); + g_free(lastgeo); + + return FALSE; +} + +gboolean +focus_cb(GtkWidget* window, GdkEventFocus* event, void *ud) { + (void) window; + (void) event; + (void) ud; + + if(event->in) + send_event(FOCUS_GAINED, "", NULL); + else + send_event(FOCUS_LOST, "", NULL); + + return FALSE; +} + +gboolean +key_press_cb (GtkWidget* window, GdkEventKey* event) { + (void) window; + + if(event->type == GDK_KEY_PRESS) + key_to_event(event->keyval, GDK_KEY_PRESS); + + return uzbl.behave.forward_keys ? FALSE : TRUE; +} + +gboolean +key_release_cb (GtkWidget* window, GdkEventKey* event) { + (void) window; + + if(event->type == GDK_KEY_RELEASE) + key_to_event(event->keyval, GDK_KEY_RELEASE); + + return uzbl.behave.forward_keys ? FALSE : TRUE; +} + +gboolean +button_press_cb (GtkWidget* window, GdkEventButton* event) { + (void) window; + gint context; + gchar *details; + gboolean propagate = FALSE, + sendev = FALSE; + + if(event->type == GDK_BUTTON_PRESS) { + if(uzbl.state.last_button) + gdk_event_free((GdkEvent *)uzbl.state.last_button); + uzbl.state.last_button = (GdkEventButton *)gdk_event_copy((GdkEvent *)event); + + context = get_click_context(NULL); + /* left click */ + if(event->button == 1) { + if((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) + send_event(FORM_ACTIVE, "button1", NULL); + else if((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)) + send_event(ROOT_ACTIVE, "button1", NULL); + } + else if(event->button == 2 && !(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) { + sendev = TRUE; + propagate = TRUE; + } + else if(event->button > 3) { + sendev = TRUE; + propagate = TRUE; + } + + if(sendev) { + details = g_strdup_printf("Button%d", event->button); + send_event(KEY_PRESS, details, NULL); + g_free(details); + } + } + + return propagate; +} + +gboolean +button_release_cb (GtkWidget* window, GdkEventButton* event) { + (void) window; + gint context; + gchar *details; + gboolean propagate = FALSE, + sendev = FALSE; + + context = get_click_context(NULL); + if(event->type == GDK_BUTTON_RELEASE) { + if(event->button == 2 && !(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) { + sendev = TRUE; + propagate = TRUE; + } + else if(event->button > 3) { + sendev = TRUE; + propagate = TRUE; + } + + if(sendev) { + details = g_strdup_printf("Button%d", event->button); + send_event(KEY_RELEASE, details, NULL); + g_free(details); + } + } + + return propagate; +} + +gboolean +navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { + (void) web_view; + (void) frame; + (void) navigation_action; + (void) user_data; + + const gchar* uri = webkit_network_request_get_uri (request); + gboolean decision_made = FALSE; + + if (uzbl.state.verbose) + printf("Navigation requested -> %s\n", uri); + + if (uzbl.behave.scheme_handler) { + GString *s = g_string_new (""); + g_string_printf(s, "'%s'", uri); + + run_handler(uzbl.behave.scheme_handler, s->str); + + if(uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) { + char *p = strchr(uzbl.comm.sync_stdout, '\n' ); + if ( p != NULL ) *p = '\0'; + if (!strcmp(uzbl.comm.sync_stdout, "USED")) { + webkit_web_policy_decision_ignore(policy_decision); + decision_made = TRUE; + } + } + if (uzbl.comm.sync_stdout) + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + g_string_free(s, TRUE); + } + if (!decision_made) + webkit_web_policy_decision_use(policy_decision); + + return TRUE; +} + +gboolean +new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { + (void) web_view; + (void) frame; + (void) navigation_action; + (void) policy_decision; + (void) user_data; + const gchar* uri = webkit_network_request_get_uri (request); + if (uzbl.state.verbose) + printf("New window requested -> %s \n", uri); + webkit_web_policy_decision_use(policy_decision); + send_event(NEW_WINDOW, uri, NULL); + return TRUE; +} + +gboolean +mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, gchar *mime_type, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { + (void) frame; + (void) request; + (void) user_data; + + /* If we can display it, let's display it... */ + if (webkit_web_view_can_show_mime_type (web_view, mime_type)) { + webkit_web_policy_decision_use (policy_decision); + return TRUE; + } + + /* ...everything we can't display is downloaded */ + webkit_web_policy_decision_download (policy_decision); + return TRUE; +} + +/*@null@*/ WebKitWebView* +create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) { + (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); + new_window_load_uri(uzbl.state.selected_url); + } else { + if (uzbl.state.verbose) + printf("New web view -> %s\n","Nothing to open, exiting"); + } + return (NULL); +} + +gboolean +download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) { + (void) web_view; + (void) user_data; + if (uzbl.behave.download_handler) { + const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download); + if (uzbl.state.verbose) + printf("Download -> %s\n",uri); + + /* if urls not escaped, we may have to escape and quote uri before this call */ + GString *args = g_string_new(uri); + + if (uzbl.net.proxy_url) { + g_string_append_c(args, ' '); + g_string_append(args, uzbl.net.proxy_url); + } + + run_handler(uzbl.behave.download_handler, args->str); + + g_string_free(args, TRUE); + } + send_event(DOWNLOAD_REQ, webkit_download_get_uri ((WebKitDownload*)download), NULL); + return (FALSE); +} + +void +run_menu_command(GtkWidget *menu, const char *line) { + (void) menu; + + parse_cmd_line(line, NULL); +} + + +void +populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) { + (void) v; + (void) c; + GUI *g = &uzbl.gui; + GtkWidget *item; + MenuItem *mi; + guint i=0; + gint context, hit=0; + + if(!g->menu_items) + return; + + /* check context */ + 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_DOCUMENT) && + (context & mi->context)) { + if(mi->issep) { + item = gtk_separator_menu_item_new(); + gtk_menu_append(GTK_MENU(m), item); + gtk_widget_show(item); + } + else { + item = gtk_menu_item_new_with_label(mi->name); + g_signal_connect(item, "activate", + G_CALLBACK(run_menu_command), mi->cmd); + gtk_menu_append(GTK_MENU(m), item); + gtk_widget_show(item); + } + hit++; + } + + if((mi->context == WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) && + (context <= WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) && + !hit) { + if(mi->issep) { + item = gtk_separator_menu_item_new(); + gtk_menu_append(GTK_MENU(m), item); + gtk_widget_show(item); + } + else { + item = gtk_menu_item_new_with_label(mi->name); + g_signal_connect(item, "activate", + G_CALLBACK(run_menu_command), mi->cmd); + gtk_menu_append(GTK_MENU(m), item); + gtk_widget_show(item); + } + } + } +} + diff --git a/src/callbacks.h b/src/callbacks.h new file mode 100644 index 0000000..3f318f2 --- /dev/null +++ b/src/callbacks.h @@ -0,0 +1,197 @@ +/* + ** Callbacks + ** (c) 2009 by Robert Manea et al. +*/ + +void +cmd_load_uri(); + +void +cmd_set_status(); + +void +set_proxy_url(); + +void +set_icon(); + +void +move_statusbar(); + +void +cmd_http_debug(); + +void +cmd_max_conns(); + +void +cmd_max_conns_host(); + +/* exported WebKitWebSettings properties */ +void +cmd_font_size(); + +void +cmd_default_font_family(); + +void +cmd_monospace_font_family(); + +void +cmd_sans_serif_font_family(); + +void +cmd_serif_font_family(); + +void +cmd_cursive_font_family(); + +void +cmd_fantasy_font_family(); + +void +cmd_zoom_level(); + +void +cmd_set_zoom_type(); + +void +cmd_disable_plugins(); + +void +cmd_disable_scripts(); + +void +cmd_minimum_font_size(); + +void +cmd_fifo_dir(); + +void +cmd_socket_dir(); + +void +cmd_useragent() ; + +void +cmd_autoload_img(); + +void +cmd_autoshrink_img(); + +void +cmd_enable_spellcheck(); + +void +cmd_enable_private(); + +void +cmd_print_bg(); + +void +cmd_style_uri(); + +void +cmd_resizable_txt(); + +void +cmd_default_encoding(); + +void +cmd_enforce_96dpi(); + +void +cmd_inject_html(); + +void +cmd_caret_browsing(); + +void +cmd_set_geometry(); + +void +cmd_view_source(); + +void +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); + +void +toggle_status_cb (WebKitWebView* page, GArray *argv, GString *result); + +void +link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data); + +void +title_change_cb (WebKitWebView* web_view, GParamSpec param_spec); + +void +progress_change_cb (WebKitWebView* page, gint progress, gpointer data); + +void +load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); + +void +load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); + +void +load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); + +void +load_error_cb (WebKitWebView* page, WebKitWebFrame* frame, gchar *uri, gpointer web_err, gpointer ud); + +void +selection_changed_cb(WebKitWebView *webkitwebview, gpointer ud); + +void +destroy_cb (GtkWidget* widget, gpointer data); + +gboolean +configure_event_cb(GtkWidget* window, GdkEventConfigure* event); + +gboolean +key_press_cb (GtkWidget* window, GdkEventKey* event); + +gboolean +key_release_cb (GtkWidget* window, GdkEventKey* event); + +gboolean +navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, + WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision, gpointer user_data); + +gboolean +new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, + WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision, gpointer user_data); + +gboolean +mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, + gchar *mime_type, WebKitWebPolicyDecision *policy_decision, gpointer user_data); + +/*@null@*/ WebKitWebView* +create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data); + +gboolean +download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data); + +void +populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c); + +gboolean +button_press_cb (GtkWidget* window, GdkEventButton* event); + +gboolean +button_release_cb (GtkWidget* window, GdkEventButton* event); + +gboolean +focus_cb(GtkWidget* window, GdkEventFocus* event, void *ud); + diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..e9b9a8e --- /dev/null +++ b/src/config.h @@ -0,0 +1,10 @@ +const struct { + /*@null@*/ char *command; +} default_config[] = { +{ "set status_format = \\@[\\@keycmd]\\@ \\@[\\@TITLE]\\@ - Uzbl browser"}, +{ "set title_format_long = \\@keycmd \\@TITLE - Uzbl browser <\\@NAME> > \\@SELECTED_URI"}, +{ "set title_format_short = \\@TITLE - Uzbl browser <\\@NAME>"}, +{ "set max_conns = 100"}, /* WebkitGTK default: 10 */ +{ "set max_conns_host = 6"}, /* WebkitGTK default: 2 */ +{ NULL } +}; diff --git a/src/events.c b/src/events.c new file mode 100644 index 0000000..acb554c --- /dev/null +++ b/src/events.c @@ -0,0 +1,206 @@ +/* + ** Uzbl event routines + ** (c) 2009 by Robert Manea +*/ + +#include "uzbl-core.h" +#include "events.h" + +UzblCore uzbl; + +/* Event id to name mapping + * Event names must be in the same + * order as in 'enum event_type' + * + * TODO: Add more useful events +*/ +const char *event_table[LAST_EVENT] = { + "LOAD_START" , + "LOAD_COMMIT" , + "LOAD_FINISH" , + "LOAD_ERROR" , + "KEY_PRESS" , + "KEY_RELEASE" , + "DOWNLOAD_REQUEST" , + "COMMAND_EXECUTED" , + "LINK_HOVER" , + "TITLE_CHANGED" , + "GEOMETRY_CHANGED" , + "WEBINSPECTOR" , + "NEW_WINDOW" , + "SELECTION_CHANGED", + "VARIABLE_SET" , + "FIFO_SET" , + "SOCKET_SET" , + "INSTANCE_START" , + "INSTANCE_EXIT" , + "LOAD_PROGRESS" , + "LINK_UNHOVER" , + "FORM_ACTIVE" , + "ROOT_ACTIVE" , + "FOCUS_LOST" , + "FOCUS_GAINED" , + "FILE_INCLUDED" , + "PLUG_CREATED" , + "COMMAND_ERROR" , + "BUILTINS" +}; + +void +event_buffer_timeout(guint sec) { + struct itimerval t; + memset(&t, 0, sizeof t); + t.it_value.tv_sec = sec; + t.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &t, NULL); +} + + +void +send_event_socket(GString *msg) { + GError *error = NULL; + GString *tmp; + GIOChannel *gio = NULL; + GIOStatus ret; + gsize len; + guint i=0, j=0; + + /* write to all --connect-socket sockets */ + if(uzbl.comm.connect_chan) { + while(i < uzbl.comm.connect_chan->len) { + gio = g_ptr_array_index(uzbl.comm.connect_chan, i++); + j=0; + + if(gio && gio->is_writeable) { + if(uzbl.state.event_buffer) { + event_buffer_timeout(0); + + /* replay buffered events */ + while(j < uzbl.state.event_buffer->len) { + tmp = g_ptr_array_index(uzbl.state.event_buffer, j++); + ret = g_io_channel_write_chars (gio, + tmp->str, tmp->len, + &len, &error); + + if (ret == G_IO_STATUS_ERROR) + g_warning ("Error sending event to socket: %s", error->message); + else + g_io_channel_flush(gio, &error); + } + } + + if(msg) { + ret = g_io_channel_write_chars (gio, + msg->str, msg->len, + &len, &error); + + if (ret == G_IO_STATUS_ERROR) + g_warning ("Error sending event to socket: %s", error->message); + else + g_io_channel_flush(gio, &error); + } + } + } + if(uzbl.state.event_buffer) { + g_ptr_array_free(uzbl.state.event_buffer, TRUE); + uzbl.state.event_buffer = NULL; + } + } + /* buffer events until a socket is set and connected + * or a timeout is encountered + */ + else { + if(!uzbl.state.event_buffer) + uzbl.state.event_buffer = g_ptr_array_new(); + g_ptr_array_add(uzbl.state.event_buffer, (gpointer)g_string_new(msg->str)); + } + + /* write to all client sockets */ + i=0; + if(msg && uzbl.comm.client_chan) { + while(i < uzbl.comm.client_chan->len) { + gio = g_ptr_array_index(uzbl.comm.client_chan, i++); + + if(gio && gio->is_writeable && msg) { + ret = g_io_channel_write_chars (gio, + msg->str, msg->len, + &len, &error); + + if (ret == G_IO_STATUS_ERROR) + g_warning ("Error sending event to socket: %s", error->message); + else + g_io_channel_flush(gio, &error); + } + } + } +} + +void +send_event_stdout(GString *msg) { + printf("%s", msg->str); + fflush(stdout); +} + +/* + * build event string and send over the supported interfaces + * custom_event == NULL indicates an internal event +*/ +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); + } + /* 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); + } + + if(event_message->str) { + if(uzbl.state.events_stdout) + send_event_stdout(event_message); + send_event_socket(event_message); + + g_string_free(event_message, TRUE); + } + g_free(p_val); +} + +/* Transform gdk key events to our own events */ +void +key_to_event(guint keyval, gint mode) { + gchar ucs[7]; + gint ulen; + guint32 ukval = gdk_keyval_to_unicode(keyval); + + /* check for printable unicode char */ + /* TODO: Pass the keyvals through a GtkIMContext so that + * we also get combining chars right + */ + if(g_unichar_isgraph(ukval)) { + ulen = g_unichar_to_utf8(ukval, ucs); + ucs[ulen] = 0; + + send_event(mode == GDK_KEY_PRESS ? KEY_PRESS : KEY_RELEASE, + ucs, NULL); + } + /* send keysym for non-printable chars */ + else { + send_event(mode == GDK_KEY_PRESS ? KEY_PRESS : KEY_RELEASE, + gdk_keyval_name(keyval), NULL); + } + +} + diff --git a/src/events.h b/src/events.h new file mode 100644 index 0000000..7b8f58b --- /dev/null +++ b/src/events.h @@ -0,0 +1,35 @@ +/* + ** Uzbl event routines + ** (c) 2009 by Robert Manea +*/ + +/* Event system */ +enum event_type { + LOAD_START, LOAD_COMMIT, LOAD_FINISH, LOAD_ERROR, + KEY_PRESS, KEY_RELEASE, DOWNLOAD_REQ, COMMAND_EXECUTED, + LINK_HOVER, TITLE_CHANGED, GEOMETRY_CHANGED, + WEBINSPECTOR, NEW_WINDOW, SELECTION_CHANGED, + VARIABLE_SET, FIFO_SET, SOCKET_SET, + INSTANCE_START, INSTANCE_EXIT, LOAD_PROGRESS, + LINK_UNHOVER, FORM_ACTIVE, ROOT_ACTIVE, + FOCUS_LOST, FOCUS_GAINED, FILE_INCLUDED, + PLUG_CREATED, COMMAND_ERROR, BUILTINS, + + /* must be last entry */ + LAST_EVENT +}; + +void +event_buffer_timeout(guint sec); + +void +send_event_socket(GString *msg); + +void +send_event_stdout(GString *msg); + +void +send_event(int type, const gchar *details, const gchar *custom_event); + +void +key_to_event(guint keyval, gint mode); diff --git a/src/inspector.c b/src/inspector.c new file mode 100644 index 0000000..de3dbcd --- /dev/null +++ b/src/inspector.c @@ -0,0 +1,103 @@ +/* + ** WebInspector + ** (c) 2009 by Robert Manea +*/ + +#include "uzbl-core.h" +#include "events.h" +#include "callbacks.h" + + +void +hide_window_cb(GtkWidget *widget, gpointer data) { + (void) data; + + gtk_widget_hide(widget); +} + +WebKitWebView* +create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){ + (void) data; + (void) page; + (void) web_inspector; + GtkWidget* scrolled_window; + GtkWidget* new_web_view; + GUI *g = &uzbl.gui; + + g->inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(G_OBJECT(g->inspector_window), "delete-event", + G_CALLBACK(hide_window_cb), NULL); + + gtk_window_set_title(GTK_WINDOW(g->inspector_window), "Uzbl WebInspector"); + gtk_window_set_default_size(GTK_WINDOW(g->inspector_window), 400, 300); + gtk_widget_show(g->inspector_window); + + scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(g->inspector_window), scrolled_window); + gtk_widget_show(scrolled_window); + + new_web_view = webkit_web_view_new(); + gtk_container_add(GTK_CONTAINER(scrolled_window), new_web_view); + + return WEBKIT_WEB_VIEW(new_web_view); +} + +gboolean +inspector_show_window_cb (WebKitWebInspector* inspector){ + (void) inspector; + gtk_widget_show(uzbl.gui.inspector_window); + + send_event(WEBINSPECTOR, "open", NULL); + return TRUE; +} + +/* TODO: Add variables and code to make use of these functions */ +gboolean +inspector_close_window_cb (WebKitWebInspector* inspector){ + (void) inspector; + send_event(WEBINSPECTOR, "close", NULL); + return TRUE; +} + +gboolean +inspector_attach_window_cb (WebKitWebInspector* inspector){ + (void) inspector; + return FALSE; +} + +gboolean +inspector_detach_window_cb (WebKitWebInspector* inspector){ + (void) inspector; + return FALSE; +} + +gboolean +inspector_uri_changed_cb (WebKitWebInspector* inspector){ + (void) inspector; + return FALSE; +} + +gboolean +inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){ + (void) inspector; + return FALSE; +} + +void +set_up_inspector() { + GUI *g = &uzbl.gui; + WebKitWebSettings *settings = view_settings(); + g_object_set(G_OBJECT(settings), "enable-developer-extras", TRUE, NULL); + + uzbl.gui.inspector = webkit_web_view_get_inspector(uzbl.gui.web_view); + g_signal_connect (G_OBJECT (g->inspector), "inspect-web-view", G_CALLBACK (create_inspector_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "show-window", G_CALLBACK (inspector_show_window_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "close-window", G_CALLBACK (inspector_close_window_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "attach-window", G_CALLBACK (inspector_attach_window_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "detach-window", G_CALLBACK (inspector_detach_window_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "finished", G_CALLBACK (inspector_inspector_destroyed_cb), NULL); + + g_signal_connect (G_OBJECT (g->inspector), "notify::inspected-uri", G_CALLBACK (inspector_uri_changed_cb), NULL); +} diff --git a/src/inspector.h b/src/inspector.h new file mode 100644 index 0000000..57d0ca9 --- /dev/null +++ b/src/inspector.h @@ -0,0 +1,7 @@ +/* + ** WebInspector + ** (c) 2009 by Robert Manea +*/ + +void +set_up_inspector(); diff --git a/src/uzbl-browser b/src/uzbl-browser new file mode 100755 index 0000000..d9b9752 --- /dev/null +++ b/src/uzbl-browser @@ -0,0 +1,66 @@ +#!/bin/sh +# this script implements a more useful out-of-the-box "browsing experience". +# it does so by combining uzbl-core with a set of "recommended" tools and practices. +# see docs for more info +# If you want to customize the behavior of the cookie-daemon or similar helper tools, +# copy them to your $XDG_DATA_HOME/uzbl/scripts/, edit them and update $PATH + +# Also, we assume existence of fifo/socket == correctly functioning cookie_daemon/event_manager. +# Checking correct functioning of the daemons here would be too complex here, and it's not implemented in uzbl-core either. +# But this shouldn't cause much problems.. + +PREFIX=/usr/local +if [ -z "$XDG_DATA_HOME" ] +then + export XDG_DATA_HOME=$HOME/.local/share +fi + +if [ -z "$XDG_CACHE_HOME" ] +then + export XDG_CACHE_HOME=$HOME/.cache +fi + +if [ -z "$XDG_CONFIG_HOME" ] +then + export XDG_CONFIG_HOME=$HOME/.config +fi + +# assure the relevant directories exist. +for dir in $XDG_CACHE_HOME/uzbl $XDG_DATA_HOME/uzbl $XDG_CONFIG_HOME/uzbl +do + if [ ! -d $dir ] + then + if ! mkdir -p $dir + then + echo "could not create $dir" >&2 + exit 2 + fi + fi +done +# if no config exists yet in the recommended location, put the default (recommended) config there +if [ ! -f $XDG_CONFIG_HOME/uzbl/config ] +then + if ! cp $PREFIX/share/uzbl/examples/config/uzbl/config $XDG_CONFIG_HOME/uzbl/config + then + echo "Could not copy default config to $XDG_CONFIG_HOME/uzbl/config" >&2 + exit 3 + fi +fi + +# Uncomment this for a slight speedup at the expense of not having +# stale cookie daemon sockets cleaned up. +#if [ ! -S $XDG_CACHE_HOME/uzbl/cookie_daemon_socket ] +#then + # if you want to customize it, copy to your $XDG_DATA_HOME/uzbl/scripts/ and update $PATH + uzbl-cookie-daemon -v start +#fi + +DAEMON_SOCKET=$XDG_CACHE_HOME/uzbl/event_daemon +DAEMON_PID=${DAEMON_SOCKET}.pid + +#if [ -f "$DAEMON_PID" ] +#then + uzbl-event-manager -va start +#fi + +uzbl-core "$@" --connect-socket $DAEMON_SOCKET diff --git a/src/uzbl-core.c b/src/uzbl-core.c new file mode 100644 index 0000000..bc294b4 --- /dev/null +++ b/src/uzbl-core.c @@ -0,0 +1,2637 @@ +/* -*- c-basic-offset: 4; -*- */ +// Original code taken from the example webkit-gtk+ application. see notice below. +// Modified code is licensed under the GPL 3. See LICENSE file. + + +/* + * Copyright (C) 2006, 2007 Apple Inc. + * Copyright (C) 2007 Alp Toker + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "uzbl-core.h" +#include "callbacks.h" +#include "events.h" +#include "inspector.h" +#include "config.h" + +UzblCore uzbl; + +/* commandline arguments (set initial values for the state variables) */ +const +GOptionEntry entries[] = +{ + { "uri", 'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri, + "Uri to load at startup (equivalent to 'uzbl ' or 'set uri = URI' after uzbl has launched)", "URI" }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &uzbl.state.verbose, + "Whether to print all messages or just errors.", NULL }, + { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, + "Name of the current instance (defaults to Xorg window id or random for GtkSocket mode)", "NAME" }, + { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, + "Path to config file or '-' for stdin", "FILE" }, + { "socket", 's', 0, G_OPTION_ARG_INT, &uzbl.state.socket_id, + "Socket ID", "SOCKET" }, + { "connect-socket", 0, 0, G_OPTION_ARG_STRING_ARRAY, &uzbl.state.connect_socket_names, + "Connect to server socket", "CSOCKET" }, + { "print-events", 'p', 0, G_OPTION_ARG_NONE, &uzbl.state.events_stdout, + "Whether to print events to stdout.", NULL }, + { "geometry", 'g', 0, G_OPTION_ARG_STRING, &uzbl.gui.geometry, + "Set window geometry (format: WIDTHxHEIGHT+-X+-Y or maximized)", "GEOMETRY" }, + { "version", 'V', 0, G_OPTION_ARG_NONE, &uzbl.behave.print_version, + "Print the version and exit", NULL }, + { NULL, 0, 0, 0, NULL, NULL, NULL } +}; + +XDG_Var XDG[] = +{ + { "XDG_CONFIG_HOME", "~/.config" }, + { "XDG_DATA_HOME", "~/.local/share" }, + { "XDG_CACHE_HOME", "~/.cache" }, + { "XDG_CONFIG_DIRS", "/etc/xdg" }, + { "XDG_DATA_DIRS", "/usr/local/share/:/usr/share/" }, +}; + +/* abbreviations to help keep the table's width humane */ +#define PTR_V_STR(var, d, fun) { .ptr.s = &(var), .type = TYPE_STR, .dump = d, .writeable = 1, .func = fun } +#define PTR_V_INT(var, d, fun) { .ptr.i = (int*)&(var), .type = TYPE_INT, .dump = d, .writeable = 1, .func = fun } +#define PTR_V_FLOAT(var, d, fun) { .ptr.f = &(var), .type = TYPE_FLOAT, .dump = d, .writeable = 1, .func = fun } +#define PTR_C_STR(var, fun) { .ptr.s = &(var), .type = TYPE_STR, .dump = 0, .writeable = 0, .func = fun } +#define PTR_C_INT(var, fun) { .ptr.i = (int*)&(var), .type = TYPE_INT, .dump = 0, .writeable = 0, .func = fun } +#define PTR_C_FLOAT(var, fun) { .ptr.f = &(var), .type = TYPE_FLOAT, .dump = 0, .writeable = 0, .func = fun } + +const struct var_name_to_ptr_t { + const char *name; + uzbl_cmdprop cp; +} var_name_to_ptr[] = { +/* variable name pointer to variable in code dump callback function */ +/* ---------------------------------------------------------------------------------------------- */ + { "uri", PTR_V_STR(uzbl.state.uri, 1, cmd_load_uri)}, + { "verbose", PTR_V_INT(uzbl.state.verbose, 1, NULL)}, + { "print_events", PTR_V_INT(uzbl.state.events_stdout, 1, NULL)}, + { "inject_html", PTR_V_STR(uzbl.behave.inject_html, 0, cmd_inject_html)}, + { "geometry", PTR_V_STR(uzbl.gui.geometry, 1, cmd_set_geometry)}, + { "keycmd", PTR_V_STR(uzbl.state.keycmd, 1, NULL)}, + { "show_status", PTR_V_INT(uzbl.behave.show_status, 1, cmd_set_status)}, + { "status_top", PTR_V_INT(uzbl.behave.status_top, 1, move_statusbar)}, + { "status_format", PTR_V_STR(uzbl.behave.status_format, 1, NULL)}, + { "status_background", PTR_V_STR(uzbl.behave.status_background, 1, NULL)}, + { "title_format_long", PTR_V_STR(uzbl.behave.title_format_long, 1, NULL)}, + { "title_format_short", PTR_V_STR(uzbl.behave.title_format_short, 1, NULL)}, + { "icon", PTR_V_STR(uzbl.gui.icon, 1, set_icon)}, + { "forward_keys", PTR_V_INT(uzbl.behave.forward_keys, 1, NULL)}, + { "download_handler", PTR_V_STR(uzbl.behave.download_handler, 1, NULL)}, + { "cookie_handler", PTR_V_STR(uzbl.behave.cookie_handler, 1, NULL)}, + { "new_window", PTR_V_STR(uzbl.behave.new_window, 1, NULL)}, + { "scheme_handler", PTR_V_STR(uzbl.behave.scheme_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)}, + { "shell_cmd", PTR_V_STR(uzbl.behave.shell_cmd, 1, NULL)}, + { "proxy_url", PTR_V_STR(uzbl.net.proxy_url, 1, set_proxy_url)}, + { "max_conns", PTR_V_INT(uzbl.net.max_conns, 1, cmd_max_conns)}, + { "max_conns_host", PTR_V_INT(uzbl.net.max_conns_host, 1, cmd_max_conns_host)}, + { "useragent", PTR_V_STR(uzbl.net.useragent, 1, cmd_useragent)}, + /* requires webkit >=1.1.14 */ + { "view_source", PTR_V_INT(uzbl.behave.view_source, 0, cmd_view_source)}, + + /* exported WebKitWebSettings properties */ + { "zoom_level", PTR_V_FLOAT(uzbl.behave.zoom_level, 1, cmd_zoom_level)}, + { "zoom_type", PTR_V_INT(uzbl.behave.zoom_type, 1, cmd_set_zoom_type)}, + { "font_size", PTR_V_INT(uzbl.behave.font_size, 1, cmd_font_size)}, + { "default_font_family", PTR_V_STR(uzbl.behave.default_font_family, 1, cmd_default_font_family)}, + { "monospace_font_family", PTR_V_STR(uzbl.behave.monospace_font_family, 1, cmd_monospace_font_family)}, + { "cursive_font_family", PTR_V_STR(uzbl.behave.cursive_font_family, 1, cmd_cursive_font_family)}, + { "sans_serif_font_family", PTR_V_STR(uzbl.behave.sans_serif_font_family, 1, cmd_sans_serif_font_family)}, + { "serif_font_family", PTR_V_STR(uzbl.behave.serif_font_family, 1, cmd_serif_font_family)}, + { "fantasy_font_family", PTR_V_STR(uzbl.behave.fantasy_font_family, 1, cmd_fantasy_font_family)}, + { "monospace_size", PTR_V_INT(uzbl.behave.monospace_size, 1, cmd_font_size)}, + { "minimum_font_size", PTR_V_INT(uzbl.behave.minimum_font_size, 1, cmd_minimum_font_size)}, + { "disable_plugins", PTR_V_INT(uzbl.behave.disable_plugins, 1, cmd_disable_plugins)}, + { "disable_scripts", PTR_V_INT(uzbl.behave.disable_scripts, 1, cmd_disable_scripts)}, + { "autoload_images", PTR_V_INT(uzbl.behave.autoload_img, 1, cmd_autoload_img)}, + { "autoshrink_images", PTR_V_INT(uzbl.behave.autoshrink_img, 1, cmd_autoshrink_img)}, + { "enable_spellcheck", PTR_V_INT(uzbl.behave.enable_spellcheck, 1, cmd_enable_spellcheck)}, + { "enable_private", PTR_V_INT(uzbl.behave.enable_private, 1, cmd_enable_private)}, + { "print_backgrounds", PTR_V_INT(uzbl.behave.print_bg, 1, cmd_print_bg)}, + { "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)}, + { "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)}, + + /* constants (not dumpable or writeable) */ + { "WEBKIT_MAJOR", PTR_C_INT(uzbl.info.webkit_major, NULL)}, + { "WEBKIT_MINOR", PTR_C_INT(uzbl.info.webkit_minor, NULL)}, + { "WEBKIT_MICRO", PTR_C_INT(uzbl.info.webkit_micro, NULL)}, + { "ARCH_UZBL", PTR_C_STR(uzbl.info.arch, NULL)}, + { "COMMIT", PTR_C_STR(uzbl.info.commit, NULL)}, + { "TITLE", PTR_C_STR(uzbl.gui.main_title, NULL)}, + { "SELECTED_URI", PTR_C_STR(uzbl.state.selected_url, NULL)}, + { "NAME", PTR_C_STR(uzbl.state.instance_name, NULL)}, + { "PID", PTR_C_STR(uzbl.info.pid_str, NULL)}, + + { NULL, {.ptr.s = NULL, .type = TYPE_INT, .dump = 0, .writeable = 0, .func = NULL}} +}; + +/* construct a hash from the var_name_to_ptr array for quick access */ +void +create_var_to_name_hash() { + const struct var_name_to_ptr_t *n2v_p = var_name_to_ptr; + uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal); + while(n2v_p->name) { + g_hash_table_insert(uzbl.comm.proto_var, + (gpointer) n2v_p->name, + (gpointer) &n2v_p->cp); + n2v_p++; + } +} + + +/* --- 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) { + /* 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; +} + +/* + * recurse == 1: don't expand '@(command)@' + * recurse == 2: don't expand '@@' +*/ +gchar * +expand(const char *s, guint recurse) { + uzbl_cmdprop *c; + enum exp_type etype; + char *end_simple_var = "^°!\"§$%&/()=?'`'+~*'#-.:,;@<>| \\{}[]¹²³¼½"; + char *ret = NULL; + char *vend = NULL; + GError *err = NULL; + gchar *cmd_stdout = NULL; + gchar *mycmd = NULL; + GString *buf = g_string_new(""); + GString *js_ret = g_string_new(""); + + while(s && *s) { + switch(*s) { + case '\\': + g_string_append_c(buf, *++s); + s++; + break; + + case '@': + etype = get_exp_type(s); + s++; + + switch(etype) { + case EXP_SIMPLE_VAR: + vend = strpbrk(s, end_simple_var); + if(!vend) vend = strchr(s, '\0'); + break; + case EXP_BRACED_VAR: + s++; + vend = strchr(s, '}'); + if(!vend) vend = strchr(s, '\0'); + break; + case EXP_EXPR: + s++; + vend = strstr(s, ")@"); + if(!vend) vend = strchr(s, '\0'); + break; + case EXP_JS: + s++; + vend = strstr(s, ">@"); + if(!vend) vend = strchr(s, '\0'); + break; + case EXP_ESCAPE: + s++; + vend = strstr(s, "]@"); + if(!vend) vend = strchr(s, '\0'); + break; + /*@notreached@*/ + case EXP_ERR: + break; + } + assert(vend); + + ret = g_strndup(s, vend-s); + + if(etype == EXP_SIMPLE_VAR || + etype == EXP_BRACED_VAR) { + if( (c = g_hash_table_lookup(uzbl.comm.proto_var, ret)) ) { + if(c->type == TYPE_STR && *c->ptr.s != NULL) { + g_string_append(buf, (gchar *)*c->ptr.s); + } + else if(c->type == TYPE_INT) { + g_string_append_printf(buf, "%d", *c->ptr.i); + } + else if(c->type == TYPE_FLOAT) { + g_string_append_printf(buf, "%f", *c->ptr.f); + } + } + + if(etype == EXP_SIMPLE_VAR) + s = vend; + else + s = vend+1; + } + else if(recurse != 1 && + etype == EXP_EXPR) { + + /* execute program directly */ + if(ret[0] == '+') { + mycmd = expand(ret+1, 1); + g_spawn_command_line_sync(mycmd, &cmd_stdout, NULL, NULL, &err); + g_free(mycmd); + } + /* execute program through shell, quote it first */ + else { + mycmd = expand(ret, 1); + gchar *quoted = g_shell_quote(mycmd); + gchar *tmp = g_strdup_printf("%s %s", + uzbl.behave.shell_cmd?uzbl.behave.shell_cmd:"/bin/sh -c", + quoted); + g_spawn_command_line_sync(tmp, &cmd_stdout, NULL, NULL, &err); + g_free(mycmd); + g_free(quoted); + g_free(tmp); + } + + if (err) { + g_printerr("error on running command: %s\n", err->message); + g_error_free (err); + } + else if (*cmd_stdout) { + size_t len = strlen(cmd_stdout); + + if(len > 0 && cmd_stdout[len-1] == '\n') + cmd_stdout[--len] = '\0'; /* strip trailing newline */ + + g_string_append(buf, cmd_stdout); + g_free(cmd_stdout); + } + s = vend+2; + } + else if(recurse != 2 && + etype == EXP_JS) { + + /* read JS from file */ + if(ret[0] == '+') { + GArray *tmp = g_array_new(TRUE, FALSE, sizeof(gchar *)); + mycmd = expand(ret+1, 2); + g_array_append_val(tmp, mycmd); + + run_external_js(uzbl.gui.web_view, tmp, js_ret); + g_array_free(tmp, TRUE); + } + /* JS from string */ + else { + mycmd = expand(ret, 2); + eval_js(uzbl.gui.web_view, mycmd, js_ret); + g_free(mycmd); + } + + if(js_ret->str) { + g_string_append(buf, js_ret->str); + g_string_free(js_ret, TRUE); + js_ret = g_string_new(""); + } + s = vend+2; + } + else if(etype == EXP_ESCAPE) { + mycmd = expand(ret, 0); + char *escaped = g_markup_escape_text(mycmd, strlen(mycmd)); + + g_string_append(buf, escaped); + + g_free(escaped); + g_free(mycmd); + s = vend+2; + } + + g_free(ret); + ret = NULL; + break; + + default: + g_string_append_c(buf, *s); + s++; + break; + } + } + g_string_free(js_ret, TRUE); + return g_string_free(buf, FALSE); +} + +char * +itos(int val) { + char tmp[20]; + + snprintf(tmp, sizeof(tmp), "%i", val); + return g_strdup(tmp); +} + +gchar* +strfree(gchar *str) { + g_free(str); + return NULL; +} + +gchar* +argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); } + +char * +str_replace (const char* search, const char* replace, const char* string) { + gchar **buf; + char *ret; + + if(!string) + return NULL; + + buf = g_strsplit (string, search, -1); + ret = g_strjoinv (replace, buf); + g_strfreev(buf); + + return ret; +} + +GArray* +read_file_by_line (const gchar *path) { + GIOChannel *chan = NULL; + gchar *readbuf = NULL; + gsize len; + GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*)); + int i = 0; + + chan = g_io_channel_new_file(path, "r", NULL); + if (chan) { + while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) { + const gchar* val = g_strdup (readbuf); + g_array_append_val (lines, val); + g_free (readbuf); + i ++; + } + + g_io_channel_unref (chan); + } else { + gchar *tmp = g_strdup_printf("File %s can not be read.", path); + send_event(COMMAND_ERROR, tmp, NULL); + g_free(tmp); + } + + return lines; +} + +/* search a PATH style string for an existing file+path combination */ +gchar* +find_existing_file(gchar* path_list) { + int i=0; + int cnt; + gchar **split; + gchar *tmp = NULL; + gchar *executable; + + if(!path_list) + return NULL; + + split = g_strsplit(path_list, ":", 0); + while(split[i]) + i++; + + 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(iweb_view, uzbl.state.last_button); + g_object_get(ht, "context", &context, NULL); + + return (gint)context; +} + +/* --- SIGNALS --- */ +int sigs[] = {SIGTERM, SIGINT, SIGSEGV, SIGILL, SIGFPE, SIGQUIT, SIGALRM, 0}; + +sigfunc* +setup_signal(int signr, sigfunc *shandler) { + struct sigaction nh, oh; + + nh.sa_handler = shandler; + sigemptyset(&nh.sa_mask); + nh.sa_flags = 0; + + if(sigaction(signr, &nh, &oh) < 0) + return SIG_ERR; + + return NULL; +} + +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) { + g_ptr_array_free(uzbl.state.event_buffer, TRUE); + uzbl.state.event_buffer = NULL; + } +} + +/* scroll a bar in a given direction */ +void +scroll (GtkAdjustment* bar, gchar *amount_str) { + gchar *end; + gdouble max_value; + + gdouble page_size = gtk_adjustment_get_page_size(bar); + gdouble value = gtk_adjustment_get_value(bar); + gdouble amount = g_ascii_strtod(amount_str, &end); + + if (*end == '%') + value += page_size * amount * 0.01; + else + value += amount; + + max_value = gtk_adjustment_get_upper(bar) - page_size; + + if (value > max_value) + value = max_value; /* don't scroll past the end of the page */ + + gtk_adjustment_set_value (bar, value); +} + +/* + * scroll vertical 20 + * scroll vertical 20% + * scroll vertical -40 + * scroll vertical begin + * scroll vertical end + * scroll horizontal 10 + * scroll horizontal -500 + * scroll horizontal begin + * scroll horizontal end + */ +void +scroll_cmd(WebKitWebView* page, GArray *argv, GString *result) { + (void) page; (void) result; + gchar *direction = g_array_index(argv, gchar*, 0); + gchar *argv1 = g_array_index(argv, gchar*, 1); + + if (g_strcmp0(direction, "horizontal") == 0) + { + if (g_strcmp0(argv1, "begin") == 0) + gtk_adjustment_set_value(uzbl.gui.bar_h, gtk_adjustment_get_lower(uzbl.gui.bar_h)); + else if (g_strcmp0(argv1, "end") == 0) + gtk_adjustment_set_value (uzbl.gui.bar_h, gtk_adjustment_get_upper(uzbl.gui.bar_h) - + gtk_adjustment_get_page_size(uzbl.gui.bar_h)); + else + scroll(uzbl.gui.bar_h, argv1); + } + else if (g_strcmp0(direction, "vertical") == 0) + { + if (g_strcmp0(argv1, "begin") == 0) + gtk_adjustment_set_value(uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v)); + else if (g_strcmp0(argv1, "end") == 0) + gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) - + gtk_adjustment_get_page_size(uzbl.gui.bar_v)); + else + scroll(uzbl.gui.bar_v, argv1); + } + else + if(uzbl.state.verbose) + puts("Unrecognized scroll format"); +} + + +/* VIEW funcs (little webkit wrappers) */ +#define VIEWFUNC(name) void view_##name(WebKitWebView *page, GArray *argv, GString *result){(void)argv; (void)result; webkit_web_view_##name(page);} +VIEWFUNC(reload) +VIEWFUNC(reload_bypass_cache) +VIEWFUNC(stop_loading) +VIEWFUNC(zoom_in) +VIEWFUNC(zoom_out) +VIEWFUNC(go_back) +VIEWFUNC(go_forward) +#undef VIEWFUNC + +/* -- command to callback/function map for things we cannot attach to any signals */ +struct {const char *key; CommandInfo value;} cmdlist[] = +{ /* key function no_split */ + { "back", {view_go_back, 0} }, + { "forward", {view_go_forward, 0} }, + { "scroll", {scroll_cmd, 0} }, + { "reload", {view_reload, 0}, }, + { "reload_ign_cache", {view_reload_bypass_cache, 0} }, + { "stop", {view_stop_loading, 0}, }, + { "zoom_in", {view_zoom_in, 0}, }, //Can crash (when max zoom reached?). + { "zoom_out", {view_zoom_out, 0}, }, + { "toggle_zoom_type", {toggle_zoom_type, 0}, }, + { "uri", {load_uri, TRUE} }, + { "js", {run_js, TRUE} }, + { "script", {run_external_js, 0} }, + { "toggle_status", {toggle_status_cb, 0} }, + { "spawn", {spawn, 0} }, + { "sync_spawn", {spawn_sync, 0} }, // needed for cookie handler + { "sh", {spawn_sh, 0} }, + { "sync_sh", {spawn_sh_sync, 0} }, // needed for cookie handler + { "talk_to_socket", {talk_to_socket, 0} }, + { "exit", {close_uzbl, 0} }, + { "search", {search_forward_text, TRUE} }, + { "search_reverse", {search_reverse_text, TRUE} }, + { "search_clear", {search_clear, TRUE} }, + { "dehilight", {dehilight, 0} }, + { "set", {set_var, TRUE} }, + { "dump_config", {act_dump_config, 0} }, + { "dump_config_as_events", {act_dump_config_as_events, 0} }, + { "chain", {chain, 0} }, + { "print", {print, TRUE} }, + { "event", {event, TRUE} }, + { "request", {event, TRUE} }, + { "menu_add", {menu_add, TRUE} }, + { "menu_link_add", {menu_add_link, TRUE} }, + { "menu_image_add", {menu_add_image, TRUE} }, + { "menu_editable_add", {menu_add_edit, TRUE} }, + { "menu_separator", {menu_add_separator, TRUE} }, + { "menu_link_separator", {menu_add_separator_link, TRUE} }, + { "menu_image_separator", {menu_add_separator_image, TRUE}}, + { "menu_editable_separator", {menu_add_separator_edit, TRUE} }, + { "menu_remove", {menu_remove, TRUE} }, + { "menu_link_remove", {menu_remove_link, TRUE} }, + { "menu_image_remove", {menu_remove_image, TRUE} }, + { "menu_editable_remove", {menu_remove_edit, TRUE} }, + { "hardcopy", {hardcopy, TRUE} }, + { "include", {include, TRUE} } +}; + +void +commands_hash(void) +{ + unsigned int i; + uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal); + + for (i = 0; i < LENGTH(cmdlist); i++) + g_hash_table_insert(uzbl.behave.commands, (gpointer) cmdlist[i].key, &cmdlist[i].value); +} + +void +builtins() { + unsigned int i, + len = LENGTH(cmdlist); + GString *command_list = g_string_new(""); + + for (i = 0; i < len; i++) { + g_string_append(command_list, cmdlist[i].key); + g_string_append_c(command_list, ' '); + } + + send_event(BUILTINS, command_list->str, NULL); + g_string_free(command_list, TRUE); +} + +/* -- CORE FUNCTIONS -- */ + +bool +file_exists (const char * filename) { + return (access(filename, F_OK) == 0); +} + +void +set_var(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + + if(!argv_idx(argv, 0)) + return; + + gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2); + if (split[0] != NULL) { + gchar *value = parseenv(split[1] ? g_strchug(split[1]) : " "); + set_var_value(g_strstrip(split[0]), value); + g_free(value); + } + g_strfreev(split); +} + +void +add_to_menu(GArray *argv, guint context) { + GUI *g = &uzbl.gui; + MenuItem *m; + gchar *item_cmd = NULL; + + if(!argv_idx(argv, 0)) + return; + + gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2); + if(!g->menu_items) + g->menu_items = g_ptr_array_new(); + + if(split[1]) + item_cmd = g_strdup(split[1]); + + if(split[0]) { + m = malloc(sizeof(MenuItem)); + m->name = g_strdup(split[0]); + m->cmd = g_strdup(item_cmd?item_cmd:""); + m->context = context; + m->issep = FALSE; + g_ptr_array_add(g->menu_items, m); + } + else + g_free(item_cmd); + + g_strfreev(split); +} + +void +menu_add(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); + +} + +void +menu_add_link(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); +} + +void +menu_add_image(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); +} + +void +menu_add_edit(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); +} + +void +add_separator_to_menu(GArray *argv, guint context) { + GUI *g = &uzbl.gui; + MenuItem *m; + gchar *sep_name; + + if(!g->menu_items) + g->menu_items = g_ptr_array_new(); + + if(!argv_idx(argv, 0)) + return; + else + sep_name = argv_idx(argv, 0); + + m = malloc(sizeof(MenuItem)); + m->name = g_strdup(sep_name); + m->cmd = NULL; + m->context = context; + m->issep = TRUE; + g_ptr_array_add(g->menu_items, m); +} + +void +menu_add_separator(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); +} + +void +menu_add_separator_link(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); +} +void +menu_add_separator_image(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); +} + +void +menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); +} + +void +remove_from_menu(GArray *argv, guint context) { + GUI *g = &uzbl.gui; + MenuItem *mi; + gchar *name = NULL; + guint i=0; + + if(!g->menu_items) + return; + + if(!argv_idx(argv, 0)) + return; + else + name = argv_idx(argv, 0); + + for(i=0; i < g->menu_items->len; i++) { + mi = g_ptr_array_index(g->menu_items, i); + + if((context == mi->context) && !strcmp(name, mi->name)) { + g_free(mi->name); + g_free(mi->cmd); + g_free(mi); + g_ptr_array_remove_index(g->menu_items, i); + } + } +} + +void +menu_remove(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); +} + +void +menu_remove_link(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); +} + +void +menu_remove_image(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); +} + +void +menu_remove_edit(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); +} + +void +event(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + GString *event_name; + gchar **split = NULL; + + if(!argv_idx(argv, 0)) + return; + + split = g_strsplit(argv_idx(argv, 0), " ", 2); + if(split[0]) + event_name = g_string_ascii_up(g_string_new(split[0])); + else + return; + + send_event(0, split[1]?split[1]:"", event_name->str); + + g_string_free(event_name, TRUE); + g_strfreev(split); +} + +void +print(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + gchar* buf; + + buf = expand(argv_idx(argv, 0), 0); + g_string_assign(result, buf); + g_free(buf); +} + +void +hardcopy(WebKitWebView *page, GArray *argv, GString *result) { + (void) argv; + (void) result; + + webkit_web_frame_print(webkit_web_view_get_main_frame(page)); +} + +void +include(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + gchar *pe = NULL, + *path = NULL, + *line; + int i=0; + + if(!argv_idx(argv, 0)) + return; + + pe = parseenv(argv_idx(argv, 0)); + if((path = find_existing_file(pe))) { + GArray* lines = read_file_by_line(path); + + while ((line = g_array_index(lines, gchar*, i))) { + parse_cmd_line (line, NULL); + i++; + g_free (line); + } + g_array_free (lines, TRUE); + + send_event(FILE_INCLUDED, path, NULL); + g_free(path); + } + g_free(pe); +} + +void +act_dump_config() { + dump_config(); +} + +void +act_dump_config_as_events() { + dump_config_as_events(); +} + +void +load_uri (WebKitWebView *web_view, GArray *argv, GString *result) { + (void) web_view; (void) result; + load_uri_imp (argv_idx (argv, 0)); +} + +/* Javascript*/ + +JSValueRef +js_run_command (JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, + size_t argumentCount, const JSValueRef arguments[], + JSValueRef* exception) { + (void) function; + (void) thisObject; + (void) exception; + + JSStringRef js_result_string; + GString *result = g_string_new(""); + + if (argumentCount >= 1) { + JSStringRef arg = JSValueToStringCopy(ctx, arguments[0], NULL); + size_t arg_size = JSStringGetMaximumUTF8CStringSize(arg); + char ctl_line[arg_size]; + JSStringGetUTF8CString(arg, ctl_line, arg_size); + + parse_cmd_line(ctl_line, result); + + JSStringRelease(arg); + } + js_result_string = JSStringCreateWithUTF8CString(result->str); + + g_string_free(result, TRUE); + + return JSValueMakeString(ctx, js_result_string); +} + +JSStaticFunction js_static_functions[] = { + {"run", js_run_command, kJSPropertyAttributeNone}, +}; + +void +js_init() { + /* This function creates the class and its definition, only once */ + if (!uzbl.js.initialized) { + /* it would be pretty cool to make this dynamic */ + uzbl.js.classdef = kJSClassDefinitionEmpty; + uzbl.js.classdef.staticFunctions = js_static_functions; + + uzbl.js.classref = JSClassCreate(&uzbl.js.classdef); + } +} + + +void +eval_js(WebKitWebView * web_view, gchar *script, GString *result) { + WebKitWebFrame *frame; + JSGlobalContextRef context; + JSObjectRef globalobject; + JSStringRef var_name; + + JSStringRef js_script; + JSValueRef js_result; + JSValueRef js_exc = NULL; + JSStringRef js_result_string; + size_t js_result_size; + + js_init(); + + frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(web_view)); + context = webkit_web_frame_get_global_context(frame); + globalobject = JSContextGetGlobalObject(context); + + /* uzbl javascript namespace */ + var_name = JSStringCreateWithUTF8CString("Uzbl"); + JSObjectSetProperty(context, globalobject, var_name, + JSObjectMake(context, uzbl.js.classref, NULL), + kJSClassAttributeNone, NULL); + + /* evaluate the script and get return value*/ + js_script = JSStringCreateWithUTF8CString(script); + js_result = JSEvaluateScript(context, js_script, globalobject, NULL, 0, &js_exc); + if (js_result && !JSValueIsUndefined(context, js_result)) { + js_result_string = JSValueToStringCopy(context, js_result, NULL); + js_result_size = JSStringGetMaximumUTF8CStringSize(js_result_string); + + if (js_result_size) { + char js_result_utf8[js_result_size]; + JSStringGetUTF8CString(js_result_string, js_result_utf8, js_result_size); + g_string_assign(result, js_result_utf8); + } + + JSStringRelease(js_result_string); + } + else if (js_exc && JSValueIsObject(context, js_exc)) { + size_t size; + JSStringRef prop, val; + JSObjectRef exc = JSValueToObject(context, js_exc, NULL); + + printf("Exception occured while executing script:\n"); + + /* Print line */ + prop = JSStringCreateWithUTF8CString("line"); + val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL); + size = JSStringGetMaximumUTF8CStringSize(val); + if(size) { + char cstr[size]; + JSStringGetUTF8CString(val, cstr, size); + printf("At line %s: ", cstr); + } + JSStringRelease(prop); + JSStringRelease(val); + + /* Print message */ + val = JSValueToStringCopy(context, exc, NULL); + size = JSStringGetMaximumUTF8CStringSize(val); + if(size) { + char cstr[size]; + JSStringGetUTF8CString(val, cstr, size); + printf("%s\n", cstr); + } + JSStringRelease(val); + } + + /* cleanup */ + JSObjectDeleteProperty(context, globalobject, var_name, NULL); + + JSStringRelease(var_name); + JSStringRelease(js_script); +} + +void +run_js (WebKitWebView * web_view, GArray *argv, GString *result) { + if (argv_idx(argv, 0)) + eval_js(web_view, argv_idx(argv, 0), result); +} + +void +run_external_js (WebKitWebView * web_view, GArray *argv, GString *result) { + (void) result; + gchar *path = NULL; + + if (argv_idx(argv, 0) && + ((path = find_existing_file(argv_idx(argv, 0)))) ) { + GArray* lines = read_file_by_line (path); + gchar* js = NULL; + int i = 0; + gchar* line; + + while ((line = g_array_index(lines, gchar*, i))) { + if (js == NULL) { + js = g_strdup (line); + } else { + gchar* newjs = g_strconcat (js, line, NULL); + js = newjs; + } + i ++; + g_free (line); + } + + if (uzbl.state.verbose) + printf ("External JavaScript file %s loaded\n", argv_idx(argv, 0)); + + gchar* newjs = str_replace("%s", argv_idx (argv, 1)?argv_idx (argv, 1):"", js); + g_free (js); + js = newjs; + + eval_js (web_view, js, result); + g_free (js); + g_array_free (lines, TRUE); + g_free(path); + } +} + +void +search_text (WebKitWebView *page, GArray *argv, const gboolean forward) { + if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0')) { + if (g_strcmp0 (uzbl.state.searchtx, argv_idx(argv, 0)) != 0) { + webkit_web_view_unmark_text_matches (page); + webkit_web_view_mark_text_matches (page, argv_idx(argv, 0), FALSE, 0); + uzbl.state.searchtx = g_strdup(argv_idx(argv, 0)); + } + } + + + if (uzbl.state.searchtx) { + if (uzbl.state.verbose) + printf ("Searching: %s\n", uzbl.state.searchtx); + webkit_web_view_set_highlight_text_matches (page, TRUE); + webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE); + } +} + +void +search_clear(WebKitWebView *page, GArray *argv, GString *result) { + (void) argv; + (void) result; + + webkit_web_view_unmark_text_matches (page); + if(uzbl.state.searchtx) { + g_free(uzbl.state.searchtx); + uzbl.state.searchtx = NULL; + } +} + +void +search_forward_text (WebKitWebView *page, GArray *argv, GString *result) { + (void) result; + search_text(page, argv, TRUE); +} + +void +search_reverse_text (WebKitWebView *page, GArray *argv, GString *result) { + (void) result; + search_text(page, argv, FALSE); +} + +void +dehilight (WebKitWebView *page, GArray *argv, GString *result) { + (void) argv; (void) result; + webkit_web_view_set_highlight_text_matches (page, FALSE); +} + + +void +new_window_load_uri (const gchar * uri) { + if (uzbl.behave.new_window) { + GString *s = g_string_new (""); + g_string_printf(s, "'%s'", uri); + run_handler(uzbl.behave.new_window, s->str); + send_event(NEW_WINDOW, s->str, NULL); + return; + } + GString* to_execute = g_string_new (""); + g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri); + int i; + for (i = 0; entries[i].long_name != NULL; i++) { + if ((entries[i].arg == G_OPTION_ARG_STRING) && + !strcmp(entries[i].long_name,"uri") && + !strcmp(entries[i].long_name,"name")) { + gchar** str = (gchar**)entries[i].arg_data; + if (*str!=NULL) + g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str); + } + else if(entries[i].arg == G_OPTION_ARG_STRING_ARRAY) { + int j; + gchar **str = *((gchar ***)entries[i].arg_data); + for(j=0; str[j]; j++) + g_string_append_printf(to_execute, " --%s '%s'", entries[i].long_name, str[j]); + } + } + if (uzbl.state.verbose) + printf("\n%s\n", to_execute->str); + g_spawn_command_line_async (to_execute->str, NULL); + /* TODO: should we just report the uri as event detail? */ + send_event(NEW_WINDOW, to_execute->str, NULL); + g_string_free (to_execute, TRUE); +} + +void +chain (WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + gchar *a = NULL; + gchar **parts = NULL; + guint i = 0; + while ((a = argv_idx(argv, i++))) { + parts = g_strsplit (a, " ", 2); + if (parts[0]) + parse_command(parts[0], parts[1], result); + g_strfreev (parts); + } +} + +void +close_uzbl (WebKitWebView *page, GArray *argv, GString *result) { + (void)page; + (void)argv; + (void)result; + gtk_main_quit (); +} + +void +sharg_append(GArray *a, const gchar *str) { + const gchar *s = (str ? str : ""); + g_array_append_val(a, s); +} + +// make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc) +gboolean +run_command (const gchar *command, const guint npre, const gchar **args, + const gboolean sync, char **output_stdout) { + //command [args] + GError *err = NULL; + + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + gchar *pid = itos(getpid()); + gchar *xwin = itos(uzbl.xwin); + guint i; + sharg_append(a, command); + for (i = 0; i < npre; i++) /* add n args before the default vars */ + sharg_append(a, args[i]); + sharg_append(a, uzbl.state.config_file); + sharg_append(a, pid); + sharg_append(a, xwin); + sharg_append(a, uzbl.comm.fifo_path); + sharg_append(a, uzbl.comm.socket_path); + sharg_append(a, uzbl.state.uri); + sharg_append(a, uzbl.gui.main_title); + + for (i = npre; i < g_strv_length((gchar**)args); i++) + sharg_append(a, args[i]); + + gboolean result; + if (sync) { + if (*output_stdout) *output_stdout = strfree(*output_stdout); + + result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, output_stdout, NULL, NULL, &err); + } else + result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, &err); + + if (uzbl.state.verbose) { + GString *s = g_string_new("spawned:"); + for (i = 0; i < (a->len); i++) { + gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i)); + g_string_append_printf(s, " %s", qarg); + g_free (qarg); + } + g_string_append_printf(s, " -- result: %s", (result ? "true" : "false")); + printf("%s\n", s->str); + g_string_free(s, TRUE); + if(output_stdout) { + printf("Stdout: %s\n", *output_stdout); + } + } + if (err) { + g_printerr("error on run_command: %s\n", err->message); + g_error_free (err); + } + g_free (pid); + g_free (xwin); + g_array_free (a, TRUE); + return result; +} + +/*@null@*/ gchar** +split_quoted(const gchar* src, const gboolean unquote) { + /* split on unquoted space, return array of strings; + remove a layer of quotes and backslashes if unquote */ + if (!src) return NULL; + + gboolean dq = FALSE; + gboolean sq = FALSE; + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + GString *s = g_string_new (""); + const gchar *p; + gchar **ret; + gchar *dup; + for (p = src; *p != '\0'; p++) { + if ((*p == '\\') && unquote) g_string_append_c(s, *++p); + else if (*p == '\\') { g_string_append_c(s, *p++); + g_string_append_c(s, *p); } + else if ((*p == '"') && unquote && !sq) dq = !dq; + else if (*p == '"' && !sq) { g_string_append_c(s, *p); + dq = !dq; } + else if ((*p == '\'') && unquote && !dq) sq = !sq; + else if (*p == '\'' && !dq) { g_string_append_c(s, *p); + sq = ! sq; } + else if ((*p == ' ') && !dq && !sq) { + dup = g_strdup(s->str); + g_array_append_val(a, dup); + g_string_truncate(s, 0); + } else g_string_append_c(s, *p); + } + dup = g_strdup(s->str); + g_array_append_val(a, dup); + ret = (gchar**)a->data; + g_array_free (a, FALSE); + g_string_free (s, TRUE); + return ret; +} + +void +spawn(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + 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)))) ) { + run_command(path, 0, + ((const gchar **) (argv->data + sizeof(gchar*))), + FALSE, NULL); + g_free(path); + } +} + +void +spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + gchar *path = NULL; + + 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_sh(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + if (!uzbl.behave.shell_cmd) { + g_printerr ("spawn_sh: 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]); + + if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL); + g_free (spacer); + g_strfreev (cmd); +} + +void +spawn_sh_sync(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]); + + 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 +talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + + int fd, len; + struct sockaddr_un sa; + char* sockpath; + ssize_t ret; + struct pollfd pfd; + struct iovec* iov; + guint i; + + if(uzbl.comm.sync_stdout) uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + /* This function could be optimised by storing a hash table of socket paths + and associated connected file descriptors rather than closing and + re-opening for every call. Also we could launch a script if socket connect + fails. */ + + /* First element argv[0] is path to socket. Following elements are tokens to + write to the socket. We write them as a single packet with each token + separated by an ASCII nul (\0). */ + if(argv->len < 2) { + g_printerr("talk_to_socket called with only %d args (need at least two).\n", + (int)argv->len); + return; + } + + /* copy socket path, null terminate result */ + sockpath = g_array_index(argv, char*, 0); + g_strlcpy(sa.sun_path, sockpath, sizeof(sa.sun_path)); + sa.sun_family = AF_UNIX; + + /* create socket file descriptor and connect it to path */ + fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if(fd == -1) { + g_printerr("talk_to_socket: creating socket failed (%s)\n", strerror(errno)); + return; + } + if(connect(fd, (struct sockaddr*)&sa, sizeof(sa))) { + g_printerr("talk_to_socket: connect failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* build request vector */ + iov = g_malloc(sizeof(struct iovec) * (argv->len - 1)); + if(!iov) { + g_printerr("talk_to_socket: unable to allocated memory for token vector\n"); + close(fd); + return; + } + for(i = 1; i < argv->len; ++i) { + iov[i - 1].iov_base = g_array_index(argv, char*, i); + iov[i - 1].iov_len = strlen(iov[i - 1].iov_base) + 1; /* string plus \0 */ + } + + /* write request */ + ret = writev(fd, iov, argv->len - 1); + g_free(iov); + if(ret == -1) { + g_printerr("talk_to_socket: write failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* wait for a response, with a 500ms timeout */ + pfd.fd = fd; + pfd.events = POLLIN; + while(1) { + ret = poll(&pfd, 1, 500); + if(ret == 1) break; + if(ret == 0) errno = ETIMEDOUT; + if(errno == EINTR) continue; + g_printerr("talk_to_socket: poll failed while waiting for input (%s)\n", + strerror(errno)); + close(fd); + return; + } + + /* get length of response */ + if(ioctl(fd, FIONREAD, &len) == -1) { + g_printerr("talk_to_socket: cannot find daemon response length, " + "ioctl failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* if there is a response, read it */ + if(len) { + uzbl.comm.sync_stdout = g_malloc(len + 1); + if(!uzbl.comm.sync_stdout) { + g_printerr("talk_to_socket: failed to allocate %d bytes\n", len); + close(fd); + return; + } + uzbl.comm.sync_stdout[len] = 0; /* ensure result is null terminated */ + + ret = read(fd, uzbl.comm.sync_stdout, len); + if(ret == -1) { + g_printerr("talk_to_socket: failed to read from socket (%s)\n", + strerror(errno)); + close(fd); + return; + } + } + + /* clean up */ + close(fd); + return; +} + +void +parse_command(const char *cmd, const char *param, GString *result) { + CommandInfo *c; + GString *tmp = g_string_new(""); + + if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) { + guint i; + gchar **par = split_quoted(param, TRUE); + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + + if (c->no_split) { /* don't split */ + sharg_append(a, param); + } else if (par) { + for (i = 0; i < g_strv_length(par); i++) + sharg_append(a, par[i]); + } + + if (result == NULL) { + GString *result_print = g_string_new(""); + + c->function(uzbl.gui.web_view, a, result_print); + if (result_print->len) + printf("%*s\n", (int)result_print->len, result_print->str); + + g_string_free(result_print, TRUE); + } else { + c->function(uzbl.gui.web_view, a, result); + } + g_strfreev (par); + g_array_free (a, TRUE); + + if(strcmp("set", cmd) && + strcmp("event", cmd) && + strcmp("request", cmd)) { + g_string_printf(tmp, "%s %s", cmd, param?param:""); + send_event(COMMAND_EXECUTED, tmp->str, NULL); + g_string_free(tmp, TRUE); + } + } + else { + gchar *tmp = g_strdup_printf("%s %s", cmd, param?param:""); + send_event(COMMAND_ERROR, tmp, NULL); + g_free(tmp); + } +} + + +void +move_statusbar() { + if (!uzbl.gui.scrolled_win && + !uzbl.gui.mainbar) + return; + + g_object_ref(uzbl.gui.scrolled_win); + g_object_ref(uzbl.gui.mainbar); + gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win); + gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar); + + if(uzbl.behave.status_top) { + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); + } + else { + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); + } + g_object_unref(uzbl.gui.scrolled_win); + g_object_unref(uzbl.gui.mainbar); + gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); + return; +} + +gboolean +set_var_value(const gchar *name, gchar *val) { + uzbl_cmdprop *c = NULL; + char *endp = NULL; + char *buf = NULL; + char *invalid_chars = "^°!\"§$%&/()=?'`'+~*'#-.:,;@<>| \\{}[]¹²³¼½"; + GString *msg; + + if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) { + if(!c->writeable) return FALSE; + + msg = g_string_new(name); + + /* check for the variable type */ + if (c->type == TYPE_STR) { + buf = g_strdup(val); + g_free(*c->ptr.s); + *c->ptr.s = buf; + g_string_append_printf(msg, " str %s", buf); + + } else if(c->type == TYPE_INT) { + *c->ptr.i = (int)strtoul(val, &endp, 10); + g_string_append_printf(msg, " int %d", *c->ptr.i); + + } else if (c->type == TYPE_FLOAT) { + *c->ptr.f = strtod(val, &endp); + g_string_append_printf(msg, " float %f", *c->ptr.f); + } + + send_event(VARIABLE_SET, msg->str, NULL); + g_string_free(msg,TRUE); + + /* invoke a command specific function */ + if(c->func) c->func(); + } else { + /* check wether name violates our naming scheme */ + if(strpbrk(name, invalid_chars)) { + if (uzbl.state.verbose) + printf("Invalid variable name\n"); + return FALSE; + } + + /* custom vars */ + c = g_malloc(sizeof(uzbl_cmdprop)); + c->type = TYPE_STR; + c->dump = 0; + c->func = NULL; + c->writeable = 1; + buf = g_strdup(val); + c->ptr.s = g_malloc(sizeof(char *)); + *c->ptr.s = buf; + g_hash_table_insert(uzbl.comm.proto_var, + g_strdup(name), (gpointer) c); + + msg = g_string_new(name); + g_string_append_printf(msg, " str %s", buf); + send_event(VARIABLE_SET, msg->str, NULL); + g_string_free(msg,TRUE); + } + update_title(); + return TRUE; +} + +void +parse_cmd_line(const char *ctl_line, GString *result) { + size_t len=0; + gchar *ctlstrip = NULL; + gchar *exp_line = NULL; + gchar *work_string = NULL; + + work_string = g_strdup(ctl_line); + + /* strip trailing newline */ + len = strlen(ctl_line); + if (work_string[len - 1] == '\n') + ctlstrip = g_strndup(work_string, len - 1); + else + ctlstrip = g_strdup(work_string); + g_free(work_string); + + if( strcmp(g_strchug(ctlstrip), "") && + strcmp(exp_line = expand(ctlstrip, 0), "") + ) { + /* ignore comments */ + if((exp_line[0] == '#')) + ; + + /* parse a command */ + else { + gchar **tokens = NULL; + + tokens = g_strsplit(exp_line, " ", 2); + parse_command(tokens[0], tokens[1], result); + g_strfreev(tokens); + } + g_free(exp_line); + } + + g_free(ctlstrip); +} + + +/*@null@*/ gchar* +build_stream_name(int type, const gchar* dir) { + State *s = &uzbl.state; + gchar *str = NULL; + + if (type == FIFO) { + str = g_strdup_printf + ("%s/uzbl_fifo_%s", dir, s->instance_name); + } else if (type == SOCKET) { + str = g_strdup_printf + ("%s/uzbl_socket_%s", dir, s->instance_name); + } + return str; +} + +gboolean +control_fifo(GIOChannel *gio, GIOCondition condition) { + if (uzbl.state.verbose) + printf("triggered\n"); + gchar *ctl_line; + GIOStatus ret; + GError *err = NULL; + + if (condition & G_IO_HUP) + g_error ("Fifo: Read end of pipe died!\n"); + + if(!gio) + g_error ("Fifo: GIOChannel broke\n"); + + ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err); + if (ret == G_IO_STATUS_ERROR) { + g_error ("Fifo: Error reading: %s\n", err->message); + g_error_free (err); + } + + parse_cmd_line(ctl_line, NULL); + g_free(ctl_line); + + return TRUE; +} + +/*@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 */ + if (unlink(uzbl.comm.fifo_path) == -1) + g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path); + g_free(uzbl.comm.fifo_path); + 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); + } 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); + + /* if we got this far, there was an error; cleanup */ + if (error) g_error_free (error); + g_free(dir); + g_free(path); + return NULL; +} + +gboolean +control_stdin(GIOChannel *gio, GIOCondition condition) { + (void) condition; + gchar *ctl_line = NULL; + GIOStatus ret; + + ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL); + if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) ) + return FALSE; + + parse_cmd_line(ctl_line, NULL); + g_free(ctl_line); + + return TRUE; +} + +void +create_stdin () { + GIOChannel *chan = NULL; + GError *error = NULL; + + chan = g_io_channel_unix_new(fileno(stdin)); + if (chan) { + if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) { + g_error ("Stdin: could not add watch\n"); + } else { + if (uzbl.state.verbose) + printf ("Stdin: watch added successfully\n"); + } + } else { + g_error ("Stdin: Error while opening: %s\n", error->message); + } + if (error) g_error_free (error); +} + +gboolean +remove_socket_from_array(GIOChannel *chan) { + gboolean ret = 0; + + /* TODO: Do wee need to manually free the IO channel or is this + * happening implicitly on unref? + */ + ret = g_ptr_array_remove_fast(uzbl.comm.connect_chan, chan); + if(!ret) + ret = g_ptr_array_remove_fast(uzbl.comm.client_chan, chan); + + return ret; +} + +gboolean +control_socket(GIOChannel *chan) { + struct sockaddr_un remote; + unsigned int t = sizeof(remote); + GIOChannel *iochan; + int clientsock; + + clientsock = accept (g_io_channel_unix_get_fd(chan), + (struct sockaddr *) &remote, &t); + + if(!uzbl.comm.client_chan) + uzbl.comm.client_chan = g_ptr_array_new(); + + if ((iochan = g_io_channel_unix_new(clientsock))) { + g_io_channel_set_encoding(iochan, NULL, NULL); + g_io_add_watch(iochan, G_IO_IN|G_IO_HUP, + (GIOFunc) control_client_socket, iochan); + g_ptr_array_add(uzbl.comm.client_chan, (gpointer)iochan); + } + return TRUE; +} + +void +init_connect_socket() { + int sockfd, replay = 0; + struct sockaddr_un local; + GIOChannel *chan; + gchar **name = NULL; + + if(!uzbl.comm.connect_chan) + uzbl.comm.connect_chan = g_ptr_array_new(); + + name = uzbl.state.connect_socket_names; + + while(name && *name) { + sockfd = socket (AF_UNIX, SOCK_STREAM, 0); + local.sun_family = AF_UNIX; + strcpy (local.sun_path, *name); + + if(!connect(sockfd, (struct sockaddr *) &local, sizeof(local))) { + if ((chan = g_io_channel_unix_new(sockfd))) { + g_io_channel_set_encoding(chan, NULL, NULL); + g_io_add_watch(chan, G_IO_IN|G_IO_HUP, + (GIOFunc) control_client_socket, chan); + g_ptr_array_add(uzbl.comm.connect_chan, (gpointer)chan); + replay++; + } + } + else + g_warning("Error connecting to socket: %s\n", *name); + name++; + } + + /* replay buffered events */ + if(replay) + send_event_socket(NULL); +} + +gboolean +control_client_socket(GIOChannel *clientchan) { + char *ctl_line; + GString *result = g_string_new(""); + GError *error = NULL; + GIOStatus ret; + gsize len; + + ret = g_io_channel_read_line(clientchan, &ctl_line, &len, NULL, &error); + if (ret == G_IO_STATUS_ERROR) { + g_warning ("Error reading: %s\n", error->message); + remove_socket_from_array(clientchan); + g_io_channel_shutdown(clientchan, TRUE, &error); + return FALSE; + } else if (ret == G_IO_STATUS_EOF) { + remove_socket_from_array(clientchan); + /* shutdown and remove channel watch from main loop */ + g_io_channel_shutdown(clientchan, TRUE, &error); + return FALSE; + } + + if (ctl_line) { + parse_cmd_line (ctl_line, result); + g_string_append_c(result, '\n'); + ret = g_io_channel_write_chars (clientchan, result->str, result->len, + &len, &error); + if (ret == G_IO_STATUS_ERROR) { + g_warning ("Error writing: %s", error->message); + } + g_io_channel_flush(clientchan, &error); + } + + if (error) g_error_free (error); + g_string_free(result, TRUE); + g_free(ctl_line); + return TRUE; +} + +/*@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 */ + if (unlink(uzbl.comm.socket_path) == -1) + g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path); + g_free(uzbl.comm.socket_path); + uzbl.comm.socket_path = NULL; + } + + if (*dir == ' ') { + g_free(dir); + 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; + } + } 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); + g_free(dir); + return NULL; +} + +/* + NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state + it will probably improve performance if we would "cache" the processed variant, but for now it works well enough... +*/ +// this function may be called very early when the templates are not set (yet), hence the checks +void +update_title (void) { + Behaviour *b = &uzbl.behave; + gchar *parsed; + + if (b->show_status) { + if (b->title_format_short) { + parsed = expand(b->title_format_short, 0); + if (uzbl.gui.main_window) + gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed); + g_free(parsed); + } + if (b->status_format) { + parsed = expand(b->status_format, 0); + gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed); + g_free(parsed); + } + if (b->status_background) { + GdkColor color; + gdk_color_parse (b->status_background, &color); + //labels and hboxes do not draw their own background. applying this on the vbox/main_window is ok as the statusbar is the only affected widget. (if not, we could also use GtkEventBox) + if (uzbl.gui.main_window) + gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color); + else if (uzbl.gui.plug) + gtk_widget_modify_bg (GTK_WIDGET(uzbl.gui.plug), GTK_STATE_NORMAL, &color); + } + } else { + if (b->title_format_long) { + parsed = expand(b->title_format_long, 0); + if (uzbl.gui.main_window) + gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed); + g_free(parsed); + } + } +} + +void +create_browser () { + GUI *g = &uzbl.gui; + + g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ()); + + g_object_connect((GObject*)g->web_view, + "signal::key-press-event", (GCallback)key_press_cb, NULL, + "signal::key-release-event", (GCallback)key_release_cb, NULL, + "signal::button-press-event", (GCallback)button_press_cb, NULL, + "signal::button-release-event", (GCallback)button_release_cb, NULL, + "signal::title-changed", (GCallback)title_change_cb, NULL, + "signal::selection-changed", (GCallback)selection_changed_cb, NULL, + "signal::load-progress-changed", (GCallback)progress_change_cb, NULL, + "signal::load-committed", (GCallback)load_commit_cb, NULL, + "signal::load-started", (GCallback)load_start_cb, NULL, + "signal::load-finished", (GCallback)load_finish_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, + "signal::new-window-policy-decision-requested", (GCallback)new_window_cb, NULL, + "signal::download-requested", (GCallback)download_cb, NULL, + "signal::create-web-view", (GCallback)create_web_view_cb, NULL, + "signal::mime-type-policy-decision-requested", (GCallback)mime_policy_cb, NULL, + "signal::populate-popup", (GCallback)populate_popup_cb, NULL, + "signal::focus-in-event", (GCallback)focus_cb, NULL, + "signal::focus-out-event", (GCallback)focus_cb, NULL, + NULL); +} + +GtkWidget* +create_mainbar () { + GUI *g = &uzbl.gui; + + g->mainbar = gtk_hbox_new (FALSE, 0); + g->mainbar_label = gtk_label_new (""); + gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE); + gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0); + gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2); + gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0); + + g_object_connect((GObject*)g->mainbar, + "signal::key-press-event", (GCallback)key_press_cb, NULL, + "signal::key-release-event", (GCallback)key_release_cb, NULL, + NULL); + + return g->mainbar; +} + + +GtkWidget* +create_window () { + GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); + gtk_widget_set_name (window, "Uzbl browser"); + + g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL); + g_signal_connect (G_OBJECT (window), "configure-event", G_CALLBACK (configure_event_cb), NULL); + + return window; +} + +GtkPlug* +create_plug () { + GtkPlug* plug = GTK_PLUG (gtk_plug_new (uzbl.state.socket_id)); + g_signal_connect (G_OBJECT (plug), "destroy", G_CALLBACK (destroy_cb), NULL); + g_signal_connect (G_OBJECT (plug), "key-press-event", G_CALLBACK (key_press_cb), NULL); + g_signal_connect (G_OBJECT (plug), "key-release-event", G_CALLBACK (key_release_cb), NULL); + + return plug; +} + + +gchar** +inject_handler_args(const gchar *actname, const gchar *origargs, const gchar *newargs) { + /* + If actname is one that calls an external command, this function will inject + newargs in front of the user-provided args in that command line. They will + come become after the body of the script (in sh) or after the name of + the command to execute (in spawn). + i.e. sh becomes sh and + spawn becomes spawn . + + The return value consist of two strings: the action (sh, ...) and its args. + + If act is not one that calls an external command, then the given action merely + gets duplicated. + */ + GArray *rets = g_array_new(TRUE, FALSE, sizeof(gchar*)); + /* Arrr! Here be memory leaks */ + gchar *actdup = g_strdup(actname); + g_array_append_val(rets, actdup); + + if ((g_strcmp0(actname, "spawn") == 0) || + (g_strcmp0(actname, "sh") == 0) || + (g_strcmp0(actname, "sync_spawn") == 0) || + (g_strcmp0(actname, "sync_sh") == 0) || + (g_strcmp0(actname, "talk_to_socket") == 0)) { + guint i; + GString *a = g_string_new(""); + gchar **spawnparts = split_quoted(origargs, FALSE); + g_string_append_printf(a, "%s", spawnparts[0]); /* sh body or script name */ + if (newargs) g_string_append_printf(a, " %s", newargs); /* handler args */ + + for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */ + if (spawnparts[i]) g_string_append_printf(a, " %s", spawnparts[i]); + + g_array_append_val(rets, a->str); + g_string_free(a, FALSE); + g_strfreev(spawnparts); + } else { + gchar *origdup = g_strdup(origargs); + g_array_append_val(rets, origdup); + } + return (gchar**)g_array_free(rets, FALSE); +} + +void +run_handler (const gchar *act, const gchar *args) { + /* Consider this code a temporary hack to make the handlers usable. + In practice, all this splicing, injection, and reconstruction is + inefficient, annoying and hard to manage. Potential pitfalls arise + when the handler specific args 1) are not quoted (the handler + callbacks should take care of this) 2) are quoted but interfere + with the users' own quotation. A more ideal solution is + to refactor parse_command so that it doesn't just take a string + and execute it; rather than that, we should have a function which + returns the argument vector parsed from the string. This vector + could be modified (e.g. insert additional args into it) before + passing it to the next function that actually executes it. Though + it still isn't perfect for chain actions.. will reconsider & re- + factor when I have the time. -duc */ + + if (!act) return; + char **parts = g_strsplit(act, " ", 2); + if (!parts || !parts[0]) return; + if (g_strcmp0(parts[0], "chain") == 0) { + GString *newargs = g_string_new(""); + gchar **chainparts = split_quoted(parts[1], FALSE); + + /* for every argument in the chain, inject the handler args + and make sure the new parts are wrapped in quotes */ + gchar **cp = chainparts; + gchar quot = '\''; + gchar *quotless = NULL; + gchar **spliced_quotless = NULL; // sigh -_-; + gchar **inpart = NULL; + + while (*cp) { + if ((**cp == '\'') || (**cp == '\"')) { /* strip old quotes */ + quot = **cp; + quotless = g_strndup(&(*cp)[1], strlen(*cp) - 2); + } else quotless = g_strdup(*cp); + + spliced_quotless = g_strsplit(quotless, " ", 2); + inpart = inject_handler_args(spliced_quotless[0], spliced_quotless[1], args); + g_strfreev(spliced_quotless); + + g_string_append_printf(newargs, " %c%s %s%c", quot, inpart[0], inpart[1], quot); + g_free(quotless); + g_strfreev(inpart); + cp++; + } + + parse_command(parts[0], &(newargs->str[1]), NULL); + g_string_free(newargs, TRUE); + g_strfreev(chainparts); + + } else { + gchar **inparts; + gchar *inparts_[2]; + if (parts[1]) { + /* expand the user-specified arguments */ + gchar* expanded = expand(parts[1], 0); + inparts = inject_handler_args(parts[0], expanded, args); + g_free(expanded); + } else { + inparts_[0] = parts[0]; + inparts_[1] = g_strdup(args); + inparts = inparts_; + } + + parse_command(inparts[0], inparts[1], NULL); + + if (inparts != inparts_) { + g_free(inparts[0]); + g_free(inparts[1]); + } else + g_free(inparts[1]); + } + g_strfreev(parts); +} + +/*@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); + } + + return return_value; +} + +/*@null@*/ gchar* +find_xdg_file (int xdg_type, const char* filename) { + /* xdg_type = 0 => config + xdg_type = 1 => data + xdg_type = 2 => cache*/ + + gchar* xdgv = get_xdg_var (XDG[xdg_type]); + gchar* temporary_file = g_strconcat (xdgv, filename, 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); + + while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) { + g_free (temporary_file); + temporary_file = g_strconcat (temporary_string, filename, NULL); + } + } + + //g_free (temporary_string); - segfaults. + + if (file_exists (temporary_file)) { + return temporary_file; + } else { + g_free(temporary_file); + return NULL; + } +} +void +settings_init () { + State *s = &uzbl.state; + Network *n = &uzbl.net; + + int i; + for (i = 0; default_config[i].command != NULL; i++) { + parse_cmd_line(default_config[i].command, NULL); + } + + if (g_strcmp0(s->config_file, "-") == 0) { + s->config_file = NULL; + create_stdin(); + } + + else if (!s->config_file) { + s->config_file = find_xdg_file (0, "/uzbl/config"); + } + + if (s->config_file) { + GArray* lines = read_file_by_line (s->config_file); + int i = 0; + gchar* line; + + while ((line = g_array_index(lines, gchar*, i))) { + parse_cmd_line (line, NULL); + i ++; + g_free (line); + } + g_array_free (lines, TRUE); + } else { + if (uzbl.state.verbose) + printf ("No configuration file loaded.\n"); + } + + if(s->connect_socket_names) + init_connect_socket(); + + g_signal_connect_after(n->soup_session, "request-started", G_CALLBACK(handle_cookies), NULL); +} + +void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){ + (void) session; + (void) user_data; + //if (!uzbl.behave.cookie_handler) + // return; + + soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL); + GString *s = g_string_new (""); + SoupURI * soup_uri = soup_message_get_uri(msg); + g_string_printf(s, "GET '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path); + if(uzbl.behave.cookie_handler) + run_handler(uzbl.behave.cookie_handler, s->str); + + if(uzbl.behave.cookie_handler && + uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) { + char *p = strchr(uzbl.comm.sync_stdout, '\n' ); + if ( p != NULL ) *p = '\0'; + soup_message_headers_replace (msg->request_headers, "Cookie", (const char *) uzbl.comm.sync_stdout); + + } + if (uzbl.comm.sync_stdout) + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + g_string_free(s, TRUE); +} + +void +save_cookies (SoupMessage *msg, gpointer user_data){ + (void) user_data; + GSList *ck; + char *cookie; + for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){ + cookie = soup_cookie_to_set_cookie_header(ck->data); + SoupURI * soup_uri = soup_message_get_uri(msg); + GString *s = g_string_new (""); + g_string_printf(s, "PUT '%s' '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path, cookie); + run_handler(uzbl.behave.cookie_handler, s->str); + g_free (cookie); + g_string_free(s, TRUE); + } + g_slist_free(ck); +} + +void +dump_var_hash(gpointer k, gpointer v, gpointer ud) { + (void) ud; + uzbl_cmdprop *c = v; + + if(!c->dump) + return; + + if(c->type == TYPE_STR) + printf("set %s = %s\n", (char *)k, *c->ptr.s ? *c->ptr.s : " "); + else if(c->type == TYPE_INT) + printf("set %s = %d\n", (char *)k, *c->ptr.i); + else if(c->type == TYPE_FLOAT) + printf("set %s = %f\n", (char *)k, *c->ptr.f); +} + +void +dump_config() { + g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash, NULL); +} + +void +dump_var_hash_as_event(gpointer k, gpointer v, gpointer ud) { + (void) ud; + uzbl_cmdprop *c = v; + GString *msg; + + if(!c->dump) + return; + + /* check for the variable type */ + msg = g_string_new((char *)k); + if (c->type == TYPE_STR) { + g_string_append_printf(msg, " str %s", *c->ptr.s ? *c->ptr.s : " "); + } else if(c->type == TYPE_INT) { + g_string_append_printf(msg, " int %d", *c->ptr.i); + } else if (c->type == TYPE_FLOAT) { + g_string_append_printf(msg, " float %f", *c->ptr.f); + } + + send_event(VARIABLE_SET, msg->str, NULL); + g_string_free(msg, TRUE); +} + +void +dump_config_as_events() { + g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash_as_event, NULL); +} + +void +retrieve_geometry() { + int w, h, x, y; + GString *buf = g_string_new(""); + + gtk_window_get_size(GTK_WINDOW(uzbl.gui.main_window), &w, &h); + gtk_window_get_position(GTK_WINDOW(uzbl.gui.main_window), &x, &y); + + g_string_printf(buf, "%dx%d+%d+%d", w, h, x, y); + + if(uzbl.gui.geometry) + g_free(uzbl.gui.geometry); + uzbl.gui.geometry = g_string_free(buf, FALSE); +} + +/* set up gtk, gobject, variable defaults and other things that tests and other + * external applications need to do anyhow */ +void +initialize(int argc, char *argv[]) { + int i; + + for(i=0; istr)) + g_string_assign (fullpath, newuri->str); + else { + gchar* wd; + wd = g_get_current_dir (); + g_string_assign (fullpath, g_build_filename (wd, newuri->str, NULL)); + free(wd); + } + struct stat stat_result; + if (! g_stat(fullpath->str, &stat_result)) { + g_string_prepend (fullpath, "file://"); + g_string_assign (newuri, fullpath->str); + } + else + g_string_prepend (newuri, "http://"); + g_string_free (fullpath, TRUE); + } + /* if we do handle cookies, ask our handler for them */ + webkit_web_view_load_uri (uzbl.gui.web_view, newuri->str); + g_string_free (newuri, TRUE); +} + + +#ifndef UZBL_LIBRARY +/** -- MAIN -- **/ +int +main (int argc, char* argv[]) { + initialize(argc, argv); + + uzbl.gui.scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (uzbl.gui.scrolled_win), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + + gtk_container_add (GTK_CONTAINER (uzbl.gui.scrolled_win), + GTK_WIDGET (uzbl.gui.web_view)); + + uzbl.gui.vbox = gtk_vbox_new (FALSE, 0); + + /* initial packing */ + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); + + if (uzbl.state.plug_mode) { + uzbl.gui.plug = create_plug (); + gtk_container_add (GTK_CONTAINER (uzbl.gui.plug), uzbl.gui.vbox); + gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug)); + /* in xembed mode the window has no unique id and thus + * socket/fifo names aren't unique either. + * we use a custom randomizer to create a random id + */ + struct timeval tv; + gettimeofday(&tv, NULL); + srand((unsigned int)tv.tv_sec*tv.tv_usec); + uzbl.xwin = rand(); + } else { + uzbl.gui.main_window = create_window (); + gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox); + gtk_widget_show_all (uzbl.gui.main_window); + uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window); + } + + uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL); + uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v); + uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL); + uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h); + gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v); + + if(!uzbl.state.instance_name) + uzbl.state.instance_name = itos((int)uzbl.xwin); + + GString *tmp = g_string_new(""); + g_string_printf(tmp, "%d", getpid()); + uzbl.info.pid_str = g_string_free(tmp, FALSE); + send_event(INSTANCE_START, uzbl.info.pid_str, NULL); + + if(uzbl.state.plug_mode) { + char *t = itos(gtk_plug_get_id(uzbl.gui.plug)); + send_event(PLUG_CREATED, t, NULL); + g_free(t); + } + + /* generate an event with a list of built in commands */ + builtins(); + + gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); + + if (uzbl.state.verbose) { + printf("Uzbl start location: %s\n", argv[0]); + if (uzbl.state.socket_id) + printf("plug_id %i\n", gtk_plug_get_id(uzbl.gui.plug)); + else + printf("window_id %i\n",(int) uzbl.xwin); + printf("pid %i\n", getpid ()); + printf("name: %s\n", uzbl.state.instance_name); + printf("commit: %s\n", uzbl.info.commit); + } + + /* Check uzbl is in window mode before getting/setting geometry */ + if (uzbl.gui.main_window) { + if(uzbl.gui.geometry) + cmd_set_geometry(); + else + retrieve_geometry(); + } + + gchar *uri_override = (uzbl.state.uri ? g_strdup(uzbl.state.uri) : NULL); + if (argc > 1 && !uzbl.state.uri) + uri_override = g_strdup(argv[1]); + gboolean verbose_override = uzbl.state.verbose; + + settings_init (); + + if (!uzbl.behave.show_status) + gtk_widget_hide(uzbl.gui.mainbar); + else + update_title(); + + /* WebInspector */ + set_up_inspector(); + + if (verbose_override > uzbl.state.verbose) + uzbl.state.verbose = verbose_override; + + if (uri_override) { + set_var_value("uri", uri_override); + g_free(uri_override); + } + + gtk_main (); + clean_up(); + + return EXIT_SUCCESS; +} +#endif + +/* vi: set et ts=4: */ diff --git a/src/uzbl-core.h b/src/uzbl-core.h new file mode 100644 index 0000000..df9eb1a --- /dev/null +++ b/src/uzbl-core.h @@ -0,0 +1,494 @@ +/* -*- c-basic-offset: 4; -*- + + * See LICENSE for license details + * + * Changelog: + * --------- + * + * (c) 2009 by Robert Manea + * - introduced struct concept + * + */ + +#define _POSIX_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LENGTH(x) (sizeof x / sizeof x[0]) + +/* gui elements */ +typedef struct { + GtkWidget* main_window; + gchar* geometry; + GtkPlug* plug; + GtkWidget* scrolled_win; + GtkWidget* vbox; + GtkWidget* mainbar; + GtkWidget* mainbar_label; + GtkScrollbar* scbar_v; // Horizontal and Vertical Scrollbar + GtkScrollbar* scbar_h; // (These are still hidden) + GtkAdjustment* bar_v; // Information about document length + GtkAdjustment* bar_h; // and scrolling position + WebKitWebView* web_view; + gchar* main_title; + gchar* icon; + + /* WebInspector */ + GtkWidget *inspector_window; + WebKitWebInspector *inspector; + + /* custom context menu item */ + GPtrArray *menu_items; +} GUI; + + +/* external communication*/ +enum { FIFO, SOCKET}; +typedef struct { + gchar *fifo_path; + gchar *socket_path; + /* stores (key)"variable name" -> (value)"pointer to var*/ + GHashTable *proto_var; + + gchar *sync_stdout; + GPtrArray *connect_chan; + GPtrArray *client_chan; +} Communication; + + +/* internal state */ +typedef struct { + gchar *uri; + gchar *config_file; + int socket_id; + char *instance_name; + gchar *selected_url; + gchar *last_selected_url; + gchar *executable_path; + gchar* keycmd; + gchar* searchtx; + gboolean verbose; + gboolean events_stdout; + GPtrArray *event_buffer; + gchar** connect_socket_names; + GdkEventButton *last_button; + gboolean plug_mode; +} State; + + +/* networking */ +typedef struct { + SoupSession *soup_session; + SoupLogger *soup_logger; + char *proxy_url; + char *useragent; + gint max_conns; + gint max_conns_host; +} Network; + + +/* behaviour */ +typedef struct { + gchar* status_format; + gchar* title_format_short; + gchar* title_format_long; + gchar* status_background; + gchar* fifo_dir; + gchar* socket_dir; + gchar* download_handler; + gchar* cookie_handler; + gchar* new_window; + gchar* default_font_family; + gchar* monospace_font_family; + gchar* sans_serif_font_family; + gchar* serif_font_family; + gchar* fantasy_font_family; + gchar* cursive_font_family; + gchar* scheme_handler; + gboolean show_status; + gboolean forward_keys; + gboolean status_top; + guint modmask; + guint http_debug; + gchar* shell_cmd; + guint view_source; + /* WebKitWebSettings exports */ + guint font_size; + guint monospace_size; + guint minimum_font_size; + gfloat zoom_level; + gboolean zoom_type; + guint disable_plugins; + guint disable_scripts; + guint autoload_img; + guint autoshrink_img; + guint enable_spellcheck; + guint enable_private; + guint print_bg; + gchar* style_uri; + guint resizable_txt; + gchar* default_encoding; + guint enforce_96dpi; + gchar *inject_html; + guint caret_browsing; + guint mode; + gchar* base_url; + gboolean print_version; + + /* command list: (key)name -> (value)Command */ + /* command list: (key)name -> (value)Command */ + GHashTable* commands; + /* event lookup: (key)event_id -> (value)event_name */ + GHashTable *event_lookup; +} Behaviour; + +/* javascript */ +typedef struct { + gboolean initialized; + JSClassDefinition classdef; + JSClassRef classref; +} Javascript; + +/* static information */ +typedef struct { + int webkit_major; + int webkit_minor; + int webkit_micro; + gchar *arch; + gchar *commit; + gchar *pid_str; +} Info; + +/* main uzbl data structure */ +typedef struct { + GUI gui; + State state; + Network net; + Behaviour behave; + Communication comm; + Javascript js; + Info info; + + Window xwin; +} UzblCore; + +/* Main Uzbl object */ +extern UzblCore uzbl; + +typedef void sigfunc(int); + +/* XDG Stuff */ +typedef struct { + gchar* environmental; + gchar* default_value; +} XDG_Var; + +/* uzbl variables */ +enum ptr_type {TYPE_INT, TYPE_STR, TYPE_FLOAT}; +typedef struct { + enum ptr_type type; + union { + int *i; + float *f; + gchar **s; + } ptr; + int dump; + int writeable; + /*@null@*/ void (*func)(void); +} uzbl_cmdprop; + +/* Functions */ +char * +itos(int val); + +char * +str_replace (const char* search, const char* replace, const char* string); + +gchar* +strfree(gchar *str); + +GArray* +read_file_by_line (const gchar *path); + +gchar* +parseenv (gchar* string); + +void +clean_up(void); + +void +catch_sigterm(int s); + +sigfunc * +setup_signal(int signe, sigfunc *shandler); + +gboolean +set_var_value(const gchar *name, gchar *val); + +void +load_uri_imp(gchar *uri); + +void +print(WebKitWebView *page, GArray *argv, GString *result); + +void +commands_hash(void); + +bool +file_exists (const char * filename); + +void +set_keycmd(); + +void +load_uri (WebKitWebView * web_view, GArray *argv, GString *result); + +void +new_window_load_uri (const gchar * uri); + +void +chain (WebKitWebView *page, GArray *argv, GString *result); + +void +close_uzbl (WebKitWebView *page, GArray *argv, GString *result); + +gboolean +run_command(const gchar *command, const guint npre, + const gchar **args, const gboolean sync, char **output_stdout); + +void +talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result); + +void +spawn(WebKitWebView *web_view, GArray *argv, GString *result); + +void +spawn_sh(WebKitWebView *web_view, GArray *argv, GString *result); + +void +spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result); + +void +spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result); + +void +parse_command(const char *cmd, const char *param, GString *result); + +void +parse_cmd_line(const char *ctl_line, GString *result); + +/*@null@*/ gchar* +build_stream_name(int type, const gchar *dir); + +gboolean +control_fifo(GIOChannel *gio, GIOCondition condition); + +/*@null@*/ gchar* +init_fifo(gchar *dir); + +gboolean +control_stdin(GIOChannel *gio, GIOCondition condition); + +void +create_stdin(); + +/*@null@*/ gchar* +init_socket(gchar *dir); + +gboolean +control_socket(GIOChannel *chan); + +gboolean +control_client_socket(GIOChannel *chan); + +void +update_title (void); + +gboolean +key_press_cb (GtkWidget* window, GdkEventKey* event); + +gboolean +key_release_cb (GtkWidget* window, GdkEventKey* event); + +void +run_keycmd(const gboolean key_ret); + +void +initialize (int argc, char *argv[]); + +void +create_browser (); + +GtkWidget* +create_mainbar (); + +GtkWidget* +create_window (); + +GtkPlug* +create_plug (); + +void +run_handler (const gchar *act, const gchar *args); + +/*@null@*/ gchar* +get_xdg_var (XDG_Var xdg); + +/*@null@*/ gchar* +find_xdg_file (int xdg_type, const char* filename); + +void +settings_init (); + +void +search_text (WebKitWebView *page, GArray *argv, const gboolean forward); + +void +search_forward_text (WebKitWebView *page, GArray *argv, GString *result); + +void +search_reverse_text (WebKitWebView *page, GArray *argv, GString *result); + +void +search_clear(WebKitWebView *page, GArray *argv, GString *result); + +void +dehilight (WebKitWebView *page, GArray *argv, GString *result); + +void +run_js (WebKitWebView * web_view, GArray *argv, GString *result); + +void +run_external_js (WebKitWebView * web_view, GArray *argv, GString *result); + +void +eval_js(WebKitWebView * web_view, gchar *script, GString *result); + +void handle_cookies (SoupSession *session, + SoupMessage *msg, + gpointer user_data); +void +save_cookies (SoupMessage *msg, gpointer user_data); + +void +set_var(WebKitWebView *page, GArray *argv, GString *result); + +void +act_dump_config(); + +void +act_dump_config_as_events(); + +void +dump_var_hash(gpointer k, gpointer v, gpointer ud); + +void +dump_key_hash(gpointer k, gpointer v, gpointer ud); + +void +dump_config(); + +void +dump_config_as_events(); + +void +retrieve_geometry(); + +void +event(WebKitWebView *page, GArray *argv, GString *result); + +void +init_connect_socket(); + +gboolean +remove_socket_from_array(GIOChannel *chan); + +void +menu_add(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_link(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_image(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_edit(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_separator(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_separator_link(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_separator_image(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_remove(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_remove_link(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_remove_image(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_remove_edit(WebKitWebView *page, GArray *argv, GString *result); + +gint +get_click_context(); + +void +hardcopy(WebKitWebView *page, GArray *argv, GString *result); + +void +include(WebKitWebView *page, GArray *argv, GString *result); + +void +builtins(); + +typedef void (*Command)(WebKitWebView*, GArray *argv, GString *result); + +typedef struct { + Command function; + gboolean no_split; +} CommandInfo; + +typedef struct { + gchar *name; + gchar *cmd; + gboolean issep; + guint context; +} MenuItem; + + +/* vi: set et ts=4: */ diff --git a/uzbl-browser b/uzbl-browser deleted file mode 100755 index d9b9752..0000000 --- a/uzbl-browser +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/sh -# this script implements a more useful out-of-the-box "browsing experience". -# it does so by combining uzbl-core with a set of "recommended" tools and practices. -# see docs for more info -# If you want to customize the behavior of the cookie-daemon or similar helper tools, -# copy them to your $XDG_DATA_HOME/uzbl/scripts/, edit them and update $PATH - -# Also, we assume existence of fifo/socket == correctly functioning cookie_daemon/event_manager. -# Checking correct functioning of the daemons here would be too complex here, and it's not implemented in uzbl-core either. -# But this shouldn't cause much problems.. - -PREFIX=/usr/local -if [ -z "$XDG_DATA_HOME" ] -then - export XDG_DATA_HOME=$HOME/.local/share -fi - -if [ -z "$XDG_CACHE_HOME" ] -then - export XDG_CACHE_HOME=$HOME/.cache -fi - -if [ -z "$XDG_CONFIG_HOME" ] -then - export XDG_CONFIG_HOME=$HOME/.config -fi - -# assure the relevant directories exist. -for dir in $XDG_CACHE_HOME/uzbl $XDG_DATA_HOME/uzbl $XDG_CONFIG_HOME/uzbl -do - if [ ! -d $dir ] - then - if ! mkdir -p $dir - then - echo "could not create $dir" >&2 - exit 2 - fi - fi -done -# if no config exists yet in the recommended location, put the default (recommended) config there -if [ ! -f $XDG_CONFIG_HOME/uzbl/config ] -then - if ! cp $PREFIX/share/uzbl/examples/config/uzbl/config $XDG_CONFIG_HOME/uzbl/config - then - echo "Could not copy default config to $XDG_CONFIG_HOME/uzbl/config" >&2 - exit 3 - fi -fi - -# Uncomment this for a slight speedup at the expense of not having -# stale cookie daemon sockets cleaned up. -#if [ ! -S $XDG_CACHE_HOME/uzbl/cookie_daemon_socket ] -#then - # if you want to customize it, copy to your $XDG_DATA_HOME/uzbl/scripts/ and update $PATH - uzbl-cookie-daemon -v start -#fi - -DAEMON_SOCKET=$XDG_CACHE_HOME/uzbl/event_daemon -DAEMON_PID=${DAEMON_SOCKET}.pid - -#if [ -f "$DAEMON_PID" ] -#then - uzbl-event-manager -va start -#fi - -uzbl-core "$@" --connect-socket $DAEMON_SOCKET diff --git a/uzbl-core.c b/uzbl-core.c deleted file mode 100644 index bc294b4..0000000 --- a/uzbl-core.c +++ /dev/null @@ -1,2637 +0,0 @@ -/* -*- c-basic-offset: 4; -*- */ -// Original code taken from the example webkit-gtk+ application. see notice below. -// Modified code is licensed under the GPL 3. See LICENSE file. - - -/* - * Copyright (C) 2006, 2007 Apple Inc. - * Copyright (C) 2007 Alp Toker - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "uzbl-core.h" -#include "callbacks.h" -#include "events.h" -#include "inspector.h" -#include "config.h" - -UzblCore uzbl; - -/* commandline arguments (set initial values for the state variables) */ -const -GOptionEntry entries[] = -{ - { "uri", 'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri, - "Uri to load at startup (equivalent to 'uzbl ' or 'set uri = URI' after uzbl has launched)", "URI" }, - { "verbose", 'v', 0, G_OPTION_ARG_NONE, &uzbl.state.verbose, - "Whether to print all messages or just errors.", NULL }, - { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, - "Name of the current instance (defaults to Xorg window id or random for GtkSocket mode)", "NAME" }, - { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, - "Path to config file or '-' for stdin", "FILE" }, - { "socket", 's', 0, G_OPTION_ARG_INT, &uzbl.state.socket_id, - "Socket ID", "SOCKET" }, - { "connect-socket", 0, 0, G_OPTION_ARG_STRING_ARRAY, &uzbl.state.connect_socket_names, - "Connect to server socket", "CSOCKET" }, - { "print-events", 'p', 0, G_OPTION_ARG_NONE, &uzbl.state.events_stdout, - "Whether to print events to stdout.", NULL }, - { "geometry", 'g', 0, G_OPTION_ARG_STRING, &uzbl.gui.geometry, - "Set window geometry (format: WIDTHxHEIGHT+-X+-Y or maximized)", "GEOMETRY" }, - { "version", 'V', 0, G_OPTION_ARG_NONE, &uzbl.behave.print_version, - "Print the version and exit", NULL }, - { NULL, 0, 0, 0, NULL, NULL, NULL } -}; - -XDG_Var XDG[] = -{ - { "XDG_CONFIG_HOME", "~/.config" }, - { "XDG_DATA_HOME", "~/.local/share" }, - { "XDG_CACHE_HOME", "~/.cache" }, - { "XDG_CONFIG_DIRS", "/etc/xdg" }, - { "XDG_DATA_DIRS", "/usr/local/share/:/usr/share/" }, -}; - -/* abbreviations to help keep the table's width humane */ -#define PTR_V_STR(var, d, fun) { .ptr.s = &(var), .type = TYPE_STR, .dump = d, .writeable = 1, .func = fun } -#define PTR_V_INT(var, d, fun) { .ptr.i = (int*)&(var), .type = TYPE_INT, .dump = d, .writeable = 1, .func = fun } -#define PTR_V_FLOAT(var, d, fun) { .ptr.f = &(var), .type = TYPE_FLOAT, .dump = d, .writeable = 1, .func = fun } -#define PTR_C_STR(var, fun) { .ptr.s = &(var), .type = TYPE_STR, .dump = 0, .writeable = 0, .func = fun } -#define PTR_C_INT(var, fun) { .ptr.i = (int*)&(var), .type = TYPE_INT, .dump = 0, .writeable = 0, .func = fun } -#define PTR_C_FLOAT(var, fun) { .ptr.f = &(var), .type = TYPE_FLOAT, .dump = 0, .writeable = 0, .func = fun } - -const struct var_name_to_ptr_t { - const char *name; - uzbl_cmdprop cp; -} var_name_to_ptr[] = { -/* variable name pointer to variable in code dump callback function */ -/* ---------------------------------------------------------------------------------------------- */ - { "uri", PTR_V_STR(uzbl.state.uri, 1, cmd_load_uri)}, - { "verbose", PTR_V_INT(uzbl.state.verbose, 1, NULL)}, - { "print_events", PTR_V_INT(uzbl.state.events_stdout, 1, NULL)}, - { "inject_html", PTR_V_STR(uzbl.behave.inject_html, 0, cmd_inject_html)}, - { "geometry", PTR_V_STR(uzbl.gui.geometry, 1, cmd_set_geometry)}, - { "keycmd", PTR_V_STR(uzbl.state.keycmd, 1, NULL)}, - { "show_status", PTR_V_INT(uzbl.behave.show_status, 1, cmd_set_status)}, - { "status_top", PTR_V_INT(uzbl.behave.status_top, 1, move_statusbar)}, - { "status_format", PTR_V_STR(uzbl.behave.status_format, 1, NULL)}, - { "status_background", PTR_V_STR(uzbl.behave.status_background, 1, NULL)}, - { "title_format_long", PTR_V_STR(uzbl.behave.title_format_long, 1, NULL)}, - { "title_format_short", PTR_V_STR(uzbl.behave.title_format_short, 1, NULL)}, - { "icon", PTR_V_STR(uzbl.gui.icon, 1, set_icon)}, - { "forward_keys", PTR_V_INT(uzbl.behave.forward_keys, 1, NULL)}, - { "download_handler", PTR_V_STR(uzbl.behave.download_handler, 1, NULL)}, - { "cookie_handler", PTR_V_STR(uzbl.behave.cookie_handler, 1, NULL)}, - { "new_window", PTR_V_STR(uzbl.behave.new_window, 1, NULL)}, - { "scheme_handler", PTR_V_STR(uzbl.behave.scheme_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)}, - { "shell_cmd", PTR_V_STR(uzbl.behave.shell_cmd, 1, NULL)}, - { "proxy_url", PTR_V_STR(uzbl.net.proxy_url, 1, set_proxy_url)}, - { "max_conns", PTR_V_INT(uzbl.net.max_conns, 1, cmd_max_conns)}, - { "max_conns_host", PTR_V_INT(uzbl.net.max_conns_host, 1, cmd_max_conns_host)}, - { "useragent", PTR_V_STR(uzbl.net.useragent, 1, cmd_useragent)}, - /* requires webkit >=1.1.14 */ - { "view_source", PTR_V_INT(uzbl.behave.view_source, 0, cmd_view_source)}, - - /* exported WebKitWebSettings properties */ - { "zoom_level", PTR_V_FLOAT(uzbl.behave.zoom_level, 1, cmd_zoom_level)}, - { "zoom_type", PTR_V_INT(uzbl.behave.zoom_type, 1, cmd_set_zoom_type)}, - { "font_size", PTR_V_INT(uzbl.behave.font_size, 1, cmd_font_size)}, - { "default_font_family", PTR_V_STR(uzbl.behave.default_font_family, 1, cmd_default_font_family)}, - { "monospace_font_family", PTR_V_STR(uzbl.behave.monospace_font_family, 1, cmd_monospace_font_family)}, - { "cursive_font_family", PTR_V_STR(uzbl.behave.cursive_font_family, 1, cmd_cursive_font_family)}, - { "sans_serif_font_family", PTR_V_STR(uzbl.behave.sans_serif_font_family, 1, cmd_sans_serif_font_family)}, - { "serif_font_family", PTR_V_STR(uzbl.behave.serif_font_family, 1, cmd_serif_font_family)}, - { "fantasy_font_family", PTR_V_STR(uzbl.behave.fantasy_font_family, 1, cmd_fantasy_font_family)}, - { "monospace_size", PTR_V_INT(uzbl.behave.monospace_size, 1, cmd_font_size)}, - { "minimum_font_size", PTR_V_INT(uzbl.behave.minimum_font_size, 1, cmd_minimum_font_size)}, - { "disable_plugins", PTR_V_INT(uzbl.behave.disable_plugins, 1, cmd_disable_plugins)}, - { "disable_scripts", PTR_V_INT(uzbl.behave.disable_scripts, 1, cmd_disable_scripts)}, - { "autoload_images", PTR_V_INT(uzbl.behave.autoload_img, 1, cmd_autoload_img)}, - { "autoshrink_images", PTR_V_INT(uzbl.behave.autoshrink_img, 1, cmd_autoshrink_img)}, - { "enable_spellcheck", PTR_V_INT(uzbl.behave.enable_spellcheck, 1, cmd_enable_spellcheck)}, - { "enable_private", PTR_V_INT(uzbl.behave.enable_private, 1, cmd_enable_private)}, - { "print_backgrounds", PTR_V_INT(uzbl.behave.print_bg, 1, cmd_print_bg)}, - { "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)}, - { "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)}, - - /* constants (not dumpable or writeable) */ - { "WEBKIT_MAJOR", PTR_C_INT(uzbl.info.webkit_major, NULL)}, - { "WEBKIT_MINOR", PTR_C_INT(uzbl.info.webkit_minor, NULL)}, - { "WEBKIT_MICRO", PTR_C_INT(uzbl.info.webkit_micro, NULL)}, - { "ARCH_UZBL", PTR_C_STR(uzbl.info.arch, NULL)}, - { "COMMIT", PTR_C_STR(uzbl.info.commit, NULL)}, - { "TITLE", PTR_C_STR(uzbl.gui.main_title, NULL)}, - { "SELECTED_URI", PTR_C_STR(uzbl.state.selected_url, NULL)}, - { "NAME", PTR_C_STR(uzbl.state.instance_name, NULL)}, - { "PID", PTR_C_STR(uzbl.info.pid_str, NULL)}, - - { NULL, {.ptr.s = NULL, .type = TYPE_INT, .dump = 0, .writeable = 0, .func = NULL}} -}; - -/* construct a hash from the var_name_to_ptr array for quick access */ -void -create_var_to_name_hash() { - const struct var_name_to_ptr_t *n2v_p = var_name_to_ptr; - uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal); - while(n2v_p->name) { - g_hash_table_insert(uzbl.comm.proto_var, - (gpointer) n2v_p->name, - (gpointer) &n2v_p->cp); - n2v_p++; - } -} - - -/* --- 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) { - /* 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; -} - -/* - * recurse == 1: don't expand '@(command)@' - * recurse == 2: don't expand '@@' -*/ -gchar * -expand(const char *s, guint recurse) { - uzbl_cmdprop *c; - enum exp_type etype; - char *end_simple_var = "^°!\"§$%&/()=?'`'+~*'#-.:,;@<>| \\{}[]¹²³¼½"; - char *ret = NULL; - char *vend = NULL; - GError *err = NULL; - gchar *cmd_stdout = NULL; - gchar *mycmd = NULL; - GString *buf = g_string_new(""); - GString *js_ret = g_string_new(""); - - while(s && *s) { - switch(*s) { - case '\\': - g_string_append_c(buf, *++s); - s++; - break; - - case '@': - etype = get_exp_type(s); - s++; - - switch(etype) { - case EXP_SIMPLE_VAR: - vend = strpbrk(s, end_simple_var); - if(!vend) vend = strchr(s, '\0'); - break; - case EXP_BRACED_VAR: - s++; - vend = strchr(s, '}'); - if(!vend) vend = strchr(s, '\0'); - break; - case EXP_EXPR: - s++; - vend = strstr(s, ")@"); - if(!vend) vend = strchr(s, '\0'); - break; - case EXP_JS: - s++; - vend = strstr(s, ">@"); - if(!vend) vend = strchr(s, '\0'); - break; - case EXP_ESCAPE: - s++; - vend = strstr(s, "]@"); - if(!vend) vend = strchr(s, '\0'); - break; - /*@notreached@*/ - case EXP_ERR: - break; - } - assert(vend); - - ret = g_strndup(s, vend-s); - - if(etype == EXP_SIMPLE_VAR || - etype == EXP_BRACED_VAR) { - if( (c = g_hash_table_lookup(uzbl.comm.proto_var, ret)) ) { - if(c->type == TYPE_STR && *c->ptr.s != NULL) { - g_string_append(buf, (gchar *)*c->ptr.s); - } - else if(c->type == TYPE_INT) { - g_string_append_printf(buf, "%d", *c->ptr.i); - } - else if(c->type == TYPE_FLOAT) { - g_string_append_printf(buf, "%f", *c->ptr.f); - } - } - - if(etype == EXP_SIMPLE_VAR) - s = vend; - else - s = vend+1; - } - else if(recurse != 1 && - etype == EXP_EXPR) { - - /* execute program directly */ - if(ret[0] == '+') { - mycmd = expand(ret+1, 1); - g_spawn_command_line_sync(mycmd, &cmd_stdout, NULL, NULL, &err); - g_free(mycmd); - } - /* execute program through shell, quote it first */ - else { - mycmd = expand(ret, 1); - gchar *quoted = g_shell_quote(mycmd); - gchar *tmp = g_strdup_printf("%s %s", - uzbl.behave.shell_cmd?uzbl.behave.shell_cmd:"/bin/sh -c", - quoted); - g_spawn_command_line_sync(tmp, &cmd_stdout, NULL, NULL, &err); - g_free(mycmd); - g_free(quoted); - g_free(tmp); - } - - if (err) { - g_printerr("error on running command: %s\n", err->message); - g_error_free (err); - } - else if (*cmd_stdout) { - size_t len = strlen(cmd_stdout); - - if(len > 0 && cmd_stdout[len-1] == '\n') - cmd_stdout[--len] = '\0'; /* strip trailing newline */ - - g_string_append(buf, cmd_stdout); - g_free(cmd_stdout); - } - s = vend+2; - } - else if(recurse != 2 && - etype == EXP_JS) { - - /* read JS from file */ - if(ret[0] == '+') { - GArray *tmp = g_array_new(TRUE, FALSE, sizeof(gchar *)); - mycmd = expand(ret+1, 2); - g_array_append_val(tmp, mycmd); - - run_external_js(uzbl.gui.web_view, tmp, js_ret); - g_array_free(tmp, TRUE); - } - /* JS from string */ - else { - mycmd = expand(ret, 2); - eval_js(uzbl.gui.web_view, mycmd, js_ret); - g_free(mycmd); - } - - if(js_ret->str) { - g_string_append(buf, js_ret->str); - g_string_free(js_ret, TRUE); - js_ret = g_string_new(""); - } - s = vend+2; - } - else if(etype == EXP_ESCAPE) { - mycmd = expand(ret, 0); - char *escaped = g_markup_escape_text(mycmd, strlen(mycmd)); - - g_string_append(buf, escaped); - - g_free(escaped); - g_free(mycmd); - s = vend+2; - } - - g_free(ret); - ret = NULL; - break; - - default: - g_string_append_c(buf, *s); - s++; - break; - } - } - g_string_free(js_ret, TRUE); - return g_string_free(buf, FALSE); -} - -char * -itos(int val) { - char tmp[20]; - - snprintf(tmp, sizeof(tmp), "%i", val); - return g_strdup(tmp); -} - -gchar* -strfree(gchar *str) { - g_free(str); - return NULL; -} - -gchar* -argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); } - -char * -str_replace (const char* search, const char* replace, const char* string) { - gchar **buf; - char *ret; - - if(!string) - return NULL; - - buf = g_strsplit (string, search, -1); - ret = g_strjoinv (replace, buf); - g_strfreev(buf); - - return ret; -} - -GArray* -read_file_by_line (const gchar *path) { - GIOChannel *chan = NULL; - gchar *readbuf = NULL; - gsize len; - GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*)); - int i = 0; - - chan = g_io_channel_new_file(path, "r", NULL); - if (chan) { - while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) { - const gchar* val = g_strdup (readbuf); - g_array_append_val (lines, val); - g_free (readbuf); - i ++; - } - - g_io_channel_unref (chan); - } else { - gchar *tmp = g_strdup_printf("File %s can not be read.", path); - send_event(COMMAND_ERROR, tmp, NULL); - g_free(tmp); - } - - return lines; -} - -/* search a PATH style string for an existing file+path combination */ -gchar* -find_existing_file(gchar* path_list) { - int i=0; - int cnt; - gchar **split; - gchar *tmp = NULL; - gchar *executable; - - if(!path_list) - return NULL; - - split = g_strsplit(path_list, ":", 0); - while(split[i]) - i++; - - 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(iweb_view, uzbl.state.last_button); - g_object_get(ht, "context", &context, NULL); - - return (gint)context; -} - -/* --- SIGNALS --- */ -int sigs[] = {SIGTERM, SIGINT, SIGSEGV, SIGILL, SIGFPE, SIGQUIT, SIGALRM, 0}; - -sigfunc* -setup_signal(int signr, sigfunc *shandler) { - struct sigaction nh, oh; - - nh.sa_handler = shandler; - sigemptyset(&nh.sa_mask); - nh.sa_flags = 0; - - if(sigaction(signr, &nh, &oh) < 0) - return SIG_ERR; - - return NULL; -} - -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) { - g_ptr_array_free(uzbl.state.event_buffer, TRUE); - uzbl.state.event_buffer = NULL; - } -} - -/* scroll a bar in a given direction */ -void -scroll (GtkAdjustment* bar, gchar *amount_str) { - gchar *end; - gdouble max_value; - - gdouble page_size = gtk_adjustment_get_page_size(bar); - gdouble value = gtk_adjustment_get_value(bar); - gdouble amount = g_ascii_strtod(amount_str, &end); - - if (*end == '%') - value += page_size * amount * 0.01; - else - value += amount; - - max_value = gtk_adjustment_get_upper(bar) - page_size; - - if (value > max_value) - value = max_value; /* don't scroll past the end of the page */ - - gtk_adjustment_set_value (bar, value); -} - -/* - * scroll vertical 20 - * scroll vertical 20% - * scroll vertical -40 - * scroll vertical begin - * scroll vertical end - * scroll horizontal 10 - * scroll horizontal -500 - * scroll horizontal begin - * scroll horizontal end - */ -void -scroll_cmd(WebKitWebView* page, GArray *argv, GString *result) { - (void) page; (void) result; - gchar *direction = g_array_index(argv, gchar*, 0); - gchar *argv1 = g_array_index(argv, gchar*, 1); - - if (g_strcmp0(direction, "horizontal") == 0) - { - if (g_strcmp0(argv1, "begin") == 0) - gtk_adjustment_set_value(uzbl.gui.bar_h, gtk_adjustment_get_lower(uzbl.gui.bar_h)); - else if (g_strcmp0(argv1, "end") == 0) - gtk_adjustment_set_value (uzbl.gui.bar_h, gtk_adjustment_get_upper(uzbl.gui.bar_h) - - gtk_adjustment_get_page_size(uzbl.gui.bar_h)); - else - scroll(uzbl.gui.bar_h, argv1); - } - else if (g_strcmp0(direction, "vertical") == 0) - { - if (g_strcmp0(argv1, "begin") == 0) - gtk_adjustment_set_value(uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v)); - else if (g_strcmp0(argv1, "end") == 0) - gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) - - gtk_adjustment_get_page_size(uzbl.gui.bar_v)); - else - scroll(uzbl.gui.bar_v, argv1); - } - else - if(uzbl.state.verbose) - puts("Unrecognized scroll format"); -} - - -/* VIEW funcs (little webkit wrappers) */ -#define VIEWFUNC(name) void view_##name(WebKitWebView *page, GArray *argv, GString *result){(void)argv; (void)result; webkit_web_view_##name(page);} -VIEWFUNC(reload) -VIEWFUNC(reload_bypass_cache) -VIEWFUNC(stop_loading) -VIEWFUNC(zoom_in) -VIEWFUNC(zoom_out) -VIEWFUNC(go_back) -VIEWFUNC(go_forward) -#undef VIEWFUNC - -/* -- command to callback/function map for things we cannot attach to any signals */ -struct {const char *key; CommandInfo value;} cmdlist[] = -{ /* key function no_split */ - { "back", {view_go_back, 0} }, - { "forward", {view_go_forward, 0} }, - { "scroll", {scroll_cmd, 0} }, - { "reload", {view_reload, 0}, }, - { "reload_ign_cache", {view_reload_bypass_cache, 0} }, - { "stop", {view_stop_loading, 0}, }, - { "zoom_in", {view_zoom_in, 0}, }, //Can crash (when max zoom reached?). - { "zoom_out", {view_zoom_out, 0}, }, - { "toggle_zoom_type", {toggle_zoom_type, 0}, }, - { "uri", {load_uri, TRUE} }, - { "js", {run_js, TRUE} }, - { "script", {run_external_js, 0} }, - { "toggle_status", {toggle_status_cb, 0} }, - { "spawn", {spawn, 0} }, - { "sync_spawn", {spawn_sync, 0} }, // needed for cookie handler - { "sh", {spawn_sh, 0} }, - { "sync_sh", {spawn_sh_sync, 0} }, // needed for cookie handler - { "talk_to_socket", {talk_to_socket, 0} }, - { "exit", {close_uzbl, 0} }, - { "search", {search_forward_text, TRUE} }, - { "search_reverse", {search_reverse_text, TRUE} }, - { "search_clear", {search_clear, TRUE} }, - { "dehilight", {dehilight, 0} }, - { "set", {set_var, TRUE} }, - { "dump_config", {act_dump_config, 0} }, - { "dump_config_as_events", {act_dump_config_as_events, 0} }, - { "chain", {chain, 0} }, - { "print", {print, TRUE} }, - { "event", {event, TRUE} }, - { "request", {event, TRUE} }, - { "menu_add", {menu_add, TRUE} }, - { "menu_link_add", {menu_add_link, TRUE} }, - { "menu_image_add", {menu_add_image, TRUE} }, - { "menu_editable_add", {menu_add_edit, TRUE} }, - { "menu_separator", {menu_add_separator, TRUE} }, - { "menu_link_separator", {menu_add_separator_link, TRUE} }, - { "menu_image_separator", {menu_add_separator_image, TRUE}}, - { "menu_editable_separator", {menu_add_separator_edit, TRUE} }, - { "menu_remove", {menu_remove, TRUE} }, - { "menu_link_remove", {menu_remove_link, TRUE} }, - { "menu_image_remove", {menu_remove_image, TRUE} }, - { "menu_editable_remove", {menu_remove_edit, TRUE} }, - { "hardcopy", {hardcopy, TRUE} }, - { "include", {include, TRUE} } -}; - -void -commands_hash(void) -{ - unsigned int i; - uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal); - - for (i = 0; i < LENGTH(cmdlist); i++) - g_hash_table_insert(uzbl.behave.commands, (gpointer) cmdlist[i].key, &cmdlist[i].value); -} - -void -builtins() { - unsigned int i, - len = LENGTH(cmdlist); - GString *command_list = g_string_new(""); - - for (i = 0; i < len; i++) { - g_string_append(command_list, cmdlist[i].key); - g_string_append_c(command_list, ' '); - } - - send_event(BUILTINS, command_list->str, NULL); - g_string_free(command_list, TRUE); -} - -/* -- CORE FUNCTIONS -- */ - -bool -file_exists (const char * filename) { - return (access(filename, F_OK) == 0); -} - -void -set_var(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; (void) result; - - if(!argv_idx(argv, 0)) - return; - - gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2); - if (split[0] != NULL) { - gchar *value = parseenv(split[1] ? g_strchug(split[1]) : " "); - set_var_value(g_strstrip(split[0]), value); - g_free(value); - } - g_strfreev(split); -} - -void -add_to_menu(GArray *argv, guint context) { - GUI *g = &uzbl.gui; - MenuItem *m; - gchar *item_cmd = NULL; - - if(!argv_idx(argv, 0)) - return; - - gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2); - if(!g->menu_items) - g->menu_items = g_ptr_array_new(); - - if(split[1]) - item_cmd = g_strdup(split[1]); - - if(split[0]) { - m = malloc(sizeof(MenuItem)); - m->name = g_strdup(split[0]); - m->cmd = g_strdup(item_cmd?item_cmd:""); - m->context = context; - m->issep = FALSE; - g_ptr_array_add(g->menu_items, m); - } - else - g_free(item_cmd); - - g_strfreev(split); -} - -void -menu_add(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); - -} - -void -menu_add_link(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); -} - -void -menu_add_image(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); -} - -void -menu_add_edit(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); -} - -void -add_separator_to_menu(GArray *argv, guint context) { - GUI *g = &uzbl.gui; - MenuItem *m; - gchar *sep_name; - - if(!g->menu_items) - g->menu_items = g_ptr_array_new(); - - if(!argv_idx(argv, 0)) - return; - else - sep_name = argv_idx(argv, 0); - - m = malloc(sizeof(MenuItem)); - m->name = g_strdup(sep_name); - m->cmd = NULL; - m->context = context; - m->issep = TRUE; - g_ptr_array_add(g->menu_items, m); -} - -void -menu_add_separator(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); -} - -void -menu_add_separator_link(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); -} -void -menu_add_separator_image(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); -} - -void -menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); -} - -void -remove_from_menu(GArray *argv, guint context) { - GUI *g = &uzbl.gui; - MenuItem *mi; - gchar *name = NULL; - guint i=0; - - if(!g->menu_items) - return; - - if(!argv_idx(argv, 0)) - return; - else - name = argv_idx(argv, 0); - - for(i=0; i < g->menu_items->len; i++) { - mi = g_ptr_array_index(g->menu_items, i); - - if((context == mi->context) && !strcmp(name, mi->name)) { - g_free(mi->name); - g_free(mi->cmd); - g_free(mi); - g_ptr_array_remove_index(g->menu_items, i); - } - } -} - -void -menu_remove(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); -} - -void -menu_remove_link(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); -} - -void -menu_remove_image(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); -} - -void -menu_remove_edit(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - - remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); -} - -void -event(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; (void) result; - GString *event_name; - gchar **split = NULL; - - if(!argv_idx(argv, 0)) - return; - - split = g_strsplit(argv_idx(argv, 0), " ", 2); - if(split[0]) - event_name = g_string_ascii_up(g_string_new(split[0])); - else - return; - - send_event(0, split[1]?split[1]:"", event_name->str); - - g_string_free(event_name, TRUE); - g_strfreev(split); -} - -void -print(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; (void) result; - gchar* buf; - - buf = expand(argv_idx(argv, 0), 0); - g_string_assign(result, buf); - g_free(buf); -} - -void -hardcopy(WebKitWebView *page, GArray *argv, GString *result) { - (void) argv; - (void) result; - - webkit_web_frame_print(webkit_web_view_get_main_frame(page)); -} - -void -include(WebKitWebView *page, GArray *argv, GString *result) { - (void) page; - (void) result; - gchar *pe = NULL, - *path = NULL, - *line; - int i=0; - - if(!argv_idx(argv, 0)) - return; - - pe = parseenv(argv_idx(argv, 0)); - if((path = find_existing_file(pe))) { - GArray* lines = read_file_by_line(path); - - while ((line = g_array_index(lines, gchar*, i))) { - parse_cmd_line (line, NULL); - i++; - g_free (line); - } - g_array_free (lines, TRUE); - - send_event(FILE_INCLUDED, path, NULL); - g_free(path); - } - g_free(pe); -} - -void -act_dump_config() { - dump_config(); -} - -void -act_dump_config_as_events() { - dump_config_as_events(); -} - -void -load_uri (WebKitWebView *web_view, GArray *argv, GString *result) { - (void) web_view; (void) result; - load_uri_imp (argv_idx (argv, 0)); -} - -/* Javascript*/ - -JSValueRef -js_run_command (JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, - size_t argumentCount, const JSValueRef arguments[], - JSValueRef* exception) { - (void) function; - (void) thisObject; - (void) exception; - - JSStringRef js_result_string; - GString *result = g_string_new(""); - - if (argumentCount >= 1) { - JSStringRef arg = JSValueToStringCopy(ctx, arguments[0], NULL); - size_t arg_size = JSStringGetMaximumUTF8CStringSize(arg); - char ctl_line[arg_size]; - JSStringGetUTF8CString(arg, ctl_line, arg_size); - - parse_cmd_line(ctl_line, result); - - JSStringRelease(arg); - } - js_result_string = JSStringCreateWithUTF8CString(result->str); - - g_string_free(result, TRUE); - - return JSValueMakeString(ctx, js_result_string); -} - -JSStaticFunction js_static_functions[] = { - {"run", js_run_command, kJSPropertyAttributeNone}, -}; - -void -js_init() { - /* This function creates the class and its definition, only once */ - if (!uzbl.js.initialized) { - /* it would be pretty cool to make this dynamic */ - uzbl.js.classdef = kJSClassDefinitionEmpty; - uzbl.js.classdef.staticFunctions = js_static_functions; - - uzbl.js.classref = JSClassCreate(&uzbl.js.classdef); - } -} - - -void -eval_js(WebKitWebView * web_view, gchar *script, GString *result) { - WebKitWebFrame *frame; - JSGlobalContextRef context; - JSObjectRef globalobject; - JSStringRef var_name; - - JSStringRef js_script; - JSValueRef js_result; - JSValueRef js_exc = NULL; - JSStringRef js_result_string; - size_t js_result_size; - - js_init(); - - frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(web_view)); - context = webkit_web_frame_get_global_context(frame); - globalobject = JSContextGetGlobalObject(context); - - /* uzbl javascript namespace */ - var_name = JSStringCreateWithUTF8CString("Uzbl"); - JSObjectSetProperty(context, globalobject, var_name, - JSObjectMake(context, uzbl.js.classref, NULL), - kJSClassAttributeNone, NULL); - - /* evaluate the script and get return value*/ - js_script = JSStringCreateWithUTF8CString(script); - js_result = JSEvaluateScript(context, js_script, globalobject, NULL, 0, &js_exc); - if (js_result && !JSValueIsUndefined(context, js_result)) { - js_result_string = JSValueToStringCopy(context, js_result, NULL); - js_result_size = JSStringGetMaximumUTF8CStringSize(js_result_string); - - if (js_result_size) { - char js_result_utf8[js_result_size]; - JSStringGetUTF8CString(js_result_string, js_result_utf8, js_result_size); - g_string_assign(result, js_result_utf8); - } - - JSStringRelease(js_result_string); - } - else if (js_exc && JSValueIsObject(context, js_exc)) { - size_t size; - JSStringRef prop, val; - JSObjectRef exc = JSValueToObject(context, js_exc, NULL); - - printf("Exception occured while executing script:\n"); - - /* Print line */ - prop = JSStringCreateWithUTF8CString("line"); - val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL); - size = JSStringGetMaximumUTF8CStringSize(val); - if(size) { - char cstr[size]; - JSStringGetUTF8CString(val, cstr, size); - printf("At line %s: ", cstr); - } - JSStringRelease(prop); - JSStringRelease(val); - - /* Print message */ - val = JSValueToStringCopy(context, exc, NULL); - size = JSStringGetMaximumUTF8CStringSize(val); - if(size) { - char cstr[size]; - JSStringGetUTF8CString(val, cstr, size); - printf("%s\n", cstr); - } - JSStringRelease(val); - } - - /* cleanup */ - JSObjectDeleteProperty(context, globalobject, var_name, NULL); - - JSStringRelease(var_name); - JSStringRelease(js_script); -} - -void -run_js (WebKitWebView * web_view, GArray *argv, GString *result) { - if (argv_idx(argv, 0)) - eval_js(web_view, argv_idx(argv, 0), result); -} - -void -run_external_js (WebKitWebView * web_view, GArray *argv, GString *result) { - (void) result; - gchar *path = NULL; - - if (argv_idx(argv, 0) && - ((path = find_existing_file(argv_idx(argv, 0)))) ) { - GArray* lines = read_file_by_line (path); - gchar* js = NULL; - int i = 0; - gchar* line; - - while ((line = g_array_index(lines, gchar*, i))) { - if (js == NULL) { - js = g_strdup (line); - } else { - gchar* newjs = g_strconcat (js, line, NULL); - js = newjs; - } - i ++; - g_free (line); - } - - if (uzbl.state.verbose) - printf ("External JavaScript file %s loaded\n", argv_idx(argv, 0)); - - gchar* newjs = str_replace("%s", argv_idx (argv, 1)?argv_idx (argv, 1):"", js); - g_free (js); - js = newjs; - - eval_js (web_view, js, result); - g_free (js); - g_array_free (lines, TRUE); - g_free(path); - } -} - -void -search_text (WebKitWebView *page, GArray *argv, const gboolean forward) { - if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0')) { - if (g_strcmp0 (uzbl.state.searchtx, argv_idx(argv, 0)) != 0) { - webkit_web_view_unmark_text_matches (page); - webkit_web_view_mark_text_matches (page, argv_idx(argv, 0), FALSE, 0); - uzbl.state.searchtx = g_strdup(argv_idx(argv, 0)); - } - } - - - if (uzbl.state.searchtx) { - if (uzbl.state.verbose) - printf ("Searching: %s\n", uzbl.state.searchtx); - webkit_web_view_set_highlight_text_matches (page, TRUE); - webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE); - } -} - -void -search_clear(WebKitWebView *page, GArray *argv, GString *result) { - (void) argv; - (void) result; - - webkit_web_view_unmark_text_matches (page); - if(uzbl.state.searchtx) { - g_free(uzbl.state.searchtx); - uzbl.state.searchtx = NULL; - } -} - -void -search_forward_text (WebKitWebView *page, GArray *argv, GString *result) { - (void) result; - search_text(page, argv, TRUE); -} - -void -search_reverse_text (WebKitWebView *page, GArray *argv, GString *result) { - (void) result; - search_text(page, argv, FALSE); -} - -void -dehilight (WebKitWebView *page, GArray *argv, GString *result) { - (void) argv; (void) result; - webkit_web_view_set_highlight_text_matches (page, FALSE); -} - - -void -new_window_load_uri (const gchar * uri) { - if (uzbl.behave.new_window) { - GString *s = g_string_new (""); - g_string_printf(s, "'%s'", uri); - run_handler(uzbl.behave.new_window, s->str); - send_event(NEW_WINDOW, s->str, NULL); - return; - } - GString* to_execute = g_string_new (""); - g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri); - int i; - for (i = 0; entries[i].long_name != NULL; i++) { - if ((entries[i].arg == G_OPTION_ARG_STRING) && - !strcmp(entries[i].long_name,"uri") && - !strcmp(entries[i].long_name,"name")) { - gchar** str = (gchar**)entries[i].arg_data; - if (*str!=NULL) - g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str); - } - else if(entries[i].arg == G_OPTION_ARG_STRING_ARRAY) { - int j; - gchar **str = *((gchar ***)entries[i].arg_data); - for(j=0; str[j]; j++) - g_string_append_printf(to_execute, " --%s '%s'", entries[i].long_name, str[j]); - } - } - if (uzbl.state.verbose) - printf("\n%s\n", to_execute->str); - g_spawn_command_line_async (to_execute->str, NULL); - /* TODO: should we just report the uri as event detail? */ - send_event(NEW_WINDOW, to_execute->str, NULL); - g_string_free (to_execute, TRUE); -} - -void -chain (WebKitWebView *page, GArray *argv, GString *result) { - (void) page; (void) result; - gchar *a = NULL; - gchar **parts = NULL; - guint i = 0; - while ((a = argv_idx(argv, i++))) { - parts = g_strsplit (a, " ", 2); - if (parts[0]) - parse_command(parts[0], parts[1], result); - g_strfreev (parts); - } -} - -void -close_uzbl (WebKitWebView *page, GArray *argv, GString *result) { - (void)page; - (void)argv; - (void)result; - gtk_main_quit (); -} - -void -sharg_append(GArray *a, const gchar *str) { - const gchar *s = (str ? str : ""); - g_array_append_val(a, s); -} - -// make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc) -gboolean -run_command (const gchar *command, const guint npre, const gchar **args, - const gboolean sync, char **output_stdout) { - //command [args] - GError *err = NULL; - - GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); - gchar *pid = itos(getpid()); - gchar *xwin = itos(uzbl.xwin); - guint i; - sharg_append(a, command); - for (i = 0; i < npre; i++) /* add n args before the default vars */ - sharg_append(a, args[i]); - sharg_append(a, uzbl.state.config_file); - sharg_append(a, pid); - sharg_append(a, xwin); - sharg_append(a, uzbl.comm.fifo_path); - sharg_append(a, uzbl.comm.socket_path); - sharg_append(a, uzbl.state.uri); - sharg_append(a, uzbl.gui.main_title); - - for (i = npre; i < g_strv_length((gchar**)args); i++) - sharg_append(a, args[i]); - - gboolean result; - if (sync) { - if (*output_stdout) *output_stdout = strfree(*output_stdout); - - result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, - NULL, NULL, output_stdout, NULL, NULL, &err); - } else - result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, - NULL, NULL, NULL, &err); - - if (uzbl.state.verbose) { - GString *s = g_string_new("spawned:"); - for (i = 0; i < (a->len); i++) { - gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i)); - g_string_append_printf(s, " %s", qarg); - g_free (qarg); - } - g_string_append_printf(s, " -- result: %s", (result ? "true" : "false")); - printf("%s\n", s->str); - g_string_free(s, TRUE); - if(output_stdout) { - printf("Stdout: %s\n", *output_stdout); - } - } - if (err) { - g_printerr("error on run_command: %s\n", err->message); - g_error_free (err); - } - g_free (pid); - g_free (xwin); - g_array_free (a, TRUE); - return result; -} - -/*@null@*/ gchar** -split_quoted(const gchar* src, const gboolean unquote) { - /* split on unquoted space, return array of strings; - remove a layer of quotes and backslashes if unquote */ - if (!src) return NULL; - - gboolean dq = FALSE; - gboolean sq = FALSE; - GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); - GString *s = g_string_new (""); - const gchar *p; - gchar **ret; - gchar *dup; - for (p = src; *p != '\0'; p++) { - if ((*p == '\\') && unquote) g_string_append_c(s, *++p); - else if (*p == '\\') { g_string_append_c(s, *p++); - g_string_append_c(s, *p); } - else if ((*p == '"') && unquote && !sq) dq = !dq; - else if (*p == '"' && !sq) { g_string_append_c(s, *p); - dq = !dq; } - else if ((*p == '\'') && unquote && !dq) sq = !sq; - else if (*p == '\'' && !dq) { g_string_append_c(s, *p); - sq = ! sq; } - else if ((*p == ' ') && !dq && !sq) { - dup = g_strdup(s->str); - g_array_append_val(a, dup); - g_string_truncate(s, 0); - } else g_string_append_c(s, *p); - } - dup = g_strdup(s->str); - g_array_append_val(a, dup); - ret = (gchar**)a->data; - g_array_free (a, FALSE); - g_string_free (s, TRUE); - return ret; -} - -void -spawn(WebKitWebView *web_view, GArray *argv, GString *result) { - (void)web_view; (void)result; - 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)))) ) { - run_command(path, 0, - ((const gchar **) (argv->data + sizeof(gchar*))), - FALSE, NULL); - g_free(path); - } -} - -void -spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result) { - (void)web_view; (void)result; - gchar *path = NULL; - - 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_sh(WebKitWebView *web_view, GArray *argv, GString *result) { - (void)web_view; (void)result; - if (!uzbl.behave.shell_cmd) { - g_printerr ("spawn_sh: 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]); - - if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL); - g_free (spacer); - g_strfreev (cmd); -} - -void -spawn_sh_sync(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]); - - 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 -talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result) { - (void)web_view; (void)result; - - int fd, len; - struct sockaddr_un sa; - char* sockpath; - ssize_t ret; - struct pollfd pfd; - struct iovec* iov; - guint i; - - if(uzbl.comm.sync_stdout) uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); - - /* This function could be optimised by storing a hash table of socket paths - and associated connected file descriptors rather than closing and - re-opening for every call. Also we could launch a script if socket connect - fails. */ - - /* First element argv[0] is path to socket. Following elements are tokens to - write to the socket. We write them as a single packet with each token - separated by an ASCII nul (\0). */ - if(argv->len < 2) { - g_printerr("talk_to_socket called with only %d args (need at least two).\n", - (int)argv->len); - return; - } - - /* copy socket path, null terminate result */ - sockpath = g_array_index(argv, char*, 0); - g_strlcpy(sa.sun_path, sockpath, sizeof(sa.sun_path)); - sa.sun_family = AF_UNIX; - - /* create socket file descriptor and connect it to path */ - fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); - if(fd == -1) { - g_printerr("talk_to_socket: creating socket failed (%s)\n", strerror(errno)); - return; - } - if(connect(fd, (struct sockaddr*)&sa, sizeof(sa))) { - g_printerr("talk_to_socket: connect failed (%s)\n", strerror(errno)); - close(fd); - return; - } - - /* build request vector */ - iov = g_malloc(sizeof(struct iovec) * (argv->len - 1)); - if(!iov) { - g_printerr("talk_to_socket: unable to allocated memory for token vector\n"); - close(fd); - return; - } - for(i = 1; i < argv->len; ++i) { - iov[i - 1].iov_base = g_array_index(argv, char*, i); - iov[i - 1].iov_len = strlen(iov[i - 1].iov_base) + 1; /* string plus \0 */ - } - - /* write request */ - ret = writev(fd, iov, argv->len - 1); - g_free(iov); - if(ret == -1) { - g_printerr("talk_to_socket: write failed (%s)\n", strerror(errno)); - close(fd); - return; - } - - /* wait for a response, with a 500ms timeout */ - pfd.fd = fd; - pfd.events = POLLIN; - while(1) { - ret = poll(&pfd, 1, 500); - if(ret == 1) break; - if(ret == 0) errno = ETIMEDOUT; - if(errno == EINTR) continue; - g_printerr("talk_to_socket: poll failed while waiting for input (%s)\n", - strerror(errno)); - close(fd); - return; - } - - /* get length of response */ - if(ioctl(fd, FIONREAD, &len) == -1) { - g_printerr("talk_to_socket: cannot find daemon response length, " - "ioctl failed (%s)\n", strerror(errno)); - close(fd); - return; - } - - /* if there is a response, read it */ - if(len) { - uzbl.comm.sync_stdout = g_malloc(len + 1); - if(!uzbl.comm.sync_stdout) { - g_printerr("talk_to_socket: failed to allocate %d bytes\n", len); - close(fd); - return; - } - uzbl.comm.sync_stdout[len] = 0; /* ensure result is null terminated */ - - ret = read(fd, uzbl.comm.sync_stdout, len); - if(ret == -1) { - g_printerr("talk_to_socket: failed to read from socket (%s)\n", - strerror(errno)); - close(fd); - return; - } - } - - /* clean up */ - close(fd); - return; -} - -void -parse_command(const char *cmd, const char *param, GString *result) { - CommandInfo *c; - GString *tmp = g_string_new(""); - - if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) { - guint i; - gchar **par = split_quoted(param, TRUE); - GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); - - if (c->no_split) { /* don't split */ - sharg_append(a, param); - } else if (par) { - for (i = 0; i < g_strv_length(par); i++) - sharg_append(a, par[i]); - } - - if (result == NULL) { - GString *result_print = g_string_new(""); - - c->function(uzbl.gui.web_view, a, result_print); - if (result_print->len) - printf("%*s\n", (int)result_print->len, result_print->str); - - g_string_free(result_print, TRUE); - } else { - c->function(uzbl.gui.web_view, a, result); - } - g_strfreev (par); - g_array_free (a, TRUE); - - if(strcmp("set", cmd) && - strcmp("event", cmd) && - strcmp("request", cmd)) { - g_string_printf(tmp, "%s %s", cmd, param?param:""); - send_event(COMMAND_EXECUTED, tmp->str, NULL); - g_string_free(tmp, TRUE); - } - } - else { - gchar *tmp = g_strdup_printf("%s %s", cmd, param?param:""); - send_event(COMMAND_ERROR, tmp, NULL); - g_free(tmp); - } -} - - -void -move_statusbar() { - if (!uzbl.gui.scrolled_win && - !uzbl.gui.mainbar) - return; - - g_object_ref(uzbl.gui.scrolled_win); - g_object_ref(uzbl.gui.mainbar); - gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win); - gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar); - - if(uzbl.behave.status_top) { - gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); - } - else { - gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); - } - g_object_unref(uzbl.gui.scrolled_win); - g_object_unref(uzbl.gui.mainbar); - gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); - return; -} - -gboolean -set_var_value(const gchar *name, gchar *val) { - uzbl_cmdprop *c = NULL; - char *endp = NULL; - char *buf = NULL; - char *invalid_chars = "^°!\"§$%&/()=?'`'+~*'#-.:,;@<>| \\{}[]¹²³¼½"; - GString *msg; - - if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) { - if(!c->writeable) return FALSE; - - msg = g_string_new(name); - - /* check for the variable type */ - if (c->type == TYPE_STR) { - buf = g_strdup(val); - g_free(*c->ptr.s); - *c->ptr.s = buf; - g_string_append_printf(msg, " str %s", buf); - - } else if(c->type == TYPE_INT) { - *c->ptr.i = (int)strtoul(val, &endp, 10); - g_string_append_printf(msg, " int %d", *c->ptr.i); - - } else if (c->type == TYPE_FLOAT) { - *c->ptr.f = strtod(val, &endp); - g_string_append_printf(msg, " float %f", *c->ptr.f); - } - - send_event(VARIABLE_SET, msg->str, NULL); - g_string_free(msg,TRUE); - - /* invoke a command specific function */ - if(c->func) c->func(); - } else { - /* check wether name violates our naming scheme */ - if(strpbrk(name, invalid_chars)) { - if (uzbl.state.verbose) - printf("Invalid variable name\n"); - return FALSE; - } - - /* custom vars */ - c = g_malloc(sizeof(uzbl_cmdprop)); - c->type = TYPE_STR; - c->dump = 0; - c->func = NULL; - c->writeable = 1; - buf = g_strdup(val); - c->ptr.s = g_malloc(sizeof(char *)); - *c->ptr.s = buf; - g_hash_table_insert(uzbl.comm.proto_var, - g_strdup(name), (gpointer) c); - - msg = g_string_new(name); - g_string_append_printf(msg, " str %s", buf); - send_event(VARIABLE_SET, msg->str, NULL); - g_string_free(msg,TRUE); - } - update_title(); - return TRUE; -} - -void -parse_cmd_line(const char *ctl_line, GString *result) { - size_t len=0; - gchar *ctlstrip = NULL; - gchar *exp_line = NULL; - gchar *work_string = NULL; - - work_string = g_strdup(ctl_line); - - /* strip trailing newline */ - len = strlen(ctl_line); - if (work_string[len - 1] == '\n') - ctlstrip = g_strndup(work_string, len - 1); - else - ctlstrip = g_strdup(work_string); - g_free(work_string); - - if( strcmp(g_strchug(ctlstrip), "") && - strcmp(exp_line = expand(ctlstrip, 0), "") - ) { - /* ignore comments */ - if((exp_line[0] == '#')) - ; - - /* parse a command */ - else { - gchar **tokens = NULL; - - tokens = g_strsplit(exp_line, " ", 2); - parse_command(tokens[0], tokens[1], result); - g_strfreev(tokens); - } - g_free(exp_line); - } - - g_free(ctlstrip); -} - - -/*@null@*/ gchar* -build_stream_name(int type, const gchar* dir) { - State *s = &uzbl.state; - gchar *str = NULL; - - if (type == FIFO) { - str = g_strdup_printf - ("%s/uzbl_fifo_%s", dir, s->instance_name); - } else if (type == SOCKET) { - str = g_strdup_printf - ("%s/uzbl_socket_%s", dir, s->instance_name); - } - return str; -} - -gboolean -control_fifo(GIOChannel *gio, GIOCondition condition) { - if (uzbl.state.verbose) - printf("triggered\n"); - gchar *ctl_line; - GIOStatus ret; - GError *err = NULL; - - if (condition & G_IO_HUP) - g_error ("Fifo: Read end of pipe died!\n"); - - if(!gio) - g_error ("Fifo: GIOChannel broke\n"); - - ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err); - if (ret == G_IO_STATUS_ERROR) { - g_error ("Fifo: Error reading: %s\n", err->message); - g_error_free (err); - } - - parse_cmd_line(ctl_line, NULL); - g_free(ctl_line); - - return TRUE; -} - -/*@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 */ - if (unlink(uzbl.comm.fifo_path) == -1) - g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path); - g_free(uzbl.comm.fifo_path); - 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); - } 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); - - /* if we got this far, there was an error; cleanup */ - if (error) g_error_free (error); - g_free(dir); - g_free(path); - return NULL; -} - -gboolean -control_stdin(GIOChannel *gio, GIOCondition condition) { - (void) condition; - gchar *ctl_line = NULL; - GIOStatus ret; - - ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL); - if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) ) - return FALSE; - - parse_cmd_line(ctl_line, NULL); - g_free(ctl_line); - - return TRUE; -} - -void -create_stdin () { - GIOChannel *chan = NULL; - GError *error = NULL; - - chan = g_io_channel_unix_new(fileno(stdin)); - if (chan) { - if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) { - g_error ("Stdin: could not add watch\n"); - } else { - if (uzbl.state.verbose) - printf ("Stdin: watch added successfully\n"); - } - } else { - g_error ("Stdin: Error while opening: %s\n", error->message); - } - if (error) g_error_free (error); -} - -gboolean -remove_socket_from_array(GIOChannel *chan) { - gboolean ret = 0; - - /* TODO: Do wee need to manually free the IO channel or is this - * happening implicitly on unref? - */ - ret = g_ptr_array_remove_fast(uzbl.comm.connect_chan, chan); - if(!ret) - ret = g_ptr_array_remove_fast(uzbl.comm.client_chan, chan); - - return ret; -} - -gboolean -control_socket(GIOChannel *chan) { - struct sockaddr_un remote; - unsigned int t = sizeof(remote); - GIOChannel *iochan; - int clientsock; - - clientsock = accept (g_io_channel_unix_get_fd(chan), - (struct sockaddr *) &remote, &t); - - if(!uzbl.comm.client_chan) - uzbl.comm.client_chan = g_ptr_array_new(); - - if ((iochan = g_io_channel_unix_new(clientsock))) { - g_io_channel_set_encoding(iochan, NULL, NULL); - g_io_add_watch(iochan, G_IO_IN|G_IO_HUP, - (GIOFunc) control_client_socket, iochan); - g_ptr_array_add(uzbl.comm.client_chan, (gpointer)iochan); - } - return TRUE; -} - -void -init_connect_socket() { - int sockfd, replay = 0; - struct sockaddr_un local; - GIOChannel *chan; - gchar **name = NULL; - - if(!uzbl.comm.connect_chan) - uzbl.comm.connect_chan = g_ptr_array_new(); - - name = uzbl.state.connect_socket_names; - - while(name && *name) { - sockfd = socket (AF_UNIX, SOCK_STREAM, 0); - local.sun_family = AF_UNIX; - strcpy (local.sun_path, *name); - - if(!connect(sockfd, (struct sockaddr *) &local, sizeof(local))) { - if ((chan = g_io_channel_unix_new(sockfd))) { - g_io_channel_set_encoding(chan, NULL, NULL); - g_io_add_watch(chan, G_IO_IN|G_IO_HUP, - (GIOFunc) control_client_socket, chan); - g_ptr_array_add(uzbl.comm.connect_chan, (gpointer)chan); - replay++; - } - } - else - g_warning("Error connecting to socket: %s\n", *name); - name++; - } - - /* replay buffered events */ - if(replay) - send_event_socket(NULL); -} - -gboolean -control_client_socket(GIOChannel *clientchan) { - char *ctl_line; - GString *result = g_string_new(""); - GError *error = NULL; - GIOStatus ret; - gsize len; - - ret = g_io_channel_read_line(clientchan, &ctl_line, &len, NULL, &error); - if (ret == G_IO_STATUS_ERROR) { - g_warning ("Error reading: %s\n", error->message); - remove_socket_from_array(clientchan); - g_io_channel_shutdown(clientchan, TRUE, &error); - return FALSE; - } else if (ret == G_IO_STATUS_EOF) { - remove_socket_from_array(clientchan); - /* shutdown and remove channel watch from main loop */ - g_io_channel_shutdown(clientchan, TRUE, &error); - return FALSE; - } - - if (ctl_line) { - parse_cmd_line (ctl_line, result); - g_string_append_c(result, '\n'); - ret = g_io_channel_write_chars (clientchan, result->str, result->len, - &len, &error); - if (ret == G_IO_STATUS_ERROR) { - g_warning ("Error writing: %s", error->message); - } - g_io_channel_flush(clientchan, &error); - } - - if (error) g_error_free (error); - g_string_free(result, TRUE); - g_free(ctl_line); - return TRUE; -} - -/*@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 */ - if (unlink(uzbl.comm.socket_path) == -1) - g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path); - g_free(uzbl.comm.socket_path); - uzbl.comm.socket_path = NULL; - } - - if (*dir == ' ') { - g_free(dir); - 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; - } - } 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); - g_free(dir); - return NULL; -} - -/* - NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state - it will probably improve performance if we would "cache" the processed variant, but for now it works well enough... -*/ -// this function may be called very early when the templates are not set (yet), hence the checks -void -update_title (void) { - Behaviour *b = &uzbl.behave; - gchar *parsed; - - if (b->show_status) { - if (b->title_format_short) { - parsed = expand(b->title_format_short, 0); - if (uzbl.gui.main_window) - gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed); - g_free(parsed); - } - if (b->status_format) { - parsed = expand(b->status_format, 0); - gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed); - g_free(parsed); - } - if (b->status_background) { - GdkColor color; - gdk_color_parse (b->status_background, &color); - //labels and hboxes do not draw their own background. applying this on the vbox/main_window is ok as the statusbar is the only affected widget. (if not, we could also use GtkEventBox) - if (uzbl.gui.main_window) - gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color); - else if (uzbl.gui.plug) - gtk_widget_modify_bg (GTK_WIDGET(uzbl.gui.plug), GTK_STATE_NORMAL, &color); - } - } else { - if (b->title_format_long) { - parsed = expand(b->title_format_long, 0); - if (uzbl.gui.main_window) - gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed); - g_free(parsed); - } - } -} - -void -create_browser () { - GUI *g = &uzbl.gui; - - g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ()); - - g_object_connect((GObject*)g->web_view, - "signal::key-press-event", (GCallback)key_press_cb, NULL, - "signal::key-release-event", (GCallback)key_release_cb, NULL, - "signal::button-press-event", (GCallback)button_press_cb, NULL, - "signal::button-release-event", (GCallback)button_release_cb, NULL, - "signal::title-changed", (GCallback)title_change_cb, NULL, - "signal::selection-changed", (GCallback)selection_changed_cb, NULL, - "signal::load-progress-changed", (GCallback)progress_change_cb, NULL, - "signal::load-committed", (GCallback)load_commit_cb, NULL, - "signal::load-started", (GCallback)load_start_cb, NULL, - "signal::load-finished", (GCallback)load_finish_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, - "signal::new-window-policy-decision-requested", (GCallback)new_window_cb, NULL, - "signal::download-requested", (GCallback)download_cb, NULL, - "signal::create-web-view", (GCallback)create_web_view_cb, NULL, - "signal::mime-type-policy-decision-requested", (GCallback)mime_policy_cb, NULL, - "signal::populate-popup", (GCallback)populate_popup_cb, NULL, - "signal::focus-in-event", (GCallback)focus_cb, NULL, - "signal::focus-out-event", (GCallback)focus_cb, NULL, - NULL); -} - -GtkWidget* -create_mainbar () { - GUI *g = &uzbl.gui; - - g->mainbar = gtk_hbox_new (FALSE, 0); - g->mainbar_label = gtk_label_new (""); - gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE); - gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END); - gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0); - gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2); - gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0); - - g_object_connect((GObject*)g->mainbar, - "signal::key-press-event", (GCallback)key_press_cb, NULL, - "signal::key-release-event", (GCallback)key_release_cb, NULL, - NULL); - - return g->mainbar; -} - - -GtkWidget* -create_window () { - GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - - gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); - gtk_widget_set_name (window, "Uzbl browser"); - - g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL); - g_signal_connect (G_OBJECT (window), "configure-event", G_CALLBACK (configure_event_cb), NULL); - - return window; -} - -GtkPlug* -create_plug () { - GtkPlug* plug = GTK_PLUG (gtk_plug_new (uzbl.state.socket_id)); - g_signal_connect (G_OBJECT (plug), "destroy", G_CALLBACK (destroy_cb), NULL); - g_signal_connect (G_OBJECT (plug), "key-press-event", G_CALLBACK (key_press_cb), NULL); - g_signal_connect (G_OBJECT (plug), "key-release-event", G_CALLBACK (key_release_cb), NULL); - - return plug; -} - - -gchar** -inject_handler_args(const gchar *actname, const gchar *origargs, const gchar *newargs) { - /* - If actname is one that calls an external command, this function will inject - newargs in front of the user-provided args in that command line. They will - come become after the body of the script (in sh) or after the name of - the command to execute (in spawn). - i.e. sh becomes sh and - spawn becomes spawn . - - The return value consist of two strings: the action (sh, ...) and its args. - - If act is not one that calls an external command, then the given action merely - gets duplicated. - */ - GArray *rets = g_array_new(TRUE, FALSE, sizeof(gchar*)); - /* Arrr! Here be memory leaks */ - gchar *actdup = g_strdup(actname); - g_array_append_val(rets, actdup); - - if ((g_strcmp0(actname, "spawn") == 0) || - (g_strcmp0(actname, "sh") == 0) || - (g_strcmp0(actname, "sync_spawn") == 0) || - (g_strcmp0(actname, "sync_sh") == 0) || - (g_strcmp0(actname, "talk_to_socket") == 0)) { - guint i; - GString *a = g_string_new(""); - gchar **spawnparts = split_quoted(origargs, FALSE); - g_string_append_printf(a, "%s", spawnparts[0]); /* sh body or script name */ - if (newargs) g_string_append_printf(a, " %s", newargs); /* handler args */ - - for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */ - if (spawnparts[i]) g_string_append_printf(a, " %s", spawnparts[i]); - - g_array_append_val(rets, a->str); - g_string_free(a, FALSE); - g_strfreev(spawnparts); - } else { - gchar *origdup = g_strdup(origargs); - g_array_append_val(rets, origdup); - } - return (gchar**)g_array_free(rets, FALSE); -} - -void -run_handler (const gchar *act, const gchar *args) { - /* Consider this code a temporary hack to make the handlers usable. - In practice, all this splicing, injection, and reconstruction is - inefficient, annoying and hard to manage. Potential pitfalls arise - when the handler specific args 1) are not quoted (the handler - callbacks should take care of this) 2) are quoted but interfere - with the users' own quotation. A more ideal solution is - to refactor parse_command so that it doesn't just take a string - and execute it; rather than that, we should have a function which - returns the argument vector parsed from the string. This vector - could be modified (e.g. insert additional args into it) before - passing it to the next function that actually executes it. Though - it still isn't perfect for chain actions.. will reconsider & re- - factor when I have the time. -duc */ - - if (!act) return; - char **parts = g_strsplit(act, " ", 2); - if (!parts || !parts[0]) return; - if (g_strcmp0(parts[0], "chain") == 0) { - GString *newargs = g_string_new(""); - gchar **chainparts = split_quoted(parts[1], FALSE); - - /* for every argument in the chain, inject the handler args - and make sure the new parts are wrapped in quotes */ - gchar **cp = chainparts; - gchar quot = '\''; - gchar *quotless = NULL; - gchar **spliced_quotless = NULL; // sigh -_-; - gchar **inpart = NULL; - - while (*cp) { - if ((**cp == '\'') || (**cp == '\"')) { /* strip old quotes */ - quot = **cp; - quotless = g_strndup(&(*cp)[1], strlen(*cp) - 2); - } else quotless = g_strdup(*cp); - - spliced_quotless = g_strsplit(quotless, " ", 2); - inpart = inject_handler_args(spliced_quotless[0], spliced_quotless[1], args); - g_strfreev(spliced_quotless); - - g_string_append_printf(newargs, " %c%s %s%c", quot, inpart[0], inpart[1], quot); - g_free(quotless); - g_strfreev(inpart); - cp++; - } - - parse_command(parts[0], &(newargs->str[1]), NULL); - g_string_free(newargs, TRUE); - g_strfreev(chainparts); - - } else { - gchar **inparts; - gchar *inparts_[2]; - if (parts[1]) { - /* expand the user-specified arguments */ - gchar* expanded = expand(parts[1], 0); - inparts = inject_handler_args(parts[0], expanded, args); - g_free(expanded); - } else { - inparts_[0] = parts[0]; - inparts_[1] = g_strdup(args); - inparts = inparts_; - } - - parse_command(inparts[0], inparts[1], NULL); - - if (inparts != inparts_) { - g_free(inparts[0]); - g_free(inparts[1]); - } else - g_free(inparts[1]); - } - g_strfreev(parts); -} - -/*@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); - } - - return return_value; -} - -/*@null@*/ gchar* -find_xdg_file (int xdg_type, const char* filename) { - /* xdg_type = 0 => config - xdg_type = 1 => data - xdg_type = 2 => cache*/ - - gchar* xdgv = get_xdg_var (XDG[xdg_type]); - gchar* temporary_file = g_strconcat (xdgv, filename, 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); - - while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) { - g_free (temporary_file); - temporary_file = g_strconcat (temporary_string, filename, NULL); - } - } - - //g_free (temporary_string); - segfaults. - - if (file_exists (temporary_file)) { - return temporary_file; - } else { - g_free(temporary_file); - return NULL; - } -} -void -settings_init () { - State *s = &uzbl.state; - Network *n = &uzbl.net; - - int i; - for (i = 0; default_config[i].command != NULL; i++) { - parse_cmd_line(default_config[i].command, NULL); - } - - if (g_strcmp0(s->config_file, "-") == 0) { - s->config_file = NULL; - create_stdin(); - } - - else if (!s->config_file) { - s->config_file = find_xdg_file (0, "/uzbl/config"); - } - - if (s->config_file) { - GArray* lines = read_file_by_line (s->config_file); - int i = 0; - gchar* line; - - while ((line = g_array_index(lines, gchar*, i))) { - parse_cmd_line (line, NULL); - i ++; - g_free (line); - } - g_array_free (lines, TRUE); - } else { - if (uzbl.state.verbose) - printf ("No configuration file loaded.\n"); - } - - if(s->connect_socket_names) - init_connect_socket(); - - g_signal_connect_after(n->soup_session, "request-started", G_CALLBACK(handle_cookies), NULL); -} - -void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){ - (void) session; - (void) user_data; - //if (!uzbl.behave.cookie_handler) - // return; - - soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL); - GString *s = g_string_new (""); - SoupURI * soup_uri = soup_message_get_uri(msg); - g_string_printf(s, "GET '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path); - if(uzbl.behave.cookie_handler) - run_handler(uzbl.behave.cookie_handler, s->str); - - if(uzbl.behave.cookie_handler && - uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) { - char *p = strchr(uzbl.comm.sync_stdout, '\n' ); - if ( p != NULL ) *p = '\0'; - soup_message_headers_replace (msg->request_headers, "Cookie", (const char *) uzbl.comm.sync_stdout); - - } - if (uzbl.comm.sync_stdout) - uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); - - g_string_free(s, TRUE); -} - -void -save_cookies (SoupMessage *msg, gpointer user_data){ - (void) user_data; - GSList *ck; - char *cookie; - for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){ - cookie = soup_cookie_to_set_cookie_header(ck->data); - SoupURI * soup_uri = soup_message_get_uri(msg); - GString *s = g_string_new (""); - g_string_printf(s, "PUT '%s' '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path, cookie); - run_handler(uzbl.behave.cookie_handler, s->str); - g_free (cookie); - g_string_free(s, TRUE); - } - g_slist_free(ck); -} - -void -dump_var_hash(gpointer k, gpointer v, gpointer ud) { - (void) ud; - uzbl_cmdprop *c = v; - - if(!c->dump) - return; - - if(c->type == TYPE_STR) - printf("set %s = %s\n", (char *)k, *c->ptr.s ? *c->ptr.s : " "); - else if(c->type == TYPE_INT) - printf("set %s = %d\n", (char *)k, *c->ptr.i); - else if(c->type == TYPE_FLOAT) - printf("set %s = %f\n", (char *)k, *c->ptr.f); -} - -void -dump_config() { - g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash, NULL); -} - -void -dump_var_hash_as_event(gpointer k, gpointer v, gpointer ud) { - (void) ud; - uzbl_cmdprop *c = v; - GString *msg; - - if(!c->dump) - return; - - /* check for the variable type */ - msg = g_string_new((char *)k); - if (c->type == TYPE_STR) { - g_string_append_printf(msg, " str %s", *c->ptr.s ? *c->ptr.s : " "); - } else if(c->type == TYPE_INT) { - g_string_append_printf(msg, " int %d", *c->ptr.i); - } else if (c->type == TYPE_FLOAT) { - g_string_append_printf(msg, " float %f", *c->ptr.f); - } - - send_event(VARIABLE_SET, msg->str, NULL); - g_string_free(msg, TRUE); -} - -void -dump_config_as_events() { - g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash_as_event, NULL); -} - -void -retrieve_geometry() { - int w, h, x, y; - GString *buf = g_string_new(""); - - gtk_window_get_size(GTK_WINDOW(uzbl.gui.main_window), &w, &h); - gtk_window_get_position(GTK_WINDOW(uzbl.gui.main_window), &x, &y); - - g_string_printf(buf, "%dx%d+%d+%d", w, h, x, y); - - if(uzbl.gui.geometry) - g_free(uzbl.gui.geometry); - uzbl.gui.geometry = g_string_free(buf, FALSE); -} - -/* set up gtk, gobject, variable defaults and other things that tests and other - * external applications need to do anyhow */ -void -initialize(int argc, char *argv[]) { - int i; - - for(i=0; istr)) - g_string_assign (fullpath, newuri->str); - else { - gchar* wd; - wd = g_get_current_dir (); - g_string_assign (fullpath, g_build_filename (wd, newuri->str, NULL)); - free(wd); - } - struct stat stat_result; - if (! g_stat(fullpath->str, &stat_result)) { - g_string_prepend (fullpath, "file://"); - g_string_assign (newuri, fullpath->str); - } - else - g_string_prepend (newuri, "http://"); - g_string_free (fullpath, TRUE); - } - /* if we do handle cookies, ask our handler for them */ - webkit_web_view_load_uri (uzbl.gui.web_view, newuri->str); - g_string_free (newuri, TRUE); -} - - -#ifndef UZBL_LIBRARY -/** -- MAIN -- **/ -int -main (int argc, char* argv[]) { - initialize(argc, argv); - - uzbl.gui.scrolled_win = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (uzbl.gui.scrolled_win), - GTK_POLICY_NEVER, GTK_POLICY_NEVER); - - gtk_container_add (GTK_CONTAINER (uzbl.gui.scrolled_win), - GTK_WIDGET (uzbl.gui.web_view)); - - uzbl.gui.vbox = gtk_vbox_new (FALSE, 0); - - /* initial packing */ - gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); - - if (uzbl.state.plug_mode) { - uzbl.gui.plug = create_plug (); - gtk_container_add (GTK_CONTAINER (uzbl.gui.plug), uzbl.gui.vbox); - gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug)); - /* in xembed mode the window has no unique id and thus - * socket/fifo names aren't unique either. - * we use a custom randomizer to create a random id - */ - struct timeval tv; - gettimeofday(&tv, NULL); - srand((unsigned int)tv.tv_sec*tv.tv_usec); - uzbl.xwin = rand(); - } else { - uzbl.gui.main_window = create_window (); - gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox); - gtk_widget_show_all (uzbl.gui.main_window); - uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window); - } - - uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL); - uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v); - uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL); - uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h); - gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v); - - if(!uzbl.state.instance_name) - uzbl.state.instance_name = itos((int)uzbl.xwin); - - GString *tmp = g_string_new(""); - g_string_printf(tmp, "%d", getpid()); - uzbl.info.pid_str = g_string_free(tmp, FALSE); - send_event(INSTANCE_START, uzbl.info.pid_str, NULL); - - if(uzbl.state.plug_mode) { - char *t = itos(gtk_plug_get_id(uzbl.gui.plug)); - send_event(PLUG_CREATED, t, NULL); - g_free(t); - } - - /* generate an event with a list of built in commands */ - builtins(); - - gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); - - if (uzbl.state.verbose) { - printf("Uzbl start location: %s\n", argv[0]); - if (uzbl.state.socket_id) - printf("plug_id %i\n", gtk_plug_get_id(uzbl.gui.plug)); - else - printf("window_id %i\n",(int) uzbl.xwin); - printf("pid %i\n", getpid ()); - printf("name: %s\n", uzbl.state.instance_name); - printf("commit: %s\n", uzbl.info.commit); - } - - /* Check uzbl is in window mode before getting/setting geometry */ - if (uzbl.gui.main_window) { - if(uzbl.gui.geometry) - cmd_set_geometry(); - else - retrieve_geometry(); - } - - gchar *uri_override = (uzbl.state.uri ? g_strdup(uzbl.state.uri) : NULL); - if (argc > 1 && !uzbl.state.uri) - uri_override = g_strdup(argv[1]); - gboolean verbose_override = uzbl.state.verbose; - - settings_init (); - - if (!uzbl.behave.show_status) - gtk_widget_hide(uzbl.gui.mainbar); - else - update_title(); - - /* WebInspector */ - set_up_inspector(); - - if (verbose_override > uzbl.state.verbose) - uzbl.state.verbose = verbose_override; - - if (uri_override) { - set_var_value("uri", uri_override); - g_free(uri_override); - } - - gtk_main (); - clean_up(); - - return EXIT_SUCCESS; -} -#endif - -/* vi: set et ts=4: */ diff --git a/uzbl-core.h b/uzbl-core.h deleted file mode 100644 index df9eb1a..0000000 --- a/uzbl-core.h +++ /dev/null @@ -1,494 +0,0 @@ -/* -*- c-basic-offset: 4; -*- - - * See LICENSE for license details - * - * Changelog: - * --------- - * - * (c) 2009 by Robert Manea - * - introduced struct concept - * - */ - -#define _POSIX_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define LENGTH(x) (sizeof x / sizeof x[0]) - -/* gui elements */ -typedef struct { - GtkWidget* main_window; - gchar* geometry; - GtkPlug* plug; - GtkWidget* scrolled_win; - GtkWidget* vbox; - GtkWidget* mainbar; - GtkWidget* mainbar_label; - GtkScrollbar* scbar_v; // Horizontal and Vertical Scrollbar - GtkScrollbar* scbar_h; // (These are still hidden) - GtkAdjustment* bar_v; // Information about document length - GtkAdjustment* bar_h; // and scrolling position - WebKitWebView* web_view; - gchar* main_title; - gchar* icon; - - /* WebInspector */ - GtkWidget *inspector_window; - WebKitWebInspector *inspector; - - /* custom context menu item */ - GPtrArray *menu_items; -} GUI; - - -/* external communication*/ -enum { FIFO, SOCKET}; -typedef struct { - gchar *fifo_path; - gchar *socket_path; - /* stores (key)"variable name" -> (value)"pointer to var*/ - GHashTable *proto_var; - - gchar *sync_stdout; - GPtrArray *connect_chan; - GPtrArray *client_chan; -} Communication; - - -/* internal state */ -typedef struct { - gchar *uri; - gchar *config_file; - int socket_id; - char *instance_name; - gchar *selected_url; - gchar *last_selected_url; - gchar *executable_path; - gchar* keycmd; - gchar* searchtx; - gboolean verbose; - gboolean events_stdout; - GPtrArray *event_buffer; - gchar** connect_socket_names; - GdkEventButton *last_button; - gboolean plug_mode; -} State; - - -/* networking */ -typedef struct { - SoupSession *soup_session; - SoupLogger *soup_logger; - char *proxy_url; - char *useragent; - gint max_conns; - gint max_conns_host; -} Network; - - -/* behaviour */ -typedef struct { - gchar* status_format; - gchar* title_format_short; - gchar* title_format_long; - gchar* status_background; - gchar* fifo_dir; - gchar* socket_dir; - gchar* download_handler; - gchar* cookie_handler; - gchar* new_window; - gchar* default_font_family; - gchar* monospace_font_family; - gchar* sans_serif_font_family; - gchar* serif_font_family; - gchar* fantasy_font_family; - gchar* cursive_font_family; - gchar* scheme_handler; - gboolean show_status; - gboolean forward_keys; - gboolean status_top; - guint modmask; - guint http_debug; - gchar* shell_cmd; - guint view_source; - /* WebKitWebSettings exports */ - guint font_size; - guint monospace_size; - guint minimum_font_size; - gfloat zoom_level; - gboolean zoom_type; - guint disable_plugins; - guint disable_scripts; - guint autoload_img; - guint autoshrink_img; - guint enable_spellcheck; - guint enable_private; - guint print_bg; - gchar* style_uri; - guint resizable_txt; - gchar* default_encoding; - guint enforce_96dpi; - gchar *inject_html; - guint caret_browsing; - guint mode; - gchar* base_url; - gboolean print_version; - - /* command list: (key)name -> (value)Command */ - /* command list: (key)name -> (value)Command */ - GHashTable* commands; - /* event lookup: (key)event_id -> (value)event_name */ - GHashTable *event_lookup; -} Behaviour; - -/* javascript */ -typedef struct { - gboolean initialized; - JSClassDefinition classdef; - JSClassRef classref; -} Javascript; - -/* static information */ -typedef struct { - int webkit_major; - int webkit_minor; - int webkit_micro; - gchar *arch; - gchar *commit; - gchar *pid_str; -} Info; - -/* main uzbl data structure */ -typedef struct { - GUI gui; - State state; - Network net; - Behaviour behave; - Communication comm; - Javascript js; - Info info; - - Window xwin; -} UzblCore; - -/* Main Uzbl object */ -extern UzblCore uzbl; - -typedef void sigfunc(int); - -/* XDG Stuff */ -typedef struct { - gchar* environmental; - gchar* default_value; -} XDG_Var; - -/* uzbl variables */ -enum ptr_type {TYPE_INT, TYPE_STR, TYPE_FLOAT}; -typedef struct { - enum ptr_type type; - union { - int *i; - float *f; - gchar **s; - } ptr; - int dump; - int writeable; - /*@null@*/ void (*func)(void); -} uzbl_cmdprop; - -/* Functions */ -char * -itos(int val); - -char * -str_replace (const char* search, const char* replace, const char* string); - -gchar* -strfree(gchar *str); - -GArray* -read_file_by_line (const gchar *path); - -gchar* -parseenv (gchar* string); - -void -clean_up(void); - -void -catch_sigterm(int s); - -sigfunc * -setup_signal(int signe, sigfunc *shandler); - -gboolean -set_var_value(const gchar *name, gchar *val); - -void -load_uri_imp(gchar *uri); - -void -print(WebKitWebView *page, GArray *argv, GString *result); - -void -commands_hash(void); - -bool -file_exists (const char * filename); - -void -set_keycmd(); - -void -load_uri (WebKitWebView * web_view, GArray *argv, GString *result); - -void -new_window_load_uri (const gchar * uri); - -void -chain (WebKitWebView *page, GArray *argv, GString *result); - -void -close_uzbl (WebKitWebView *page, GArray *argv, GString *result); - -gboolean -run_command(const gchar *command, const guint npre, - const gchar **args, const gboolean sync, char **output_stdout); - -void -talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result); - -void -spawn(WebKitWebView *web_view, GArray *argv, GString *result); - -void -spawn_sh(WebKitWebView *web_view, GArray *argv, GString *result); - -void -spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result); - -void -spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result); - -void -parse_command(const char *cmd, const char *param, GString *result); - -void -parse_cmd_line(const char *ctl_line, GString *result); - -/*@null@*/ gchar* -build_stream_name(int type, const gchar *dir); - -gboolean -control_fifo(GIOChannel *gio, GIOCondition condition); - -/*@null@*/ gchar* -init_fifo(gchar *dir); - -gboolean -control_stdin(GIOChannel *gio, GIOCondition condition); - -void -create_stdin(); - -/*@null@*/ gchar* -init_socket(gchar *dir); - -gboolean -control_socket(GIOChannel *chan); - -gboolean -control_client_socket(GIOChannel *chan); - -void -update_title (void); - -gboolean -key_press_cb (GtkWidget* window, GdkEventKey* event); - -gboolean -key_release_cb (GtkWidget* window, GdkEventKey* event); - -void -run_keycmd(const gboolean key_ret); - -void -initialize (int argc, char *argv[]); - -void -create_browser (); - -GtkWidget* -create_mainbar (); - -GtkWidget* -create_window (); - -GtkPlug* -create_plug (); - -void -run_handler (const gchar *act, const gchar *args); - -/*@null@*/ gchar* -get_xdg_var (XDG_Var xdg); - -/*@null@*/ gchar* -find_xdg_file (int xdg_type, const char* filename); - -void -settings_init (); - -void -search_text (WebKitWebView *page, GArray *argv, const gboolean forward); - -void -search_forward_text (WebKitWebView *page, GArray *argv, GString *result); - -void -search_reverse_text (WebKitWebView *page, GArray *argv, GString *result); - -void -search_clear(WebKitWebView *page, GArray *argv, GString *result); - -void -dehilight (WebKitWebView *page, GArray *argv, GString *result); - -void -run_js (WebKitWebView * web_view, GArray *argv, GString *result); - -void -run_external_js (WebKitWebView * web_view, GArray *argv, GString *result); - -void -eval_js(WebKitWebView * web_view, gchar *script, GString *result); - -void handle_cookies (SoupSession *session, - SoupMessage *msg, - gpointer user_data); -void -save_cookies (SoupMessage *msg, gpointer user_data); - -void -set_var(WebKitWebView *page, GArray *argv, GString *result); - -void -act_dump_config(); - -void -act_dump_config_as_events(); - -void -dump_var_hash(gpointer k, gpointer v, gpointer ud); - -void -dump_key_hash(gpointer k, gpointer v, gpointer ud); - -void -dump_config(); - -void -dump_config_as_events(); - -void -retrieve_geometry(); - -void -event(WebKitWebView *page, GArray *argv, GString *result); - -void -init_connect_socket(); - -gboolean -remove_socket_from_array(GIOChannel *chan); - -void -menu_add(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_add_link(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_add_image(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_add_edit(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_add_separator(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_add_separator_link(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_add_separator_image(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_remove(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_remove_link(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_remove_image(WebKitWebView *page, GArray *argv, GString *result); - -void -menu_remove_edit(WebKitWebView *page, GArray *argv, GString *result); - -gint -get_click_context(); - -void -hardcopy(WebKitWebView *page, GArray *argv, GString *result); - -void -include(WebKitWebView *page, GArray *argv, GString *result); - -void -builtins(); - -typedef void (*Command)(WebKitWebView*, GArray *argv, GString *result); - -typedef struct { - Command function; - gboolean no_split; -} CommandInfo; - -typedef struct { - gchar *name; - gchar *cmd; - gboolean issep; - guint context; -} MenuItem; - - -/* vi: set et ts=4: */ -- cgit v1.2.3 From e4b58e1c2333ebe20c9d746dfd4aed00af6eedff Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 12:09:29 +0100 Subject: fix for config.h location --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 15e1851..93cc79f 100644 --- a/Makefile +++ b/Makefile @@ -96,9 +96,9 @@ install-uzbl-core: all install -d $(INSTALLDIR)/bin install -d $(INSTALLDIR)/share/uzbl/docs install -d $(INSTALLDIR)/share/uzbl/examples - cp -rp docs $(INSTALLDIR)/share/uzbl/ - cp -rp config.h $(INSTALLDIR)/share/uzbl/docs/ - cp -rp examples $(INSTALLDIR)/share/uzbl/ + cp -rp docs $(INSTALLDIR)/share/uzbl/ + cp -rp src/config.h $(INSTALLDIR)/share/uzbl/docs/ + cp -rp examples $(INSTALLDIR)/share/uzbl/ install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core install -m644 AUTHORS $(INSTALLDIR)/share/uzbl/docs install -m644 README $(INSTALLDIR)/share/uzbl/docs -- cgit v1.2.3 From 97392b5c3ec7254ec075b7b7902ee4933d5082c3 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 12:11:29 +0100 Subject: "fix" for broken Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 93cc79f..f48d1f0 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ options: ${OBJ}: ${HEAD} -uzbl-core: ${OBJ} +uzbl-core: ${TOBJ} # why doesn't ${OBJ} work? @echo @echo LINKING object files @${CC} -o $@ ${OBJ} ${LDFLAGS} -- cgit v1.2.3 From 39c4907435f75389594cab9a1eae1848336ea339 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 12:45:46 +0100 Subject: faq entry for memory usage --- docs/FAQ | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/FAQ b/docs/FAQ index 1c54ad1..4a29663 100644 --- a/docs/FAQ +++ b/docs/FAQ @@ -75,6 +75,12 @@ They both have advantages and disadvantages: So, when writing scripts, using fifo's is usually the fastest method (because you do not need to fork another process), so fifo is preferred unless you need a response. +### Uzbl uses too much memory! Especially when having multiple windows (i.e. with uzbl-tabbed) +Don't be fooled with how memory usage is measured and reported on Linux. (or other systems) +You need to be aware of the difference between RSS and VSS. +And dynamic libraries (libwebkit, libgtk, etc) that are used by multiple processes are only stored in RAM once. +See [this page](http://virtualthreads.blogspot.com/2006/02/understanding-memory-usage-on-linux.html) for a good explanation. + ### What the hell is this 'XDG' stuff?? You'll notice our example/default scripts and configs use variables such as `$XDG_CONFIG_HOME` and `$XDG_DATA_HOME`. Most of us really like the [xdg basedir spec](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html). -- cgit v1.2.3 From 379c716bc5e703fb5ca229256457868acb533fa6 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 16:35:55 +0100 Subject: give a more objective pov of the multiple instances discussion + elaborate on possible directions and scripts --- docs/FAQ | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/docs/FAQ b/docs/FAQ index 4a29663..b61107d 100644 --- a/docs/FAQ +++ b/docs/FAQ @@ -24,21 +24,61 @@ tools and scripts, by itself doesn't do many usefull things. See README. The layout of uzbl (and derivatives) only contains what you really need to see. we only have a statusbar, which even can also be disabled. There are no buttons, but we do have lots of keybinding possibilities. -### Why can uzbl-browser only show one page? -Among uzbl hackers, there are 2 groups: some people prefer tabs, they use -uzbl-tabbed. The others stick to "one page per uzbl-browser instance" because -it's a very flexible approach. They believe "multiple instances management" is something that must -be handled outside of uzbl by a separate/different program. Here are some solutions: - - * Many window managers can handle this by default. Xmonads tabbed layout, Wmii's stacked layout, fluxbox or kwin tabs and so on. - * Uzbl supports acting as a GtkPlug to plug into GtkSockets (Xembed) so you can embed uzbl instances in other Gtk applications. - (This is also what uzbl-tabbed does) - * If you want highest customizablity, you need the 3rd option: - You can also write a custom script. The only thing you need to do is focus/maximize the instance you want, - keep the others out of sight and use tools like dmenu and wmctrl to switch instances. - This allows you to use application-specific properties (such as uzbl tag, name etc). - For more information about this approach, see docs/multiple-instances-management. - (If you want to work on such script, let us know and we might include it along with the other sample scripts) +### Why can uzbl-core/uzbl-browser only show one page? +It is nearly unanimously agreed that one page per uzbl-core is best. +It allows a simple implementation of both uzbl-core and +uzbl-browser, and it makes things more robust. +But read the next entry... + +### How to have multiple pages in one window? +So, given that uzbl-core and uzbl-browser only deal with one page at a time (see +above), how can you have a window with multiple pages? + +Basically this is involves concerns on two sides: +* window management: can I keep all pages together in 1 X window so + that I can move all at once to a different workspace, can I "split off" + pages into separate windows (i.e. move only one specific page/window to a + different desktop), can I integrate uzbl pages/windows into WM features? + (alt-tab, tiling layouts, taskbar, ...), etc +* application-level: having a realtime overview of all page titles of all uzbl + instances, special representation styles which are tightly coupled to the + application such as treeviews that show from which you page you opened + others, or page state (loading etc) + +Uzbl itself can hardly be a limiting factor, as it supports/has: +* Xembed (GtkPlug mode) so you can embed a uzbl-browser or uzbl-core into another window +* an events system you can have realtime updates of window title, pageload state, etc. +* command interface to programmatically change it's behavior. + +And then there is the style of representation (tabs, tree overviews, visual +thumbnails etc) which can be handled from the WM side or the application +side. + +There are multiple approaches, each with pros and cons and you can pick the one that suits you best. + +* Tabbing in the WM: Xmonads tabbed layout, Wmii's stacked layout, fluxbox or kwin tabs and so on. +* Visual overview in the WM: commonly used with dwm or Awesome's tiling layouts with master/slave areas. + The [dynamic zoom script](http://www.uzbl.org/wiki/dynamic_zooming) is useful here. +* A container application whih embeds multiple uzbl-browsers and provide tablists, tree views, and more. + Examples: + - [uzbl-tabbed](http://www.uzbl.org/wiki/uzbl_tabbed) (officially supported) + - [uzbltreetab](http://www.uzbl.org/wiki/uzbltreetab) + - [uzbltab](http://www.uzbl.org/wiki/uzbltab) + - [suckless tabbed](http://tools.suckless.org/tabbed) +* An application to mimic tabbing independently of WM support. + The only thing you need to do is focus/maximize the instance you want, + keep the others out of sight and use tools like dmenu/xbindkeys and wmctrl to switch instances. + This allows you to use application-specific properties (such as uzbl tag, name etc). + For more ideas on such an approach, see docs/multiple-instances-management. + Examples: + - [wmctrl-based](http://www.uzbl.org/wiki/metacity-tabs) (works on at least Metacity) + - [wmii] (http://www.uzbl.org/wiki/wmii) + +There are really a lot of options. +On the wiki you'll find a lot of related scripts, some of them providing new +workflows (do you really need open windows for all pages you intend to read, or is a list enough? +[articlecue](http://www.uzbl.org/wiki/article_queue.py)), some providing integration with WM's such as +[awesome](http://www.uzbl.org/wiki/awesome), and more. ### Okay, what can I actually do? What commands are there? How do I get more information? * Commands and other features are documented in README. Read it. -- cgit v1.2.3 From 9b23103c2c042b126c5701634530e2dc4c2d1b84 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 16:48:20 +0100 Subject: layout fixes --- docs/FAQ | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/FAQ b/docs/FAQ index b61107d..3294999 100644 --- a/docs/FAQ +++ b/docs/FAQ @@ -35,17 +35,21 @@ So, given that uzbl-core and uzbl-browser only deal with one page at a time (see above), how can you have a window with multiple pages? Basically this is involves concerns on two sides: -* window management: can I keep all pages together in 1 X window so - that I can move all at once to a different workspace, can I "split off" - pages into separate windows (i.e. move only one specific page/window to a - different desktop), can I integrate uzbl pages/windows into WM features? - (alt-tab, tiling layouts, taskbar, ...), etc -* application-level: having a realtime overview of all page titles of all uzbl - instances, special representation styles which are tightly coupled to the - application such as treeviews that show from which you page you opened - others, or page state (loading etc) + +* window management + - can I keep all pages together in 1 X window so that I can move all at once to a different workspace + - can I "split off" pages into separate windows (i.e. move only one specific page/window to a different desktop) + or merge windows together? + - can I integrate uzbl pages/windows into WM features? (alt-tab, tiling layouts, taskbar, ...) + - ... +* application-level + - realtime overview of all page titles of all uzbl instances + - representation styles which are tightly coupled to the application such as treeviews that show from which you page you opened + others, or page state (loading etc) + - ... Uzbl itself can hardly be a limiting factor, as it supports/has: + * Xembed (GtkPlug mode) so you can embed a uzbl-browser or uzbl-core into another window * an events system you can have realtime updates of window title, pageload state, etc. * command interface to programmatically change it's behavior. @@ -54,7 +58,7 @@ And then there is the style of representation (tabs, tree overviews, visual thumbnails etc) which can be handled from the WM side or the application side. -There are multiple approaches, each with pros and cons and you can pick the one that suits you best. +There are multiple approaches, each with pros and cons. * Tabbing in the WM: Xmonads tabbed layout, Wmii's stacked layout, fluxbox or kwin tabs and so on. * Visual overview in the WM: commonly used with dwm or Awesome's tiling layouts with master/slave areas. @@ -72,9 +76,10 @@ There are multiple approaches, each with pros and cons and you can pick the one For more ideas on such an approach, see docs/multiple-instances-management. Examples: - [wmctrl-based](http://www.uzbl.org/wiki/metacity-tabs) (works on at least Metacity) - - [wmii] (http://www.uzbl.org/wiki/wmii) + - [wmii](http://www.uzbl.org/wiki/wmii) -There are really a lot of options. +There are really a lot of options. You need to think about what you need, +what you want and what you don't care about. On the wiki you'll find a lot of related scripts, some of them providing new workflows (do you really need open windows for all pages you intend to read, or is a list enough? [articlecue](http://www.uzbl.org/wiki/article_queue.py)), some providing integration with WM's such as -- cgit v1.2.3 From 069a2ccef8c9b55b8da5b211134945290a4a05d6 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Mon, 28 Dec 2009 19:45:12 +0100 Subject: uzbl-tabbed: don't use uzbl fifo --- examples/data/uzbl/scripts/uzbl-tabbed | 36 +++++++--------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index d93a3f4..169e773 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -383,25 +383,22 @@ class UzblTabbed: class UzblInstance: '''Uzbl instance meta-data/meta-action object.''' - def __init__(self, parent, tab, fifo_socket, socket_file, pid,\ - uri, title, switch): + def __init__(self, parent, tab, socket_file, pid, uri, title, switch): self.parent = parent self.tab = tab - self.fifo_socket = fifo_socket self.socket_file = socket_file self.pid = pid self.title = title self.uri = uri self.timers = {} self._lastprobe = 0 - self._fifoout = [] self._socketout = [] self._socket = None self._buffer = "" # Switch to tab after loading self._switch = switch - # fifo/socket files exists and socket connected. + # socket files exists and socket connected. self._connected = False # The kill switch self._kill = False @@ -422,7 +419,7 @@ class UzblTabbed: def flush(self, timer_call=False): - '''Flush messages from the socket-out and fifo-out queues.''' + '''Flush messages from the socket-out queue.''' if self._kill: if self._socket: @@ -432,14 +429,6 @@ class UzblTabbed: error("Flush called on dead tab.") return False - if len(self._fifoout): - if os.path.exists(self.fifo_socket): - h = open(self.fifo_socket, 'w') - while len(self._fifoout): - msg = self._fifoout.pop(0) - h.write("%s\n"%msg) - h.close() - if len(self._socketout): if not self._socket and os.path.exists(self.socket_file): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) @@ -452,7 +441,7 @@ class UzblTabbed: self._socket.send("%s\n"%msg) if not self._connected and timer_call: - if not len(self._fifoout + self._socketout): + if not self._socketout: self._connected = True if timer_call in self.timers.keys(): @@ -462,7 +451,7 @@ class UzblTabbed: if self._switch: self.grabfocus() - return len(self._fifoout + self._socketout) + return len(self._socketout) def grabfocus(self): @@ -481,14 +470,6 @@ class UzblTabbed: self._lastprobe = time.time() - def write(self, msg): - '''Child fifo write function.''' - - self._fifoout.append(msg) - # Flush messages from the queue if able. - return self.flush() - - def send(self, msg): '''Child socket send function.''' @@ -965,8 +946,6 @@ class UzblTabbed: sid = tab.get_id() uri = uri.strip() - fifo_filename = 'uzbl_fifo_%s_%0.2d' % (self.wid, pid) - fifo_socket = os.path.join(config['fifo_dir'], fifo_filename) socket_filename = 'uzbl_socket_%s_%0.2d' % (self.wid, pid) socket_file = os.path.join(config['socket_dir'], socket_filename) @@ -976,7 +955,7 @@ class UzblTabbed: if not title: title = config['new_tab_title'] - uzbl = self.UzblInstance(self, tab, fifo_socket, socket_file, pid,\ + uzbl = self.UzblInstance(self, tab, socket_file, pid,\ uri, title, switch) if len(uri): @@ -986,7 +965,7 @@ class UzblTabbed: cmd = 'uzbl-browser -s %s -n %s_%0.2d %s &' % (sid, self.wid, pid, uri) subprocess.Popen([cmd], shell=True) # TODO: do i need close_fds=True ? - # Add gobject timer to make sure the config is pushed when fifo socket + # Add gobject timer to make sure the config is pushed when socket # has been created. timerid = timeout_add(100, uzbl.flush, "flush-initial-config") uzbl.timers['flush-initial-config'] = timerid @@ -1138,7 +1117,6 @@ class UzblTabbed: uzbl._socket.close() uzbl._socket = None - uzbl._fifoout = [] uzbl._socketout = [] uzbl._kill = True self._closed.append((uzbl.uri, uzbl.title)) -- cgit v1.2.3 From f2f1600e33e6f9487ea5cc96dcefb576cb5684d3 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Mon, 28 Dec 2009 19:45:44 +0100 Subject: uzbl-tabbed: get UzblInstance out of UzblTabbed --- examples/data/uzbl/scripts/uzbl-tabbed | 153 ++++++++++++++++----------------- 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index 169e773..6bab849 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -377,106 +377,105 @@ def gen_endmarker(): return hashlib.md5(str(random.random()*time.time())).hexdigest() -class UzblTabbed: - '''A tabbed version of uzbl using gtk.Notebook''' - - class UzblInstance: - '''Uzbl instance meta-data/meta-action object.''' - - def __init__(self, parent, tab, socket_file, pid, uri, title, switch): - - self.parent = parent - self.tab = tab - self.socket_file = socket_file - self.pid = pid - self.title = title - self.uri = uri - self.timers = {} - self._lastprobe = 0 - self._socketout = [] - self._socket = None - self._buffer = "" - # Switch to tab after loading - self._switch = switch - # socket files exists and socket connected. - self._connected = False - # The kill switch - self._kill = False +class UzblInstance: + '''Uzbl instance meta-data/meta-action object.''' + + def __init__(self, parent, tab, socket_file, pid, uri, title, switch): + + self.parent = parent + self.tab = tab + self.socket_file = socket_file + self.pid = pid + self.title = title + self.uri = uri + self.timers = {} + self._lastprobe = 0 + self._socketout = [] + self._socket = None + self._buffer = "" + # Switch to tab after loading + self._switch = switch + # socket files exists and socket connected. + self._connected = False + # The kill switch + self._kill = False - # Message termination endmarker. - self._marker = gen_endmarker() + # Message termination endmarker. + self._marker = gen_endmarker() - # Gen probe commands string - probes = [] - probe = probes.append - probe('print uri %d @uri %s' % (self.pid, self._marker)) - probe('print title %d @@ %s' % (self.pid,\ - self._marker)) - self._probecmds = '\n'.join(probes) + # Gen probe commands string + probes = [] + probe = probes.append + probe('print uri %d @uri %s' % (self.pid, self._marker)) + probe('print title %d @@ %s' % (self.pid,\ + self._marker)) + self._probecmds = '\n'.join(probes) - # Enqueue keybinding config for child uzbl instance - self.parent.config_uzbl(self) + # Enqueue keybinding config for child uzbl instance + self.parent.config_uzbl(self) - def flush(self, timer_call=False): - '''Flush messages from the socket-out queue.''' + def flush(self, timer_call=False): + '''Flush messages from the socket-out queue.''' - if self._kill: - if self._socket: - self._socket.close() - self._socket = None + if self._kill: + if self._socket: + self._socket.close() + self._socket = None - error("Flush called on dead tab.") - return False + error("Flush called on dead tab.") + return False - if len(self._socketout): - if not self._socket and os.path.exists(self.socket_file): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(self.socket_file) - self._socket = sock + if len(self._socketout): + if not self._socket and os.path.exists(self.socket_file): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(self.socket_file) + self._socket = sock - if self._socket: - while len(self._socketout): - msg = self._socketout.pop(0) - self._socket.send("%s\n"%msg) + if self._socket: + while len(self._socketout): + msg = self._socketout.pop(0) + self._socket.send("%s\n"%msg) - if not self._connected and timer_call: - if not self._socketout: - self._connected = True + if not self._connected and timer_call: + if not self._socketout: + self._connected = True - if timer_call in self.timers.keys(): - source_remove(self.timers[timer_call]) - del self.timers[timer_call] + if timer_call in self.timers.keys(): + source_remove(self.timers[timer_call]) + del self.timers[timer_call] - if self._switch: - self.grabfocus() + if self._switch: + self.grabfocus() - return len(self._socketout) + return len(self._socketout) - def grabfocus(self): - '''Steal parent focus and switch the notebook to my own tab.''' + def grabfocus(self): + '''Steal parent focus and switch the notebook to my own tab.''' - tabs = list(self.parent.notebook) - tabid = tabs.index(self.tab) - self.parent.goto_tab(tabid) + tabs = list(self.parent.notebook) + tabid = tabs.index(self.tab) + self.parent.goto_tab(tabid) - def probe(self): - '''Probes the client for information about its self.''' + def probe(self): + '''Probes the client for information about its self.''' - if self._connected: - self.send(self._probecmds) - self._lastprobe = time.time() + if self._connected: + self.send(self._probecmds) + self._lastprobe = time.time() - def send(self, msg): - '''Child socket send function.''' + def send(self, msg): + '''Child socket send function.''' - self._socketout.append(msg) - # Flush messages from queue if able. - return self.flush() + self._socketout.append(msg) + # Flush messages from queue if able. + return self.flush() +class UzblTabbed: + '''A tabbed version of uzbl using gtk.Notebook''' def __init__(self): '''Create tablist, window and notebook.''' -- cgit v1.2.3 From 6a2b62a798bbaca10ded2d95d739b877b735b10e Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Mon, 28 Dec 2009 23:49:35 +0100 Subject: uzbl-tabbed changes : * Remove bind_* options: does not work with current uzbl revisions, and a better implementation will come soon. * Remove reading of uzbl config file (same reason) * Better IPC handling (to sumarize: less periodic polling): * Better separation between network layers (communication is in SocketClient, protocol in UzblInstance, user interface in UzblTabbed) * use io_add_watch instead of select for reading uzbl events * does not use a generated hash to separate events, but the LF character * get rid of all the flush()ing logic * does not probe periodically for the title anymore; use uzbl events * create a /tmp/uzbltabbed_socket instead of polling the /tmp/uzbl_socket_* socket --- examples/data/uzbl/scripts/uzbl-tabbed | 442 ++++++++++++++------------------- 1 file changed, 183 insertions(+), 259 deletions(-) diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index 6bab849..9d8e1e6 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -96,25 +96,6 @@ # window_size = 800,800 # verbose = 0 # -# And the key bindings: -# bind_new_tab = gn -# bind_tab_from_clip = gY -# bind_tab_from_uri = go _ -# bind_close_tab = gC -# bind_next_tab = gt -# bind_prev_tab = gT -# bind_goto_tab = gi_ -# bind_goto_first = g< -# bind_goto_last = g> -# bind_clean_slate = gQ -# bind_exit = gZ -# -# Session preset key bindings: -# bind_save_preset = gsave _ -# bind_load_preset = gload _ -# bind_del_preset = gdel _ -# bind_list_presets = glist -# # And uzbl_tabbed.py takes care of the actual binding of the commands via each # instances fifo socket. # @@ -209,12 +190,6 @@ for path in [DATA_DIR, CONFIG_DIR]: if not os.path.exists(path): os.makedirs(path) -# Path to uzbl config -UZBL_CONFIG = os.path.join(CONFIG_DIR, 'config') -if not os.path.exists(UZBL_CONFIG): - error("cannot find uzbl config file at %r" % UZBL_CONFIG) - sys.exit(1) - # All of these settings can be inherited from your uzbl config file. config = { # Tab options @@ -249,25 +224,6 @@ config = { 'window_size': "800,800", # width,height in pixels. 'verbose': False, # Print verbose output. - # Key bindings - 'bind_new_tab': 'gn', # Open new tab. - 'bind_tab_from_clip': 'gY', # Open tab from clipboard. - 'bind_tab_from_uri': 'go _', # Open new tab and goto entered uri. - 'bind_close_tab': 'gC', # Close tab. - 'bind_next_tab': 'gt', # Next tab. - 'bind_prev_tab': 'gT', # Prev tab. - 'bind_goto_tab': 'gi_', # Goto tab by tab-number (in title). - 'bind_goto_first': 'g<', # Goto first tab. - 'bind_goto_last': 'g>', # Goto last tab. - 'bind_clean_slate': 'gQ', # Close all tabs and open new tab. - 'bind_exit': 'gZ', # Exit nicely. - - # Session preset key bindings - 'bind_save_preset': 'gsave _', # Save session to file %s. - 'bind_load_preset': 'gload _', # Load preset session from file %s. - 'bind_del_preset': 'gdel _', # Delete preset session %s. - 'bind_list_presets': 'glist', # List all session presets. - # Add custom tab style definitions to be used by the tab colour policy # handler here. Because these are added to the config dictionary like # any other uzbl_tabbed configuration option remember that they can @@ -371,108 +327,132 @@ def escape(s): return s -def gen_endmarker(): - '''Generates a random md5 for socket message-termination endmarkers.''' +class SocketClient: + '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance''' + + # List of UzblInstance objects not already linked with a SocketClient + instances_queue = {} + + def __init__(self, socket): + self._buffer = "" + self._socket = socket + self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\ + io_add_watch(socket, IO_HUP, self._socket_closed)] + self.uzbl = None + + + def _socket_recv(self, fd, condition): + '''Data available on socket, process it''' + + self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ? + return True + + + def _socket_closed(self, fd, condition): + '''Remote client exited''' + self.uzbl.close() + return False - return hashlib.md5(str(random.random()*time.time())).hexdigest() + + def _feed(self, data): + '''An Uzbl instance sent some data, parse it''' + + self._buffer += data + if self.uzbl: + if "\n" in self._buffer: + cmds = self._buffer.split("\n") + + if cmds[-1]: # Last command has been received incomplete, don't process it + self._buffer, cmds = cmds[-1], cmds[:-1] + else: + self._buffer = "" + + for cmd in cmds: + if cmd: + self.uzbl.parse_command(cmd) + else: + name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M) + uzbl = self.instances_queue.get(name[0]) + if uzbl: + del self.instances_queue[name[0]] + self.uzbl = uzbl + self.uzbl.got_socket(self) + self._feed("") + + def send(self, data): + '''Child socket send function.''' + + self._socket.send(data + "\n") + + def close(self): + '''Close the connection''' + + if self._socket: + self._socket.close() + self._socket = None + map(source_remove, self._watchers) + self._watchers = [] class UzblInstance: '''Uzbl instance meta-data/meta-action object.''' - def __init__(self, parent, tab, socket_file, pid, uri, title, switch): + def __init__(self, parent, tab, name, uri, title, switch): self.parent = parent self.tab = tab - self.socket_file = socket_file - self.pid = pid + self.name = name self.title = title self.uri = uri - self.timers = {} - self._lastprobe = 0 - self._socketout = [] - self._socket = None - self._buffer = "" - # Switch to tab after loading - self._switch = switch - # socket files exists and socket connected. - self._connected = False - # The kill switch - self._kill = False - - # Message termination endmarker. - self._marker = gen_endmarker() - - # Gen probe commands string - probes = [] - probe = probes.append - probe('print uri %d @uri %s' % (self.pid, self._marker)) - probe('print title %d @@ %s' % (self.pid,\ - self._marker)) - self._probecmds = '\n'.join(probes) - - # Enqueue keybinding config for child uzbl instance - self.parent.config_uzbl(self) + self._client = None + self._switch = switch # Switch to tab after loading ? - def flush(self, timer_call=False): - '''Flush messages from the socket-out queue.''' + def got_socket(self, client): + '''Uzbl instance is now connected''' + self._client = client - if self._kill: - if self._socket: - self._socket.close() - self._socket = None + self.parent.tabs[self.tab] = self + self.parent.update_tablist() - error("Flush called on dead tab.") - return False - - if len(self._socketout): - if not self._socket and os.path.exists(self.socket_file): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(self.socket_file) - self._socket = sock + self.parent.config_uzbl(self) - if self._socket: - while len(self._socketout): - msg = self._socketout.pop(0) - self._socket.send("%s\n"%msg) + if self._switch: + tabs = list(self.parent.notebook) + tabid = tabs.index(self.tab) + self.parent.goto_tab(tabid) - if not self._connected and timer_call: - if not self._socketout: - self._connected = True - if timer_call in self.timers.keys(): - source_remove(self.timers[timer_call]) - del self.timers[timer_call] + def set(self, key, val): + ''' Send the SET command to Uzbl ''' - if self._switch: - self.grabfocus() + self._client.send('set %s = %s') #TODO: escape chars ? - return len(self._socketout) + def exit(sedf): + ''' Ask the Uzbl instance to close ''' - def grabfocus(self): - '''Steal parent focus and switch the notebook to my own tab.''' + self._client.send('exit') #TODO: escape chars ? - tabs = list(self.parent.notebook) - tabid = tabs.index(self.tab) - self.parent.goto_tab(tabid) + def parse_command(self, cmd): + ''' Parse event givent by the Uzbl instance ''' - def probe(self): - '''Probes the client for information about its self.''' + type, _, args = cmd.split(" ", 2) + if type == "EVENT": + type, args = args.split(" ", 1) + if type == "TITLE_CHANGED": + self.title = args + self.parent.update_tablist() - if self._connected: - self.send(self._probecmds) - self._lastprobe = time.time() + def close(self): + '''The remote instance exited''' - def send(self, msg): - '''Child socket send function.''' + if self._client: + self._client.close() + self._client = None - self._socketout.append(msg) - # Flush messages from queue if able. - return self.flush() class UzblTabbed: '''A tabbed version of uzbl using gtk.Notebook''' @@ -480,9 +460,6 @@ class UzblTabbed: def __init__(self): '''Create tablist, window and notebook.''' - # Store information about the applications fifo_socket. - self._fifo = None - self._timers = {} self._buffer = "" self._killed = False @@ -493,6 +470,9 @@ class UzblTabbed: # Holds metadata on the uzbl childen open. self.tabs = {} + # Uzbl sockets (socket => SocketClient) + self.clients = {} + # Generates a unique id for uzbl socket filenames. self.next_pid = counter().next @@ -577,12 +557,18 @@ class UzblTabbed: self.window.show() self.wid = self.notebook.window.xid - # Generate the fifo socket filename. - fifo_filename = 'uzbltabbed_%d' % os.getpid() - self.fifo_socket = os.path.join(config['fifo_dir'], fifo_filename) - # Now initialise the fifo socket at self.fifo_socket - self.init_fifo_socket() + # Store information about the applications fifo and socket. + fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid() + socket_filename = 'uzbltabbed_%d.socket' % os.getpid() + self._fifo = None + self._socket = None + self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename) + self.socket_path = os.path.join(config['socket_dir'], socket_filename) + + # Now initialise the fifo and the socket + self.init_fifo() + self.init_socket() # If we are using sessions then load the last one if it exists. if config['save_session']: @@ -592,7 +578,7 @@ class UzblTabbed: def run(self): '''UzblTabbed main function that calls the gtk loop.''' - if not len(self.tabs): + if not self.clients and not SocketClient.instances_queue and not self.tabs: self.new_tab() gtk_refresh = int(config['gtk_refresh']) @@ -603,10 +589,6 @@ class UzblTabbed: timerid = timeout_add(gtk_refresh, self.update_tablist) self._timers["update-tablist"] = timerid - # Probe clients every second for window titles and location - timerid = timeout_add(gtk_refresh, self.probe_clients) - self._timers["probe-clients"] = timerid - # Make SIGTERM act orderly. signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) @@ -620,7 +602,7 @@ class UzblTabbed: error("encounted error %r" % sys.exc_info()[1]) # Unlink fifo socket - self.unlink_fifo_socket() + self.unlink_fifo() # Attempt to close all uzbl instances nicely. self.quitrequest() @@ -651,40 +633,72 @@ class UzblTabbed: self.quitrequest() - def init_fifo_socket(self): - '''Create interprocess communication fifo socket.''' + def init_socket(self): + '''Create interprocess communication socket.''' + + def accept(sock, condition): + '''A new uzbl instance was created''' + + client, _ = sock.accept() + self.clients[client] = SocketClient(client) + + return True + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(self.socket_path) + sock.listen(1) + + # Add event handler for IO_IN event. + self._socket = (sock, io_add_watch(sock, IO_IN, accept)) + + echo("[socket] listening at %r" % self.socket_path) + + # Add atexit register to destroy the socket on program termination. + atexit.register(self.close_socket) + + + def close_socket(self): + '''Close the socket when closing the application''' + + (fd, watcher) = self._socket + source_remove(watcher) + fd.close() + + + def init_fifo(self): + '''Create interprocess communication fifo.''' - if os.path.exists(self.fifo_socket): - if not os.access(self.fifo_socket, os.F_OK | os.R_OK | os.W_OK): - os.mkfifo(self.fifo_socket) + if os.path.exists(self.fifo_path): + if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK): + os.mkfifo(self.fifo_path) else: - basedir = os.path.dirname(self.fifo_socket) + basedir = os.path.dirname(self.fifo_path) if not os.path.exists(basedir): os.makedirs(basedir) - os.mkfifo(self.fifo_socket) + os.mkfifo(self.fifo_path) # Add event handlers for IO_IN & IO_HUP events. self.setup_fifo_watchers() - echo("listening at %r" % self.fifo_socket) + echo("[fifo] listening at %r" % self.fifo_path) - # Add atexit register to destroy the socket on program termination. - atexit.register(self.unlink_fifo_socket) + # Add atexit register to destroy the fifo on program termination. + atexit.register(self.unlink_fifo) - def unlink_fifo_socket(self): + def unlink_fifo(self): '''Unlink the fifo socket. Note: This function is called automatically on exit by an atexit register.''' - # Make sure the fifo_socket fd is closed. + # Make sure the fifo fd is closed. self.close_fifo() - # And unlink if the real fifo_socket exists. - if os.path.exists(self.fifo_socket): - os.unlink(self.fifo_socket) - echo("unlinked %r" % self.fifo_socket) + # And unlink if the real fifo exists. + if os.path.exists(self.fifo_path): + os.unlink(self.fifo_path) + echo("unlinked %r" % self.fifo_path) def close_fifo(self): @@ -707,10 +721,10 @@ class UzblTabbed: '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event handlers.''' - # Close currently open fifo_socket fd and kill all watchers + # Close currently open fifo fd and kill all watchers self.close_fifo() - fd = os.open(self.fifo_socket, os.O_RDONLY | os.O_NONBLOCK) + fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK) # Add gobject io event handlers to the fifo socket. watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\ @@ -749,43 +763,6 @@ class UzblTabbed: return True - def probe_clients(self): - '''Probe all uzbl clients for up-to-date window titles and uri's.''' - - save_session = config['save_session'] - - sockd = {} - tabskeys = self.tabs.keys() - notebooklist = list(self.notebook) - - for tab in notebooklist: - if tab not in tabskeys: continue - uzbl = self.tabs[tab] - uzbl.probe() - if uzbl._socket: - sockd[uzbl._socket] = uzbl - - sockets = sockd.keys() - (reading, _, errors) = select.select(sockets, [], sockets, 0) - - for sock in reading: - uzbl = sockd[sock] - uzbl._buffer = sock.recv(1024).replace('\n',' ') - temp = uzbl._buffer.split(uzbl._marker) - self._buffer = temp.pop() - cmds = [s.strip().split() for s in temp if len(s.strip())] - for cmd in cmds: - try: - #print cmd - self.parse_command(cmd) - - except: - error("parse_command: invalid command %s" % ' '.join(cmd)) - raise - - return True - - def parse_command(self, cmd): '''Parse instructions from uzbl child processes.''' @@ -858,7 +835,7 @@ class UzblTabbed: elif cmd[0] in ["title", "uri"]: if len(cmd) > 2: - uzbl = self.get_tab_by_pid(int(cmd[1])) + uzbl = self.get_tab_by_name(int(cmd[1])) if uzbl: old = getattr(uzbl, cmd[0]) new = ' '.join(cmd[2:]) @@ -867,7 +844,7 @@ class UzblTabbed: self.update_tablist() else: - error("parse_command: no uzbl with pid %r" % int(cmd[1])) + error("parse_command: no uzbl with name %r" % int(cmd[1])) elif cmd[0] == "preset": if len(cmd) < 3: @@ -890,20 +867,20 @@ class UzblTabbed: error("parse_command: preset %r does not exist." % path) elif cmd[1] == "list": - uzbl = self.get_tab_by_pid(int(cmd[2])) + uzbl = self.get_tab_by_name(int(cmd[2])) if uzbl: if not os.path.isdir(config['saved_sessions_dir']): js = "js alert('No saved presets.');" - uzbl.send(js) + uzbl._client.send(js) else: listdir = os.listdir(config['saved_sessions_dir']) listdir = "\\n".join(listdir) js = "js alert('Session presets:\\n\\n%s');" % listdir - uzbl.send(js) + uzbl._client.send(js) else: - error("parse_command: unknown tab pid.") + error("parse_command: unknown tab name.") else: error("parse_command: unknown parse command %r"\ @@ -922,11 +899,11 @@ class UzblTabbed: error("parse_command: unknown command %r" % ' '.join(cmd)) - def get_tab_by_pid(self, pid): - '''Return uzbl instance by pid.''' + def get_tab_by_name(self, name): + '''Return uzbl instance by name.''' for (tab, uzbl) in self.tabs.items(): - if uzbl.pid == pid: + if uzbl.name == name: return uzbl return False @@ -938,15 +915,12 @@ class UzblTabbed: when you need to load multiple tabs at a time (I.e. like when restoring a session from a file).''' - pid = self.next_pid() tab = gtk.Socket() tab.show() self.notebook.append_page(tab) sid = tab.get_id() uri = uri.strip() - - socket_filename = 'uzbl_socket_%s_%0.2d' % (self.wid, pid) - socket_file = os.path.join(config['socket_dir'], socket_filename) + name = "%d-%d" % (os.getpid(), self.next_pid()) if switch is None: switch = config['switch_to_new_tabs'] @@ -954,22 +928,12 @@ class UzblTabbed: if not title: title = config['new_tab_title'] - uzbl = self.UzblInstance(self, tab, socket_file, pid,\ - uri, title, switch) - - if len(uri): - uri = "--uri %r" % uri - - self.tabs[tab] = uzbl - cmd = 'uzbl-browser -s %s -n %s_%0.2d %s &' % (sid, self.wid, pid, uri) - subprocess.Popen([cmd], shell=True) # TODO: do i need close_fds=True ? - - # Add gobject timer to make sure the config is pushed when socket - # has been created. - timerid = timeout_add(100, uzbl.flush, "flush-initial-config") - uzbl.timers['flush-initial-config'] = timerid + cmd = ['uzbl-browser', '-n', name, '-s', str(sid), + '--connect-socket', self.socket_path, '--uri', uri] + subprocess.Popen(cmd) # TODO: do i need close_fds=True ? - self.update_tablist() + uzbl = UzblInstance(self, tab, name, uri, title, switch) + SocketClient.instances_queue[name] = uzbl def clean_slate(self): @@ -980,48 +944,17 @@ class UzblTabbed: for tab in list(self.notebook)[:-1]: if tab not in tabs: continue uzbl = self.tabs[tab] - uzbl.send("exit") + uzbl.exit() def config_uzbl(self, uzbl): '''Send bind commands for tab new/close/next/prev to a uzbl instance.''' - binds = [] - bind_format = r'@bind %s = sh "echo \"%s\" > \"%s\""' - bind = lambda key, action: binds.append(bind_format % (key, action,\ - self.fifo_socket)) - - sets = [] - set_format = r'set %s = sh \"echo \\"%s\\" > \\"%s\\""' - set = lambda key, action: binds.append(set_format % (key, action,\ - self.fifo_socket)) - - # Bind definitions here - # bind(key, command back to fifo) - bind(config['bind_new_tab'], 'new') - bind(config['bind_tab_from_clip'], 'newfromclip') - bind(config['bind_tab_from_uri'], 'new %s') - bind(config['bind_close_tab'], 'close') - bind(config['bind_next_tab'], 'next') - bind(config['bind_prev_tab'], 'prev') - bind(config['bind_goto_tab'], 'goto %s') - bind(config['bind_goto_first'], 'goto 0') - bind(config['bind_goto_last'], 'goto -1') - bind(config['bind_clean_slate'], 'clean') - bind(config['bind_save_preset'], 'preset save %s') - bind(config['bind_load_preset'], 'preset load %s') - bind(config['bind_del_preset'], 'preset del %s') - bind(config['bind_list_presets'], 'preset list %d' % uzbl.pid) - bind(config['bind_exit'], 'exit') - # Set definitions here # set(key, command back to fifo) if config['capture_new_windows']: - set("new_window", r'new $8') - - # Send config to uzbl instance via its socket file. - uzbl.send("\n".join(binds+sets)) + uzbl.set("new_window", r'new $8') def goto_tab(self, index): @@ -1107,17 +1040,8 @@ class UzblTabbed: if tab in self.tabs.keys(): uzbl = self.tabs[tab] - for (timer, gid) in uzbl.timers.items(): - error("tab_closed: removing timer %r" % timer) - source_remove(gid) - del uzbl.timers[timer] - - if uzbl._socket: - uzbl._socket.close() - uzbl._socket = None + uzbl.close() - uzbl._socketout = [] - uzbl._kill = True self._closed.append((uzbl.uri, uzbl.title)) self._closed = self._closed[-10:] del self.tabs[tab] @@ -1375,7 +1299,7 @@ class UzblTabbed: os.remove(config['session_file']) for (tab, uzbl) in self.tabs.items(): - uzbl.send("exit") + uzbl.exit() # Add a gobject timer to make sure the application force-quits after a # reasonable period. Calling quit when all the tabs haven't had time to @@ -1390,7 +1314,7 @@ class UzblTabbed: # Close the fifo socket, remove any gobject io event handlers and # delete socket. - self.unlink_fifo_socket() + self.unlink_fifo() # Remove all gobject timers that are still ticking. for (timerid, gid) in self._timers.items(): -- cgit v1.2.3 From 0787a00be40badff461e4f931944f199b68a5408 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Tue, 29 Dec 2009 00:59:15 +0100 Subject: uzbl-tabbed: * misc fixes (problems introduced in last commit) * correct implemenation of the configuration (use uzbl events insted of config file; you can now do :set show_tablist=0) --- examples/data/uzbl/scripts/uzbl-tabbed | 166 +++++++++++++++++---------------- 1 file changed, 88 insertions(+), 78 deletions(-) diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index 9d8e1e6..dc6c773 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -183,12 +183,10 @@ def xdghome(key, default): # Setup xdg paths. DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') -CONFIG_DIR = os.path.join(xdghome('CONFIG', '.config/'), 'uzbl/') # Ensure uzbl xdg paths exist -for path in [DATA_DIR, CONFIG_DIR]: - if not os.path.exists(path): - os.makedirs(path) +if not os.path.exists(DATA_DIR): + os.makedirs(DATA_DIR) # All of these settings can be inherited from your uzbl config file. config = { @@ -240,6 +238,8 @@ config = { } # End of config dict. +UZBL_TABBED_VARS = config.keys() + # This is the tab style policy handler. Every time the tablist is updated # this function is called to determine how to colourise that specific tab # according the simple/complex rules as defined here. You may even wish to @@ -279,37 +279,6 @@ def echo(msg): sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) -def readconfig(uzbl_config, config): - '''Loads relevant config from the users uzbl config file into the global - config dictionary.''' - - if not os.path.exists(uzbl_config): - error("Unable to load config %r" % uzbl_config) - return None - - # Define parsing regular expressions - isint = re.compile("^(\-|)[0-9]+$").match - findsets = re.compile("^set\s+([^\=]+)\s*\=\s*(.+)$",\ - re.MULTILINE).findall - - h = open(os.path.expandvars(uzbl_config), 'r') - rawconfig = h.read() - h.close() - - configkeys, strip = config.keys(), str.strip - for (key, value) in findsets(rawconfig): - key, value = strip(key), strip(value) - if key not in configkeys: continue - if isint(value): value = int(value) - config[key] = value - - # Ensure that config keys that relate to paths are expanded. - pathkeys = ['fifo_dir', 'socket_dir', 'session_file', 'icon_path', - 'saved_sessions_dir'] - for key in pathkeys: - config[key] = os.path.expandvars(config[key]) - - def counter(): '''To infinity and beyond!''' @@ -429,7 +398,7 @@ class UzblInstance: self._client.send('set %s = %s') #TODO: escape chars ? - def exit(sedf): + def exit(self): ''' Ask the Uzbl instance to close ''' self._client.send('exit') #TODO: escape chars ? @@ -444,6 +413,34 @@ class UzblInstance: if type == "TITLE_CHANGED": self.title = args self.parent.update_tablist() + elif type == "VARIABLE_SET": + var, _, val = args.split(" ", 2) + try: + val = int(val) + except: + pass + + if var in UZBL_TABBED_VARS: + if config[var] != val: + config[var] = val + if var == "show_gtk_tabs": + self.parent.notebook.set_show_tabs(bool(val)) + elif var == "show_tablist" or var == "tablist_top": + self.parent.update_tablist_display() + elif var == "gtk_tab_pos": + self.parent.update_gtk_tab_pos() + elif var == "status_background": + col = gtk.gdk.color_parse(config['status_background']) + self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) + self.parent.update_tablist() + else: + config[var] = val + + if var == "uri": + self.uri = var + self.parent.update_tablist() + else: + print type, args def close(self): @@ -502,35 +499,33 @@ class UzblTabbed: self.window.connect("delete-event", self.quitrequest) # Create tab list - if config['show_tablist']: - vbox = gtk.VBox() - self.window.add(vbox) - ebox = gtk.EventBox() - self.tablist = gtk.Label() - - self.tablist.set_use_markup(True) - self.tablist.set_justify(gtk.JUSTIFY_LEFT) - self.tablist.set_line_wrap(False) - self.tablist.set_selectable(False) - self.tablist.set_padding(2,2) - self.tablist.set_alignment(0,0) - self.tablist.set_ellipsize(pango.ELLIPSIZE_END) - self.tablist.set_text(" ") - self.tablist.show() - ebox.add(self.tablist) - ebox.show() - bgcolor = gtk.gdk.color_parse(config['status_background']) - ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) + vbox = gtk.VBox() + self.vbox = vbox + self.window.add(vbox) + ebox = gtk.EventBox() + self.ebox = ebox + self.tablist = gtk.Label() + + self.tablist.set_use_markup(True) + self.tablist.set_justify(gtk.JUSTIFY_LEFT) + self.tablist.set_line_wrap(False) + self.tablist.set_selectable(False) + self.tablist.set_padding(2,2) + self.tablist.set_alignment(0,0) + self.tablist.set_ellipsize(pango.ELLIPSIZE_END) + self.tablist.set_text(" ") + self.tablist.show() + ebox.add(self.tablist) + ebox.show() + bgcolor = gtk.gdk.color_parse(config['status_background']) + ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) # Create notebook self.notebook = gtk.Notebook() self.notebook.set_show_tabs(config['show_gtk_tabs']) # Set tab position - allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, - 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} - if config['gtk_tab_pos'] in allposes.keys(): - self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) + self.update_gtk_tab_pos() self.notebook.set_show_border(False) self.notebook.set_scrollable(True) @@ -541,20 +536,11 @@ class UzblTabbed: self.notebook.connect("page-added", self.tab_opened) self.notebook.show() - if config['show_tablist']: - if config['tablist_top']: - vbox.pack_start(ebox, False, False, 0) - vbox.pack_end(self.notebook, True, True, 0) - - else: - vbox.pack_start(self.notebook, True, True, 0) - vbox.pack_end(ebox, False, False, 0) - - vbox.show() - - else: - self.window.add(self.notebook) + vbox.pack_start(self.notebook, True, True, 0) + vbox.reorder_child(self.notebook, 1) + self.update_tablist_display() + self.vbox.show() self.window.show() self.wid = self.notebook.window.xid @@ -603,6 +589,7 @@ class UzblTabbed: # Unlink fifo socket self.unlink_fifo() + self.close_socket() # Attempt to close all uzbl instances nicely. self.quitrequest() @@ -660,9 +647,12 @@ class UzblTabbed: def close_socket(self): '''Close the socket when closing the application''' - (fd, watcher) = self._socket - source_remove(watcher) - fd.close() + if self._socket: + (fd, watcher) = self._socket + source_remove(watcher) + fd.close() + os.unlink(self.socket_path) + self._socket = None def init_fifo(self): @@ -1067,6 +1057,28 @@ class UzblTabbed: return True + def update_tablist_display(self): + '''Called when show_tablist or tablist_top has changed''' + + if self.ebox in self.vbox.get_children(): + self.vbox.remove(self.ebox) + + if config['show_tablist']: + self.vbox.pack_start(self.ebox, False, False, 0) + if config['tablist_top']: + self.vbox.reorder_child(self.ebox, 0) + else: + self.vbox.reorder_child(self.ebox, 2) + + def update_gtk_tab_pos(self): + ''' Called when gtk_tab_pos has changed ''' + + allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, + 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} + if config['gtk_tab_pos'] in allposes.keys(): + self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) + + def update_tablist(self, curpage=None): '''Upate tablist status bar.''' @@ -1315,6 +1327,7 @@ class UzblTabbed: # Close the fifo socket, remove any gobject io event handlers and # delete socket. self.unlink_fifo() + self.close_socket() # Remove all gobject timers that are still ticking. for (timerid, gid) in self._timers.items(): @@ -1330,9 +1343,6 @@ class UzblTabbed: if __name__ == "__main__": - # Read from the uzbl config into the global config dictionary. - readconfig(UZBL_CONFIG, config) - # Build command line parser usage = "usage: %prog [OPTIONS] {URIS}..." parser = OptionParser(usage=usage) -- cgit v1.2.3 From a5724db578e2595f57698eb5ad01f033a3e4a510 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Tue, 29 Dec 2009 01:28:33 +0100 Subject: implemented new actions for uzbl_tabbed --- examples/data/uzbl/scripts/uzbl-tabbed | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index dc6c773..0752895 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -439,8 +439,32 @@ class UzblInstance: if var == "uri": self.uri = var self.parent.update_tablist() - else: - print type, args + elif type == "NEW_TAB": + self.parent.new_tab(args) + elif type == "NEXT_TAB": + if args: + self.parent.next_tab(int(args)) + else: + self.parent.next_tab() + elif type == "PREV_TAB": + if args: + self.parent.prev_tab(int(args)) + else: + self.parent.prev_tab() + elif type == "GOTO_TAB": + self.parent.goto_tab(int(args)) + elif type == "FIRST_TAB": + self.parent.goto_tab(0) + elif type == "LAST_TAB": + self.parent.goto_tab(-1) + elif type == "PRESET_TABS": + self.parent.parse_command(["preset"] + args.split()) + elif type == "BRING_TO_FRONT": + self.parent.window.present() + elif type == "CLEAN_TABS": + self.parent.clean_slate() + elif type == "EXIT_ALL_TABS": + self.parent.quitrequest() def close(self): -- cgit v1.2.3 From ddddb35a2b5127847a3e354c7e9725e9fcecaeb3 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Tue, 29 Dec 2009 14:00:46 +0100 Subject: uzbl-tabbed: get rid of update-tablist timer --- examples/data/uzbl/scripts/uzbl-tabbed | 163 ++++++++++++++++----------------- 1 file changed, 81 insertions(+), 82 deletions(-) diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index 0752895..84f1158 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -372,9 +372,11 @@ class UzblInstance: self.tab = tab self.name = name self.title = title + self.tabtitle = "" self.uri = uri self._client = None self._switch = switch # Switch to tab after loading ? + self.title_changed() def got_socket(self, client): @@ -382,16 +384,45 @@ class UzblInstance: self._client = client self.parent.tabs[self.tab] = self - self.parent.update_tablist() - self.parent.config_uzbl(self) if self._switch: - tabs = list(self.parent.notebook) - tabid = tabs.index(self.tab) + tabid = self.parent.notebook.page_num(self.tab) self.parent.goto_tab(tabid) + def title_changed(self, gtk_only = True): # GTK-only is for indexes + '''self.title has changed, update the tabs list''' + + tab_titles = config['tab_titles'] + show_ellipsis = config['show_ellipsis'] + max_title_len = config['max_title_len'] + + # Unicode heavy strings do not like being truncated/sliced so by + # re-encoding the string sliced of limbs are removed. + self.tabtitle = self.title[:max_title_len + int(show_ellipsis)] + if type(self.tabtitle) != types.UnicodeType: + self.tabtitle = unicode(self.tabtitle, 'utf-8', 'ignore') + + self.tabtitle = self.tabtitle.encode('utf-8', 'ignore').strip() + + if show_ellipsis and len(self.tabtitle) != len(self.title): + self.tabtitle += "\xe2\x80\xa6" + + gtk_tab_format = "%d %s" + tab_titles = config['tab_titles'] + index = self.parent.notebook.page_num(self.tab) + if tab_titles: + self.parent.notebook.set_tab_label_text(self.tab, + gtk_tab_format % (index, self.tabtitle)) + else: + self.parent.notebook.set_tab_label_text(self.tab, str(index)) + + # Non-GTK tabs + if not gtk_only: + self.parent.update_tablist() + + def set(self, key, val): ''' Send the SET command to Uzbl ''' @@ -401,7 +432,7 @@ class UzblInstance: def exit(self): ''' Ask the Uzbl instance to close ''' - self._client.send('exit') #TODO: escape chars ? + self._client.send('exit') def parse_command(self, cmd): @@ -412,7 +443,7 @@ class UzblInstance: type, args = args.split(" ", 1) if type == "TITLE_CHANGED": self.title = args - self.parent.update_tablist() + self.title_changed() elif type == "VARIABLE_SET": var, _, val = args.split(" ", 2) try: @@ -595,10 +626,6 @@ class UzblTabbed: if gtk_refresh < 100: gtk_refresh = 100 - # Update tablist timer - timerid = timeout_add(gtk_refresh, self.update_tablist) - self._timers["update-tablist"] = timerid - # Make SIGTERM act orderly. signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) @@ -974,9 +1001,13 @@ class UzblTabbed: def goto_tab(self, index): '''Goto tab n (supports negative indexing).''' + title_format = "%s - Uzbl Browser" + tabs = list(self.notebook) if 0 <= index < len(tabs): self.notebook.set_current_page(index) + uzbl = self.tabs[self.notebook.get_nth_page(index)] + self.window.set_title(title_format % uzbl.title) self.update_tablist() return None @@ -986,6 +1017,8 @@ class UzblTabbed: # negative index. index = tabs.index(tab) self.notebook.set_current_page(index) + uzbl = self.tabs[self.notebook.get_nth_page(index)] + self.window.set_title(title_format % uzbl.title) self.update_tablist() except IndexError: @@ -1001,8 +1034,7 @@ class UzblTabbed: ntabs = self.notebook.get_n_pages() tabn = (self.notebook.get_current_page() + step) % ntabs - self.notebook.set_current_page(tabn) - self.update_tablist() + self.goto_tab(tabn) def prev_tab(self, step=1): @@ -1015,8 +1047,7 @@ class UzblTabbed: ntabs = self.notebook.get_n_pages() tabn = self.notebook.get_current_page() - step while tabn < 0: tabn += ntabs - self.notebook.set_current_page(tabn) - self.update_tablist() + self.goto_tab(tabn) def close_tab(self, tabn=None): @@ -1067,6 +1098,8 @@ class UzblTabbed: self.quit() + for tab in self.notebook: + self.tabs[tab].title_changed(True) self.update_tablist() return True @@ -1106,102 +1139,68 @@ class UzblTabbed: def update_tablist(self, curpage=None): '''Upate tablist status bar.''' - show_tablist = config['show_tablist'] - show_gtk_tabs = config['show_gtk_tabs'] + if not config['show_tablist']: + return True + tab_titles = config['tab_titles'] - show_ellipsis = config['show_ellipsis'] multiline_tabs = config['multiline_tabs'] if multiline_tabs: multiline = [] - if not show_tablist and not show_gtk_tabs: - return True - tabs = self.tabs.keys() if curpage is None: curpage = self.notebook.get_current_page() - title_format = "%s - Uzbl Browser" - max_title_len = config['max_title_len'] - - if show_tablist: - pango = "" - normal = (config['tab_colours'], config['tab_text_colours']) - selected = (config['selected_tab'], config['selected_tab_text']) + pango = "" + normal = (config['tab_colours'], config['tab_text_colours']) + selected = (config['selected_tab'], config['selected_tab_text']) - if tab_titles: - tab_format = " [ %d %s ] " - - else: - tab_format = " [ %d ] " + if tab_titles: + tab_format = " [ %d %s ] " - if show_gtk_tabs: - gtk_tab_format = "%d %s" + else: + tab_format = " [ %d ] " for index, tab in enumerate(self.notebook): if tab not in tabs: continue uzbl = self.tabs[tab] - if index == curpage: - self.window.set_title(title_format % uzbl.title) - - # Unicode heavy strings do not like being truncated/sliced so by - # re-encoding the string sliced of limbs are removed. - tabtitle = uzbl.title[:max_title_len + int(show_ellipsis)] - if type(tabtitle) != types.UnicodeType: - tabtitle = unicode(tabtitle, 'utf-8', 'ignore') - - tabtitle = tabtitle.encode('utf-8', 'ignore').strip() + style = colour_selector(index, curpage, uzbl) + (tabc, textc) = style - if show_ellipsis and len(tabtitle) != len(uzbl.title): - tabtitle += "\xe2\x80\xa6" + if multiline_tabs: + opango = pango - if show_gtk_tabs: if tab_titles: - self.notebook.set_tab_label_text(tab, - gtk_tab_format % (index, tabtitle)) + pango += tab_format % (tabc, index, textc, + escape(uzbl.tabtitle)) else: - self.notebook.set_tab_label_text(tab, str(index)) - - if show_tablist: - style = colour_selector(index, curpage, uzbl) - (tabc, textc) = style - - if multiline_tabs: - opango = pango - - if tab_titles: - pango += tab_format % (tabc, index, textc, - escape(tabtitle)) - - else: - pango += tab_format % (tabc, textc, index) + pango += tab_format % (tabc, textc, index) - self.tablist.set_markup(pango) - listwidth = self.tablist.get_layout().get_pixel_size()[0] - winwidth = self.window.get_size()[0] + self.tablist.set_markup(pango) + listwidth = self.tablist.get_layout().get_pixel_size()[0] + winwidth = self.window.get_size()[0] - if listwidth > (winwidth - 20): - multiline.append(opango) - pango = tab_format % (tabc, index, textc, - escape(tabtitle)) + if listwidth > (winwidth - 20): + multiline.append(opango) + pango = tab_format % (tabc, index, textc, + escape(uzbl.tabtitle)) - elif tab_titles: - pango += tab_format % (tabc, index, textc, - escape(tabtitle)) + elif tab_titles: + pango += tab_format % (tabc, index, textc, + escape(uzbl.tabtitle)) - else: - pango += tab_format % (tabc, textc, index) + else: + pango += tab_format % (tabc, textc, index) - if show_tablist: - if multiline_tabs: - multiline.append(pango) - self.tablist.set_markup(' '.join(multiline)) + if multiline_tabs: + multiline.append(pango) + self.tablist.set_markup(' '.join(multiline)) - else: - self.tablist.set_markup(pango) + else: + self.tablist.set_markup(pango) return True -- cgit v1.2.3 From 1e6d390fb965f0f5692d8a5c441fb12b01283941 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Tue, 29 Dec 2009 15:40:09 +0100 Subject: uzbl-tabbed: add a tab_indexes option --- examples/data/uzbl/scripts/uzbl-tabbed | 56 +++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index 84f1158..0252f5c 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -77,6 +77,7 @@ # # Tab title options: # tab_titles = 1 +# tab_indexes = 1 # new_tab_title = Loading # max_title_len = 50 # show_ellipsis = 1 @@ -202,6 +203,7 @@ config = { # Tab title options 'tab_titles': True, # Display tab titles (else only tab-nums) + 'tab_indexes': True, # Display tab nums (else only tab titles) 'new_tab_title': 'Loading', # New tab title 'max_title_len': 50, # Truncate title at n characters 'show_ellipsis': True, # Show ellipsis when truncating titles @@ -381,11 +383,9 @@ class UzblInstance: def got_socket(self, client): '''Uzbl instance is now connected''' - self._client = client - self.parent.tabs[self.tab] = self + self._client = client self.parent.config_uzbl(self) - if self._switch: tabid = self.parent.notebook.page_num(self.tab) self.parent.goto_tab(tabid) @@ -395,6 +395,7 @@ class UzblInstance: '''self.title has changed, update the tabs list''' tab_titles = config['tab_titles'] + tab_indexes = config['tab_indexes'] show_ellipsis = config['show_ellipsis'] max_title_len = config['max_title_len'] @@ -410,14 +411,20 @@ class UzblInstance: self.tabtitle += "\xe2\x80\xa6" gtk_tab_format = "%d %s" - tab_titles = config['tab_titles'] index = self.parent.notebook.page_num(self.tab) - if tab_titles: + if tab_titles and tab_indexes: self.parent.notebook.set_tab_label_text(self.tab, gtk_tab_format % (index, self.tabtitle)) + elif tab_titles: + self.parent.notebook.set_tab_label_text(self.tab, self.tabtitle) else: self.parent.notebook.set_tab_label_text(self.tab, str(index)) + # If instance is current tab, update window title + if index == self.parent.notebook.get_current_page(): + title_format = "%s - Uzbl Browser" + self.parent.window.set_title(title_format % self.title) + # Non-GTK tabs if not gtk_only: self.parent.update_tablist() @@ -426,13 +433,15 @@ class UzblInstance: def set(self, key, val): ''' Send the SET command to Uzbl ''' - self._client.send('set %s = %s') #TODO: escape chars ? + if self._client: + self._client.send('set %s = %s') #TODO: escape chars ? def exit(self): ''' Ask the Uzbl instance to close ''' - self._client.send('exit') + if self._client: + self._client.send('exit') def parse_command(self, cmd): @@ -463,6 +472,10 @@ class UzblInstance: elif var == "status_background": col = gtk.gdk.color_parse(config['status_background']) self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) + elif var == "tab_titles" or var == "tab_indexes": + for tab in self.parent.notebook: + self.parent.tabs[tab].title_changed(True) + self.parent.update_tablist() else: config[var] = val @@ -975,6 +988,7 @@ class UzblTabbed: uzbl = UzblInstance(self, tab, name, uri, title, switch) SocketClient.instances_queue[name] = uzbl + self.tabs[tab] = uzbl def clean_slate(self): @@ -1143,6 +1157,7 @@ class UzblTabbed: return True tab_titles = config['tab_titles'] + tab_indexes = config['tab_indexes'] multiline_tabs = config['multiline_tabs'] if multiline_tabs: @@ -1156,15 +1171,17 @@ class UzblTabbed: normal = (config['tab_colours'], config['tab_text_colours']) selected = (config['selected_tab'], config['selected_tab_text']) - if tab_titles: - tab_format = " [ %d %s ] " - + if tab_titles and tab_indexes: + tab_format = " [ %(index)d %(title)s ] " + elif tab_titles: + tab_format = " [ %(title)s ] " else: - tab_format = " [ %d ] " + tab_format = " [ %(index)d ] " for index, tab in enumerate(self.notebook): if tab not in tabs: continue uzbl = self.tabs[tab] + title = escape(uzbl.tabtitle) style = colour_selector(index, curpage, uzbl) (tabc, textc) = style @@ -1172,12 +1189,7 @@ class UzblTabbed: if multiline_tabs: opango = pango - if tab_titles: - pango += tab_format % (tabc, index, textc, - escape(uzbl.tabtitle)) - - else: - pango += tab_format % (tabc, textc, index) + pango += tab_format % locals() self.tablist.set_markup(pango) listwidth = self.tablist.get_layout().get_pixel_size()[0] @@ -1185,15 +1197,9 @@ class UzblTabbed: if listwidth > (winwidth - 20): multiline.append(opango) - pango = tab_format % (tabc, index, textc, - escape(uzbl.tabtitle)) - - elif tab_titles: - pango += tab_format % (tabc, index, textc, - escape(uzbl.tabtitle)) - + pango = tab_format % locals() else: - pango += tab_format % (tabc, textc, index) + pango += tab_format % locals() if multiline_tabs: multiline.append(pango) -- cgit v1.2.3 From 30b7d1630e487f01f0f2ddc0fffca9d492213619 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 18:21:25 +0100 Subject: authors file updates --- AUTHORS | 2 +- examples/data/uzbl/scripts/uzbl-tabbed | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index b5d538f..6f540d0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -54,7 +54,7 @@ In alphabetical order: Přemysl Hrubý (anydot) - several C contributions and cleanups Robert Manea (robm) - C code all over the place Sergey Shepelev (temoto) - doc patch - Simon Lipp (sloonz) - various patches + Simon Lipp (sloonz) - various patches, EM contributions Sylvester Johansson (scj) - form filler script & different take on link follower Tassilo Horn (tsdh) - $VISUAL patch Thorsten Wilms - logo design diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed index 0252f5c..5d1a9f8 100755 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ b/examples/data/uzbl/scripts/uzbl-tabbed @@ -44,6 +44,9 @@ # # Devon Jones # Fifo command bring_to_front which brings the gtk window to focus. +# +# Simon Lipp (sloonz) +# Various # Dependencies: -- cgit v1.2.3 From 9dd1370d0b7cd876f004f7a822b0357039252184 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 20:11:15 +0100 Subject: remove the 'uzbl' subdirectory in examples/*/, since the sandbox they are no longer needed + update paths everywhere + remove examples/config/enchant (also not needed since sandbox) + bugfix: set /home/dieter in sandbox --- .gitignore | 1 - Makefile | 18 +- examples/config/config | 376 ++++++ examples/config/cookies | 22 + examples/config/uzbl/config | 376 ------ examples/config/uzbl/cookies | 22 - examples/data/bookmarks | 4 + examples/data/forms/bbs.archlinux.org | 5 + examples/data/plugins/bind.py | 521 +++++++ examples/data/plugins/cmd_expand.py | 42 + examples/data/plugins/completion.py | 206 +++ examples/data/plugins/config.py | 97 ++ examples/data/plugins/keycmd.py | 571 ++++++++ examples/data/plugins/mode.py | 176 +++ examples/data/plugins/on_event.py | 107 ++ examples/data/plugins/plugin_template.py | 76 ++ examples/data/plugins/progress_bar.py | 159 +++ examples/data/scripts/cookies.sh | 154 +++ examples/data/scripts/download.sh | 22 + examples/data/scripts/extedit.js | 102 ++ examples/data/scripts/follow_Numbers.js | 228 ++++ examples/data/scripts/follow_Numbers_Strings.js | 212 +++ examples/data/scripts/formfiller.pl | 99 ++ examples/data/scripts/formfiller.sh | 62 + examples/data/scripts/hint.js | 26 + examples/data/scripts/history.sh | 5 + examples/data/scripts/insert_bookmark.sh | 16 + examples/data/scripts/instance-select-wmii.sh | 54 + examples/data/scripts/linkfollow.js | 269 ++++ examples/data/scripts/load_url_from_bookmarks.sh | 20 + examples/data/scripts/load_url_from_history.sh | 24 + examples/data/scripts/scheme.py | 24 + examples/data/scripts/scroll-percentage.js | 68 + examples/data/scripts/session.sh | 62 + examples/data/scripts/uzbl-cookie-daemon | 664 +++++++++ examples/data/scripts/uzbl-event-manager | 833 ++++++++++++ examples/data/scripts/uzbl-tabbed | 1417 ++++++++++++++++++++ examples/data/scripts/uzblcat | 12 + examples/data/style.css | 25 + examples/data/uzbl.png | Bin 0 -> 2185 bytes examples/data/uzbl/bookmarks | 4 - examples/data/uzbl/forms/bbs.archlinux.org | 5 - examples/data/uzbl/plugins/bind.py | 521 ------- examples/data/uzbl/plugins/cmd_expand.py | 42 - examples/data/uzbl/plugins/completion.py | 206 --- examples/data/uzbl/plugins/config.py | 97 -- examples/data/uzbl/plugins/keycmd.py | 571 -------- examples/data/uzbl/plugins/mode.py | 176 --- examples/data/uzbl/plugins/on_event.py | 107 -- examples/data/uzbl/plugins/plugin_template.py | 76 -- examples/data/uzbl/plugins/progress_bar.py | 159 --- examples/data/uzbl/scripts/cookies.sh | 154 --- examples/data/uzbl/scripts/download.sh | 22 - examples/data/uzbl/scripts/extedit.js | 102 -- examples/data/uzbl/scripts/follow_Numbers.js | 228 ---- .../data/uzbl/scripts/follow_Numbers_Strings.js | 212 --- examples/data/uzbl/scripts/formfiller.pl | 99 -- examples/data/uzbl/scripts/formfiller.sh | 62 - examples/data/uzbl/scripts/hint.js | 26 - examples/data/uzbl/scripts/history.sh | 5 - examples/data/uzbl/scripts/insert_bookmark.sh | 16 - examples/data/uzbl/scripts/instance-select-wmii.sh | 54 - examples/data/uzbl/scripts/linkfollow.js | 269 ---- .../data/uzbl/scripts/load_url_from_bookmarks.sh | 20 - .../data/uzbl/scripts/load_url_from_history.sh | 24 - examples/data/uzbl/scripts/scheme.py | 24 - examples/data/uzbl/scripts/scroll-percentage.js | 68 - examples/data/uzbl/scripts/session.sh | 62 - examples/data/uzbl/scripts/uzbl-cookie-daemon | 664 --------- examples/data/uzbl/scripts/uzbl-event-manager | 833 ------------ examples/data/uzbl/scripts/uzbl-tabbed | 1417 -------------------- examples/data/uzbl/scripts/uzblcat | 12 - examples/data/uzbl/style.css | 25 - examples/data/uzbl/uzbl.png | Bin 2185 -> 0 bytes sandbox/env.sh | 9 +- src/uzbl-browser | 2 +- 76 files changed, 6780 insertions(+), 6770 deletions(-) create mode 100644 examples/config/config create mode 100644 examples/config/cookies delete mode 100644 examples/config/uzbl/config delete mode 100644 examples/config/uzbl/cookies create mode 100644 examples/data/bookmarks create mode 100644 examples/data/forms/bbs.archlinux.org create mode 100644 examples/data/plugins/bind.py create mode 100644 examples/data/plugins/cmd_expand.py create mode 100644 examples/data/plugins/completion.py create mode 100644 examples/data/plugins/config.py create mode 100644 examples/data/plugins/keycmd.py create mode 100644 examples/data/plugins/mode.py create mode 100644 examples/data/plugins/on_event.py create mode 100644 examples/data/plugins/plugin_template.py create mode 100644 examples/data/plugins/progress_bar.py create mode 100755 examples/data/scripts/cookies.sh create mode 100755 examples/data/scripts/download.sh create mode 100644 examples/data/scripts/extedit.js create mode 100644 examples/data/scripts/follow_Numbers.js create mode 100644 examples/data/scripts/follow_Numbers_Strings.js create mode 100755 examples/data/scripts/formfiller.pl create mode 100755 examples/data/scripts/formfiller.sh create mode 100644 examples/data/scripts/hint.js create mode 100755 examples/data/scripts/history.sh create mode 100755 examples/data/scripts/insert_bookmark.sh create mode 100755 examples/data/scripts/instance-select-wmii.sh create mode 100644 examples/data/scripts/linkfollow.js create mode 100755 examples/data/scripts/load_url_from_bookmarks.sh create mode 100755 examples/data/scripts/load_url_from_history.sh create mode 100755 examples/data/scripts/scheme.py create mode 100644 examples/data/scripts/scroll-percentage.js create mode 100755 examples/data/scripts/session.sh create mode 100755 examples/data/scripts/uzbl-cookie-daemon create mode 100755 examples/data/scripts/uzbl-event-manager create mode 100755 examples/data/scripts/uzbl-tabbed create mode 100755 examples/data/scripts/uzblcat create mode 100644 examples/data/style.css create mode 100644 examples/data/uzbl.png delete mode 100644 examples/data/uzbl/bookmarks delete mode 100644 examples/data/uzbl/forms/bbs.archlinux.org delete mode 100644 examples/data/uzbl/plugins/bind.py delete mode 100644 examples/data/uzbl/plugins/cmd_expand.py delete mode 100644 examples/data/uzbl/plugins/completion.py delete mode 100644 examples/data/uzbl/plugins/config.py delete mode 100644 examples/data/uzbl/plugins/keycmd.py delete mode 100644 examples/data/uzbl/plugins/mode.py delete mode 100644 examples/data/uzbl/plugins/on_event.py delete mode 100644 examples/data/uzbl/plugins/plugin_template.py delete mode 100644 examples/data/uzbl/plugins/progress_bar.py delete mode 100755 examples/data/uzbl/scripts/cookies.sh delete mode 100755 examples/data/uzbl/scripts/download.sh delete mode 100644 examples/data/uzbl/scripts/extedit.js delete mode 100644 examples/data/uzbl/scripts/follow_Numbers.js delete mode 100644 examples/data/uzbl/scripts/follow_Numbers_Strings.js delete mode 100755 examples/data/uzbl/scripts/formfiller.pl delete mode 100755 examples/data/uzbl/scripts/formfiller.sh delete mode 100644 examples/data/uzbl/scripts/hint.js delete mode 100755 examples/data/uzbl/scripts/history.sh delete mode 100755 examples/data/uzbl/scripts/insert_bookmark.sh delete mode 100755 examples/data/uzbl/scripts/instance-select-wmii.sh delete mode 100644 examples/data/uzbl/scripts/linkfollow.js delete mode 100755 examples/data/uzbl/scripts/load_url_from_bookmarks.sh delete mode 100755 examples/data/uzbl/scripts/load_url_from_history.sh delete mode 100755 examples/data/uzbl/scripts/scheme.py delete mode 100644 examples/data/uzbl/scripts/scroll-percentage.js delete mode 100755 examples/data/uzbl/scripts/session.sh delete mode 100755 examples/data/uzbl/scripts/uzbl-cookie-daemon delete mode 100755 examples/data/uzbl/scripts/uzbl-event-manager delete mode 100755 examples/data/uzbl/scripts/uzbl-tabbed delete mode 100755 examples/data/uzbl/scripts/uzblcat delete mode 100644 examples/data/uzbl/style.css delete mode 100644 examples/data/uzbl/uzbl.png diff --git a/.gitignore b/.gitignore index 803a154..078164f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ uzbl-core *.pyc *~ tags -examples/config/enchant diff --git a/Makefile b/Makefile index f48d1f0..626d21e 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,7 @@ test-uzbl-browser: uzbl-browser test-uzbl-core-sandbox: uzbl-core make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core + make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data source ./sandbox/env.sh && uzbl-core --uri http://www.uzbl.org --verbose make DESTDIR=./sandbox uninstall rm -rf ./sandbox/usr @@ -72,6 +73,7 @@ test-uzbl-core-sandbox: uzbl-core test-uzbl-browser-sandbox: uzbl-browser make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-browser + make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data source ./sandbox/env.sh && uzbl-cookie-daemon restart -nv & source ./sandbox/env.sh && uzbl-event-manager restart -nav & source ./sandbox/env.sh && uzbl-browser --uri http://www.uzbl.org --verbose @@ -102,19 +104,27 @@ install-uzbl-core: all install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core install -m644 AUTHORS $(INSTALLDIR)/share/uzbl/docs install -m644 README $(INSTALLDIR)/share/uzbl/docs - sed -i 's#^set prefix.*=.*#set prefix = $(RUN_PREFIX)#' $(INSTALLDIR)/share/uzbl/examples/config/uzbl/config + sed -i 's#^set prefix.*=.*#set prefix = $(RUN_PREFIX)#' $(INSTALLDIR)/share/uzbl/examples/config/config install-uzbl-browser: install -d $(INSTALLDIR)/bin install -m755 src/uzbl-browser $(INSTALLDIR)/bin/uzbl-browser - install -m755 examples/data/uzbl/scripts/uzbl-cookie-daemon $(INSTALLDIR)/bin/uzbl-cookie-daemon - install -m755 examples/data/uzbl/scripts/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager + install -m755 examples/data/scripts/uzbl-cookie-daemon $(INSTALLDIR)/bin/uzbl-cookie-daemon + install -m755 examples/data/scripts/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager sed -i 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' $(INSTALLDIR)/bin/uzbl-browser sed -i "s#^PREFIX = .*#PREFIX = '$(RUN_PREFIX)'#" $(INSTALLDIR)/bin/uzbl-event-manager install-uzbl-tabbed: install -d $(INSTALLDIR)/bin - install -m755 examples/data/uzbl/scripts/uzbl-tabbed $(INSTALLDIR)/bin/uzbl-tabbed + install -m755 examples/data/scripts/uzbl-tabbed $(INSTALLDIR)/bin/uzbl-tabbed + +# you probably only want to do this manually when testing and/or to the sandbox. not meant for distributors +install-example-data: + install -d $(INSTALLDIR)/home/.config/uzbl + install -d $(INSTALLDIR)/home/.cache/uzbl + install -d $(INSTALLDIR)/home/.local/share/uzbl + cp -rp examples/config/* $(INSTALLDIR)/home/.config/uzbl/ + cp -rp examples/data/* $(INSTALLDIR)/home/.local/share/uzbl/ uninstall: rm -rf $(INSTALLDIR)/bin/uzbl-* diff --git a/examples/config/config b/examples/config/config new file mode 100644 index 0000000..eb17813 --- /dev/null +++ b/examples/config/config @@ -0,0 +1,376 @@ +# example uzbl config. +# all settings are optional. you can use uzbl without any config at all (but it won't do much) + +set prefix = /usr/local + +# === Shortcuts / Aliases =================================================== + +# Config related events (use the request function): +# request BIND = +set bind = request BIND +# request MODE_BIND = +set mode_bind = request MODE_BIND +# request MODE_CONFIG = +set mode_config = request MODE_CONFIG +# request ON_EVENT +set on_event = request ON_EVENT +# request PROGRESS_CONFIG = +set progress = request PROGRESS_CONFIG +# request MODMAP +set modmap = request MODMAP +# request IGNORE_KEY +set ignore_key = request IGNORE_KEY +# request MODKEY_ADDITION +set modkey_addition = request MODKEY_ADDITION + +# Action related events (use the event function): +# event TOGGLE_MODES ... +set toggle_modes = event TOGGLE_MODES + +set set_mode = set mode = +set set_status = set status_message = +set shell_cmd = sh -c + +# Spawn path shortcuts. In spawn the first dir+path match is used in "dir1:dir2:dir3:executable" +set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data:scripts + +# Javascipt helpers. +set jsh = js var run=Uzbl.run; function get(k){return run("print \\\@"+k)}; function set(k, v) {run("set "+k+" = "+v)}; + +# === Handlers =============================================================== + +# --- Hardcoded event handlers ----------------------------------------------- + +# These handlers can't be moved to the new event system yet as we don't +# support events that can wait for a response from a script. +set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket +set scheme_handler = sync_spawn @scripts_dir/scheme.py + +# Open in the same window. +#set new_window = sh 'echo uri "$8" > $4' +# Open a link in a new window. equivalent to default behavior +set new_window = sh 'uzbl-browser -u $8' + +# --- Optional dynamic event handlers ---------------------------------------- + +# Download handler +@on_event DOWNLOAD_REQUEST spawn @scripts_dir/download.sh %s \@proxy_url + +# Load start handler +@on_event LOAD_START @set_status wait + +# Load commit handlers +@on_event LOAD_COMMIT @set_status recv +@on_event LOAD_COMMIT script @scripts_dir/scroll-percentage.js +# Reset the keycmd on navigation +@on_event LOAD_COMMIT @set_mode + +# Load finish handlers +@on_event LOAD_FINISH @set_status done +@on_event LOAD_FINISH spawn @scripts_dir/history.sh + +# Generate a FORM_ACTIVE event if an editable +# element on the loaded site has initial focus +@on_event LOAD_FINISH js if(document.activeElement.type == 'text') {Uzbl.run("event FORM_ACTIVE");} + +# Switch to insert mode if a (editable) html form is clicked +@on_event FORM_ACTIVE @set_mode insert +# Switch to command mode if anything else is clicked +@on_event ROOT_ACTIVE @set_mode command + +# Example CONFIG_CHANGED event handler +#@on_event CONFIG_CHANGED print Config changed: %1 = %2 + +# === Behaviour and appearance =============================================== + +set show_status = 1 +set status_top = 0 +set status_background = #303030 + +set modcmd_style = weight="bold" foreground="red" +set keycmd_style = weight="light" foreground="red" +set prompt_style = foreground="grey" +set cursor_style = underline="single" +set completion_style = foreground="green" +set hint_style = weight="bold" + +set mode_section = [\@[\@mode_indicator]\@] +set keycmd_section = [\@[\@keycmd_prompt]\@\@modcmd\@keycmd\@completion_list] +set progress_section = \@[\@progress_format]\@ +set scroll_section = \@[\@scroll_message]\@ +set uri_section = \@[\@uri]\@ +set name_section = \@[\@NAME]\@ +set status_section = \@status_message +set selected_section = \@[\@SELECTED_URI]\@ + +set status_format = @mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section + +set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI + +# Progress bar config +@progress width = 8 +# %d = done, %p = pending %c = percent done, %i = int done, %s = spinner, +# %t = percent pending, %o = int pending, %r = sprite scroll +@progress format = [%d>%p]%c +@progress done = = +@progress pending = + +# Or ride those spinnas' +#@progress format = [%d%s%p] +#@progress spinner = -\\|/ +#@progress done = - +#@progress pending = + + +# === Core settings ========================================================== + +set useragent = Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO) (@(+uname -o)@ @(+uname -m)@ [@ARCH_UZBL]) (Commit @COMMIT) +set fifo_dir = /tmp +set socket_dir = /tmp + + +# === Key modmapping and ignoring ============================================ + +#modmap +@modmap +@modmap +@modmap + +#modkey_addition +@modkey_addition +@modkey_addition + +#ignore_key +@ignore_key +@ignore_key + + +# === Mode bind aliases ====================================================== + +# Global binding alias (this is done automatically inside the bind plugin). +#set bind = @mode_bind global + +# Insert mode binding alias +set ibind = @mode_bind insert + +# Command mode binding alias +set cbind = @mode_bind command + +# Non-insert mode bindings alias (ebind for edit-bind). +set ebind = @mode_bind global,-insert + + +# === Global & keycmd editing binds ========================================== + +# Resets keycmd and returns to default mode. +@bind = @set_mode + +# Commands for editing and traversing the keycmd. +@ebind = event KEYCMD_EXEC_CURRENT +@ebind = event SET_CURSOR_POS +@ebind = event SET_CURSOR_POS -1 +@ebind = event SET_CURSOR_POS - +@ebind = event SET_CURSOR_POS + +@ebind = event KEYCMD_BACKSPACE +@ebind = event KEYCMD_DELETE +@ebind = event START_COMPLETION +# Readline-ish bindings. +@ebind w = event KEYCMD_STRIP_WORD +@ebind u = event SET_KEYCMD +@ebind a = event SET_CURSOR_POS 0 +@ebind e = event SET_CURSOR_POS -1 + +# Keycmd injection/append examples. +#@ebind su = event INJECT_KEYCMD \@uri +#@ebind st = event INJECT_KEYCMD \@title +#@ebind du = event APPEND_KEYCMD \@uri +#@ebind dt = event APPEND_KEYCMD \@title + + +# === Mouse bindings ========================================================= + +# Middle click +# if clicked on a link open the link in a new uzbl window +# otherwise open the selection in the current window +set load_from_xclip = sh 'echo "uri $(xclip -o)" > $4' +set open_new_window = sh 'uzbl-browser -u \@SELECTED_URI' +@bind = @jsh if(get("SELECTED_URI")) { run("\@open_new_window"); } else { run("\\\@load_from_xclip"); } + + +# === Keyboard bindings ====================================================== + +# With this command you can enter in any command at runtime when prefixed with +# a colon. +@cbind :_ = %s + +# --- Page movement binds --- +@cbind j = scroll vertical 20 +@cbind k = scroll vertical -20 +@cbind h = scroll horizontal -20 +@cbind l = scroll horizontal 20 +@cbind = scroll vertical -100% +@cbind = scroll vertical 100% +@cbind << = scroll vertical begin +@cbind >> = scroll vertical end +@cbind ^ = scroll horizontal begin +@cbind $ = scroll horizontal end +@cbind = scroll vertical end + +# --- Navigation binds --- +@cbind b = back +@cbind m = forward +@cbind S = stop +@cbind r = reload +@cbind R = reload_ign_cache + +# --- Zoom binds --- +@cbind + = zoom_in +@cbind - = zoom_out +@cbind T = toggle_zoom_type +@cbind 1 = set zoom_level 1.0 +@cbind 2 = set zoom_level 2.0 + +# --- Appearance binds --- +@cbind t = toggle_status + +# --- Page searching binds --- +@cbind /* = search %s +@cbind ?* = search_reverse %s +# Jump to next and previous items +@cbind n = search +@cbind N = search_reverse + +# --- Web searching binds --- +@cbind gg_ = uri http://www.google.com/search?q=\@\@ +@cbind \\awiki_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=\@\@&go=Go +@cbind \\wiki_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=\@\@&go=Go + +# --- Handy binds --- +# Set function shortcut +@cbind s__ = set %1 = %2 +# Exit binding +@cbind ZZ = exit +# Dump config to stdout +@cbind !dump = sh "echo dump_config > $4" +# Reload config +@cbind !reload = sh "sed '/^# === Post-load misc commands/,$d' $1 > $4" +# Uzbl Terminal. TODO explain why this is useful +@cbind t = sh 'xterm -e "socat unix-connect:$5 -"' +#@cbind t = sh 'urxvt -e socat unix-connect:$5 -' + +# --- Uri opening prompts --- +@cbind o_ = uri %s +# Or have it load the current uri into the keycmd for editing +@cbind O_ = uri %s + +# --- Mode setting binds --- +# Changing mode via set. +@cbind I = @set_mode insert +# Or toggle between modes by raising the toggle event. +set toggle_cmd_ins = @toggle_modes command insert +@cbind i = @toggle_cmd_ins +# And the global toggle bind. +@bind i = @toggle_cmd_ins + +# --- Hard-bound bookmarks --- +@cbind gh = uri http://www.uzbl.org + +# --- Yanking & pasting binds --- +@cbind y* = @jsh if('%s' == 'u') { run("sh 'echo -n $6 | xclip'"); } else if('%s' == 't') { run("sh 'echo -n $7 | xclip'"); }; run('event SET_KEYCMD'); + +# Go the page from primary selection +@cbind p = sh 'echo "uri `xclip -selection primary -o`" > $4' +# Go to the page in clipboard +@cbind P = sh 'echo "uri `xclip -selection clipboard -o`" > $4' +# Start a new uzbl instance from the page in primary selection +@cbind 'p = sh 'exec uzbl-browser --uri $(xclip -o)' + +# --- Bookmark inserting binds --- +@cbind b_ = sh 'echo -e "$6 %s" >> $XDG_DATA_HOME/uzbl/bookmarks' +# Or use a script to insert a bookmark. +@cbind B = spawn @scripts_dir/insert_bookmark.sh + +# --- Bookmark/history loading --- +@cbind U = spawn @scripts_dir/load_url_from_history.sh +@cbind u = spawn @scripts_dir/load_url_from_bookmarks.sh + +# --- Link following (similar to vimperator and konqueror) --- +@cbind fl* = script @scripts_dir/follow_Numbers.js %s +# Or number with strings instead of numbers: +@cbind fL* = script @scripts_dir/follow_Numbers_Strings.js %s + +# --- Form filler binds --- +# this script allows you to configure (per domain) values to fill in form +# fields (eg login information) and to fill in these values automatically +set formfiller = spawn @scripts_dir/formfiller +@cbind za = @{formfiller}.sh +@cbind ze = @{formfiller}.sh edit +@cbind zn = @{formfiller}.sh new +@cbind zl = @{formfiller}.sh load +# Or the more advanced implementation using perl: (could not get this to run - Dieter) +@cbind LL = @{formfiller}.pl load +@cbind LN = @{formfiller}.pl new +@cbind LE = @{formfiller}.pl edit + +# --- External edit script configuration & binds --- +# Edit form input fields in an external editor (gvim, emacs, urxvt -e vim, ..) +set external_editor = gvim +#set external_editor = xterm -e vim +@cbind E = script @scripts_dir/extedit.js +# And add menu option. +menu_editable_add Open in @external_editor = script @scripts_dir/extedit.js + +# --- Examples --- +# Example showing how to use uzbl's fifo to execute a command. +#@bind X1 = sh 'echo "set zoom_level = 1.0" > "$4"' +#@bind X2 = sh 'echo "js alert (\\"This is sent by the shell via a fifo\\")" > "$4"' + +# Working with the javascript helper variable jsh. +#@bind X3 = @jsh alert(get('zoom_level')); +#@bind X4 = @jsh if(get('mode') == "insert") { alert("You are in insert mode") } else { alert(get('mode')+" is a silly mode.") }; + + +# === Context menu items ===================================================== + +# Default context menu +menu_add Google = set uri = http://google.com +menu_add Go Home = set uri = http://uzbl.org +menu_separator separator_1 +menu_add Quit uzbl = exit + +# Link context menu +menu_link_add Print Link = print \@SELECTED_URI + + +# === Mode configuration ===================================================== + +# Define some mode specific uzbl configurations. +set command = @mode_config command +set insert = @mode_config insert +set stack = @mode_config stack + +# Command mode config. +@command keycmd_style = foreground="red" +@command status_background = #202020 +@command mode_indicator = Cmd + +# Insert mode config. +@insert status_background = #303030 +@insert mode_indicator = Ins + +# Multi-stage-binding mode config. +@stack keycmd_events = 1 +@stack modcmd_updates = 1 +@stack forward_keys = 0 +@stack keycmd_style = foreground="red" +@stack prompt_style = foreground="#888" weight="light" +@stack status_background = #202020 +@stack mode_indicator = Bnd + +set default_mode = command + + +# === Post-load misc commands =============================================== + +# Set the "home" page. +set uri = uzbl.org/doesitwork/@COMMIT diff --git a/examples/config/cookies b/examples/config/cookies new file mode 100644 index 0000000..9b7374a --- /dev/null +++ b/examples/config/cookies @@ -0,0 +1,22 @@ +# This file demonstrates how one *could* manage his cookies. this file is used by the example cookie handler script. +# stick to this format. +# trusted -> always store what we get, send what we have (TODO: by default, or when requested?) +# deny -> deny storing + sending + +# if you don't like to edit this file manually, you could even write a script that adds/removes entries using sed, and call the script from uzbl with a keybind... + + +TRUSTED +bbs.archlinux.org +archlinux.org +linux.com + + + + +DENY +www.icanhascheezburger.com + + + +# rest -> ask \ No newline at end of file diff --git a/examples/config/uzbl/config b/examples/config/uzbl/config deleted file mode 100644 index 3edb36c..0000000 --- a/examples/config/uzbl/config +++ /dev/null @@ -1,376 +0,0 @@ -# example uzbl config. -# all settings are optional. you can use uzbl without any config at all (but it won't do much) - -set prefix = /usr/local - -# === Shortcuts / Aliases =================================================== - -# Config related events (use the request function): -# request BIND = -set bind = request BIND -# request MODE_BIND = -set mode_bind = request MODE_BIND -# request MODE_CONFIG = -set mode_config = request MODE_CONFIG -# request ON_EVENT -set on_event = request ON_EVENT -# request PROGRESS_CONFIG = -set progress = request PROGRESS_CONFIG -# request MODMAP -set modmap = request MODMAP -# request IGNORE_KEY -set ignore_key = request IGNORE_KEY -# request MODKEY_ADDITION -set modkey_addition = request MODKEY_ADDITION - -# Action related events (use the event function): -# event TOGGLE_MODES ... -set toggle_modes = event TOGGLE_MODES - -set set_mode = set mode = -set set_status = set status_message = -set shell_cmd = sh -c - -# Spawn path shortcuts. In spawn the first dir+path match is used in "dir1:dir2:dir3:executable" -set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data/uzbl:scripts - -# Javascipt helpers. -set jsh = js var run=Uzbl.run; function get(k){return run("print \\\@"+k)}; function set(k, v) {run("set "+k+" = "+v)}; - -# === Handlers =============================================================== - -# --- Hardcoded event handlers ----------------------------------------------- - -# These handlers can't be moved to the new event system yet as we don't -# support events that can wait for a response from a script. -set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket -set scheme_handler = sync_spawn @scripts_dir/scheme.py - -# Open in the same window. -#set new_window = sh 'echo uri "$8" > $4' -# Open a link in a new window. equivalent to default behavior -set new_window = sh 'uzbl-browser -u $8' - -# --- Optional dynamic event handlers ---------------------------------------- - -# Download handler -@on_event DOWNLOAD_REQUEST spawn @scripts_dir/download.sh %s \@proxy_url - -# Load start handler -@on_event LOAD_START @set_status wait - -# Load commit handlers -@on_event LOAD_COMMIT @set_status recv -@on_event LOAD_COMMIT script @scripts_dir/scroll-percentage.js -# Reset the keycmd on navigation -@on_event LOAD_COMMIT @set_mode - -# Load finish handlers -@on_event LOAD_FINISH @set_status done -@on_event LOAD_FINISH spawn @scripts_dir/history.sh - -# Generate a FORM_ACTIVE event if an editable -# element on the loaded site has initial focus -@on_event LOAD_FINISH js if(document.activeElement.type == 'text') {Uzbl.run("event FORM_ACTIVE");} - -# Switch to insert mode if a (editable) html form is clicked -@on_event FORM_ACTIVE @set_mode insert -# Switch to command mode if anything else is clicked -@on_event ROOT_ACTIVE @set_mode command - -# Example CONFIG_CHANGED event handler -#@on_event CONFIG_CHANGED print Config changed: %1 = %2 - -# === Behaviour and appearance =============================================== - -set show_status = 1 -set status_top = 0 -set status_background = #303030 - -set modcmd_style = weight="bold" foreground="red" -set keycmd_style = weight="light" foreground="red" -set prompt_style = foreground="grey" -set cursor_style = underline="single" -set completion_style = foreground="green" -set hint_style = weight="bold" - -set mode_section = [\@[\@mode_indicator]\@] -set keycmd_section = [\@[\@keycmd_prompt]\@\@modcmd\@keycmd\@completion_list] -set progress_section = \@[\@progress_format]\@ -set scroll_section = \@[\@scroll_message]\@ -set uri_section = \@[\@uri]\@ -set name_section = \@[\@NAME]\@ -set status_section = \@status_message -set selected_section = \@[\@SELECTED_URI]\@ - -set status_format = @mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section - -set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI - -# Progress bar config -@progress width = 8 -# %d = done, %p = pending %c = percent done, %i = int done, %s = spinner, -# %t = percent pending, %o = int pending, %r = sprite scroll -@progress format = [%d>%p]%c -@progress done = = -@progress pending = - -# Or ride those spinnas' -#@progress format = [%d%s%p] -#@progress spinner = -\\|/ -#@progress done = - -#@progress pending = - - -# === Core settings ========================================================== - -set useragent = Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO) (@(+uname -o)@ @(+uname -m)@ [@ARCH_UZBL]) (Commit @COMMIT) -set fifo_dir = /tmp -set socket_dir = /tmp - - -# === Key modmapping and ignoring ============================================ - -#modmap -@modmap -@modmap -@modmap - -#modkey_addition -@modkey_addition -@modkey_addition - -#ignore_key -@ignore_key -@ignore_key - - -# === Mode bind aliases ====================================================== - -# Global binding alias (this is done automatically inside the bind plugin). -#set bind = @mode_bind global - -# Insert mode binding alias -set ibind = @mode_bind insert - -# Command mode binding alias -set cbind = @mode_bind command - -# Non-insert mode bindings alias (ebind for edit-bind). -set ebind = @mode_bind global,-insert - - -# === Global & keycmd editing binds ========================================== - -# Resets keycmd and returns to default mode. -@bind = @set_mode - -# Commands for editing and traversing the keycmd. -@ebind = event KEYCMD_EXEC_CURRENT -@ebind = event SET_CURSOR_POS -@ebind = event SET_CURSOR_POS -1 -@ebind = event SET_CURSOR_POS - -@ebind = event SET_CURSOR_POS + -@ebind = event KEYCMD_BACKSPACE -@ebind = event KEYCMD_DELETE -@ebind = event START_COMPLETION -# Readline-ish bindings. -@ebind w = event KEYCMD_STRIP_WORD -@ebind u = event SET_KEYCMD -@ebind a = event SET_CURSOR_POS 0 -@ebind e = event SET_CURSOR_POS -1 - -# Keycmd injection/append examples. -#@ebind su = event INJECT_KEYCMD \@uri -#@ebind st = event INJECT_KEYCMD \@title -#@ebind du = event APPEND_KEYCMD \@uri -#@ebind dt = event APPEND_KEYCMD \@title - - -# === Mouse bindings ========================================================= - -# Middle click -# if clicked on a link open the link in a new uzbl window -# otherwise open the selection in the current window -set load_from_xclip = sh 'echo "uri $(xclip -o)" > $4' -set open_new_window = sh 'uzbl-browser -u \@SELECTED_URI' -@bind = @jsh if(get("SELECTED_URI")) { run("\@open_new_window"); } else { run("\\\@load_from_xclip"); } - - -# === Keyboard bindings ====================================================== - -# With this command you can enter in any command at runtime when prefixed with -# a colon. -@cbind :_ = %s - -# --- Page movement binds --- -@cbind j = scroll vertical 20 -@cbind k = scroll vertical -20 -@cbind h = scroll horizontal -20 -@cbind l = scroll horizontal 20 -@cbind = scroll vertical -100% -@cbind = scroll vertical 100% -@cbind << = scroll vertical begin -@cbind >> = scroll vertical end -@cbind ^ = scroll horizontal begin -@cbind $ = scroll horizontal end -@cbind = scroll vertical end - -# --- Navigation binds --- -@cbind b = back -@cbind m = forward -@cbind S = stop -@cbind r = reload -@cbind R = reload_ign_cache - -# --- Zoom binds --- -@cbind + = zoom_in -@cbind - = zoom_out -@cbind T = toggle_zoom_type -@cbind 1 = set zoom_level 1.0 -@cbind 2 = set zoom_level 2.0 - -# --- Appearance binds --- -@cbind t = toggle_status - -# --- Page searching binds --- -@cbind /* = search %s -@cbind ?* = search_reverse %s -# Jump to next and previous items -@cbind n = search -@cbind N = search_reverse - -# --- Web searching binds --- -@cbind gg_ = uri http://www.google.com/search?q=\@\@ -@cbind \\awiki_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=\@\@&go=Go -@cbind \\wiki_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=\@\@&go=Go - -# --- Handy binds --- -# Set function shortcut -@cbind s__ = set %1 = %2 -# Exit binding -@cbind ZZ = exit -# Dump config to stdout -@cbind !dump = sh "echo dump_config > $4" -# Reload config -@cbind !reload = sh "sed '/^# === Post-load misc commands/,$d' $1 > $4" -# Uzbl Terminal. TODO explain why this is useful -@cbind t = sh 'xterm -e "socat unix-connect:$5 -"' -#@cbind t = sh 'urxvt -e socat unix-connect:$5 -' - -# --- Uri opening prompts --- -@cbind o_ = uri %s -# Or have it load the current uri into the keycmd for editing -@cbind O_ = uri %s - -# --- Mode setting binds --- -# Changing mode via set. -@cbind I = @set_mode insert -# Or toggle between modes by raising the toggle event. -set toggle_cmd_ins = @toggle_modes command insert -@cbind i = @toggle_cmd_ins -# And the global toggle bind. -@bind i = @toggle_cmd_ins - -# --- Hard-bound bookmarks --- -@cbind gh = uri http://www.uzbl.org - -# --- Yanking & pasting binds --- -@cbind y* = @jsh if('%s' == 'u') { run("sh 'echo -n $6 | xclip'"); } else if('%s' == 't') { run("sh 'echo -n $7 | xclip'"); }; run('event SET_KEYCMD'); - -# Go the page from primary selection -@cbind p = sh 'echo "uri `xclip -selection primary -o`" > $4' -# Go to the page in clipboard -@cbind P = sh 'echo "uri `xclip -selection clipboard -o`" > $4' -# Start a new uzbl instance from the page in primary selection -@cbind 'p = sh 'exec uzbl-browser --uri $(xclip -o)' - -# --- Bookmark inserting binds --- -@cbind b_ = sh 'echo -e "$6 %s" >> $XDG_DATA_HOME/uzbl/bookmarks' -# Or use a script to insert a bookmark. -@cbind B = spawn @scripts_dir/insert_bookmark.sh - -# --- Bookmark/history loading --- -@cbind U = spawn @scripts_dir/load_url_from_history.sh -@cbind u = spawn @scripts_dir/load_url_from_bookmarks.sh - -# --- Link following (similar to vimperator and konqueror) --- -@cbind fl* = script @scripts_dir/follow_Numbers.js %s -# Or number with strings instead of numbers: -@cbind fL* = script @scripts_dir/follow_Numbers_Strings.js %s - -# --- Form filler binds --- -# this script allows you to configure (per domain) values to fill in form -# fields (eg login information) and to fill in these values automatically -set formfiller = spawn @scripts_dir/formfiller -@cbind za = @{formfiller}.sh -@cbind ze = @{formfiller}.sh edit -@cbind zn = @{formfiller}.sh new -@cbind zl = @{formfiller}.sh load -# Or the more advanced implementation using perl: (could not get this to run - Dieter) -@cbind LL = @{formfiller}.pl load -@cbind LN = @{formfiller}.pl new -@cbind LE = @{formfiller}.pl edit - -# --- External edit script configuration & binds --- -# Edit form input fields in an external editor (gvim, emacs, urxvt -e vim, ..) -set external_editor = gvim -#set external_editor = xterm -e vim -@cbind E = script @scripts_dir/extedit.js -# And add menu option. -menu_editable_add Open in @external_editor = script @scripts_dir/extedit.js - -# --- Examples --- -# Example showing how to use uzbl's fifo to execute a command. -#@bind X1 = sh 'echo "set zoom_level = 1.0" > "$4"' -#@bind X2 = sh 'echo "js alert (\\"This is sent by the shell via a fifo\\")" > "$4"' - -# Working with the javascript helper variable jsh. -#@bind X3 = @jsh alert(get('zoom_level')); -#@bind X4 = @jsh if(get('mode') == "insert") { alert("You are in insert mode") } else { alert(get('mode')+" is a silly mode.") }; - - -# === Context menu items ===================================================== - -# Default context menu -menu_add Google = set uri = http://google.com -menu_add Go Home = set uri = http://uzbl.org -menu_separator separator_1 -menu_add Quit uzbl = exit - -# Link context menu -menu_link_add Print Link = print \@SELECTED_URI - - -# === Mode configuration ===================================================== - -# Define some mode specific uzbl configurations. -set command = @mode_config command -set insert = @mode_config insert -set stack = @mode_config stack - -# Command mode config. -@command keycmd_style = foreground="red" -@command status_background = #202020 -@command mode_indicator = Cmd - -# Insert mode config. -@insert status_background = #303030 -@insert mode_indicator = Ins - -# Multi-stage-binding mode config. -@stack keycmd_events = 1 -@stack modcmd_updates = 1 -@stack forward_keys = 0 -@stack keycmd_style = foreground="red" -@stack prompt_style = foreground="#888" weight="light" -@stack status_background = #202020 -@stack mode_indicator = Bnd - -set default_mode = command - - -# === Post-load misc commands =============================================== - -# Set the "home" page. -set uri = uzbl.org/doesitwork/@COMMIT diff --git a/examples/config/uzbl/cookies b/examples/config/uzbl/cookies deleted file mode 100644 index 9b7374a..0000000 --- a/examples/config/uzbl/cookies +++ /dev/null @@ -1,22 +0,0 @@ -# This file demonstrates how one *could* manage his cookies. this file is used by the example cookie handler script. -# stick to this format. -# trusted -> always store what we get, send what we have (TODO: by default, or when requested?) -# deny -> deny storing + sending - -# if you don't like to edit this file manually, you could even write a script that adds/removes entries using sed, and call the script from uzbl with a keybind... - - -TRUSTED -bbs.archlinux.org -archlinux.org -linux.com - - - - -DENY -www.icanhascheezburger.com - - - -# rest -> ask \ No newline at end of file diff --git a/examples/data/bookmarks b/examples/data/bookmarks new file mode 100644 index 0000000..13fcd48 --- /dev/null +++ b/examples/data/bookmarks @@ -0,0 +1,4 @@ +http://www.archlinux.org linux arch +http://www.uzbl.org uzbl browser +http://dieter.plaetinck.be uzbl +http://www.icanhascheezburger.com lolcats fun diff --git a/examples/data/forms/bbs.archlinux.org b/examples/data/forms/bbs.archlinux.org new file mode 100644 index 0000000..73c1539 --- /dev/null +++ b/examples/data/forms/bbs.archlinux.org @@ -0,0 +1,5 @@ +form_sent: +redirect_url: +req_username: +req_password: +login: diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py new file mode 100644 index 0000000..9e09337 --- /dev/null +++ b/examples/data/plugins/bind.py @@ -0,0 +1,521 @@ +'''Plugin provides support for binds in uzbl. + +For example: + event BIND ZZ = exit -> bind('ZZ', 'exit') + event BIND o _ = uri %s -> bind('o _', 'uri %s') + event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'") + +And it is also possible to execute a function on activation: + bind('DD', myhandler) +''' + +import sys +import re +import pprint + +# Hold the bind dicts for each uzbl instance. +UZBLS = {} + +# Commonly used regular expressions. +MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match +# Matches , <'x':y>, <:'y'>, , <'x'!y>, ... +PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>' +FIND_PROMPTS = re.compile(PROMPTS).split +VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match + +# For accessing a bind glob stack. +ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) + + +# Custom errors. +class ArgumentError(Exception): pass + + +class Bindlet(object): + '''Per-instance bind status/state tracker.''' + + def __init__(self, uzbl): + self.binds = {'global': {}} + self.uzbl = uzbl + self.depth = 0 + self.args = [] + self.last_mode = None + self.after_cmds = None + self.stack_binds = [] + + # A subset of the global mode binds containing non-stack and modkey + # activiated binds for use in the stack mode. + self.globals = [] + + + def __getitem__(self, key): + return self.get_binds(key) + + + def reset(self): + '''Reset the tracker state and return to last mode.''' + + self.depth = 0 + self.args = [] + self.after_cmds = None + self.stack_binds = [] + + if self.last_mode: + mode, self.last_mode = self.last_mode, None + self.uzbl.set_mode(mode) + + self.uzbl.set('keycmd_prompt') + + + def stack(self, bind, args, depth): + '''Enter or add new bind in the next stack level.''' + + if self.depth != depth: + if bind not in self.stack_binds: + self.stack_binds.append(bind) + + return + + current_mode = self.uzbl.get_mode() + if current_mode != 'stack': + self.last_mode = current_mode + self.uzbl.set_mode('stack') + + self.stack_binds = [bind,] + self.args += args + self.depth += 1 + self.after_cmds = bind.prompts[depth] + + + def after(self): + '''If a stack was triggered then set the prompt and default value.''' + + if self.after_cmds is None: + return + + (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None + + self.uzbl.clear_keycmd() + if prompt: + self.uzbl.set('keycmd_prompt', prompt) + + if set and is_cmd: + self.uzbl.send(set) + + elif set and not is_cmd: + self.uzbl.send('event SET_KEYCMD %s' % set) + + + def get_binds(self, mode=None): + '''Return the mode binds + globals. If we are stacked then return + the filtered stack list and modkey & non-stack globals.''' + + if mode is None: + mode = self.uzbl.get_mode() + + if not mode: + mode = 'global' + + if self.depth: + return self.stack_binds + self.globals + + globals = self.binds['global'] + if mode not in self.binds or mode == 'global': + return filter(None, globals.values()) + + binds = dict(globals.items() + self.binds[mode].items()) + return filter(None, binds.values()) + + + def add_bind(self, mode, glob, bind=None): + '''Insert (or override) a bind into the mode bind dict.''' + + if mode not in self.binds: + self.binds[mode] = {glob: bind} + return + + binds = self.binds[mode] + binds[glob] = bind + + if mode == 'global': + # Regen the global-globals list. + self.globals = [] + for bind in binds.values(): + if bind is not None and bind.is_global: + self.globals.append(bind) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = Bindlet(uzbl) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_bindlet(uzbl): + '''Return the bind tracklet for the given uzbl instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def ismodbind(glob): + '''Return True if the glob specifies a modbind.''' + + return bool(MOD_START(glob)) + + +def split_glob(glob): + '''Take a string of the form "cmd _" and return a list of the + modkeys in the glob and the command.''' + + mods = set() + while True: + match = MOD_START(glob) + if not match: + break + + end = match.span()[1] + mods.add(glob[:end]) + glob = glob[end:] + + return (mods, glob) + + +def unquote(str): + '''Remove quotation marks around string.''' + + if str and str[0] == str[-1] and str[0] in ['"', "'"]: + str = str[1:-1] + + return str + + +class Bind(object): + + # Class attribute to hold the number of Bind classes created. + counter = [0,] + + def __init__(self, glob, handler, *args, **kargs): + self.is_callable = callable(handler) + self._repr_cache = None + + if not glob: + raise ArgumentError('glob cannot be blank') + + if self.is_callable: + self.function = handler + self.args = args + self.kargs = kargs + + elif kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + elif hasattr(handler, '__iter__'): + self.commands = handler + + else: + self.commands = [handler,] + list(args) + + self.glob = glob + + # Assign unique id. + self.counter[0] += 1 + self.bid = self.counter[0] + + self.split = split = FIND_PROMPTS(glob) + self.prompts = [] + for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]): + prompt, set = map(unquote, [prompt, set]) + cmd = True if cmd == '!' else False + if prompt and prompt[-1] != ":": + prompt = "%s:" % prompt + + self.prompts.append((prompt, cmd, set)) + + # Check that there is nothing like: fl** + for glob in split[:-1:4]: + if glob.endswith('*'): + msg = "token '*' not at the end of a prompt bind: %r" % split + raise SyntaxError(msg) + + # Check that there is nothing like: fl_ + for glob in split[4::4]: + if not glob: + msg = 'found null segment after first prompt: %r' % split + raise SyntaxError(msg) + + stack = [] + for (index, glob) in enumerate(reversed(split[::4])): + # Is the binding a MODCMD or KEYCMD: + mod_cmd = ismodbind(glob) + + # Do we execute on UPDATES or EXEC events? + on_exec = True if glob[-1] in ['!', '_'] else False + + # Does the command take arguments? + has_args = True if glob[-1] in ['*', '_'] else False + + glob = glob[:-1] if has_args or on_exec else glob + mods, glob = split_glob(glob) + stack.append((on_exec, has_args, mods, glob, index)) + + self.stack = list(reversed(stack)) + self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD]) + + + def __getitem__(self, depth): + '''Get bind info at a depth.''' + + if self.is_global: + return self.stack[0] + + return self.stack[depth] + + + def __repr__(self): + if self._repr_cache: + return self._repr_cache + + args = ['glob=%r' % self.glob, 'bid=%d' % self.bid] + + if self.is_callable: + args.append('function=%r' % self.function) + if self.args: + args.append('args=%r' % self.args) + + if self.kargs: + args.append('kargs=%r' % self.kargs) + + else: + cmdlen = len(self.commands) + cmds = self.commands[0] if cmdlen == 1 else self.commands + args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) + + self._repr_cache = '' % ', '.join(args) + return self._repr_cache + + +def exec_bind(uzbl, bind, *args, **kargs): + '''Execute bind objects.''' + + uzbl.event("EXEC_BIND", bind, args, kargs) + + if bind.is_callable: + args += bind.args + kargs = dict(bind.kargs.items()+kargs.items()) + bind.function(uzbl, *args, **kargs) + return + + if kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + commands = [] + cmd_expand = uzbl.cmd_expand + for cmd in bind.commands: + cmd = cmd_expand(cmd, args) + uzbl.send(cmd) + + +def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs): + '''Add a mode bind.''' + + bindlet = get_bindlet(uzbl) + + if not hasattr(modes, '__iter__'): + modes = unicode(modes).split(',') + + # Sort and filter binds. + modes = filter(None, map(unicode.strip, modes)) + + if callable(handler) or (handler is not None and handler.strip()): + bind = Bind(glob, handler, *args, **kargs) + + else: + bind = None + + for mode in modes: + if not VALID_MODE(mode): + raise NameError('invalid mode name: %r' % mode) + + for mode in modes: + if mode[0] == '-': + mode, bind = mode[1:], None + + bindlet.add_bind(mode, glob, bind) + uzbl.event('ADDED_MODE_BIND', mode, glob, bind) + + +def bind(uzbl, glob, handler, *args, **kargs): + '''Legacy bind function.''' + + mode_bind(uzbl, 'global', glob, handler, *args, **kargs) + + +def parse_mode_bind(uzbl, args): + '''Parser for the MODE_BIND event. + + Example events: + MODE_BIND = + MODE_BIND command o_ = uri %s + MODE_BIND insert,command = ... + MODE_BIND global ... = ... + MODE_BIND global,-insert ... = ... + ''' + + if not args: + raise ArgumentError('missing bind arguments') + + split = map(unicode.strip, args.split(' ', 1)) + if len(split) != 2: + raise ArgumentError('missing mode or bind section: %r' % args) + + modes, args = split[0].split(','), split[1] + split = map(unicode.strip, args.split('=', 1)) + if len(split) != 2: + raise ArgumentError('missing delimiter in bind section: %r' % args) + + glob, command = split + mode_bind(uzbl, modes, glob, command) + + +def parse_bind(uzbl, args): + '''Legacy parsing of the BIND event and conversion to the new format. + + Example events: + request BIND = + request BIND o_ = uri %s + request BIND = ... + request BIND ... = ... + ''' + + parse_mode_bind(uzbl, "global %s" % args) + + +def mode_changed(uzbl, mode): + '''Clear the stack on all non-stack mode changes.''' + + if mode != 'stack': + get_bindlet(uzbl).reset() + + +def match_and_exec(uzbl, bind, depth, keylet, bindlet): + + (on_exec, has_args, mod_cmd, glob, more) = bind[depth] + cmd = keylet.modcmd if mod_cmd else keylet.keycmd + + if mod_cmd and keylet.held != mod_cmd: + return False + + if has_args: + if not cmd.startswith(glob): + return False + + args = [cmd[len(glob):],] + + elif cmd != glob: + return False + + else: + args = [] + + if bind.is_global or (not more and depth == 0): + exec_bind(uzbl, bind, *args) + if not has_args: + uzbl.clear_current() + + return True + + elif more: + bindlet.stack(bind, args, depth) + return False + + args = bindlet.args + args + exec_bind(uzbl, bind, *args) + uzbl.set_mode() + if not has_args: + bindlet.reset() + uzbl.clear_current() + + return True + + +def keycmd_update(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if t[MOD_CMD] or t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return + + bindlet.after() + + +def keycmd_exec(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if t[MOD_CMD] or not t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return uzbl.clear_keycmd() + + bindlet.after() + + +def modcmd_update(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if not t[MOD_CMD] or t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return + + bindlet.after() + + +def modcmd_exec(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if not t[MOD_CMD] or not t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return uzbl.clear_modcmd() + + bindlet.after() + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'BIND': parse_bind, + 'KEYCMD_EXEC': keycmd_exec, + 'KEYCMD_UPDATE': keycmd_update, + 'MODCMD_EXEC': modcmd_exec, + 'MODCMD_UPDATE': modcmd_update, + 'MODE_BIND': parse_mode_bind, + 'MODE_CHANGED': mode_changed, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'bind': bind, + 'mode_bind': mode_bind, + 'get_bindlet': get_bindlet, + }) diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py new file mode 100644 index 0000000..3f6ae2b --- /dev/null +++ b/examples/data/plugins/cmd_expand.py @@ -0,0 +1,42 @@ +def escape(str): + for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]: + str = str.replace(char, (level * '\\') + char) + + return str + + +def cmd_expand(uzbl, cmd, args): + '''Exports a function that provides the following + expansions in any uzbl command string: + + %s = replace('%s', ' '.join(args)) + %r = replace('%r', "'%s'" % escaped(' '.join(args))) + %1 = replace('%1', arg[0]) + %2 = replace('%2', arg[1]) + %n = replace('%n', arg[n-1]) + ''' + + # Ensure (1) all string representable and (2) correct string encoding. + args = map(unicode, args) + + # Direct string replace. + if '%s' in cmd: + cmd = cmd.replace('%s', ' '.join(args)) + + # Escaped and quoted string replace. + if '%r' in cmd: + cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args))) + + # Arg index string replace. + for (index, arg) in enumerate(args): + index += 1 + if '%%%d' % index in cmd: + cmd = cmd.replace('%%%d' % index, unicode(arg)) + + return cmd + + +def init(uzbl): + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export('cmd_expand', cmd_expand) diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py new file mode 100644 index 0000000..8cea203 --- /dev/null +++ b/examples/data/plugins/completion.py @@ -0,0 +1,206 @@ +'''Keycmd completion.''' + +# A list of functions this plugin exports to be used via uzbl object. +__export__ = ['start_completion', 'get_completion_dict'] + +import re + +# Holds the per-instance completion dicts. +UZBLS = {} + +# Completion level +NONE, ONCE, LIST, COMPLETE = range(4) + +# Default instance dict. +DEFAULTS = {'completions': [], 'level': NONE, 'lock': False} + +# The reverse keyword finding re. +FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall + +# Formats +LIST_FORMAT = " %s " +ITEM_FORMAT = "%s%s" + + +def escape(str): + return str.replace("@", "\@") + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + # Make sure the config keys for all possible completions are known. + uzbl.send('dump_config_as_events') + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_completion_dict(uzbl): + '''Get data stored for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_incomplete_keyword(uzbl): + '''Gets the segment of the keycmd leading up to the cursor position and + uses a regular expression to search backwards finding parially completed + keywords or @variables. Returns a null string if the correct completion + conditions aren't met.''' + + keylet = uzbl.get_keylet() + left_segment = keylet.keycmd[:keylet.cursor] + partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip() + if partial.startswith('set '): + return ('@%s' % partial[4:].lstrip(), True) + + return (partial, False) + + +def stop_completion(uzbl, *args): + '''Stop command completion and return the level to NONE.''' + + d = get_completion_dict(uzbl) + d['level'] = NONE + uzbl.set('completion_list') + + +def complete_completion(uzbl, partial, hint, set_completion=False): + '''Inject the remaining porition of the keyword into the keycmd then stop + the completioning.''' + + if set_completion: + remainder = "%s = " % hint[len(partial):] + + else: + remainder = "%s " % hint[len(partial):] + + uzbl.inject_keycmd(remainder) + stop_completion(uzbl) + + +def partial_completion(uzbl, partial, hint): + '''Inject a common portion of the hints into the keycmd.''' + + remainder = hint[len(partial):] + uzbl.inject_keycmd(remainder) + + +def update_completion_list(uzbl, *args): + '''Checks if the user still has a partially completed keyword under his + cursor then update the completion hints list.''' + + partial = get_incomplete_keyword(uzbl)[0] + if not partial: + return stop_completion(uzbl) + + d = get_completion_dict(uzbl) + if d['level'] < LIST: + return + + hints = [h for h in d['completions'] if h.startswith(partial)] + if not hints: + return uzbl.set('completion_list') + + j = len(partial) + l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)] + uzbl.set('completion_list', LIST_FORMAT % ' '.join(l)) + + +def start_completion(uzbl, *args): + + d = get_completion_dict(uzbl) + if d['lock']: + return + + (partial, set_completion) = get_incomplete_keyword(uzbl) + if not partial: + return stop_completion(uzbl) + + if d['level'] < COMPLETE: + d['level'] += 1 + + hints = [h for h in d['completions'] if h.startswith(partial)] + if not hints: + return + + elif len(hints) == 1: + d['lock'] = True + complete_completion(uzbl, partial, hints[0], set_completion) + d['lock'] = False + return + + elif partial in hints and d['level'] == COMPLETE: + d['lock'] = True + complete_completion(uzbl, partial, partial, set_completion) + d['lock'] = False + return + + smalllen, smallest = sorted([(len(h), h) for h in hints])[0] + common = '' + for i in range(len(partial), smalllen): + char, same = smallest[i], True + for hint in hints: + if hint[i] != char: + same = False + break + + if not same: + break + + common += char + + if common: + d['lock'] = True + partial_completion(uzbl, partial, partial+common) + d['lock'] = False + + update_completion_list(uzbl) + + +def add_builtins(uzbl, args): + '''Pump the space delimited list of builtin commands into the + builtin list.''' + + completions = get_completion_dict(uzbl)['completions'] + builtins = filter(None, map(unicode.strip, args.split(" "))) + for builtin in builtins: + if builtin not in completions: + completions.append(builtin) + + +def add_config_key(uzbl, key, value): + '''Listen on the CONFIG_CHANGED event and add config keys to the variable + list for @var like expansion support.''' + + completions = get_completion_dict(uzbl)['completions'] + key = "@%s" % key + if key not in completions: + completions.append(key) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'BUILTINS': add_builtins, + 'CONFIG_CHANGED': add_config_key, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'KEYCMD_CLEARED': stop_completion, + 'KEYCMD_EXEC': stop_completion, + 'KEYCMD_UPDATE': update_completion_list, + 'START_COMPLETION': start_completion, + 'STOP_COMPLETION': stop_completion, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_completion_dict': get_completion_dict, + 'start_completion': start_completion, + }) diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py new file mode 100644 index 0000000..4a848a3 --- /dev/null +++ b/examples/data/plugins/config.py @@ -0,0 +1,97 @@ +import re +import types + +__export__ = ['set', 'get_config'] + +VALIDKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match +TYPECONVERT = {'int': int, 'float': float, 'str': unicode} + +UZBLS = {} + + +def escape(value): + '''A real escaping function may be required.''' + + return unicode(value) + + +def set(uzbl, key, value='', config=None, force=False): + '''Sends a: "set key = value" command to the uzbl instance. If force is + False then only send a set command if the values aren't equal.''' + + if type(value) == types.BooleanType: + value = int(value) + + else: + value = unicode(value) + + if not VALIDKEY(key): + raise KeyError("%r" % key) + + value = escape(value) + if '\n' in value: + value = value.replace("\n", "\\n") + + if not force: + if config is None: + config = get_config(uzbl) + + if key in config and config[key] == value: + return + + uzbl.send('set %s = %s' % (key, value)) + + +class ConfigDict(dict): + def __init__(self, uzbl): + self._uzbl = uzbl + + def __setitem__(self, key, value): + '''Makes "config[key] = value" a wrapper for the set function.''' + + set(self._uzbl, key, value, config=self) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = ConfigDict(uzbl) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del uzbl + + +def get_config(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def variable_set(uzbl, args): + config = get_config(uzbl) + + key, type, value = list(args.split(' ', 2) + ['',])[:3] + old = config[key] if key in config else None + value = TYPECONVERT[type](value) + + dict.__setitem__(config, key, value) + + if old != value: + uzbl.event("CONFIG_CHANGED", key, value) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'VARIABLE_SET': variable_set, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_config': get_config, + 'set': set, + }) diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py new file mode 100644 index 0000000..c119077 --- /dev/null +++ b/examples/data/plugins/keycmd.py @@ -0,0 +1,571 @@ +import re + +# Hold the keylets. +UZBLS = {} + +# Keycmd format which includes the markup for the cursor. +KEYCMD_FORMAT = "%s%s%s" +MODCMD_FORMAT = " %s " + + +def escape(str): + for char in ['\\', '@']: + str = str.replace(char, '\\'+char) + + return str + + +def uzbl_escape(str): + return "@[%s]@" % escape(str) if str else '' + + +class Keylet(object): + '''Small per-instance object that tracks all the keys held and characters + typed.''' + + def __init__(self): + # Modcmd tracking + self.held = set() + self.ignored = set() + self.modcmd = '' + self.is_modcmd = False + + # Keycmd tracking + self.keycmd = '' + self.cursor = 0 + + self.modmaps = {} + self.ignores = {} + self.additions = {} + + # Keylet string repr cache. + self._repr_cache = None + + + def get_keycmd(self): + '''Get the keycmd-part of the keylet.''' + + return self.keycmd + + + def get_modcmd(self): + '''Get the modcmd-part of the keylet.''' + + if not self.is_modcmd: + return '' + + return ''.join(self.held) + self.modcmd + + + def modmap_key(self, key): + '''Make some obscure names for some keys friendlier.''' + + if key in self.modmaps: + return self.modmaps[key] + + elif key.endswith('_L') or key.endswith('_R'): + # Remove left-right discrimination and try again. + return self.modmap_key(key[:-2]) + + else: + return key + + + def find_addition(self, modkey): + '''Key has just been pressed, check if this key + the held list + results in a modkey addition. Return that addition and remove all + modkeys that created it.''' + + # Intersection of (held list + modkey) and additions. + added = (self.held | set([modkey])) & set(self.additions.keys()) + for key in added: + if key == modkey or modkey in self.additions[key]: + self.held -= self.additions[key] + return key + + # Held list + ignored list + modkey. + modkeys = self.held | self.ignored | set([modkey]) + for (key, value) in self.additions.items(): + if modkeys.issuperset(value): + self.held -= value + return key + + return modkey + + + def key_ignored(self, key): + '''Check if the given key is ignored by any ignore rules.''' + + for (glob, match) in self.ignores.items(): + if match(key): + return True + + return False + + + def __repr__(self): + '''Return a string representation of the keylet.''' + + if self._repr_cache: + return self._repr_cache + + l = [] + if self.is_modcmd: + l.append('modcmd=%r' % self.get_modcmd()) + + elif self.held: + l.append('held=%r' % ''.join(sorted(self.held))) + + if self.keycmd: + l.append('keycmd=%r' % self.get_keycmd()) + + self._repr_cache = '' % ', '.join(l) + return self._repr_cache + + +def add_modmap(uzbl, key, map): + '''Add modmaps. + + Examples: + set modmap = request MODMAP + @modmap + @modmap + ... + + Then: + @bind = + @bind x = + ... + + ''' + + assert len(key) + modmaps = get_keylet(uzbl).modmaps + + if key[0] == "<" and key[-1] == ">": + key = key[1:-1] + + modmaps[key] = map + uzbl.event("NEW_MODMAP", key, map) + + +def modmap_parse(uzbl, map): + '''Parse a modmap definiton.''' + + split = [s.strip() for s in map.split(' ') if s.split()] + + if not split or len(split) > 2: + raise Exception('Invalid modmap arugments: %r' % map) + + add_modmap(uzbl, *split) + + +def add_key_ignore(uzbl, glob): + '''Add an ignore definition. + + Examples: + set ignore_key = request IGNORE_KEY + @ignore_key + @ignore_key + ... + ''' + + assert len(glob) > 1 + ignores = get_keylet(uzbl).ignores + + glob = "<%s>" % glob.strip("<> ") + restr = glob.replace('*', '[^\s]*') + match = re.compile(restr).match + + ignores[glob] = match + uzbl.event('NEW_KEY_IGNORE', glob) + + +def add_modkey_addition(uzbl, modkeys, result): + '''Add a modkey addition definition. + + Examples: + set mod_addition = request MODKEY_ADDITION + @mod_addition + @mod_addition + @mod_addition + ... + + Then: + @bind = + @bind o = + ... + ''' + + additions = get_keylet(uzbl).additions + modkeys = set(modkeys) + + assert len(modkeys) and result and result not in modkeys + + for (existing_result, existing_modkeys) in additions.items(): + if existing_result != result: + assert modkeys != existing_modkeys + + additions[result] = modkeys + uzbl.event('NEW_MODKEY_ADDITION', modkeys, result) + + +def modkey_addition_parse(uzbl, modkeys): + '''Parse modkey addition definition.''' + + keys = filter(None, map(unicode.strip, modkeys.split(" "))) + keys = ['<%s>' % key.strip("<>") for key in keys if key.strip("<>")] + + assert len(keys) > 1 + add_modkey_addition(uzbl, keys[:-1], keys[-1]) + + +def add_instance(uzbl, *args): + '''Create the Keylet object for this uzbl instance.''' + + UZBLS[uzbl] = Keylet() + + +def del_instance(uzbl, *args): + '''Delete the Keylet object for this uzbl instance.''' + + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_keylet(uzbl): + '''Return the corresponding keylet for this uzbl instance.''' + + # Startup events are not correctly captured and sent over the uzbl socket + # yet so this line is needed because the INSTANCE_START event is lost. + if uzbl not in UZBLS: + add_instance(uzbl) + + keylet = UZBLS[uzbl] + keylet._repr_cache = False + return keylet + + +def clear_keycmd(uzbl): + '''Clear the keycmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.keycmd = '' + k.cursor = 0 + k._repr_cache = False + uzbl.set('keycmd') + uzbl.set('raw_keycmd') + uzbl.event('KEYCMD_CLEARED') + + +def clear_modcmd(uzbl, clear_held=False): + '''Clear the modcmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.modcmd = '' + k.is_modcmd = False + k._repr_cache = False + if clear_held: + k.ignored = set() + k.held = set() + + uzbl.set('modcmd') + uzbl.set('raw_modcmd') + uzbl.event('MODCMD_CLEARED') + + +def clear_current(uzbl): + '''Clear the modcmd if is_modcmd else clear keycmd.''' + + k = get_keylet(uzbl) + if k.is_modcmd: + clear_modcmd(uzbl) + + else: + clear_keycmd(uzbl) + + +def focus_changed(uzbl, *args): + '''Focus to the uzbl instance has now been lost which means all currently + held keys in the held list will not get a KEY_RELEASE event so clear the + entire held list.''' + + clear_modcmd(uzbl, clear_held=True) + + +def update_event(uzbl, k, execute=True): + '''Raise keycmd & modcmd update events.''' + + config = uzbl.get_config() + keycmd, modcmd = k.get_keycmd(), k.get_modcmd() + + if k.is_modcmd: + uzbl.event('MODCMD_UPDATE', k) + + else: + uzbl.event('KEYCMD_UPDATE', k) + + if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': + new_modcmd = k.get_modcmd() + if not new_modcmd: + uzbl.set('modcmd', config=config) + uzbl.set('raw_modcmd', config=config) + + elif new_modcmd == modcmd: + uzbl.set('raw_modcmd', escape(modcmd), config=config) + uzbl.set('modcmd', MODCMD_FORMAT % uzbl_escape(modcmd), + config=config) + + if 'keycmd_events' in config and config['keycmd_events'] != '1': + return + + new_keycmd = k.get_keycmd() + if not new_keycmd: + uzbl.set('keycmd', config=config) + uzbl.set('raw_keycmd', config=config) + + elif new_keycmd == keycmd: + # Generate the pango markup for the cursor in the keycmd. + curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' ' + chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]] + value = KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks)) + uzbl.set('keycmd', value, config=config) + uzbl.set('raw_keycmd', escape(keycmd), config=config) + + +def inject_str(str, index, inj): + '''Inject a string into string at at given index.''' + + return "%s%s%s" % (str[:index], inj, str[index:]) + + +def get_keylet_and_key(uzbl, key, add=True): + '''Return the keylet and apply any transformations to the key as defined + by the modmapping or modkey addition rules. Return None if the key is + ignored.''' + + keylet = get_keylet(uzbl) + key = keylet.modmap_key(key) + if len(key) == 1: + return (keylet, key) + + modkey = "<%s>" % key.strip("<>") + + if keylet.key_ignored(modkey): + if add: + keylet.ignored.add(modkey) + + elif modkey in keylet.ignored: + keylet.ignored.remove(modkey) + + modkey = keylet.find_addition(modkey) + + if keylet.key_ignored(modkey): + return (keylet, None) + + return (keylet, modkey) + + +def key_press(uzbl, key): + '''Handle KEY_PRESS events. Things done by this function include: + + 1. Ignore all shift key presses (shift can be detected by capital chars) + 3. In non-modcmd mode: + a. append char to keycmd + 4. If not in modcmd mode and a modkey was pressed set modcmd mode. + 5. If in modcmd mode the pressed key is added to the held keys list. + 6. Keycmd is updated and events raised if anything is changed.''' + + (k, key) = get_keylet_and_key(uzbl, key.strip()) + if not key: + return + + if key.lower() == '' and not k.held and k.keycmd: + k.keycmd = inject_str(k.keycmd, k.cursor, ' ') + k.cursor += 1 + + elif not k.held and len(key) == 1: + config = uzbl.get_config() + if 'keycmd_events' in config and config['keycmd_events'] != '1': + k.keycmd = '' + k.cursor = 0 + uzbl.set('keycmd', config=config) + uzbl.set('raw_keycmd', config=config) + return + + k.keycmd = inject_str(k.keycmd, k.cursor, key) + k.cursor += 1 + + elif len(key) > 1: + k.is_modcmd = True + if key not in k.held: + k.held.add(key) + + else: + k.is_modcmd = True + k.modcmd += key + + update_event(uzbl, k) + + +def key_release(uzbl, key): + '''Respond to KEY_RELEASE event. Things done by this function include: + + 1. Remove the key from the keylet held list. + 2. If in a mod-command then raise a MODCMD_EXEC. + 3. Check if any modkey is held, if so set modcmd mode. + 4. Update the keycmd uzbl variable if anything changed.''' + + (k, key) = get_keylet_and_key(uzbl, key.strip(), add=False) + + if key in k.held: + if k.is_modcmd: + uzbl.event('MODCMD_EXEC', k) + + k.held.remove(key) + clear_modcmd(uzbl) + + +def set_keycmd(uzbl, keycmd): + '''Allow setting of the keycmd externally.''' + + k = get_keylet(uzbl) + k.keycmd = keycmd + k._repr_cache = None + k.cursor = len(keycmd) + update_event(uzbl, k, False) + + +def inject_keycmd(uzbl, keycmd): + '''Allow injecting of a string into the keycmd at the cursor position.''' + + k = get_keylet(uzbl) + k.keycmd = inject_str(k.keycmd, k.cursor, keycmd) + k._repr_cache = None + k.cursor += len(keycmd) + update_event(uzbl, k, False) + + +def append_keycmd(uzbl, keycmd): + '''Allow appening of a string to the keycmd.''' + + k = get_keylet(uzbl) + k.keycmd += keycmd + k._repr_cache = None + k.cursor = len(k.keycmd) + update_event(uzbl, k, False) + + +def keycmd_strip_word(uzbl, sep): + ''' Removes the last word from the keycmd, similar to readline ^W ''' + + sep = sep or ' ' + k = get_keylet(uzbl) + if not k.keycmd: + return + + head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] + rfind = head.rfind(sep) + head = head[:rfind] if rfind + 1 else '' + k.keycmd = head + tail + k.cursor = len(head) + update_event(uzbl, k, False) + + +def keycmd_backspace(uzbl, *args): + '''Removes the character at the cursor position in the keycmd.''' + + k = get_keylet(uzbl) + if not k.keycmd: + return + + k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] + k.cursor -= 1 + update_event(uzbl, k, False) + + +def keycmd_delete(uzbl, *args): + '''Removes the character after the cursor position in the keycmd.''' + + k = get_keylet(uzbl) + if not k.keycmd: + return + + k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] + update_event(uzbl, k, False) + + +def keycmd_exec_current(uzbl, *args): + '''Raise a KEYCMD_EXEC with the current keylet and then clear the + keycmd.''' + + k = get_keylet(uzbl) + uzbl.event('KEYCMD_EXEC', k) + clear_keycmd(uzbl) + + +def set_cursor_pos(uzbl, index): + '''Allow setting of the cursor position externally. Supports negative + indexing and relative stepping with '+' and '-'.''' + + k = get_keylet(uzbl) + if index == '-': + cursor = k.cursor - 1 + + elif index == '+': + cursor = k.cursor + 1 + + else: + cursor = int(index.strip()) + if cursor < 0: + cursor = len(k.keycmd) + cursor + 1 + + if cursor < 0: + cursor = 0 + + if cursor > len(k.keycmd): + cursor = len(k.keycmd) + + k.cursor = cursor + update_event(uzbl, k, False) + + +def init(uzbl): + '''Connect handlers to uzbl events.''' + + # Event handling hooks. + uzbl.connect_dict({ + 'APPEND_KEYCMD': append_keycmd, + 'FOCUS_GAINED': focus_changed, + 'FOCUS_LOST': focus_changed, + 'IGNORE_KEY': add_key_ignore, + 'INJECT_KEYCMD': inject_keycmd, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'KEYCMD_BACKSPACE': keycmd_backspace, + 'KEYCMD_DELETE': keycmd_delete, + 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, + 'KEYCMD_STRIP_WORD': keycmd_strip_word, + 'KEY_PRESS': key_press, + 'KEY_RELEASE': key_release, + 'MODKEY_ADDITION': modkey_addition_parse, + 'MODMAP': modmap_parse, + 'SET_CURSOR_POS': set_cursor_pos, + 'SET_KEYCMD': set_keycmd, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'add_key_ignore': add_key_ignore, + 'add_modkey_addition': add_modkey_addition, + 'add_modmap': add_modmap, + 'append_keycmd': append_keycmd, + 'clear_current': clear_current, + 'clear_keycmd': clear_keycmd, + 'clear_modcmd': clear_modcmd, + 'get_keylet': get_keylet, + 'inject_keycmd': inject_keycmd, + 'set_cursor_pos': set_cursor_pos, + 'set_keycmd': set_keycmd, + }) diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py new file mode 100644 index 0000000..54d865a --- /dev/null +++ b/examples/data/plugins/mode.py @@ -0,0 +1,176 @@ +import sys +import re + +__export__ = ['set_mode', 'get_mode', 'set_mode_config', 'get_mode_config'] + +UZBLS = {} + +DEFAULTS = { + 'mode': '', + 'modes': { + 'insert': { + 'forward_keys': True, + 'keycmd_events': False, + 'modcmd_updates': False, + 'mode_indicator': 'I'}, + 'command': { + 'forward_keys': False, + 'keycmd_events': True, + 'modcmd_updates': True, + 'mode_indicator': 'C'}}} + +FINDSPACES = re.compile("\s+") +VALID_KEY = re.compile("^[\w_]+$").match + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_mode_dict(uzbl): + '''Return the mode dict for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_mode_config(uzbl, mode): + '''Return the mode config for a given mode.''' + + modes = get_mode_dict(uzbl)['modes'] + if mode not in modes: + modes[mode] = {} + + return modes[mode] + + +def get_mode(uzbl): + return get_mode_dict(uzbl)['mode'] + + +def mode_changed(uzbl, mode): + '''The mode has just been changed, now set the per-mode config.''' + + if get_mode(uzbl) != mode: + return + + config = uzbl.get_config() + mode_config = get_mode_config(uzbl, mode) + for (key, value) in mode_config.items(): + uzbl.set(key, value, config=config) + + if 'mode_indicator' not in mode_config: + config['mode_indicator'] = mode + + uzbl.clear_keycmd() + uzbl.clear_modcmd() + + +def set_mode(uzbl, mode=None): + '''Set the mode and raise the MODE_CHANGED event if the mode has changed. + Fallback on the default mode if no mode argument was given and the default + mode is not null.''' + + config = uzbl.get_config() + mode_dict = get_mode_dict(uzbl) + if mode is None: + mode_dict['mode'] = '' + if 'default_mode' in config: + mode = config['default_mode'] + + else: + mode = 'command' + + if not VALID_KEY(mode): + raise KeyError("invalid mode name: %r" % mode) + + if 'mode' not in config or config['mode'] != mode: + config['mode'] = mode + + elif mode_dict['mode'] != mode: + mode_dict['mode'] = mode + uzbl.event("MODE_CHANGED", mode) + + +def config_changed(uzbl, key, value): + '''Check for mode related config changes.''' + + value = None if not value else value + if key == 'default_mode': + if not get_mode(uzbl): + set_mode(uzbl, value) + + elif key == 'mode': + set_mode(uzbl, value) + + +def set_mode_config(uzbl, mode, key, value): + '''Set mode specific configs. If the mode being modified is the current + mode then apply the changes on the go.''' + + assert VALID_KEY(mode) and VALID_KEY(key) + + mode_config = get_mode_config(uzbl, mode) + mode_config[key] = value + + if get_mode(uzbl) == mode: + uzbl.set(key, value) + + +def mode_config(uzbl, args): + '''Parse mode config events.''' + + split = map(unicode.strip, FINDSPACES.split(args.lstrip(), 1)) + if len(split) != 2: + raise SyntaxError('invalid mode config syntax: %r' % args) + + mode, set = split + split = map(unicode.strip, set.split('=', 1)) + if len(split) != 2: + raise SyntaxError('invalid set syntax: %r' % args) + + key, value = split + set_mode_config(uzbl, mode, key, value) + + +def toggle_modes(uzbl, modes): + '''Toggle or cycle between or through a list of modes.''' + + assert len(modes.strip()) + + modelist = filter(None, map(unicode.strip, modes.split(' '))) + mode = get_mode(uzbl) + + index = 0 + if mode in modelist: + index = (modelist.index(mode)+1) % len(modelist) + + set_mode(uzbl, modelist[index]) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'CONFIG_CHANGED': config_changed, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'MODE_CHANGED': mode_changed, + 'MODE_CONFIG': mode_config, + 'TOGGLE_MODES': toggle_modes, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_mode': get_mode, + 'get_mode_config': get_mode_config, + 'set_mode': set_mode, + 'set_mode_config': set_mode_config, + }) diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py new file mode 100644 index 0000000..b9c504a --- /dev/null +++ b/examples/data/plugins/on_event.py @@ -0,0 +1,107 @@ +'''Plugin provides arbitrary binding of uzbl events to uzbl commands. + +Formatting options: + %s = space separated string of the arguments + %r = escaped and quoted version of %s + %1 = argument 1 + %2 = argument 2 + %n = argument n + +Usage: + request ON_EVENT LINK_HOVER set selected_uri = $1 + --> LINK_HOVER http://uzbl.org/ + <-- set selected_uri = http://uzbl.org/ + + request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2 + --> CONFIG_CHANGED selected_uri http://uzbl.org/ + <-- print Config changed: selected_uri = http://uzbl.org/ +''' + +import sys +import re + +__export__ = ['get_on_events', 'on_event'] + +UZBLS = {} + + +def error(msg): + sys.stderr.write('on_event plugin: error: %s\n' % msg) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = {} + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_on_events(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def event_handler(uzbl, *args, **kargs): + '''This function handles all the events being watched by various + on_event definitions and responds accordingly.''' + + events = get_on_events(uzbl) + event = kargs['on_event'] + if event not in events: + return + + commands = events[event] + cmd_expand = uzbl.cmd_expand + for cmd in commands: + cmd = cmd_expand(cmd, args) + uzbl.send(cmd) + + +def on_event(uzbl, event, cmd): + '''Add a new event to watch and respond to.''' + + event = event.upper() + events = get_on_events(uzbl) + if event not in events: + uzbl.connect(event, event_handler, on_event=event) + events[event] = [] + + cmds = events[event] + if cmd not in cmds: + cmds.append(cmd) + + +def parse_on_event(uzbl, args): + '''Parse ON_EVENT events and pass them to the on_event function. + + Syntax: "event ON_EVENT commands".''' + + if not args: + return error("missing on_event arguments") + + split = args.split(' ', 1) + if len(split) != 2: + return error("invalid ON_EVENT syntax: %r" % args) + + event, cmd = split + on_event(uzbl, event, cmd) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'ON_EVENT': parse_on_event, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_on_events': get_on_events, + 'on_event': on_event, + }) diff --git a/examples/data/plugins/plugin_template.py b/examples/data/plugins/plugin_template.py new file mode 100644 index 0000000..565a999 --- /dev/null +++ b/examples/data/plugins/plugin_template.py @@ -0,0 +1,76 @@ +'''Plugin template.''' + +# Holds the per-instance data dict. +UZBLS = {} + +# The default instance dict. +DEFAULTS = {} + + +def add_instance(uzbl, *args): + '''Add a new instance with default config options.''' + + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + '''Delete data stored for an instance.''' + + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_myplugin_dict(uzbl): + '''Get data stored for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def myplugin_function(uzbl, *args, **kargs): + '''Custom plugin function which is exported by the __export__ list at the + top of the file for use by other functions/callbacks.''' + + print "My plugin function arguments:", args, kargs + + # Get the per-instance data object. + data = get_myplugin_dict(uzbl) + + # Function logic goes here. + + +def myplugin_event_parser(uzbl, args): + '''Parses MYPLUGIN_EVENT raised by uzbl or another plugin.''' + + print "Got MYPLUGIN_EVENT with arguments: %r" % args + + # Parsing logic goes here. + + +def init(uzbl): + '''The main function of the plugin which is used to attach all the event + hooks that are going to be used throughout the plugins life. This function + is called each time a UzblInstance() object is created in the event + manager.''' + + # Make a dictionary comprising of {"EVENT_NAME": handler, ..} to the event + # handler stack: + uzbl.connect_dict({ + # event name function + 'INSTANCE_START': add_instance, + 'INSTANCE_EXIT': del_instance, + 'MYPLUGIN_EVENT': myplugin_event_parser, + }) + + # Or connect a handler to an event manually and supply additional optional + # arguments: + #uzbl.connect("MYOTHER_EVENT", myother_event_parser, True, limit=20) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.connect_dict({ + # external name function + 'myplugin_function': myplugin_function, + }) diff --git a/examples/data/plugins/progress_bar.py b/examples/data/plugins/progress_bar.py new file mode 100644 index 0000000..89ba175 --- /dev/null +++ b/examples/data/plugins/progress_bar.py @@ -0,0 +1,159 @@ +import sys + +UZBLS = {} + +DEFAULTS = {'width': 8, + 'done': '=', + 'pending': '.', + 'format': '[%d%a%p]%c', + 'spinner': '-\\|/', + 'sprites': 'loading', + 'updates': 0, + 'progress': 100} + + +def error(msg): + sys.stderr.write("progress_bar plugin: error: %s\n" % msg) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_progress_config(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def update_progress(uzbl, prog=None): + '''Updates the progress_format variable on LOAD_PROGRESS update. + + The current substitution options are: + %d = done char * done + %p = pending char * remaining + %c = percent done + %i = int done + %s = -\|/ spinner + %t = percent pending + %o = int pending + %r = sprites + ''' + + prog_config = get_progress_config(uzbl) + config = uzbl.get_config() + + if prog is None: + prog = prog_config['progress'] + + else: + prog = int(prog) + prog_config['progress'] = prog + + prog_config['updates'] += 1 + format = prog_config['format'] + width = prog_config['width'] + + # Inflate the done and pending bars to stop the progress bar + # jumping around. + if '%c' in format or '%i' in format: + count = format.count('%c') + format.count('%i') + width += (3-len(str(prog))) * count + + if '%t' in format or '%o' in format: + count = format.count('%t') + format.count('%o') + width += (3-len(str(100-prog))) * count + + done = int(((prog/100.0)*width)+0.5) + pending = width - done + + if '%d' in format: + format = format.replace('%d', prog_config['done']*done) + + if '%p' in format: + format = format.replace('%p', prog_config['pending']*pending) + + if '%c' in format: + format = format.replace('%c', '%d%%' % prog) + + if '%i' in format: + format = format.replace('%i', '%d' % prog) + + if '%t' in format: + format = format.replace('%t', '%d%%' % (100-prog)) + + if '%o' in format: + format = format.replace('%o', '%d' % (100-prog)) + + if '%s' in format: + spinner = prog_config['spinner'] + spin = '-' if not spinner else spinner + index = 0 if prog == 100 else prog_config['updates'] % len(spin) + char = '\\\\' if spin[index] == '\\' else spin[index] + format = format.replace('%s', char) + + if '%r' in format: + sprites = prog_config['sprites'] + sprites = '-' if not sprites else sprites + index = int(((prog/100.0)*len(sprites))+0.5)-1 + sprite = '\\\\' if sprites[index] == '\\' else sprites[index] + format = format.replace('%r', sprite) + + if 'progress_format' not in config or config['progress_format'] != format: + config['progress_format'] = format + + +def progress_config(uzbl, args): + '''Parse PROGRESS_CONFIG events from the uzbl instance. + + Syntax: event PROGRESS_CONFIG = + ''' + + split = args.split('=', 1) + if len(split) != 2: + return error("invalid syntax: %r" % args) + + key, value = map(unicode.strip, split) + prog_config = get_progress_config(uzbl) + + if key not in prog_config: + return error("key error: %r" % args) + + if type(prog_config[key]) == type(1): + try: + value = int(value) + + except: + return error("invalid type: %r" % args) + + elif not value: + value = ' ' + + prog_config[key] = value + update_progress(uzbl) + + +def reset_progress(uzbl, args): + '''Reset the spinner counter, reset the progress int and re-draw the + progress bar on LOAD_COMMIT.''' + + prog_dict = get_progress_config(uzbl) + prog_dict['updates'] = prog_dict['progress'] = 0 + update_progress(uzbl) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'LOAD_COMMIT': reset_progress, + 'LOAD_PROGRESS': update_progress, + 'PROGRESS_CONFIG': progress_config, + }) diff --git a/examples/data/scripts/cookies.sh b/examples/data/scripts/cookies.sh new file mode 100755 index 0000000..ee2ce51 --- /dev/null +++ b/examples/data/scripts/cookies.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +set -n; + +# THIS IS EXPERIMENTAL AND COULD BE INSECURE !!!!!! + +# this is an example bash script of how you could manage your cookies. it is very raw and basic and not as good as uzbl-cookie-daemon +# we use the cookies.txt format (See http://kb.mozillazine.org/Cookies.txt) +# This is one textfile with entries like this: +# kb.mozillazine.org FALSE / FALSE 1146030396 wikiUserID 16993 +# domain alow-read-other-subdomains path http-required expiration name value +# you probably want your cookies config file in your $XDG_CONFIG_HOME ( eg $HOME/.config/uzbl/cookies) +# Note. in uzbl there is no strict definition on what a session is. it's YOUR job to clear cookies marked as end_session if you want to keep cookies only valid during a "session" +# MAYBE TODO: allow user to edit cookie before saving. this cannot be done with zenity :( +# TODO: different cookie paths per config (eg per group of uzbl instances) + +# TODO: correct implementation. +# see http://curl.haxx.se/rfc/cookie_spec.html +# http://en.wikipedia.org/wiki/HTTP_cookie + +# TODO : check expires= before sending. +# write sample script that cleans up cookies dir based on expires attribute. +# TODO: check uri against domain attribute. and path also. +# implement secure attribute. +# support blocking or not for 3rd parties +# http://kb.mozillazine.org/Cookies.txt +# don't always append cookies, sometimes we need to overwrite + +cookie_config=${XDG_CONFIG_HOME:-${HOME}/.config}/uzbl/cookies +[ "x$cookie_config" = x ] && exit 1 +[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/" ] &&\ +cookie_data=${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/cookies.txt || exit 1 + +notifier= +#notifier=notify-send +#notify_wrapper () { +# echo "$@" >> $HOME/cookielog +#} +#notifier=notifier_wrapper + +# if this variable is set, we will use it to inform you when and which cookies we store, and when/which we send. +# it's primarily used for debugging +notifier= +which zenity &>/dev/null || exit 2 + +# Example cookie: +# test_cookie=CheckForPermission; expires=Thu, 07-May-2009 19:17:55 GMT; path=/; domain=.doubleclick.net + +# uri=$6 +# uri=${uri/http:\/\/} # strip 'http://' part +# host=${uri/\/*/} +action=$8 # GET/PUT +shift +host=$9 +shift +path=$9 +shift +cookie=$9 + +field_domain=$host +field_path=$path +field_name= +field_value= +field_exp='end_session' + +notify() { + [ -n "$notifier" ] && $notifier "$@" +} + + +# FOR NOW LETS KEEP IT SIMPLE AND JUST ALWAYS PUT AND ALWAYS GET +parse_cookie() { + IFS=$';' + first_pair=1 + for pair in $cookie + do + if [ "x$first_pair" = x1 ] + then + field_name=${pair%%=*} + field_value=${pair#*=} + first_pair=0 + else + echo "$pair" | read -r pair #strip leading/trailing wite space + key=${pair%%=*} + val=${pair#*=} + [ "$key" == expires ] && field_exp=`date -u -d "$val" +'%s'` + # TODO: domain + [ "$key" == path ] && field_path=$val + fi + done + unset IFS +} + +# match cookies in cookies.txt against hostname and path +get_cookie() { + path_esc=${path//\//\\/} + search="^[^\t]*$host\t[^\t]*\t$path_esc" + cookie=`awk "/$search/" $cookie_data 2>/dev/null | tail -n 1` + if [ -z "$cookie" ] + then + notify "Get_cookie: search: $search in $cookie_data -> no result" + false + else + notify "Get_cookie: search: $search in $cookie_data -> result: $cookie" + echo "$cookie" | \ + read domain alow_read_other_subdomains path http_required expiration name \ + value; + cookie="$name=$value" + true + fi +} + +save_cookie() { + if parse_cookie + then + data="$field_domain\tFALSE\t$field_path\tFALSE\t$field_exp\t$field_name\t$field_value" + notify "save_cookie: adding $data to $cookie_data" + echo -e "$data" >> $cookie_data + else + notify "not saving a cookie. since we don't have policies yet, parse_cookie must have returned false. this is a bug" + fi +} + +[ "x$action" = xPUT ] && save_cookie +[ "x$action" = xGET ] && get_cookie && echo "$cookie" + +exit + + +# TODO: implement this later. +# $1 = section (TRUSTED or DENY) +# $2 =url +match() { + sed -n "/$1/,/^\$/p" $cookie_config 2>/dev/null | grep -q "^$host" +} + +fetch_cookie() { + cookie=`cat $cookie_data` +} + +store_cookie() { + echo $cookie > $cookie_data +} + +if match TRUSTED $host +then + [ "x$action" = xPUT ] && store_cookie $host + [ "x$action" = xGET ] && fetch_cookie && echo "$cookie" +elif ! match DENY $host +then + [ "x$action" = xPUT ] && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Accept this cookie from $host ?" --entry-text="$cookie"` && store_cookie $host + [ "x$action" = xGET ] && fetch_cookie && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Submit this cookie to $host ?" --entry-text="$cookie"` && echo $cookie +fi +exit 0 diff --git a/examples/data/scripts/download.sh b/examples/data/scripts/download.sh new file mode 100755 index 0000000..1c7d039 --- /dev/null +++ b/examples/data/scripts/download.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# just an example of how you could handle your downloads +# try some pattern matching on the uri to determine what we should do + +# Some sites block the default wget --user-agent.. +GET="wget --user-agent=Firefox" + +dest="$HOME" +url="$8" + +http_proxy="$9" +export http_proxy + +test "x$url" = "x" && { echo "you must supply a url! ($url)"; exit 1; } + +# only changes the dir for the $get sub process +if echo "$url" | grep -E '.*\.torrent' >/dev/null; +then + ( cd "$dest"; $GET "$url") +else + ( cd "$dest"; $GET "$url") +fi diff --git a/examples/data/scripts/extedit.js b/examples/data/scripts/extedit.js new file mode 100644 index 0000000..8ed346d --- /dev/null +++ b/examples/data/scripts/extedit.js @@ -0,0 +1,102 @@ +/* + * Edit forms in external editor + * + * (c) 2009, Robert Manea + * utf8 functions are (c) by Webtoolkit.info (http://www.webtoolkit.info/) + * + * + * Installation: + * - Copy this script to $HOME/.local/share/uzbl/scripts + * - Add the following to $HOME/.config/uzbl/config: + * @bind E = script @scripts_dir/extedit.js + * - Set your preferred editor + * set editor = gvim + * - non-GUI editors + * set editor = xterm -e vim + * + * Usage: + * Select (click) an editable form, go to command mode and hit E + * +*/ + + +function utf8_decode ( str_data ) { + var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0; + + str_data += ''; + + while ( i < str_data.length ) { + c1 = str_data.charCodeAt(i); + if (c1 < 128) { + tmp_arr[ac++] = String.fromCharCode(c1); + i++; + } else if ((c1 > 191) && (c1 < 224)) { + c2 = str_data.charCodeAt(i+1); + tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = str_data.charCodeAt(i+1); + c3 = str_data.charCodeAt(i+2); + tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + + return tmp_arr.join(''); +} + + +function utf8_encode ( argString ) { + var string = (argString+''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + + var utftext = ""; + var start, end; + var stringl = 0; + + start = end = 0; + stringl = string.length; + for (var n = 0; n < stringl; n++) { + var c1 = string.charCodeAt(n); + var enc = null; + + if (c1 < 128) { + end++; + } else if (c1 > 127 && c1 < 2048) { + enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128); + } else { + enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128); + } + if (enc !== null) { + if (end > start) { + utftext += string.substring(start, end); + } + utftext += enc; + start = end = n+1; + } + } + + if (end > start) { + utftext += string.substring(start, string.length); + } + + return utftext; +} + + +(function() { + var actelem = document.activeElement; + + if(actelem.type == 'text' || actelem.type == 'textarea') { + var editor = Uzbl.run("print @external_editor") || "gvim"; + var filename = Uzbl.run("print @(mktemp /tmp/uzbl_edit.XXXXXX)@"); + + if(actelem.value) + Uzbl.run("sh 'echo " + window.btoa(utf8_encode(actelem.value)) + " | base64 -d > " + filename + "'"); + + Uzbl.run("sync_sh '" + editor + " " + filename + "'"); + actelem.value = utf8_decode(window.atob(Uzbl.run("print @(base64 -w 0 " + filename + ")@"))); + + Uzbl.run("sh 'rm -f " + filename + "'"); + } + + })(); diff --git a/examples/data/scripts/follow_Numbers.js b/examples/data/scripts/follow_Numbers.js new file mode 100644 index 0000000..00b279e --- /dev/null +++ b/examples/data/scripts/follow_Numbers.js @@ -0,0 +1,228 @@ +/* This is the basic linkfollowing script. + * Its pretty stable, only using numbers to navigate. + * + * TODO: Some pages mess around a lot with the zIndex which + * lets some hints in the background. + * TODO: Some positions are not calculated correctly (mostly + * because of uber-fancy-designed-webpages. Basic HTML and CSS + * works good + * TODO: Still some links can't be followed/unexpected things + * happen. Blame some freaky webdesigners ;) + */ + +//Just some shortcuts and globals +var uzblid = 'uzbl_link_hint'; +var uzbldivid = uzblid + '_div_container'; +var doc = document; +var win = window; +var links = document.links; +var forms = document.forms; + +//Reset keycmd, modcmd and return to default mode. +function clearKeycmd() { Uzbl.run('set mode ='); } + +//Make onlick-links "clickable" +try { + HTMLElement.prototype.click = function() { + if (typeof this.onclick == 'function') { + this.onclick({ + type: 'click' + }); + } + }; +} catch(e) {} +//Catch the ESC keypress to stop linkfollowing +function keyPressHandler(e) { + var kC = window.event ? event.keyCode: e.keyCode; + var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; + if (kC == Esc) { + removeAllHints(); + } +} +//Calculate element position to draw the hint +//Pretty accurate but on fails in some very fancy cases +function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; + var height = el.offsetHeight; + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return [up, left, width, height]; +} +//Calculate if an element is visible +function isVisible(el) { + if (el == doc) { + return true; + } + if (!el) { + return false; + } + if (!el.parentNode) { + return false; + } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); +} +//Calculate if an element is on the viewport. +function elementInViewport(el) { + offset = elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; + var height = offset[3]; + return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; +} +//Removes all hints/leftovers that might be generated +//by this script. +function removeAllHints() { + var elements = doc.getElementById(uzbldivid); + if (elements) { + elements.parentNode.removeChild(elements); + } +} +//Generate a hint for an element with the given label +//Here you can play around with the style of the hints! +function generateHint(el, label) { + var pos = elementPosition(el); + var hint = doc.createElement('div'); + hint.setAttribute('name', uzblid); + hint.innerText = label; + hint.style.display = 'inline'; + hint.style.backgroundColor = '#B9FF00'; + hint.style.border = '2px solid #4A6600'; + hint.style.color = 'black'; + hint.style.fontSize = '9px'; + hint.style.fontWeight = 'bold'; + hint.style.lineHeight = '9px'; + hint.style.margin = '0px'; + hint.style.padding = '1px'; + hint.style.position = 'absolute'; + hint.style.zIndex = '1000'; + hint.style.left = pos[1] + 'px'; + hint.style.top = pos[0] + 'px'; + var img = el.getElementsByTagName('img'); + if (img.length > 0) { + hint.style.left = pos[1] + img[0].width / 2 + 'px'; + } + hint.style.textDecoration = 'none'; + hint.style.webkitBorderRadius = '6px'; + // Play around with this, pretty funny things to do :) + hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; + return hint; +} +//Here we choose what to do with an element if we +//want to "follow" it. On form elements we "select" +//or pass the focus, on links we try to perform a click, +//but at least set the href of the link. (needs some improvements) +function clickElem(item) { + removeAllHints(); + clearKeycmd(); + if (item) { + var name = item.tagName; + if (name == 'A') { + item.click(); + window.location = item.href; + } else if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { + item.focus(); + item.select(); + } else { + item.click(); + } + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + } else { + item.click(); + window.location = item.href; + } + } +} +//Returns a list of all links (in this version +//just the elements itself, but in other versions, we +//add the label here. +function addLinks() { + res = [[], []]; + for (var l = 0; l < links.length; l++) { + var li = links[l]; + if (isVisible(li) && elementInViewport(li)) { + res[0].push(li); + } + } + return res; +} +//Same as above, just for the form elements +function addFormElems() { + res = [[], []]; + for (var f = 0; f < forms.length; f++) { + for (var e = 0; e < forms[f].elements.length; e++) { + var el = forms[f].elements[e]; + if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { + res[0].push(el); + } + } + } + return res; +} +//Draw all hints for all elements passed. "len" is for +//the number of chars we should use to avoid collisions +function reDrawHints(elems, chars) { + removeAllHints(); + var hintdiv = doc.createElement('div'); + hintdiv.setAttribute('id', uzbldivid); + for (var i = 0; i < elems[0].length; i++) { + if (elems[0][i]) { + var label = elems[1][i].substring(chars); + var h = generateHint(elems[0][i], label); + hintdiv.appendChild(h); + } + } + if (document.body) { + document.body.appendChild(hintdiv); + } +} +//Put it all together +function followLinks(follow) { + var s = follow.split(''); + var linknr = parseInt(follow, 10); + if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); + var linkelems = addLinks(); + var formelems = addFormElems(); + var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; + var len = (elems[0].length + '').length; + var oldDiv = doc.getElementById(uzbldivid); + var leftover = [[], []]; + if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { + clickElem(elems[0][linknr]); + } else { + for (var j = 0; j < elems[0].length; j++) { + var b = true; + var label = j + ''; + var n = label.length; + for (n; n < len; n++) { + label = '0' + label; + } + for (var k = 0; k < s.length; k++) { + b = b && label.charAt(k) == s[k]; + } + if (b) { + leftover[0].push(elems[0][j]); + leftover[1].push(label); + } + } + reDrawHints(leftover, s.length); + } +} +followLinks('%s'); diff --git a/examples/data/scripts/follow_Numbers_Strings.js b/examples/data/scripts/follow_Numbers_Strings.js new file mode 100644 index 0000000..e50da5d --- /dev/null +++ b/examples/data/scripts/follow_Numbers_Strings.js @@ -0,0 +1,212 @@ +var uzblid = 'uzbl_link_hint'; +var uzbldivid = uzblid + '_div_container'; +var doc = document; +var win = window; +var links = document.links; +var forms = document.forms; + +//Reset keycmd, modcmd and return to default mode. +function clearKeycmd() { Uzbl.run('set mode ='); } + +try { + HTMLElement.prototype.click = function() { + if (typeof this.onclick == 'function') { + this.onclick({ + type: 'click' + }); + } + }; +} catch(e) {} +function keyPressHandler(e) { + var kC = window.event ? event.keyCode: e.keyCode; + var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; + if (kC == Esc) { + removeAllHints(); + } +} +function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; + var height = el.offsetHeight; + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return [up, left, width, height]; +} +function isVisible(el) { + if (el == doc) { + return true; + } + if (!el) { + return false; + } + if (!el.parentNode) { + return false; + } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); +} +function elementInViewport(el) { + offset = elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; + var height = offset[3]; + return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; +} +function removeAllHints() { + var elements = doc.getElementById(uzbldivid); + if (elements) { + elements.parentNode.removeChild(elements); + } +} +function generateHint(el, label) { + var pos = elementPosition(el); + var hint = doc.createElement('div'); + hint.setAttribute('name', uzblid); + hint.innerText = label; + hint.style.display = 'inline'; + hint.style.backgroundColor = '#B9FF00'; + hint.style.border = '2px solid #4A6600'; + hint.style.color = 'black'; + hint.style.zIndex = '1000'; + hint.style.fontSize = '9px'; + hint.style.fontWeight = 'bold'; + hint.style.lineHeight = '9px'; + hint.style.margin = '0px'; + hint.style.padding = '1px'; + hint.style.position = 'absolute'; + hint.style.left = pos[1] + 'px'; + hint.style.top = pos[0] + 'px'; + var img = el.getElementsByTagName('img'); + if (img.length > 0) { + hint.style.left = pos[1] + img[0].width / 2 + 'px'; + } + hint.style.textDecoration = 'none'; + hint.style.webkitBorderRadius = '6px'; + hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; + return hint; +} + +function clickElem(item) { + removeAllHints(); + clearKeycmd(); + if (item) { + var name = item.tagName; + if (name == 'A') { + item.click(); + window.location = item.href; + } else if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { + item.focus(); + item.select(); + } else { + item.click(); + } + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + } else { + item.click(); + window.location = item.href; + } + } +} + +function addLinks() { + res = [[], []]; + for (var l = 0; l < links.length; l++) { + var li = links[l]; + if (isVisible(li) && elementInViewport(li)) { + res[0].push(li); + res[1].push(li.innerText.toLowerCase()); + } + } + return res; +} +function addFormElems() { + res = [[], []]; + for (var f = 0; f < forms.length; f++) { + for (var e = 0; e < forms[f].elements.length; e++) { + var el = forms[f].elements[e]; + if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { + res[0].push(el); + if (el.getAttribute('value')) { + res[1].push(el.getAttribute('value').toLowerCase()); + } else { + res[1].push(el.getAttribute('name').toLowerCase()); + } + } + } + } + return res; +} +function reDrawHints(elems, len) { + var hintdiv = doc.createElement('div'); + hintdiv.setAttribute('id', uzbldivid); + hintdiv.style.opacity = '0.0'; + for (var i = 0; i < elems[0].length; i++) { + var label = i + ''; + var n = label.length; + for (n; n < len; n++) { + label = '0' + label; + } + if (elems[0][i]) { + var h = generateHint(elems[0][i], label); + hintdiv.appendChild(h); + } + } + if (document.body) { + document.body.appendChild(hintdiv); + hintdiv.style.opacity = '0.7' + } +} +function followLinks(follow) { + var s = follow.split(''); + var linknr = parseInt(follow, 10); + if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); + var linkelems = addLinks(); + var formelems = addFormElems(); + var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; + var len = (elems[0].length + '').length; + var oldDiv = doc.getElementById(uzbldivid); + var leftover = [[], []]; + if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { + clickElem(elems[0][linknr]); + } else { + for (var j = 0; j < elems[0].length; j++) { + var b = true; + for (var k = 0; k < s.length; k++) { + b = b && elems[1][j].charAt(k) == s[k]; + } + if (!b) { + elems[0][j] = null; + elems[1][j] = null; + } else { + leftover[0].push(elems[0][j]); + leftover[1].push(elems[1][j]); + } + } + if (leftover[0].length == 1) { + clickElem(leftover[0][0]); + } else if (!oldDiv) { + if (linknr + 1 || s.length == 0) { + reDrawHints(elems, len); + } else { + reDrawHints(leftover, len); + } + } + } +} +followLinks('%s'); diff --git a/examples/data/scripts/formfiller.pl b/examples/data/scripts/formfiller.pl new file mode 100755 index 0000000..74dcc80 --- /dev/null +++ b/examples/data/scripts/formfiller.pl @@ -0,0 +1,99 @@ +#!/usr/bin/perl + +# a slightly more advanced form filler +# +# uses settings file like: $keydir/ +#TODO: fallback to $HOME/.local/share +# user arg 1: +# edit: force editing of the file (fetches if file is missing) +# load: fill forms from file (fetches if file is missing) +# new: fetch new file + +# usage example: +# bind LL = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl load +# bind LN = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl new +# bind LE = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl edit + +use strict; +use warnings; + +my $keydir = $ENV{XDG_CONFIG_HOME} . "/uzbl/forms"; +my ($config,$pid,$xid,$fifoname,$socket,$url,$title,$cmd) = @ARGV; +if (!defined $fifoname || $fifoname eq "") { die "No fifo"; } + +sub domain { + my ($url) = @_; + $url =~ s#http(s)?://([A-Za-z0-9\.-]+)(/.*)?#$2#; + return $url; +}; + +my $editor = "xterm -e vim"; +#my $editor = "gvim"; + +# ideally, there would be some way to ask uzbl for the html content instead of having to redownload it with +# Also, you may need to fake the user-agent on some sites (like facebook) + my $downloader = "curl -A 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.10) Gecko/2009042810 GranParadiso/3.0.10' "; +#my $downloader = "curl -s"; + +my @fields = ("type","name","value"); + +my %command; + +$command{load} = sub { + my ($domain) = @_; + my $filename = "$keydir/$domain"; + if (-e $filename){ + open(my $file, $filename) or die "Failed to open $filename: $!"; + my (@lines) = <$file>; + close($file); + $|++; + open(my $fifo, ">>", $fifoname) or die "Failed to open $fifoname: $!"; + foreach my $line (@lines) { + next if ($line =~ m/^#/); + my ($type,$name,$value) = ($line =~ /^\s*(\w+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*$/); + if ($type eq "checkbox") + { + printf $fifo 'js document.getElementsByName("%s")[0].checked = %s;', $name, $value; + } elsif ($type eq "submit") + { + printf $fifo 'js function fs (n) {try{n.submit()} catch (e){fs(n.parentNode)}}; fs(document.getElementsByName("%s")[0]);', $name; + } elsif ($type ne "") + { + printf $fifo 'js document.getElementsByName("%s")[0].value = "%s";', $name, $value; + } + print $fifo "\n"; + } + $|--; + } else { + $command{new}->($domain); + $command{edit}->($domain); + } +}; +$command{edit} = sub { + my ($domain) = @_; + my $file = "$keydir/$domain"; + if(-e $file){ + system ($editor, $file); + } else { + $command{new}->($domain); + } +}; +$command{new} = sub { + my ($domain) = @_; + my $filename = "$keydir/$domain"; + open (my $file,">>", $filename) or die "Failed to open $filename: $!"; + $|++; + print $file "# Make sure that there are no extra submits, since it may trigger the wrong one.\n"; + printf $file "#%-10s | %-10s | %s\n", @fields; + print $file "#------------------------------\n"; + my @data = `$downloader $url`; + foreach my $line (@data){ + if($line =~ m/].*?)>/i){ + $line =~ s/.*(].*?)>).*/$1/; + printf $file " %-10s | %-10s | %s\n", map { my ($r) = $line =~ /.*$_=["'](.*?)["']/;$r } @fields; + }; + }; + $|--; +}; + +$command{$cmd}->(domain($url)); diff --git a/examples/data/scripts/formfiller.sh b/examples/data/scripts/formfiller.sh new file mode 100755 index 0000000..10afaba --- /dev/null +++ b/examples/data/scripts/formfiller.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# simple html form (eg for logins) filler (and manager) for uzbl. +# uses settings files like: $keydir/ +# files contain lines like: : + + +# user arg 1: +# edit: force editing the file (falls back to new if not found) +# new: start with a new file. +# load: try to load from file into form + +# something else (or empty): if file not available: new, otherwise load. + +keydir=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/forms +[ -d "`dirname $keydir`" ] || exit 1 +[ -d "$keydir" ] || mkdir "$keydir" + +editor=${VISUAL} +if [[ -z ${editor} ]]; then + #editor='gvim' + editor='urxvt -e vim' +fi + +config=$1; shift +pid=$1; shift +xid=$1; shift +fifo=$1; shift +socket=$1; shift +url=$1; shift +title=$1; shift +action=$1 + +[ -d $keydir ] || mkdir $keydir || exit 1 + +if [ "$action" != 'edit' -a "$action" != 'new' -a "$action" != 'load' ] +then + action=new + [[ -e $keydir/$domain ]] && action=load +elif [ "$action" == 'edit' ] && [[ ! -e $keydir/$domain ]] +then + action=new +fi +domain=$(echo $url | sed -re 's|(http\|https)+://([A-Za-z0-9\.]+)/.*|\2|') + + +#regex='s|.*.*|\1: |p' # sscj's first version, does not work on http://wiki.archlinux.org/index.php?title=Special:UserLogin&returnto=Main_Page + regex='s|.*> $fifo +else + if [ "$action" == 'new' ] + then + curl "$url" | grep ' $keydir/$domain + fi + [[ -e $keydir/$domain ]] || exit 3 #this should never happen, but you never know. + $editor $keydir/$domain #TODO: if user aborts save in editor, the file is already overwritten +fi diff --git a/examples/data/scripts/hint.js b/examples/data/scripts/hint.js new file mode 100644 index 0000000..ec7f1e2 --- /dev/null +++ b/examples/data/scripts/hint.js @@ -0,0 +1,26 @@ +for (var i=0; i < document.links.length; i++) { + var uzblid = 'uzbl_link_hint_'; + var li = document.links[i]; + var pre = document.getElementById(uzblid+i); + + if (pre) { + li.removeChild(pre); + } else { + var hint = document.createElement('div'); + hint.setAttribute('id',uzblid+i); + hint.innerHTML = i; + hint.style.display='inline'; + hint.style.lineHeight='90%'; + hint.style.backgroundColor='red'; + hint.style.color='white'; + hint.style.fontSize='small-xx'; + hint.style.fontWeight='light'; + hint.style.margin='0px'; + hint.style.padding='2px'; + hint.style.position='absolute'; + hint.style.textDecoration='none'; + hint.style.left=li.style.left; + hint.style.top=li.style.top; + li.insertAdjacentElement('afterBegin',hint); + } +} diff --git a/examples/data/scripts/history.sh b/examples/data/scripts/history.sh new file mode 100755 index 0000000..7c83aa6 --- /dev/null +++ b/examples/data/scripts/history.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history +[ -d `dirname $file` ] || exit 1 +echo `date +'%Y-%m-%d %H:%M:%S'`" $6 $7" >> $file diff --git a/examples/data/scripts/insert_bookmark.sh b/examples/data/scripts/insert_bookmark.sh new file mode 100755 index 0000000..c34e7db --- /dev/null +++ b/examples/data/scripts/insert_bookmark.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +[ -d "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl" ] || exit 1 +file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/bookmarks + +which zenity &>/dev/null || exit 2 + +entry=`zenity --entry --text="Add bookmark. add tags after the '\t', separated by spaces" --entry-text="$6 $7\t"` +exitstatus=$? +if [ $exitstatus -ne 0 ]; then exit $exitstatus; fi +url=`echo $entry | awk '{print $1}'` + +# TODO: check if already exists, if so, and tags are different: ask if you want to replace tags +echo "$entry" >/dev/null #for some reason we need this.. don't ask me why +echo -e "$entry" >> $file +true diff --git a/examples/data/scripts/instance-select-wmii.sh b/examples/data/scripts/instance-select-wmii.sh new file mode 100755 index 0000000..2bf13ba --- /dev/null +++ b/examples/data/scripts/instance-select-wmii.sh @@ -0,0 +1,54 @@ +#!/bin/sh + + +# This script allows you to focus another uzbl window +# It considers all uzbl windows in the current tag +# you can select one from a list, or go to the next/previous one +# It does not change the layout (stacked/tiled/floating) nor does it +# changes the size or viewing mode of a uzbl window +# When your current uzbl window is maximized, the one you change to +# will be maximized as well. +# See http://www.uzbl.org/wiki/wmii for more info +# $1 must be one of 'list', 'next', 'prev' + +COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" + +if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch +else + DMENU="dmenu -i" +fi + +if [ "$1" == 'list' ] +then + list= + # get window id's of uzbl clients. we could also get the label in one shot but it's pretty tricky + for i in $(wmiir read /tag/sel/index | grep uzbl |cut -d ' ' -f2) + do + label=$(wmiir read /client/$i/label) + list="$list$i : $label\n" + done + window=$(echo -e "$list" | $DMENU $COLORS | cut -d ' ' -f1) + wmiir xwrite /tag/sel/ctl "select client $window" +elif [ "$1" == 'next' ] +then + current=$(wmiir read /client/sel/ctl | head -n 1) + # find the next uzbl window and focus it + next=$(wmiir read /tag/sel/index | grep -A 10000 " $current " | grep -m 1 uzbl | cut -d ' ' -f2) + if [ x"$next" != "x" ] + then + wmiir xwrite /tag/sel/ctl "select client $next" + fi +elif [ "$1" == 'prev' ] +then + current=$(wmiir read /client/sel/ctl | head -n 1) + prev=$(wmiir read /tag/sel/index | grep -B 10000 " $current " | tac | grep -m 1 uzbl | cut -d ' ' -f2) + if [ x"$prev" != "x" ] + then + wmiir xwrite /tag/sel/ctl "select client $prev" + fi +else + echo "\$1 not valid" >&2 + exit 2 +fi diff --git a/examples/data/scripts/linkfollow.js b/examples/data/scripts/linkfollow.js new file mode 100644 index 0000000..3109cda --- /dev/null +++ b/examples/data/scripts/linkfollow.js @@ -0,0 +1,269 @@ +// link follower for uzbl +// requires http://github.com/DuClare/uzbl/commit/6c11777067bdb8aac09bba78d54caea04f85e059 +// +// first, it needs to be loaded before every time it is used. +// One way would be to use the load_commit_handler: +// set load_commit_handler = sh 'echo "script /usr/share/uzbl/examples/data/scripts/linkfollow.js" > "$4"' +// +// when script is loaded, it can be invoked with +// bind f* = js hints.set("%s", hints.open) +// bind f_ = js hints.follow("%s",hints.open) +// +// At the moment, it may be useful to have way of forcing uzbl to load the script +// bind :lf = script /usr/share/uzbl/examples/data/scripts/linkfollow.js +// +// The default style for the hints are pretty ugly, so it is recommended to add the following +// to config file +// set stylesheet_uri = /usr/share/uzbl/examples/data/style.css +// +// based on follow_Numbers.js +// +// TODO: fix styling for the first element +// TODO: emulate mouseover events when visiting some elements +// TODO: rewrite the element->action handling + + +function Hints(){ + + // Settings + //////////////////////////////////////////////////////////////////////////// + + // if set to true, you must explicitly call hints.follow(), otherwise it will + // follow the link if there is only one matching result + var requireReturn = true; + + // Case sensitivity flag + var matchCase = "i"; + + // For case sensitive matching, uncomment: + // var matchCase = ""; + + + var uzblid = 'uzbl_hint'; + var uzblclass = 'uzbl_highlight'; + var uzblclassfirst = 'uzbl_h_first'; + var doc = document; + var visible = []; + var hintdiv; + + this.set = hint; + this.follow = follow; + this.keyPressHandler = keyPressHandler; + + function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; var width = el.offsetWidth; + var height = el.offsetHeight; + + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return {up: up, left: left, width: width, height: height}; + } + + function elementInViewport(p) { + return (p.up < window.pageYOffset + window.innerHeight && + p.left < window.pageXOffset + window.innerWidth && + (p.up + p.height) > window.pageYOffset && + (p.left + p.width) > window.pageXOffset); + } + + function isVisible(el) { + if (el == doc) { return true; } + if (!el) { return false; } + if (!el.parentNode) { return false; } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); + } + + // the vimperator defaults minus the xhtml elements, since it gave DOM errors + var hintable = " //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select"; + + function Matcher(str){ + var numbers = str.replace(/[^\d]/g,""); + var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,matchCase)}); + this.test = test; + this.toString = toString; + this.numbers = numbers; + function matchAgainst(element){ + if(element.node.nodeName == "INPUT"){ + return element.node.value; + } else { + return element.node.textContent; + } + } + function test(element) { + // test all the regexp + var item = matchAgainst(element); + return words.every(function (regex) { return item.match(regex)}); + } + } + + function HintElement(node,pos){ + + this.node = node; + this.isHinted = false; + this.position = pos; + this.num = 0; + + this.addHint = function (labelNum) { + // TODO: fix uzblclassfirst + if(!this.isHinted){ + this.node.className += " " + uzblclass; + } + this.isHinted = true; + + // create hint + var hintNode = doc.createElement('div'); + hintNode.name = uzblid; + hintNode.innerText = labelNum; + hintNode.style.left = this.position.left + 'px'; + hintNode.style.top = this.position.up + 'px'; + hintNode.style.position = "absolute"; + doc.body.firstChild.appendChild(hintNode); + + } + this.removeHint = function(){ + if(this.isHinted){ + var s = (this.num)?uzblclassfirst:uzblclass; + this.node.className = this.node.className.replace(new RegExp(" "+s,"g"),""); + this.isHinted = false; + } + } + } + + function createHintDiv(){ + var hintdiv = doc.getElementById(uzblid); + if(hintdiv){ + hintdiv.parentNode.removeChild(hintdiv); + } + hintdiv = doc.createElement("div"); + hintdiv.setAttribute('id',uzblid); + doc.body.insertBefore(hintdiv,doc.body.firstChild); + return hintdiv; + } + + function init(){ + // WHAT? + doc.body.setAttribute("onkeyup","hints.keyPressHandler(event)"); + hintdiv = createHintDiv(); + visible = []; + + var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null); + for (var i = 0;i&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch + # show tags as well + goto=`$DMENU $COLORS < $file | awk '{print $1}'` +else + DMENU="dmenu -i" + # because they are all after each other, just show the url, not their tags. + goto=`awk '{print $1}' $file | $DMENU $COLORS` +fi + +#[ -n "$goto" ] && echo "uri $goto" > $4 +[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/scripts/load_url_from_history.sh b/examples/data/scripts/load_url_from_history.sh new file mode 100755 index 0000000..62e02ac --- /dev/null +++ b/examples/data/scripts/load_url_from_history.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +history_file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history +[ -r "$history_file" ] || exit 1 + +# choose from all entries, sorted and uniqued +# goto=`awk '{print $3}' $history_file | sort -u | dmenu -i` +COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" +if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]'; +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch + # choose an item in reverse order, showing also the date and page titles + # pick the last field from the first 3 fields. this way you can pick a url (prefixed with date & time) or type just a new url. + goto=`tac $history_file | $DMENU $COLORS | cut -d ' ' -f -3 | awk '{print $NF}'` +else + DMENU="dmenu -i" + # choose from all entries (no date or title), the first one being current url, and after that all others, sorted and uniqued, in ascending order + current=`tail -n 1 $history_file | awk '{print $3}'`; + goto=`(echo $current; awk '{print $3}' $history_file | grep -v "^$current\$" \ + | sort -u) | $DMENU $COLORS` +fi + +[ -n "$goto" ] && echo "uri $goto" > $4 +#[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/scripts/scheme.py b/examples/data/scripts/scheme.py new file mode 100755 index 0000000..0916466 --- /dev/null +++ b/examples/data/scripts/scheme.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import os, subprocess, sys, urlparse + +def detach_open(cmd): + # Thanks to the vast knowledge of Laurence Withers (lwithers) and this message: + # http://mail.python.org/pipermail/python-list/2006-November/587523.html + if not os.fork(): + null = os.open(os.devnull,os.O_WRONLY) + for i in range(3): os.dup2(null,i) + os.close(null) + subprocess.Popen(cmd) + print 'USED' + +if __name__ == '__main__': + uri = sys.argv[8] + u = urlparse.urlparse(uri) + if u.scheme == 'mailto': + detach_open(['xterm', '-e', 'mail', u.path]) + elif u.scheme == 'xmpp': + # Someone check for safe arguments to gajim-remote + detach_open(['gajim-remote', 'open_chat', uri]) + elif u.scheme == 'git': + detach_open(['git', 'clone', '--', uri], cwd=os.path.expanduser('~/src')) diff --git a/examples/data/scripts/scroll-percentage.js b/examples/data/scripts/scroll-percentage.js new file mode 100644 index 0000000..c9a51aa --- /dev/null +++ b/examples/data/scripts/scroll-percentage.js @@ -0,0 +1,68 @@ +// VIM ruler style scroll message +(function() { + var run = Uzbl.run; + var update_message = function() { + var innerHeight = window.innerHeight; + var scrollY = window.scrollY; + var height = document.height; + var message; + + if (UzblZoom.type === "full") { + var zoom_level = UzblZoom.level; + innerHeight = Math.ceil(innerHeight * zoom_level); + scrollY = Math.ceil(scrollY * zoom_level); + height -= 1; + } + + if (! height) { + message = ""; + } + else if (height <= innerHeight) { + message = run("print @scroll_all_indicator") || "All"; + } + else if (scrollY === 0) { + message = run("print @scroll_top_indicator") || "Top"; + } + else if (scrollY + innerHeight >= height) { + message = run("print @scroll_bottom_indicator") || "Bot"; + } + else { + var percentage = Math.round(scrollY / (height - innerHeight) * 100); + message = percentage + "%"; + } + run("set scroll_message=" + message); + }; + + self.UzblZoom = { + get level() { + return Number(run("print @zoom_level")) || 1; + }, + set level(level) { + if (typeof level === "number" && level > 0) { + run("set zoom_level = " + level); + update_message(); + } + }, + get type() { + return run("print @zoom_type") || "text"; + }, + set type(type) { + if ((type === "text" || type === "full") && this.type != type) { + run("toggle_zoom_type"); + run("set zoom_type = " + type); + update_message(); + } + }, + toggle_type: function() { + this.type = (this.type === "text" ? "full" : "text"); + } + }; + + window.addEventListener("DOMContentLoaded", update_message, false); + window.addEventListener("load", update_message, false); + window.addEventListener("resize", update_message, false); + window.addEventListener("scroll", update_message, false); + update_message(); +})(); + +// vim: set noet ff=unix diff --git a/examples/data/scripts/session.sh b/examples/data/scripts/session.sh new file mode 100755 index 0000000..1059b5e --- /dev/null +++ b/examples/data/scripts/session.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Very simple session manager for uzbl-browser. When called with "endsession" as the +# argument, it'll backup $sessionfile, look for fifos in $fifodir and +# instruct each of them to store their current url in $sessionfile and +# terminate themselves. Run with "launch" as the argument and an instance of +# uzbl-browser will be launched for each stored url. "endinstance" is used internally +# and doesn't need to be called manually at any point. +# Add a line like 'bind quit = /path/to/session.sh endsession' to your config + +[ -d ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl ] || exit 1 +scriptfile=$0 # this script +sessionfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/browser-session # the file in which the "session" (i.e. urls) are stored +configfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/config # uzbl configuration file +UZBL="uzbl-browser -c $configfile" # add custom flags and whatever here. + +fifodir=/tmp # remember to change this if you instructed uzbl to put its fifos elsewhere +thisfifo="$4" +act="$8" +url="$6" + +if [ "$act." = "." ]; then + act="$1" +fi + + +case $act in + "launch" ) + urls=`cat $sessionfile` + if [ "$urls." = "." ]; then + $UZBL + else + for url in $urls; do + $UZBL --uri "$url" & + done + fi + exit 0 + ;; + + "endinstance" ) + if [ "$url" != "(null)" ]; then + echo "$url" >> $sessionfile; + fi + echo "exit" > "$thisfifo" + ;; + + "endsession" ) + mv "$sessionfile" "$sessionfile~" + for fifo in $fifodir/uzbl_fifo_*; do + if [ "$fifo" != "$thisfifo" ]; then + echo "spawn $scriptfile endinstance" > "$fifo" + fi + done + echo "spawn $scriptfile endinstance" > "$thisfifo" + ;; + + * ) echo "session manager: bad action" + echo "Usage: $scriptfile [COMMAND] where commands are:" + echo " launch - Restore a saved session or start a new one" + echo " endsession - Quit the running session. Must be called from uzbl" + ;; +esac diff --git a/examples/data/scripts/uzbl-cookie-daemon b/examples/data/scripts/uzbl-cookie-daemon new file mode 100755 index 0000000..fde8b8e --- /dev/null +++ b/examples/data/scripts/uzbl-cookie-daemon @@ -0,0 +1,664 @@ +#!/usr/bin/env python + +# The Python Cookie Daemon for Uzbl. +# Copyright (c) 2009, Tom Adams +# Copyright (c) 2009, Dieter Plaetinck +# Copyright (c) 2009, Mason Larobina +# Copyright (c) 2009, Michael Fiano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +''' +The Python Cookie Daemon +======================== + +This daemon is a re-write of the original cookies.py script found in uzbl's +master branch. This script provides more functionality than the original +cookies.py by adding numerous command line options to specify different cookie +jar locations, socket locations, verbose output, etc. This functionality is +very useful as it allows you to run multiple daemons at once serving cookies +to different groups of uzbl instances as required. + +Keeping up to date +================== + +Check the cookie daemon uzbl-wiki page for more information on where to +find the latest version of the cookie_daemon.py + + http://www.uzbl.org/wiki/cookie_daemon.py + +Command line options +==================== + +Use the following command to get a full list of the cookie_daemon.py command +line options: + + ./cookie_daemon.py --help + +Talking with uzbl +================= + +In order to get uzbl to talk to a running cookie daemon you add the following +to your uzbl config: + + set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket + +Or if you prefer using the $HOME variable: + + set cookie_handler = talk_to_socket $HOME/.cache/uzbl/cookie_daemon_socket + +Todo list +========= + + - Use a pid file to make force killing a running daemon possible. + +Reporting bugs / getting help +============================= + +The best way to report bugs and or get help with the cookie daemon is to +contact the maintainers it the #uzbl irc channel found on the Freenode IRC +network (irc.freenode.org). +''' + +import cookielib +import os +import sys +import urllib2 +import select +import socket +import time +import atexit +from traceback import print_exc +from signal import signal, SIGTERM +from optparse import OptionParser +from os.path import join + +try: + import cStringIO as StringIO + +except ImportError: + import StringIO + + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return join(os.environ['HOME'], default) + +# Setup xdg paths. +CACHE_DIR = join(xdghome('CACHE', '.cache/'), 'uzbl/') +DATA_DIR = join(xdghome('DATA', '.local/share/'), 'uzbl/') +CONFIG_DIR = join(xdghome('CONFIG', '.config/'), 'uzbl/') + +# Ensure data paths exist. +for path in [CACHE_DIR, DATA_DIR, CONFIG_DIR]: + if not os.path.exists(path): + os.makedirs(path) + +# Default config +config = { + + # Default cookie jar, whitelist, and daemon socket locations. + 'cookie_jar': join(DATA_DIR, 'cookies.txt'), + 'cookie_whitelist': join(CONFIG_DIR, 'cookie_whitelist'), + 'cookie_socket': join(CACHE_DIR, 'cookie_daemon_socket'), + + # Don't use a cookie whitelist policy by default. + 'use_whitelist': False, + + # Time out after x seconds of inactivity (set to 0 for never time out). + # WARNING: Do not use this option if you are manually launching the daemon. + 'daemon_timeout': 0, + + # Daemonise by default. + 'daemon_mode': True, + + # Optionally print helpful debugging messages to the terminal. + 'verbose': False, + +} # End of config dictionary. + + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + + +_SCRIPTNAME = os.path.basename(sys.argv[0]) +def echo(msg): + '''Prints only if the verbose flag has been set.''' + + if config['verbose']: + sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) + + +def error(msg): + '''Prints error message and exits.''' + + sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) + sys.exit(1) + + +def mkbasedir(filepath): + '''Create the base directories of the file in the file-path if the dirs + don't exist.''' + + dirname = os.path.dirname(filepath) + if not os.path.exists(dirname): + echo("creating dirs: %r" % dirname) + os.makedirs(dirname) + + +def daemon_running(cookie_socket): + '''Check if another process (hopefully a cookie_daemon.py) is listening + on the cookie daemon socket. If another process is found to be + listening on the socket exit the daemon immediately and leave the + socket alone. If the connect fails assume the socket has been abandoned + and delete it (to be re-created in the create socket function).''' + + if not os.path.exists(cookie_socket): + return False + + if os.path.isfile(cookie_socket): + raise Exception("regular file at %r is not a socket" % cookie_socket) + + + if os.path.isdir(cookie_socket): + raise Exception("directory at %r is not a socket" % cookie_socket) + + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + sock.connect(cookie_socket) + sock.close() + echo("detected daemon listening on %r" % cookie_socket) + return True + + except socket.error: + # Failed to connect to cookie_socket so assume it has been + # abandoned by another cookie daemon process. + if os.path.exists(cookie_socket): + echo("deleting abandoned socket at %r" % cookie_socket) + os.remove(cookie_socket) + + return False + + +def send_command(cookie_socket, cmd): + '''Send a command to a running cookie daemon.''' + + if not daemon_running(cookie_socket): + return False + + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + sock.connect(cookie_socket) + sock.send(cmd) + sock.close() + echo("sent command %r to %r" % (cmd, cookie_socket)) + return True + + except socket.error: + print_exc() + error("failed to send message %r to %r" % (cmd, cookie_socket)) + return False + + +def kill_daemon(cookie_socket): + '''Send the "EXIT" command to running cookie_daemon.''' + + if send_command(cookie_socket, "EXIT"): + # Now ensure the cookie_socket is cleaned up. + start = time.time() + while os.path.exists(cookie_socket): + time.sleep(0.1) + if (time.time() - start) > 5: + error("force deleting socket %r" % cookie_socket) + os.remove(cookie_socket) + return + + echo("stopped daemon listening on %r"% cookie_socket) + + else: + if os.path.exists(cookie_socket): + os.remove(cookie_socket) + echo("removed abandoned/broken socket %r" % cookie_socket) + + +def daemonize(): + '''Daemonize the process using the Stevens' double-fork magic.''' + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #1 failed") + sys.exit(1) + + os.chdir('/') + os.setsid() + os.umask(0) + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #2 failed") + sys.exit(1) + + sys.stdout.flush() + sys.stderr.flush() + + devnull = '/dev/null' + stdin = file(devnull, 'r') + stdout = file(devnull, 'a+') + stderr = file(devnull, 'a+', 0) + + os.dup2(stdin.fileno(), sys.stdin.fileno()) + os.dup2(stdout.fileno(), sys.stdout.fileno()) + os.dup2(stderr.fileno(), sys.stderr.fileno()) + + +class CookieMonster: + '''The uzbl cookie daemon class.''' + + def __init__(self): + '''Initialise class variables.''' + + self.server_socket = None + self.jar = None + self.last_request = time.time() + self._running = False + + + def run(self): + '''Start the daemon.''' + + # The check healthy function will exit if another daemon is detected + # listening on the cookie socket and remove the abandoned socket if + # there isnt. + if os.path.exists(config['cookie_socket']): + if daemon_running(config['cookie_socket']): + sys.exit(1) + + # Create cookie daemon socket. + self.create_socket() + + # Daemonize process. + if config['daemon_mode']: + echo("entering daemon mode") + daemonize() + + # Register a function to cleanup on exit. + atexit.register(self.quit) + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) + + # Create cookie jar object from file. + self.open_cookie_jar() + + # Create a way to exit nested loops by setting a running flag. + self._running = True + + while self._running: + try: + # Enter main listen loop. + self.listen() + + except KeyboardInterrupt: + self._running = False + print + + except socket.error: + print_exc() + + except: + # Clean up + self.del_socket() + + # Raise exception + raise + + # Always delete the socket before calling create again. + self.del_socket() + # Create cookie daemon socket. + self.create_socket() + + + def load_whitelist(self): + '''Load the cookie jar whitelist policy.''' + + cookie_whitelist = config['cookie_whitelist'] + + if cookie_whitelist: + mkbasedir(cookie_whitelist) + + # Create cookie whitelist file if it does not exist. + if not os.path.exists(cookie_whitelist): + open(cookie_whitelist, 'w').close() + + # Read cookie whitelist file into list. + file = open(cookie_whitelist,'r') + domain_list = [line.rstrip('\n') for line in file] + file.close() + + # Define policy of allowed domains + policy = cookielib.DefaultCookiePolicy(allowed_domains=domain_list) + self.jar.set_policy(policy) + + # Save the last modified time of the whitelist. + self._whitelistmtime = os.stat(cookie_whitelist).st_mtime + + + def open_cookie_jar(self): + '''Open the cookie jar.''' + + cookie_jar = config['cookie_jar'] + cookie_whitelist = config['cookie_whitelist'] + + if cookie_jar: + mkbasedir(cookie_jar) + + # Create cookie jar object from file. + self.jar = cookielib.MozillaCookieJar(cookie_jar) + + # Load cookie whitelist policy. + if config['use_whitelist']: + self.load_whitelist() + + if cookie_jar: + try: + # Attempt to load cookies from the cookie jar. + self.jar.load(ignore_discard=True) + + # Ensure restrictive permissions are set on the cookie jar + # to prevent other users on the system from hi-jacking your + # authenticated sessions simply by copying your cookie jar. + os.chmod(cookie_jar, 0600) + + except: + pass + + + def reload_whitelist(self): + '''Reload the cookie whitelist.''' + + cookie_whitelist = config['cookie_whitelist'] + if os.path.exists(cookie_whitelist): + echo("reloading whitelist %r" % cookie_whitelist) + self.open_cookie_jar() + + + def create_socket(self): + '''Create AF_UNIX socket for communication with uzbl instances.''' + + cookie_socket = config['cookie_socket'] + mkbasedir(cookie_socket) + + self.server_socket = socket.socket(socket.AF_UNIX, + socket.SOCK_SEQPACKET) + + self.server_socket.bind(cookie_socket) + + # Set restrictive permissions on the cookie socket to prevent other + # users on the system from data-mining your cookies. + os.chmod(cookie_socket, 0600) + + + def listen(self): + '''Listen for incoming cookie PUT and GET requests.''' + + daemon_timeout = config['daemon_timeout'] + echo("listening on %r" % config['cookie_socket']) + + while self._running: + # This line tells the socket how many pending incoming connections + # to enqueue at once. Raising this number may or may not increase + # performance. + self.server_socket.listen(1) + + if bool(select.select([self.server_socket], [], [], 1)[0]): + client_socket, _ = self.server_socket.accept() + self.handle_request(client_socket) + self.last_request = time.time() + client_socket.close() + continue + + if daemon_timeout: + # Checks if the daemon has been idling for too long. + idle = time.time() - self.last_request + if idle > daemon_timeout: + self._running = False + + + def handle_request(self, client_socket): + '''Connection made, now to serve a cookie PUT or GET request.''' + + # Receive cookie request from client. + data = client_socket.recv(8192) + if not data: + return + + # Cookie argument list in packet is null separated. + argv = data.split("\0") + action = argv[0].upper().strip() + + # Catch the EXIT command sent to kill running daemons. + if action == "EXIT": + self._running = False + return + + # Catch whitelist RELOAD command. + elif action == "RELOAD": + self.reload_whitelist() + return + + # Return if command unknown. + elif action not in ['GET', 'PUT']: + error("unknown command %r." % argv) + return + + # Determine whether or not to print cookie data to terminal. + print_cookie = (config['verbose'] and not config['daemon_mode']) + if print_cookie: + print ' '.join(argv[:4]) + + uri = urllib2.urlparse.ParseResult( + scheme=argv[1], + netloc=argv[2], + path=argv[3], + params='', + query='', + fragment='').geturl() + + req = urllib2.Request(uri) + + if action == "GET": + self.jar.add_cookie_header(req) + if req.has_header('Cookie'): + cookie = req.get_header('Cookie') + client_socket.send(cookie) + if print_cookie: + print cookie + + else: + client_socket.send("\0") + + elif action == "PUT": + cookie = argv[4] if len(argv) > 3 else None + if print_cookie: + print cookie + + self.put_cookie(req, cookie) + + if print_cookie: + print + + + def put_cookie(self, req, cookie=None): + '''Put a cookie in the cookie jar.''' + + hdr = urllib2.httplib.HTTPMessage(\ + StringIO.StringIO('Set-Cookie: %s' % cookie)) + res = urllib2.addinfourl(StringIO.StringIO(), hdr, + req.get_full_url()) + self.jar.extract_cookies(res, req) + if config['cookie_jar']: + self.jar.save(ignore_discard=True) + + + def del_socket(self): + '''Remove the cookie_socket file on exit. In a way the cookie_socket + is the daemons pid file equivalent.''' + + if self.server_socket: + try: + self.server_socket.close() + + except: + pass + + self.server_socket = None + + cookie_socket = config['cookie_socket'] + if os.path.exists(cookie_socket): + echo("deleting socket %r" % cookie_socket) + os.remove(cookie_socket) + + + def quit(self): + '''Called on exit to make sure all loose ends are tied up.''' + + self.del_socket() + sys.exit(0) + + +def main(): + '''Main function.''' + + # Define command line parameters. + usage = "usage: %prog [options] {start|stop|restart|reload}" + parser = OptionParser(usage=usage) + parser.add_option('-n', '--no-daemon', dest='no_daemon', + action='store_true', help="don't daemonise the process.") + + parser.add_option('-v', '--verbose', dest="verbose", + action='store_true', help="print verbose output.") + + parser.add_option('-t', '--daemon-timeout', dest='daemon_timeout', + action="store", metavar="SECONDS", help="shutdown the daemon after x "\ + "seconds inactivity. WARNING: Do not use this when launching the "\ + "cookie daemon manually.") + + parser.add_option('-s', '--cookie-socket', dest="cookie_socket", + metavar="SOCKET", help="manually specify the socket location.") + + parser.add_option('-j', '--cookie-jar', dest='cookie_jar', + metavar="FILE", help="manually specify the cookie jar location.") + + parser.add_option('-m', '--memory', dest='memory', action='store_true', + help="store cookies in memory only - do not write to disk") + + parser.add_option('-u', '--use-whitelist', dest='usewhitelist', + action='store_true', help="use cookie whitelist policy") + + parser.add_option('-w', '--cookie-whitelist', dest='whitelist', + action='store', help="manually specify whitelist location", + metavar='FILE') + + # Parse the command line arguments. + (options, args) = parser.parse_args() + + expand = lambda p: os.path.realpath(os.path.expandvars(p)) + + initcommands = ['start', 'stop', 'restart', 'reload'] + for arg in args: + if arg not in initcommands: + error("unknown argument %r" % args[0]) + sys.exit(1) + + if len(args) > 1: + error("the daemon only accepts one {%s} action at a time." + % '|'.join(initcommands)) + sys.exit(1) + + if len(args): + action = args[0] + + else: + action = "start" + + if options.no_daemon: + config['daemon_mode'] = False + + if options.cookie_socket: + config['cookie_socket'] = expand(options.cookie_socket) + + if options.cookie_jar: + config['cookie_jar'] = expand(options.cookie_jar) + + if options.memory: + config['cookie_jar'] = None + + if options.whitelist: + config['cookie_whitelist'] = expand(options.whitelist) + + if options.whitelist or options.usewhitelist: + config['use_whitelist'] = True + + if options.daemon_timeout: + try: + config['daemon_timeout'] = int(options.daemon_timeout) + + except ValueError: + error("expected int argument for -t, --daemon-timeout") + + # Expand $VAR's in config keys that relate to paths. + for key in ['cookie_socket', 'cookie_jar', 'cookie_whitelist']: + if config[key]: + config[key] = os.path.expandvars(config[key]) + + if options.verbose: + config['verbose'] = True + import pprint + sys.stderr.write("%s\n" % pprint.pformat(config)) + + # It would be better if we didn't need to start this python process just + # to send a command to the socket, but unfortunately socat doesn't seem + # to support SEQPACKET. + if action == "reload": + send_command(config['cookie_socket'], "RELOAD") + + if action in ['stop', 'restart']: + kill_daemon(config['cookie_socket']) + + if action in ['start', 'restart']: + CookieMonster().run() + + +if __name__ == "__main__": + main() diff --git a/examples/data/scripts/uzbl-event-manager b/examples/data/scripts/uzbl-event-manager new file mode 100755 index 0000000..99b215a --- /dev/null +++ b/examples/data/scripts/uzbl-event-manager @@ -0,0 +1,833 @@ +#!/usr/bin/env python + +# Event Manager for Uzbl +# Copyright (c) 2009, Mason Larobina +# Copyright (c) 2009, Dieter Plaetinck +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +''' + +E V E N T _ M A N A G E R . P Y +=============================== + +Event manager for uzbl written in python. + +''' + +import imp +import os +import sys +import re +import socket +import pprint +import time +import atexit +from select import select +from signal import signal, SIGTERM +from optparse import OptionParser +from traceback import print_exc +from functools import partial + + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return os.path.join(os.environ['HOME'], default) + + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +# `make install` will put the correct value here for your system +PREFIX = '/usr/local/' + +# Setup xdg paths. +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') +CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') + +# Event manager config dictionary. This is not to be confused with the config +# dict that tracks variables in the uzbl instance. +CONFIG = { + 'verbose': False, + 'daemon_mode': True, + 'auto_close': False, + + 'plugins_load': [], + 'plugins_ignore': [], + + 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'), + os.path.join(PREFIX, 'share/uzbl/examples/data/plugins/')], + + 'server_socket': os.path.join(CACHE_DIR, 'event_daemon'), + 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'), +} + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + + +# Define some globals. +SCRIPTNAME = os.path.basename(sys.argv[0]) +FINDSPACES = re.compile("\s+") + + +class ArgumentError(Exception): + pass + + +def echo(msg): + '''Prints only if the verbose flag has been set.''' + + if CONFIG['verbose']: + sys.stdout.write("%s: %s\n" % (SCRIPTNAME, msg)) + + +def error(msg): + '''Prints error messages to stderr.''' + + sys.stderr.write("%s: error: %s\n" % (SCRIPTNAME, msg)) + + +def counter(): + '''Generate unique object id's.''' + + i = 0 + while True: + i += 1 + yield i + + +def find_plugins(plugin_dirs): + '''Find all event manager plugins in the plugin dirs and return a + dictionary of {'plugin-name.py': '/full/path/to/plugin-name.py', ...}''' + + plugins = {} + + for plugin_dir in plugin_dirs: + plugin_dir = os.path.realpath(os.path.expandvars(plugin_dir)) + if not os.path.isdir(plugin_dir): + continue + + for filename in os.listdir(plugin_dir): + if not filename.lower().endswith('.py'): + continue + + path = os.path.join(plugin_dir, filename) + if not os.path.isfile(path): + continue + + if filename not in plugins: + plugins[filename] = plugin_dir + + return plugins + + +def load_plugins(plugin_dirs, load=None, ignore=None): + '''Load event manager plugins found in the plugin_dirs.''' + + load = [] if load is None else load + ignore = [] if ignore is None else ignore + + # Find the plugins in the plugin_dirs. + found = find_plugins(plugin_dirs) + + if load: + # Ignore anything not in the load list. + for plugin in found.keys(): + if plugin not in load: + del found[plugin] + + if ignore: + # Ignore anything in the ignore list. + for plugin in found.keys(): + if plugin in ignore: + del found[plugin] + + # Print plugin list to be loaded. + pprint.pprint(found) + + loaded = {} + # Load all found plugins into the loaded dict. + for (filename, plugin_dir) in found.items(): + name = filename[:-3] + info = imp.find_module(name, [plugin_dir]) + plugin = imp.load_module(name, *info) + loaded[(plugin_dir, filename)] = plugin + + return loaded + + +def daemonize(): + '''Daemonize the process using the Stevens' double-fork magic.''' + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #1 failed") + sys.exit(1) + + os.chdir('/') + os.setsid() + os.umask(0) + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #2 failed") + sys.exit(1) + + sys.stdout.flush() + sys.stderr.flush() + + devnull = '/dev/null' + stdin = file(devnull, 'r') + stdout = file(devnull, 'a+') + stderr = file(devnull, 'a+', 0) + + os.dup2(stdin.fileno(), sys.stdin.fileno()) + os.dup2(stdout.fileno(), sys.stdout.fileno()) + os.dup2(stderr.fileno(), sys.stderr.fileno()) + + +def make_dirs(path): + '''Make all basedirs recursively as required.''' + + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + +def make_pid_file(pid_file): + '''Make pid file at given pid_file location.''' + + make_dirs(pid_file) + fileobj = open(pid_file, 'w') + fileobj.write('%d' % os.getpid()) + fileobj.close() + + +def del_pid_file(pid_file): + '''Delete pid file at given pid_file location.''' + + if os.path.isfile(pid_file): + os.remove(pid_file) + + +def get_pid(pid_file): + '''Read pid from pid_file.''' + + try: + fileobj = open(pid_file, 'r') + pid = int(fileobj.read()) + fileobj.close() + return pid + + except IOError, ValueError: + print_exc() + return None + + +def pid_running(pid): + '''Returns True if a process with the given pid is running.''' + + try: + os.kill(pid, 0) + + except OSError: + return False + + else: + return True + + +def term_process(pid): + '''Send a SIGTERM signal to the process with the given pid.''' + + if not pid_running(pid): + return False + + os.kill(pid, SIGTERM) + + start = time.time() + while True: + if not pid_running(pid): + return True + + if time.time() - start > 5: + raise OSError('failed to stop process with pid: %d' % pid) + + time.sleep(0.25) + + +def parse_msg(uzbl, msg): + '''Parse an incoming msg from a uzbl instance. All non-event messages + will be printed here and not be passed to the uzbl instance event + handler function.''' + + if not msg: + return + + cmd = FINDSPACES.split(msg, 3) + if not cmd or cmd[0] != 'EVENT': + # Not an event message. + print '---', msg + return + + while len(cmd) < 4: + cmd.append('') + + event, args = cmd[2], cmd[3] + if not event: + return + + try: + uzbl.event(event, args) + + except: + print_exc() + + +class EventHandler(object): + + nexthid = counter().next + + def __init__(self, event, handler, *args, **kargs): + if not callable(handler): + raise ArgumentError("EventHandler object requires a callable " + "object function for the handler argument not: %r" % handler) + + self.function = handler + self.args = args + self.kargs = kargs + self.event = event + self.hid = self.nexthid() + + + def __repr__(self): + args = ["event=%s" % self.event, "hid=%d" % self.hid, + "function=%r" % self.function] + + if self.args: + args.append("args=%r" % self.args) + + if self.kargs: + args.append("kargs=%r" % self.kargs) + + return "" % ', '.join(args) + + +class UzblInstance(object): + + # Give all plugins access to the main config dict. + config = CONFIG + + def __init__(self, parent, client_socket): + + # Internal variables. + self.exports = {} + self.handlers = {} + self.parent = parent + self.client_socket = client_socket + + self.depth = 0 + self.buffer = '' + self.pid = None + + # Call the init function in every plugin. The init function in each + # plugin is where that plugin connects functions to events and exports + # functions to the uzbl object. + for plugin in self.parent['plugins'].values(): + try: + plugin.init(self) + + except: + raise + + + def send(self, msg): + '''Send a command to the uzbl instance via the socket file.''' + + msg = msg.strip() + if self.client_socket: + print '%s<-- %s' % (' ' * self.depth, msg) + self.client_socket.send(("%s\n" % msg).encode('utf-8')) + + else: + print '%s!-- %s' % (' ' * self.depth, msg) + + + def export(self, name, function): + '''Export `function(uzbl, *args, ..)` inside a plugin to the uzbl + object like so `uzbl.function(*args, ..)`. This will allow other + plugins to call functions inside the current plugin (which is currently + calling this function) via the uzbl object.''' + + self.__dict__.__setitem__(name, partial(function, self)) + + + def export_dict(self, export_dict): + '''Export multiple (name, function)'s at once inside a dict of the + form `{name1: function1, name2: function2, ...}`.''' + + for (name, function) in export_dict.items(): + self.export(name, function) + + + def connect(self, event, handler, *args, **kargs): + '''Connect a uzbl event with a handler. Handlers can either be a + function or a uzbl command string.''' + + event = event.upper().strip() + assert event and ' ' not in event + + if event not in self.handlers.keys(): + self.handlers[event] = [] + + handlerobj = EventHandler(event, handler, *args, **kargs) + self.handlers[event].append(handlerobj) + print handlerobj + + + def connect_dict(self, connect_dict): + '''Connect a dictionary comprising of {"EVENT_NAME": handler, ..} to + the event handler stack. + + If you need to supply args or kargs to an event use the normal connect + function.''' + + for (event, handler) in connect_dict.items(): + self.connect(event, handler) + + + def remove_by_id(self, hid): + '''Remove connected event handler by unique handler id.''' + + for (event, handlers) in self.handlers.items(): + for handler in list(handlers): + if hid != handler.hid: + continue + + echo("removed %r" % handler) + handlers.remove(handler) + return + + echo('unable to find & remove handler with id: %d' % hid) + + + def remove(self, handler): + '''Remove connected event handler.''' + + for (event, handlers) in self.handlers.items(): + if handler in handlers: + echo("removed %r" % handler) + handlers.remove(handler) + return + + echo('unable to find & remove handler: %r' % handler) + + + def exec_handler(self, handler, *args, **kargs): + '''Execute event handler function.''' + + args += handler.args + kargs = dict(handler.kargs.items()+kargs.items()) + handler.function(self, *args, **kargs) + + + def event(self, event, *args, **kargs): + '''Raise an event.''' + + event = event.upper() + elems = [event,] + if args: elems.append(unicode(args)) + if kargs: elems.append(unicode(kargs)) + print "%s--> %s" % (' ' * self.depth, ' '.join(elems)) + + if event == "INSTANCE_START" and args: + self.pid = int(args[0]) + + if event not in self.handlers: + return + + for handler in self.handlers[event]: + self.depth += 1 + try: + self.exec_handler(handler, *args, **kargs) + + except: + print_exc() + + self.depth -= 1 + + + def close(self): + '''Close the client socket and clean up.''' + + try: + self.client_socket.close() + + except: + pass + + for (name, plugin) in self.parent['plugins'].items(): + if hasattr(plugin, 'cleanup'): + plugin.cleanup(self) + + +class UzblEventDaemon(dict): + def __init__(self): + + # Init variables and dict keys. + dict.__init__(self, {'uzbls': {}}) + self.running = None + self.server_socket = None + self.socket_location = None + + # Register that the event daemon server has started by creating the + # pid file. + make_pid_file(CONFIG['pid_file']) + + # Register a function to clean up the socket and pid file on exit. + atexit.register(self.quit) + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) + + # Load plugins, first-build of the plugins may be a costly operation. + self['plugins'] = load_plugins(CONFIG['plugin_dirs'], + CONFIG['plugins_load'], CONFIG['plugins_ignore']) + + + def _create_server_socket(self): + '''Create the event manager daemon socket for uzbl instance duplex + communication.''' + + server_socket = CONFIG['server_socket'] + server_socket = os.path.realpath(os.path.expandvars(server_socket)) + self.socket_location = server_socket + + # Delete socket if it exists. + if os.path.exists(server_socket): + os.remove(server_socket) + + self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.server_socket.bind(server_socket) + self.server_socket.listen(5) + + + def _close_server_socket(self): + '''Close and delete the server socket.''' + + try: + self.server_socket.close() + self.server_socket = None + + if os.path.exists(self.socket_location): + os.remove(self.socket_location) + + except: + pass + + + def run(self): + '''Main event daemon loop.''' + + # Create event daemon socket. + self._create_server_socket() + echo('listening on: %s' % self.socket_location) + + if CONFIG['daemon_mode']: + echo('entering daemon mode.') + daemonize() + # The pid has changed so update the pid file. + make_pid_file(CONFIG['pid_file']) + + # Now listen for incoming connections and or data. + self.listen() + + # Clean up. + self.quit() + + + def listen(self): + '''Accept incoming connections and constantly poll instance sockets + for incoming data.''' + + self.running = True + while self.running: + + sockets = [self.server_socket] + self['uzbls'].keys() + + reads, _, errors = select(sockets, [], sockets, 1) + + if self.server_socket in reads: + self.accept_connection() + reads.remove(self.server_socket) + + for client in reads: + self.read_socket(client) + + for client in errors: + error('Unknown error on socket: %r' % client) + self.close_connection(client) + + + def read_socket(self, client): + '''Read data from an instance socket and pass to the uzbl objects + event handler function.''' + + uzbl = self['uzbls'][client] + try: + raw = unicode(client.recv(8192), 'utf-8', 'ignore') + + except: + print_exc() + raw = None + + if not raw: + # Read null byte, close socket. + return self.close_connection(client) + + uzbl.buffer += raw + msgs = uzbl.buffer.split('\n') + uzbl.buffer = msgs.pop() + + for msg in msgs: + try: + parse_msg(uzbl, msg.strip()) + + except: + print_exc() + + + def accept_connection(self): + '''Accept incoming connection to the server socket.''' + + client_socket = self.server_socket.accept()[0] + + uzbl = UzblInstance(self, client_socket) + self['uzbls'][client_socket] = uzbl + + + def close_connection(self, client): + '''Clean up after instance close.''' + + try: + if client in self['uzbls']: + uzbl = self['uzbls'][client] + uzbl.close() + del self['uzbls'][client] + + except: + print_exc() + + if not len(self['uzbls']) and CONFIG['auto_close']: + echo('auto closing event manager.') + self.running = False + + + def quit(self): + '''Close all instance socket objects, server socket and delete the + pid file.''' + + echo('shutting down event manager.') + + for client in self['uzbls'].keys(): + self.close_connection(client) + + echo('unlinking: %r' % self.socket_location) + self._close_server_socket() + + echo('deleting pid file: %r' % CONFIG['pid_file']) + del_pid_file(CONFIG['pid_file']) + + +def stop_action(): + '''Stop the event manager daemon.''' + + pid_file = CONFIG['pid_file'] + if not os.path.isfile(pid_file): + return echo('no running daemon found.') + + echo('found pid file: %r' % pid_file) + pid = get_pid(pid_file) + if not pid_running(pid): + echo('no process with pid: %d' % pid) + return os.remove(pid_file) + + echo("terminating process with pid: %d" % pid) + term_process(pid) + if os.path.isfile(pid_file): + os.remove(pid_file) + + echo('stopped event daemon.') + + +def start_action(): + '''Start the event manager daemon.''' + + pid_file = CONFIG['pid_file'] + if os.path.isfile(pid_file): + echo('found pid file: %r' % pid_file) + pid = get_pid(pid_file) + if pid_running(pid): + return echo('event daemon already started with pid: %d' % pid) + + echo('no process with pid: %d' % pid) + os.remove(pid_file) + + echo('starting event manager.') + UzblEventDaemon().run() + + +def restart_action(): + '''Restart the event manager daemon.''' + + echo('restarting event manager daemon.') + stop_action() + start_action() + + +def list_action(): + '''List all the plugins being loaded by the event daemon.''' + + plugins = find_plugins(CONFIG['plugin_dirs']) + dirs = {} + + for (plugin, plugin_dir) in plugins.items(): + if plugin_dir not in dirs: + dirs[plugin_dir] = [] + + dirs[plugin_dir].append(plugin) + + for (index, (plugin_dir, plugin_list)) in enumerate(sorted(dirs.items())): + if index: + print + + print "%s:" % plugin_dir + for plugin in sorted(plugin_list): + print " %s" % plugin + + +if __name__ == "__main__": + USAGE = "usage: %prog [options] {start|stop|restart|list}" + PARSER = OptionParser(usage=USAGE) + PARSER.add_option('-v', '--verbose', dest='verbose', action="store_true", + help="print verbose output.") + + PARSER.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store", + metavar="DIRS", help="Specify plugin directories in the form of "\ + "'dir1:dir2:dir3'.") + + PARSER.add_option('-l', '--load-plugins', dest="load", action="store", + metavar="PLUGINS", help="comma separated list of plugins to load") + + PARSER.add_option('-i', '--ignore-plugins', dest="ignore", action="store", + metavar="PLUGINS", help="comma separated list of plugins to ignore") + + PARSER.add_option('-p', '--pid-file', dest='pid', action='store', + metavar='FILE', help="specify pid file location") + + PARSER.add_option('-s', '--server-socket', dest='socket', action='store', + metavar='SOCKET', help="specify the daemon socket location") + + PARSER.add_option('-n', '--no-daemon', dest="daemon", + action="store_true", help="don't enter daemon mode.") + + PARSER.add_option('-a', '--auto-close', dest='autoclose', + action='store_true', help='auto close after all instances disconnect.') + + (OPTIONS, ARGS) = PARSER.parse_args() + + # init like {start|stop|..} daemon actions dict. + DAEMON_ACTIONS = {'start': start_action, 'stop': stop_action, + 'restart': restart_action, 'list': list_action} + + if not ARGS: + ACTION = 'start' + + elif len(ARGS) == 1: + ACTION = ARGS[0] + if ACTION not in DAEMON_ACTIONS: + raise ArgumentError("unknown argument: %r" % ACTION) + + else: + raise ArgumentError("too many arguments: %r" % ARGS) + + # parse other flags & options. + if OPTIONS.verbose: + CONFIG['verbose'] = True + + if OPTIONS.plugin_dirs: + PLUGIN_DIRS = [] + for DIR in OPTIONS.plugin_dirs.split(':'): + if not DIR: + continue + + PLUGIN_DIRS.append(os.path.realpath(DIR)) + + CONFIG['plugin_dirs'] = PLUGIN_DIRS + echo("plugin search dirs: %r" % PLUGIN_DIRS) + + if OPTIONS.load and OPTIONS.ignore: + error("you can't load and ignore at the same time.") + sys.exit(1) + + elif OPTIONS.load: + LOAD = CONFIG['plugins_load'] + for PLUGIN in OPTIONS.load.split(','): + if PLUGIN.strip(): + LOAD.append(PLUGIN.strip()) + + echo('only loading plugin(s): %s' % ', '.join(LOAD)) + + elif OPTIONS.ignore: + IGNORE = CONFIG['plugins_ignore'] + for PLUGIN in OPTIONS.ignore.split(','): + if PLUGIN.strip(): + IGNORE.append(PLUGIN.strip()) + + echo('ignoring plugin(s): %s' % ', '.join(IGNORE)) + + if OPTIONS.autoclose: + CONFIG['auto_close'] = True + echo('will auto close.') + + if OPTIONS.pid: + CONFIG['pid_file'] = os.path.realpath(OPTIONS.pid) + echo("pid file location: %r" % CONFIG['pid_file']) + + if OPTIONS.socket: + CONFIG['server_socket'] = os.path.realpath(OPTIONS.socket) + echo("daemon socket location: %s" % CONFIG['server_socket']) + + if OPTIONS.daemon: + CONFIG['daemon_mode'] = False + + # Now {start|stop|...} + DAEMON_ACTIONS[ACTION]() diff --git a/examples/data/scripts/uzbl-tabbed b/examples/data/scripts/uzbl-tabbed new file mode 100755 index 0000000..7bd90d5 --- /dev/null +++ b/examples/data/scripts/uzbl-tabbed @@ -0,0 +1,1417 @@ +#!/usr/bin/env python + +# Uzbl tabbing wrapper using a fifo socket interface +# Copyright (c) 2009, Tom Adams +# Copyright (c) 2009, Chris van Dijk +# Copyright (c) 2009, Mason Larobina +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# Author(s): +# Tom Adams +# Wrote the original uzbl_tabbed.py as a proof of concept. +# +# Chris van Dijk (quigybo) +# Made signifigant headway on the old uzbl_tabbing.py script on the +# uzbl wiki +# +# Mason Larobina +# Rewrite of the uzbl_tabbing.py script to use a fifo socket interface +# and inherit configuration options from the user's uzbl config. +# +# Contributor(s): +# mxey +# uzbl_config path now honors XDG_CONFIG_HOME if it exists. +# +# Romain Bignon +# Fix for session restoration code. +# +# Jake Probst +# Wrote a patch that overflows tabs in the tablist on to new lines when +# running of room. +# +# Devon Jones +# Fifo command bring_to_front which brings the gtk window to focus. +# +# Simon Lipp (sloonz) +# Various + + +# Dependencies: +# pygtk - python bindings for gtk. +# pango - python bindings needed for text rendering & layout in gtk widgets. +# pygobject - GLib's GObject bindings for python. +# +# Optional dependencies: +# simplejson - save uzbl_tabbed.py sessions & presets in json. +# +# Note: I haven't included version numbers with this dependency list because +# I've only ever tested uzbl_tabbed.py on the latest stable versions of these +# packages in Gentoo's portage. Package names may vary on different systems. + + +# Configuration: +# Because this version of uzbl_tabbed is able to inherit options from your main +# uzbl configuration file you may wish to configure uzbl tabbed from there. +# Here is a list of configuration options that can be customised and some +# example values for each: +# +# General tabbing options: +# show_tablist = 1 +# show_gtk_tabs = 0 +# tablist_top = 1 +# gtk_tab_pos = (top|left|bottom|right) +# gtk_refresh = 1000 +# switch_to_new_tabs = 1 +# capture_new_windows = 1 +# multiline_tabs = 1 +# +# Tab title options: +# tab_titles = 1 +# tab_indexes = 1 +# new_tab_title = Loading +# max_title_len = 50 +# show_ellipsis = 1 +# +# Session options: +# save_session = 1 +# json_session = 0 +# session_file = $HOME/.local/share/uzbl/session +# +# Inherited uzbl options: +# fifo_dir = /tmp +# socket_dir = /tmp +# icon_path = $HOME/.local/share/uzbl/uzbl.png +# status_background = #303030 +# +# Misc options: +# window_size = 800,800 +# verbose = 0 +# +# And uzbl_tabbed.py takes care of the actual binding of the commands via each +# instances fifo socket. +# +# Custom tab styling: +# tab_colours = foreground = "#888" background = "#303030" +# tab_text_colours = foreground = "#bbb" +# selected_tab = foreground = "#fff" +# selected_tab_text = foreground = "green" +# tab_indicate_https = 1 +# https_colours = foreground = "#888" +# https_text_colours = foreground = "#9c8e2d" +# selected_https = foreground = "#fff" +# selected_https_text = foreground = "gold" +# +# How these styling values are used are soley defined by the syling policy +# handler below (the function in the config section). So you can for example +# turn the tab text colour Firetruck-Red in the event "error" appears in the +# tab title or some other arbitrary event. You may wish to make a trusted +# hosts file and turn tab titles of tabs visiting trusted hosts purple. + + +# Issues: +# - new windows are not caught and opened in a new tab. +# - when uzbl_tabbed.py crashes it takes all the children with it. +# - when a new tab is opened when using gtk tabs the tab button itself +# grabs focus from its child for a few seconds. +# - when switch_to_new_tabs is not selected the notebook page is +# maintained but the new window grabs focus (try as I might to stop it). + + +# Todo: +# - add command line options to use a different session file, not use a +# session file and or open a uri on starup. +# - ellipsize individual tab titles when the tab-list becomes over-crowded +# - add "<" & ">" arrows to tablist to indicate that only a subset of the +# currently open tabs are being displayed on the tablist. +# - add the small tab-list display when both gtk tabs and text vim-like +# tablist are hidden (I.e. [ 1 2 3 4 5 ]) +# - check spelling. +# - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into +# the collective. Resistance is futile! + + +import pygtk +import gtk +import subprocess +import os +import re +import time +import getopt +import pango +import select +import sys +import gobject +import socket +import random +import hashlib +import atexit +import types + +from gobject import io_add_watch, source_remove, timeout_add, IO_IN, IO_HUP +from signal import signal, SIGTERM, SIGINT +from optparse import OptionParser, OptionGroup + + +pygtk.require('2.0') + +_SCRIPTNAME = os.path.basename(sys.argv[0]) +def error(msg): + sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return os.path.join(os.environ['HOME'], default) + +# Setup xdg paths. +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') + +# Ensure uzbl xdg paths exist +if not os.path.exists(DATA_DIR): + os.makedirs(DATA_DIR) + +# All of these settings can be inherited from your uzbl config file. +config = { + # Tab options + 'show_tablist': True, # Show text uzbl like statusbar tab-list + 'show_gtk_tabs': False, # Show gtk notebook tabs + 'tablist_top': True, # Display tab-list at top of window + 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) + 'gtk_refresh': 1000, # Tablist refresh millisecond interval + 'switch_to_new_tabs': True, # Upon opening a new tab switch to it + 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows + 'multiline_tabs': True, # Tabs overflow onto new tablist lines. + + # Tab title options + 'tab_titles': True, # Display tab titles (else only tab-nums) + 'tab_indexes': True, # Display tab nums (else only tab titles) + 'new_tab_title': 'Loading', # New tab title + 'max_title_len': 50, # Truncate title at n characters + 'show_ellipsis': True, # Show ellipsis when truncating titles + + # Session options + 'save_session': True, # Save session in file when quit + 'json_session': False, # Use json to save session. + 'saved_sessions_dir': os.path.join(DATA_DIR, 'sessions/'), + 'session_file': os.path.join(DATA_DIR, 'session'), + + # Inherited uzbl options + 'fifo_dir': '/tmp', # Path to look for uzbl fifo. + 'socket_dir': '/tmp', # Path to look for uzbl socket. + 'icon_path': os.path.join(DATA_DIR, 'uzbl.png'), + 'status_background': "#303030", # Default background for all panels. + + # Misc options + 'window_size': "800,800", # width,height in pixels. + 'verbose': False, # Print verbose output. + + # Add custom tab style definitions to be used by the tab colour policy + # handler here. Because these are added to the config dictionary like + # any other uzbl_tabbed configuration option remember that they can + # be superseeded from your main uzbl config file. + 'tab_colours': 'foreground = "#888" background = "#303030"', + 'tab_text_colours': 'foreground = "#bbb"', + 'selected_tab': 'foreground = "#fff"', + 'selected_tab_text': 'foreground = "green"', + 'tab_indicate_https': True, + 'https_colours': 'foreground = "#888"', + 'https_text_colours': 'foreground = "#9c8e2d"', + 'selected_https': 'foreground = "#fff"', + 'selected_https_text': 'foreground = "gold"', + +} # End of config dict. + +UZBL_TABBED_VARS = config.keys() + +# This is the tab style policy handler. Every time the tablist is updated +# this function is called to determine how to colourise that specific tab +# according the simple/complex rules as defined here. You may even wish to +# move this function into another python script and import it using: +# from mycustomtabbingconfig import colour_selector +# Remember to rename, delete or comment out this function if you do that. + +def colour_selector(tabindex, currentpage, uzbl): + '''Tablist styling policy handler. This function must return a tuple of + the form (tab style, text style).''' + + # Just as an example: + # if 'error' in uzbl.title: + # if tabindex == currentpage: + # return ('foreground="#fff"', 'foreground="red"') + # return ('foreground="#888"', 'foreground="red"') + + # Style tabs to indicate connected via https. + if config['tab_indicate_https'] and uzbl.uri.startswith("https://"): + if tabindex == currentpage: + return (config['selected_https'], config['selected_https_text']) + return (config['https_colours'], config['https_text_colours']) + + # Style to indicate selected. + if tabindex == currentpage: + return (config['selected_tab'], config['selected_tab_text']) + + # Default tab style. + return (config['tab_colours'], config['tab_text_colours']) + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def echo(msg): + if config['verbose']: + sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) + + +def counter(): + '''To infinity and beyond!''' + + i = 0 + while True: + i += 1 + yield i + + +def escape(s): + '''Replaces html markup in tab titles that screw around with pango.''' + + for (split, glue) in [('&','&'), ('<', '<'), ('>', '>')]: + s = s.replace(split, glue) + return s + + +class SocketClient: + '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance''' + + # List of UzblInstance objects not already linked with a SocketClient + instances_queue = {} + + def __init__(self, socket): + self._buffer = "" + self._socket = socket + self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\ + io_add_watch(socket, IO_HUP, self._socket_closed)] + self.uzbl = None + + + def _socket_recv(self, fd, condition): + '''Data available on socket, process it''' + + self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ? + return True + + + def _socket_closed(self, fd, condition): + '''Remote client exited''' + self.uzbl.close() + return False + + + def _feed(self, data): + '''An Uzbl instance sent some data, parse it''' + + self._buffer += data + if self.uzbl: + if "\n" in self._buffer: + cmds = self._buffer.split("\n") + + if cmds[-1]: # Last command has been received incomplete, don't process it + self._buffer, cmds = cmds[-1], cmds[:-1] + else: + self._buffer = "" + + for cmd in cmds: + if cmd: + self.uzbl.parse_command(cmd) + else: + name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M) + uzbl = self.instances_queue.get(name[0]) + if uzbl: + del self.instances_queue[name[0]] + self.uzbl = uzbl + self.uzbl.got_socket(self) + self._feed("") + + def send(self, data): + '''Child socket send function.''' + + self._socket.send(data + "\n") + + def close(self): + '''Close the connection''' + + if self._socket: + self._socket.close() + self._socket = None + map(source_remove, self._watchers) + self._watchers = [] + + +class UzblInstance: + '''Uzbl instance meta-data/meta-action object.''' + + def __init__(self, parent, tab, name, uri, title, switch): + + self.parent = parent + self.tab = tab + self.name = name + self.title = title + self.tabtitle = "" + self.uri = uri + self._client = None + self._switch = switch # Switch to tab after loading ? + self.title_changed() + + + def got_socket(self, client): + '''Uzbl instance is now connected''' + + self._client = client + self.parent.config_uzbl(self) + if self._switch: + tabid = self.parent.notebook.page_num(self.tab) + self.parent.goto_tab(tabid) + + + def title_changed(self, gtk_only = True): # GTK-only is for indexes + '''self.title has changed, update the tabs list''' + + tab_titles = config['tab_titles'] + tab_indexes = config['tab_indexes'] + show_ellipsis = config['show_ellipsis'] + max_title_len = config['max_title_len'] + + # Unicode heavy strings do not like being truncated/sliced so by + # re-encoding the string sliced of limbs are removed. + self.tabtitle = self.title[:max_title_len + int(show_ellipsis)] + if type(self.tabtitle) != types.UnicodeType: + self.tabtitle = unicode(self.tabtitle, 'utf-8', 'ignore') + + self.tabtitle = self.tabtitle.encode('utf-8', 'ignore').strip() + + if show_ellipsis and len(self.tabtitle) != len(self.title): + self.tabtitle += "\xe2\x80\xa6" + + gtk_tab_format = "%d %s" + index = self.parent.notebook.page_num(self.tab) + if tab_titles and tab_indexes: + self.parent.notebook.set_tab_label_text(self.tab, + gtk_tab_format % (index, self.tabtitle)) + elif tab_titles: + self.parent.notebook.set_tab_label_text(self.tab, self.tabtitle) + else: + self.parent.notebook.set_tab_label_text(self.tab, str(index)) + + # If instance is current tab, update window title + if index == self.parent.notebook.get_current_page(): + title_format = "%s - Uzbl Browser" + self.parent.window.set_title(title_format % self.title) + + # Non-GTK tabs + if not gtk_only: + self.parent.update_tablist() + + + def set(self, key, val): + ''' Send the SET command to Uzbl ''' + + if self._client: + self._client.send('set %s = %s') #TODO: escape chars ? + + + def exit(self): + ''' Ask the Uzbl instance to close ''' + + if self._client: + self._client.send('exit') + + + def parse_command(self, cmd): + ''' Parse event givent by the Uzbl instance ''' + + type, _, args = cmd.split(" ", 2) + if type == "EVENT": + type, args = args.split(" ", 1) + if type == "TITLE_CHANGED": + self.title = args + self.title_changed() + elif type == "VARIABLE_SET": + var, _, val = args.split(" ", 2) + try: + val = int(val) + except: + pass + + if var in UZBL_TABBED_VARS: + if config[var] != val: + config[var] = val + if var == "show_gtk_tabs": + self.parent.notebook.set_show_tabs(bool(val)) + elif var == "show_tablist" or var == "tablist_top": + self.parent.update_tablist_display() + elif var == "gtk_tab_pos": + self.parent.update_gtk_tab_pos() + elif var == "status_background": + col = gtk.gdk.color_parse(config['status_background']) + self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) + elif var == "tab_titles" or var == "tab_indexes": + for tab in self.parent.notebook: + self.parent.tabs[tab].title_changed(True) + + self.parent.update_tablist() + else: + config[var] = val + + if var == "uri": + self.uri = var + self.parent.update_tablist() + elif type == "NEW_TAB": + self.parent.new_tab(args) + elif type == "NEXT_TAB": + if args: + self.parent.next_tab(int(args)) + else: + self.parent.next_tab() + elif type == "PREV_TAB": + if args: + self.parent.prev_tab(int(args)) + else: + self.parent.prev_tab() + elif type == "GOTO_TAB": + self.parent.goto_tab(int(args)) + elif type == "FIRST_TAB": + self.parent.goto_tab(0) + elif type == "LAST_TAB": + self.parent.goto_tab(-1) + elif type == "PRESET_TABS": + self.parent.parse_command(["preset"] + args.split()) + elif type == "BRING_TO_FRONT": + self.parent.window.present() + elif type == "CLEAN_TABS": + self.parent.clean_slate() + elif type == "EXIT_ALL_TABS": + self.parent.quitrequest() + + + def close(self): + '''The remote instance exited''' + + if self._client: + self._client.close() + self._client = None + + +class UzblTabbed: + '''A tabbed version of uzbl using gtk.Notebook''' + + def __init__(self): + '''Create tablist, window and notebook.''' + + self._timers = {} + self._buffer = "" + self._killed = False + + # A list of the recently closed tabs + self._closed = [] + + # Holds metadata on the uzbl childen open. + self.tabs = {} + + # Uzbl sockets (socket => SocketClient) + self.clients = {} + + # Generates a unique id for uzbl socket filenames. + self.next_pid = counter().next + + # Create main window + self.window = gtk.Window() + try: + window_size = map(int, config['window_size'].split(',')) + self.window.set_default_size(*window_size) + + except: + error("Invalid value for default_size in config file.") + + self.window.set_title("Uzbl Browser") + self.window.set_border_width(0) + + # Set main window icon + icon_path = config['icon_path'] + if os.path.exists(icon_path): + self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) + + else: + icon_path = '/usr/share/uzbl/examples/data/uzbl.png' + if os.path.exists(icon_path): + self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) + + # Attach main window event handlers + self.window.connect("delete-event", self.quitrequest) + + # Create tab list + vbox = gtk.VBox() + self.vbox = vbox + self.window.add(vbox) + ebox = gtk.EventBox() + self.ebox = ebox + self.tablist = gtk.Label() + + self.tablist.set_use_markup(True) + self.tablist.set_justify(gtk.JUSTIFY_LEFT) + self.tablist.set_line_wrap(False) + self.tablist.set_selectable(False) + self.tablist.set_padding(2,2) + self.tablist.set_alignment(0,0) + self.tablist.set_ellipsize(pango.ELLIPSIZE_END) + self.tablist.set_text(" ") + self.tablist.show() + ebox.add(self.tablist) + ebox.show() + bgcolor = gtk.gdk.color_parse(config['status_background']) + ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) + + # Create notebook + self.notebook = gtk.Notebook() + self.notebook.set_show_tabs(config['show_gtk_tabs']) + + # Set tab position + self.update_gtk_tab_pos() + + self.notebook.set_show_border(False) + self.notebook.set_scrollable(True) + self.notebook.set_border_width(0) + + self.notebook.connect("page-removed", self.tab_closed) + self.notebook.connect("switch-page", self.tab_changed) + self.notebook.connect("page-added", self.tab_opened) + + self.notebook.show() + vbox.pack_start(self.notebook, True, True, 0) + vbox.reorder_child(self.notebook, 1) + self.update_tablist_display() + + self.vbox.show() + self.window.show() + self.wid = self.notebook.window.xid + + # Store information about the applications fifo and socket. + fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid() + socket_filename = 'uzbltabbed_%d.socket' % os.getpid() + self._fifo = None + self._socket = None + self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename) + self.socket_path = os.path.join(config['socket_dir'], socket_filename) + + # Now initialise the fifo and the socket + self.init_fifo() + self.init_socket() + + # If we are using sessions then load the last one if it exists. + if config['save_session']: + self.load_session() + + + def run(self): + '''UzblTabbed main function that calls the gtk loop.''' + + if not self.clients and not SocketClient.instances_queue and not self.tabs: + self.new_tab() + + gtk_refresh = int(config['gtk_refresh']) + if gtk_refresh < 100: + gtk_refresh = 100 + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) + + # Catch keyboard interrupts + signal(SIGINT, lambda signum, stack_frame: self.terminate(SIGINT)) + + try: + gtk.main() + + except: + error("encounted error %r" % sys.exc_info()[1]) + + # Unlink fifo socket + self.unlink_fifo() + self.close_socket() + + # Attempt to close all uzbl instances nicely. + self.quitrequest() + + # Allow time for all the uzbl instances to quit. + time.sleep(1) + + raise + + + def terminate(self, termsig=None): + '''Handle termination signals and exit safely and cleanly.''' + + # Not required but at least it lets the user know what killed his + # browsing session. + if termsig == SIGTERM: + error("caught SIGTERM signal") + + elif termsig == SIGINT: + error("caught keyboard interrupt") + + else: + error("caught unknown signal") + + error("commencing infanticide!") + + # Sends the exit signal to all uzbl instances. + self.quitrequest() + + + def init_socket(self): + '''Create interprocess communication socket.''' + + def accept(sock, condition): + '''A new uzbl instance was created''' + + client, _ = sock.accept() + self.clients[client] = SocketClient(client) + + return True + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(self.socket_path) + sock.listen(1) + + # Add event handler for IO_IN event. + self._socket = (sock, io_add_watch(sock, IO_IN, accept)) + + echo("[socket] listening at %r" % self.socket_path) + + # Add atexit register to destroy the socket on program termination. + atexit.register(self.close_socket) + + + def close_socket(self): + '''Close the socket when closing the application''' + + if self._socket: + (fd, watcher) = self._socket + source_remove(watcher) + fd.close() + os.unlink(self.socket_path) + self._socket = None + + + def init_fifo(self): + '''Create interprocess communication fifo.''' + + if os.path.exists(self.fifo_path): + if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK): + os.mkfifo(self.fifo_path) + + else: + basedir = os.path.dirname(self.fifo_path) + if not os.path.exists(basedir): + os.makedirs(basedir) + + os.mkfifo(self.fifo_path) + + # Add event handlers for IO_IN & IO_HUP events. + self.setup_fifo_watchers() + + echo("[fifo] listening at %r" % self.fifo_path) + + # Add atexit register to destroy the fifo on program termination. + atexit.register(self.unlink_fifo) + + + def unlink_fifo(self): + '''Unlink the fifo socket. Note: This function is called automatically + on exit by an atexit register.''' + + # Make sure the fifo fd is closed. + self.close_fifo() + + # And unlink if the real fifo exists. + if os.path.exists(self.fifo_path): + os.unlink(self.fifo_path) + echo("unlinked %r" % self.fifo_path) + + + def close_fifo(self): + '''Remove all event handlers watching the fifo and close the fd.''' + + # Already closed + if self._fifo is None: return + + (fd, watchers) = self._fifo + os.close(fd) + + # Stop all gobject io watchers watching the fifo. + for gid in watchers: + source_remove(gid) + + self._fifo = None + + + def setup_fifo_watchers(self): + '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event + handlers.''' + + # Close currently open fifo fd and kill all watchers + self.close_fifo() + + fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK) + + # Add gobject io event handlers to the fifo socket. + watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\ + io_add_watch(fd, IO_HUP, self.main_fifo_hangup)] + + self._fifo = (fd, watchers) + + + def main_fifo_hangup(self, fd, cb_condition): + '''Handle main fifo socket hangups.''' + + # Close old fd, open new fifo socket and add io event handlers. + self.setup_fifo_watchers() + + # Kill the gobject event handler calling this handler function. + return False + + + def main_fifo_read(self, fd, cb_condition): + '''Read from main fifo socket.''' + + self._buffer = os.read(fd, 1024) + temp = self._buffer.split("\n") + self._buffer = temp.pop() + cmds = [s.strip().split() for s in temp if len(s.strip())] + + for cmd in cmds: + try: + #print cmd + self.parse_command(cmd) + + except: + error("parse_command: invalid command %s" % ' '.join(cmd)) + raise + + return True + + + def parse_command(self, cmd): + '''Parse instructions from uzbl child processes.''' + + # Commands ( [] = optional, {} = required ) + # new [uri] + # open new tab and head to optional uri. + # close [tab-num] + # close current tab or close via tab id. + # next [n-tabs] + # open next tab or n tabs down. Supports negative indexing. + # prev [n-tabs] + # open prev tab or n tabs down. Supports negative indexing. + # goto {tab-n} + # goto tab n. + # first + # goto first tab. + # last + # goto last tab. + # title {pid} {document-title} + # updates tablist title. + # uri {pid} {document-location} + # updates tablist uri + # bring_to_front + # brings the gtk window to focus. + # exit + # exits uzbl_tabbed.py + + if cmd[0] == "new": + if len(cmd) == 2: + self.new_tab(cmd[1]) + + else: + self.new_tab() + + elif cmd[0] == "newfromclip": + uri = subprocess.Popen(['xclip','-selection','clipboard','-o'],\ + stdout=subprocess.PIPE).communicate()[0] + if uri: + self.new_tab(uri) + + elif cmd[0] == "close": + if len(cmd) == 2: + self.close_tab(int(cmd[1])) + + else: + self.close_tab() + + elif cmd[0] == "next": + if len(cmd) == 2: + self.next_tab(int(cmd[1])) + + else: + self.next_tab() + + elif cmd[0] == "prev": + if len(cmd) == 2: + self.prev_tab(int(cmd[1])) + + else: + self.prev_tab() + + elif cmd[0] == "goto": + self.goto_tab(int(cmd[1])) + + elif cmd[0] == "first": + self.goto_tab(0) + + elif cmd[0] == "last": + self.goto_tab(-1) + + elif cmd[0] in ["title", "uri"]: + if len(cmd) > 2: + uzbl = self.get_tab_by_name(int(cmd[1])) + if uzbl: + old = getattr(uzbl, cmd[0]) + new = ' '.join(cmd[2:]) + setattr(uzbl, cmd[0], new) + if old != new: + self.update_tablist() + + else: + error("parse_command: no uzbl with name %r" % int(cmd[1])) + + elif cmd[0] == "preset": + if len(cmd) < 3: + error("parse_command: invalid preset command") + + elif cmd[1] == "save": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + self.save_session(path) + + elif cmd[1] == "load": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + self.load_session(path) + + elif cmd[1] == "del": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + if os.path.isfile(path): + os.remove(path) + + else: + error("parse_command: preset %r does not exist." % path) + + elif cmd[1] == "list": + uzbl = self.get_tab_by_name(int(cmd[2])) + if uzbl: + if not os.path.isdir(config['saved_sessions_dir']): + js = "js alert('No saved presets.');" + uzbl._client.send(js) + + else: + listdir = os.listdir(config['saved_sessions_dir']) + listdir = "\\n".join(listdir) + js = "js alert('Session presets:\\n\\n%s');" % listdir + uzbl._client.send(js) + + else: + error("parse_command: unknown tab name.") + + else: + error("parse_command: unknown parse command %r"\ + % ' '.join(cmd)) + + elif cmd[0] == "bring_to_front": + self.window.present() + + elif cmd[0] == "clean": + self.clean_slate() + + elif cmd[0] == "exit": + self.quitrequest() + + else: + error("parse_command: unknown command %r" % ' '.join(cmd)) + + + def get_tab_by_name(self, name): + '''Return uzbl instance by name.''' + + for (tab, uzbl) in self.tabs.items(): + if uzbl.name == name: + return uzbl + + return False + + + def new_tab(self, uri='', title='', switch=None): + '''Add a new tab to the notebook and start a new instance of uzbl. + Use the switch option to negate config['switch_to_new_tabs'] option + when you need to load multiple tabs at a time (I.e. like when + restoring a session from a file).''' + + tab = gtk.Socket() + tab.show() + self.notebook.append_page(tab) + sid = tab.get_id() + uri = uri.strip() + name = "%d-%d" % (os.getpid(), self.next_pid()) + + if switch is None: + switch = config['switch_to_new_tabs'] + + if not title: + title = config['new_tab_title'] + + cmd = ['uzbl-browser', '-n', name, '-s', str(sid), + '--connect-socket', self.socket_path, '--uri', uri] + subprocess.Popen(cmd) # TODO: do i need close_fds=True ? + + uzbl = UzblInstance(self, tab, name, uri, title, switch) + SocketClient.instances_queue[name] = uzbl + self.tabs[tab] = uzbl + + + def clean_slate(self): + '''Close all open tabs and open a fresh brand new one.''' + + self.new_tab() + tabs = self.tabs.keys() + for tab in list(self.notebook)[:-1]: + if tab not in tabs: continue + uzbl = self.tabs[tab] + uzbl.exit() + + + def config_uzbl(self, uzbl): + '''Send bind commands for tab new/close/next/prev to a uzbl + instance.''' + + # Set definitions here + # set(key, command back to fifo) + if config['capture_new_windows']: + uzbl.set("new_window", r'new $8') + + + def goto_tab(self, index): + '''Goto tab n (supports negative indexing).''' + + title_format = "%s - Uzbl Browser" + + tabs = list(self.notebook) + if 0 <= index < len(tabs): + self.notebook.set_current_page(index) + uzbl = self.tabs[self.notebook.get_nth_page(index)] + self.window.set_title(title_format % uzbl.title) + self.update_tablist() + return None + + try: + tab = tabs[index] + # Update index because index might have previously been a + # negative index. + index = tabs.index(tab) + self.notebook.set_current_page(index) + uzbl = self.tabs[self.notebook.get_nth_page(index)] + self.window.set_title(title_format % uzbl.title) + self.update_tablist() + + except IndexError: + pass + + + def next_tab(self, step=1): + '''Switch to next tab or n tabs right.''' + + if step < 1: + error("next_tab: invalid step %r" % step) + return None + + ntabs = self.notebook.get_n_pages() + tabn = (self.notebook.get_current_page() + step) % ntabs + self.goto_tab(tabn) + + + def prev_tab(self, step=1): + '''Switch to prev tab or n tabs left.''' + + if step < 1: + error("prev_tab: invalid step %r" % step) + return None + + ntabs = self.notebook.get_n_pages() + tabn = self.notebook.get_current_page() - step + while tabn < 0: tabn += ntabs + self.goto_tab(tabn) + + + def close_tab(self, tabn=None): + '''Closes current tab. Supports negative indexing.''' + + if tabn is None: + tabn = self.notebook.get_current_page() + + else: + try: + tab = list(self.notebook)[tabn] + + except IndexError: + error("close_tab: invalid index %r" % tabn) + return None + + self.notebook.remove_page(tabn) + + + def tab_opened(self, notebook, tab, index): + '''Called upon tab creation. Called by page-added signal.''' + + if config['switch_to_new_tabs']: + self.notebook.set_focus_child(tab) + + else: + oldindex = self.notebook.get_current_page() + oldtab = self.notebook.get_nth_page(oldindex) + self.notebook.set_focus_child(oldtab) + + + def tab_closed(self, notebook, tab, index): + '''Close the window if no tabs are left. Called by page-removed + signal.''' + + if tab in self.tabs.keys(): + uzbl = self.tabs[tab] + uzbl.close() + + self._closed.append((uzbl.uri, uzbl.title)) + self._closed = self._closed[-10:] + del self.tabs[tab] + + if self.notebook.get_n_pages() == 0: + if not self._killed and config['save_session']: + if os.path.exists(config['session_file']): + os.remove(config['session_file']) + + self.quit() + + for tab in self.notebook: + self.tabs[tab].title_changed(True) + self.update_tablist() + + return True + + + def tab_changed(self, notebook, page, index): + '''Refresh tab list. Called by switch-page signal.''' + + tab = self.notebook.get_nth_page(index) + self.notebook.set_focus_child(tab) + self.update_tablist(index) + return True + + + def update_tablist_display(self): + '''Called when show_tablist or tablist_top has changed''' + + if self.ebox in self.vbox.get_children(): + self.vbox.remove(self.ebox) + + if config['show_tablist']: + self.vbox.pack_start(self.ebox, False, False, 0) + if config['tablist_top']: + self.vbox.reorder_child(self.ebox, 0) + else: + self.vbox.reorder_child(self.ebox, 2) + + def update_gtk_tab_pos(self): + ''' Called when gtk_tab_pos has changed ''' + + allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, + 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} + if config['gtk_tab_pos'] in allposes.keys(): + self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) + + + def update_tablist(self, curpage=None): + '''Upate tablist status bar.''' + + if not config['show_tablist']: + return True + + tab_titles = config['tab_titles'] + tab_indexes = config['tab_indexes'] + multiline_tabs = config['multiline_tabs'] + + if multiline_tabs: + multiline = [] + + tabs = self.tabs.keys() + if curpage is None: + curpage = self.notebook.get_current_page() + + pango = "" + normal = (config['tab_colours'], config['tab_text_colours']) + selected = (config['selected_tab'], config['selected_tab_text']) + + if tab_titles and tab_indexes: + tab_format = " [ %(index)d %(title)s ] " + elif tab_titles: + tab_format = " [ %(title)s ] " + else: + tab_format = " [ %(index)d ] " + + for index, tab in enumerate(self.notebook): + if tab not in tabs: continue + uzbl = self.tabs[tab] + title = escape(uzbl.tabtitle) + + style = colour_selector(index, curpage, uzbl) + (tabc, textc) = style + + if multiline_tabs: + opango = pango + + pango += tab_format % locals() + + self.tablist.set_markup(pango) + listwidth = self.tablist.get_layout().get_pixel_size()[0] + winwidth = self.window.get_size()[0] + + if listwidth > (winwidth - 20): + multiline.append(opango) + pango = tab_format % locals() + else: + pango += tab_format % locals() + + if multiline_tabs: + multiline.append(pango) + self.tablist.set_markup(' '.join(multiline)) + + else: + self.tablist.set_markup(pango) + + return True + + + def save_session(self, session_file=None): + '''Save the current session to file for restoration on next load.''' + + strip = str.strip + + if session_file is None: + session_file = config['session_file'] + + tabs = self.tabs.keys() + state = [] + for tab in list(self.notebook): + if tab not in tabs: continue + uzbl = self.tabs[tab] + if not uzbl.uri: continue + state += [(uzbl.uri, uzbl.title),] + + session = {'curtab': self.notebook.get_current_page(), + 'tabs': state} + + if config['json_session']: + raw = json.dumps(session) + + else: + lines = ["curtab = %d" % session['curtab'],] + for (uri, title) in session['tabs']: + lines += ["%s\t%s" % (strip(uri), strip(title)),] + + raw = "\n".join(lines) + + if not os.path.isfile(session_file): + dirname = os.path.dirname(session_file) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + h = open(session_file, 'w') + h.write(raw) + h.close() + + + def load_session(self, session_file=None): + '''Load a saved session from file.''' + + default_path = False + strip = str.strip + json_session = config['json_session'] + delete_loaded = False + + if session_file is None: + default_path = True + delete_loaded = True + session_file = config['session_file'] + + if not os.path.isfile(session_file): + return False + + h = open(session_file, 'r') + raw = h.read() + h.close() + if json_session: + if sum([1 for s in raw.split("\n") if strip(s)]) != 1: + error("Warning: The session file %r does not look json. "\ + "Trying to load it as a non-json session file."\ + % session_file) + json_session = False + + if json_session: + try: + session = json.loads(raw) + curtab, tabs = session['curtab'], session['tabs'] + + except: + error("Failed to load jsonifed session from %r"\ + % session_file) + return None + + else: + tabs = [] + strip = str.strip + curtab, tabs = 0, [] + lines = [s for s in raw.split("\n") if strip(s)] + if len(lines) < 2: + error("Warning: The non-json session file %r looks invalid."\ + % session_file) + return None + + try: + for line in lines: + if line.startswith("curtab"): + curtab = int(line.split()[-1]) + + else: + uri, title = line.split("\t",1) + tabs += [(strip(uri), strip(title)),] + + except: + error("Warning: failed to load session file %r" % session_file) + return None + + session = {'curtab': curtab, 'tabs': tabs} + + # Now populate notebook with the loaded session. + for (index, (uri, title)) in enumerate(tabs): + self.new_tab(uri=uri, title=title, switch=(curtab==index)) + + # A saved session has been loaded now delete it. + if delete_loaded and os.path.exists(session_file): + os.remove(session_file) + + # There may be other state information in the session dict of use to + # other functions. Of course however the non-json session object is + # just a dummy object of no use to no one. + return session + + + def quitrequest(self, *args): + '''Attempt to close all uzbl instances nicely and exit.''' + + self._killed = True + + if config['save_session']: + if len(list(self.notebook)) > 1: + self.save_session() + + else: + # Notebook has one page open so delete the session file. + if os.path.isfile(config['session_file']): + os.remove(config['session_file']) + + for (tab, uzbl) in self.tabs.items(): + uzbl.exit() + + # Add a gobject timer to make sure the application force-quits after a + # reasonable period. Calling quit when all the tabs haven't had time to + # close should be a last resort. + timer = "force-quit" + timerid = timeout_add(5000, self.quit, timer) + self._timers[timer] = timerid + + + def quit(self, *args): + '''Cleanup and quit. Called by delete-event signal.''' + + # Close the fifo socket, remove any gobject io event handlers and + # delete socket. + self.unlink_fifo() + self.close_socket() + + # Remove all gobject timers that are still ticking. + for (timerid, gid) in self._timers.items(): + source_remove(gid) + del self._timers[timerid] + + try: + gtk.main_quit() + + except: + pass + + +if __name__ == "__main__": + + # Build command line parser + usage = "usage: %prog [OPTIONS] {URIS}..." + parser = OptionParser(usage=usage) + parser.add_option('-n', '--no-session', dest='nosession', + action='store_true', help="ignore session saving a loading.") + parser.add_option('-v', '--verbose', dest='verbose', + action='store_true', help='print verbose output.') + + # Parse command line options + (options, uris) = parser.parse_args() + + if options.nosession: + config['save_session'] = False + + if options.verbose: + config['verbose'] = True + + if config['json_session']: + try: + import simplejson as json + + except: + error("Warning: json_session set but cannot import the python "\ + "module simplejson. Fix: \"set json_session = 0\" or "\ + "install the simplejson python module to remove this warning.") + config['json_session'] = False + + if config['verbose']: + import pprint + sys.stderr.write("%s\n" % pprint.pformat(config)) + + uzbl = UzblTabbed() + + # All extra arguments given to uzbl_tabbed.py are interpreted as + # web-locations to opened in new tabs. + lasturi = len(uris)-1 + for (index,uri) in enumerate(uris): + uzbl.new_tab(uri, switch=(index==lasturi)) + + uzbl.run() diff --git a/examples/data/scripts/uzblcat b/examples/data/scripts/uzblcat new file mode 100755 index 0000000..e955608 --- /dev/null +++ b/examples/data/scripts/uzblcat @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# uzblcat - safely push html to uzbl +# See http://www.uzbl.org/wiki/html-mode + +from sys import stdin, stdout + +stdout.write("uri data:text/html,") +for line in stdin: + stdout.write(line[0:-1]) + +# vim: set noet ff=unix + diff --git a/examples/data/style.css b/examples/data/style.css new file mode 100644 index 0000000..f9b111e --- /dev/null +++ b/examples/data/style.css @@ -0,0 +1,25 @@ +.uzbl_highlight { background-color: yellow;} +.uzbl_h_first { background-color: lightgreen;} + +.uzbl_follow { border-style: dotted; + border-width: thin; +} + +#uzbl_hint > div { + display: inline; + border: 2px solid #4a6600; + background-color: #b9ff00; + color: black; + font-size: 9px; + font-weight: bold; + line-height: 9px; + margin: 0px; + padding: 0px; + position: absolute; + z-index: 1000; + -webkit-border-radius: 6px; + text-decoration: none; + -wekit-transform: scale(1) rotate(0deg) translate(-6px,-5px); +} + +/* vim:set et ts=4: */ diff --git a/examples/data/uzbl.png b/examples/data/uzbl.png new file mode 100644 index 0000000..773ea84 Binary files /dev/null and b/examples/data/uzbl.png differ diff --git a/examples/data/uzbl/bookmarks b/examples/data/uzbl/bookmarks deleted file mode 100644 index 13fcd48..0000000 --- a/examples/data/uzbl/bookmarks +++ /dev/null @@ -1,4 +0,0 @@ -http://www.archlinux.org linux arch -http://www.uzbl.org uzbl browser -http://dieter.plaetinck.be uzbl -http://www.icanhascheezburger.com lolcats fun diff --git a/examples/data/uzbl/forms/bbs.archlinux.org b/examples/data/uzbl/forms/bbs.archlinux.org deleted file mode 100644 index 73c1539..0000000 --- a/examples/data/uzbl/forms/bbs.archlinux.org +++ /dev/null @@ -1,5 +0,0 @@ -form_sent: -redirect_url: -req_username: -req_password: -login: diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py deleted file mode 100644 index 9e09337..0000000 --- a/examples/data/uzbl/plugins/bind.py +++ /dev/null @@ -1,521 +0,0 @@ -'''Plugin provides support for binds in uzbl. - -For example: - event BIND ZZ = exit -> bind('ZZ', 'exit') - event BIND o _ = uri %s -> bind('o _', 'uri %s') - event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'") - -And it is also possible to execute a function on activation: - bind('DD', myhandler) -''' - -import sys -import re -import pprint - -# Hold the bind dicts for each uzbl instance. -UZBLS = {} - -# Commonly used regular expressions. -MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match -# Matches , <'x':y>, <:'y'>, , <'x'!y>, ... -PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>' -FIND_PROMPTS = re.compile(PROMPTS).split -VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match - -# For accessing a bind glob stack. -ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) - - -# Custom errors. -class ArgumentError(Exception): pass - - -class Bindlet(object): - '''Per-instance bind status/state tracker.''' - - def __init__(self, uzbl): - self.binds = {'global': {}} - self.uzbl = uzbl - self.depth = 0 - self.args = [] - self.last_mode = None - self.after_cmds = None - self.stack_binds = [] - - # A subset of the global mode binds containing non-stack and modkey - # activiated binds for use in the stack mode. - self.globals = [] - - - def __getitem__(self, key): - return self.get_binds(key) - - - def reset(self): - '''Reset the tracker state and return to last mode.''' - - self.depth = 0 - self.args = [] - self.after_cmds = None - self.stack_binds = [] - - if self.last_mode: - mode, self.last_mode = self.last_mode, None - self.uzbl.set_mode(mode) - - self.uzbl.set('keycmd_prompt') - - - def stack(self, bind, args, depth): - '''Enter or add new bind in the next stack level.''' - - if self.depth != depth: - if bind not in self.stack_binds: - self.stack_binds.append(bind) - - return - - current_mode = self.uzbl.get_mode() - if current_mode != 'stack': - self.last_mode = current_mode - self.uzbl.set_mode('stack') - - self.stack_binds = [bind,] - self.args += args - self.depth += 1 - self.after_cmds = bind.prompts[depth] - - - def after(self): - '''If a stack was triggered then set the prompt and default value.''' - - if self.after_cmds is None: - return - - (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None - - self.uzbl.clear_keycmd() - if prompt: - self.uzbl.set('keycmd_prompt', prompt) - - if set and is_cmd: - self.uzbl.send(set) - - elif set and not is_cmd: - self.uzbl.send('event SET_KEYCMD %s' % set) - - - def get_binds(self, mode=None): - '''Return the mode binds + globals. If we are stacked then return - the filtered stack list and modkey & non-stack globals.''' - - if mode is None: - mode = self.uzbl.get_mode() - - if not mode: - mode = 'global' - - if self.depth: - return self.stack_binds + self.globals - - globals = self.binds['global'] - if mode not in self.binds or mode == 'global': - return filter(None, globals.values()) - - binds = dict(globals.items() + self.binds[mode].items()) - return filter(None, binds.values()) - - - def add_bind(self, mode, glob, bind=None): - '''Insert (or override) a bind into the mode bind dict.''' - - if mode not in self.binds: - self.binds[mode] = {glob: bind} - return - - binds = self.binds[mode] - binds[glob] = bind - - if mode == 'global': - # Regen the global-globals list. - self.globals = [] - for bind in binds.values(): - if bind is not None and bind.is_global: - self.globals.append(bind) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = Bindlet(uzbl) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_bindlet(uzbl): - '''Return the bind tracklet for the given uzbl instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def ismodbind(glob): - '''Return True if the glob specifies a modbind.''' - - return bool(MOD_START(glob)) - - -def split_glob(glob): - '''Take a string of the form "cmd _" and return a list of the - modkeys in the glob and the command.''' - - mods = set() - while True: - match = MOD_START(glob) - if not match: - break - - end = match.span()[1] - mods.add(glob[:end]) - glob = glob[end:] - - return (mods, glob) - - -def unquote(str): - '''Remove quotation marks around string.''' - - if str and str[0] == str[-1] and str[0] in ['"', "'"]: - str = str[1:-1] - - return str - - -class Bind(object): - - # Class attribute to hold the number of Bind classes created. - counter = [0,] - - def __init__(self, glob, handler, *args, **kargs): - self.is_callable = callable(handler) - self._repr_cache = None - - if not glob: - raise ArgumentError('glob cannot be blank') - - if self.is_callable: - self.function = handler - self.args = args - self.kargs = kargs - - elif kargs: - raise ArgumentError('cannot supply kargs for uzbl commands') - - elif hasattr(handler, '__iter__'): - self.commands = handler - - else: - self.commands = [handler,] + list(args) - - self.glob = glob - - # Assign unique id. - self.counter[0] += 1 - self.bid = self.counter[0] - - self.split = split = FIND_PROMPTS(glob) - self.prompts = [] - for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]): - prompt, set = map(unquote, [prompt, set]) - cmd = True if cmd == '!' else False - if prompt and prompt[-1] != ":": - prompt = "%s:" % prompt - - self.prompts.append((prompt, cmd, set)) - - # Check that there is nothing like: fl** - for glob in split[:-1:4]: - if glob.endswith('*'): - msg = "token '*' not at the end of a prompt bind: %r" % split - raise SyntaxError(msg) - - # Check that there is nothing like: fl_ - for glob in split[4::4]: - if not glob: - msg = 'found null segment after first prompt: %r' % split - raise SyntaxError(msg) - - stack = [] - for (index, glob) in enumerate(reversed(split[::4])): - # Is the binding a MODCMD or KEYCMD: - mod_cmd = ismodbind(glob) - - # Do we execute on UPDATES or EXEC events? - on_exec = True if glob[-1] in ['!', '_'] else False - - # Does the command take arguments? - has_args = True if glob[-1] in ['*', '_'] else False - - glob = glob[:-1] if has_args or on_exec else glob - mods, glob = split_glob(glob) - stack.append((on_exec, has_args, mods, glob, index)) - - self.stack = list(reversed(stack)) - self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD]) - - - def __getitem__(self, depth): - '''Get bind info at a depth.''' - - if self.is_global: - return self.stack[0] - - return self.stack[depth] - - - def __repr__(self): - if self._repr_cache: - return self._repr_cache - - args = ['glob=%r' % self.glob, 'bid=%d' % self.bid] - - if self.is_callable: - args.append('function=%r' % self.function) - if self.args: - args.append('args=%r' % self.args) - - if self.kargs: - args.append('kargs=%r' % self.kargs) - - else: - cmdlen = len(self.commands) - cmds = self.commands[0] if cmdlen == 1 else self.commands - args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) - - self._repr_cache = '' % ', '.join(args) - return self._repr_cache - - -def exec_bind(uzbl, bind, *args, **kargs): - '''Execute bind objects.''' - - uzbl.event("EXEC_BIND", bind, args, kargs) - - if bind.is_callable: - args += bind.args - kargs = dict(bind.kargs.items()+kargs.items()) - bind.function(uzbl, *args, **kargs) - return - - if kargs: - raise ArgumentError('cannot supply kargs for uzbl commands') - - commands = [] - cmd_expand = uzbl.cmd_expand - for cmd in bind.commands: - cmd = cmd_expand(cmd, args) - uzbl.send(cmd) - - -def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs): - '''Add a mode bind.''' - - bindlet = get_bindlet(uzbl) - - if not hasattr(modes, '__iter__'): - modes = unicode(modes).split(',') - - # Sort and filter binds. - modes = filter(None, map(unicode.strip, modes)) - - if callable(handler) or (handler is not None and handler.strip()): - bind = Bind(glob, handler, *args, **kargs) - - else: - bind = None - - for mode in modes: - if not VALID_MODE(mode): - raise NameError('invalid mode name: %r' % mode) - - for mode in modes: - if mode[0] == '-': - mode, bind = mode[1:], None - - bindlet.add_bind(mode, glob, bind) - uzbl.event('ADDED_MODE_BIND', mode, glob, bind) - - -def bind(uzbl, glob, handler, *args, **kargs): - '''Legacy bind function.''' - - mode_bind(uzbl, 'global', glob, handler, *args, **kargs) - - -def parse_mode_bind(uzbl, args): - '''Parser for the MODE_BIND event. - - Example events: - MODE_BIND = - MODE_BIND command o_ = uri %s - MODE_BIND insert,command = ... - MODE_BIND global ... = ... - MODE_BIND global,-insert ... = ... - ''' - - if not args: - raise ArgumentError('missing bind arguments') - - split = map(unicode.strip, args.split(' ', 1)) - if len(split) != 2: - raise ArgumentError('missing mode or bind section: %r' % args) - - modes, args = split[0].split(','), split[1] - split = map(unicode.strip, args.split('=', 1)) - if len(split) != 2: - raise ArgumentError('missing delimiter in bind section: %r' % args) - - glob, command = split - mode_bind(uzbl, modes, glob, command) - - -def parse_bind(uzbl, args): - '''Legacy parsing of the BIND event and conversion to the new format. - - Example events: - request BIND = - request BIND o_ = uri %s - request BIND = ... - request BIND ... = ... - ''' - - parse_mode_bind(uzbl, "global %s" % args) - - -def mode_changed(uzbl, mode): - '''Clear the stack on all non-stack mode changes.''' - - if mode != 'stack': - get_bindlet(uzbl).reset() - - -def match_and_exec(uzbl, bind, depth, keylet, bindlet): - - (on_exec, has_args, mod_cmd, glob, more) = bind[depth] - cmd = keylet.modcmd if mod_cmd else keylet.keycmd - - if mod_cmd and keylet.held != mod_cmd: - return False - - if has_args: - if not cmd.startswith(glob): - return False - - args = [cmd[len(glob):],] - - elif cmd != glob: - return False - - else: - args = [] - - if bind.is_global or (not more and depth == 0): - exec_bind(uzbl, bind, *args) - if not has_args: - uzbl.clear_current() - - return True - - elif more: - bindlet.stack(bind, args, depth) - return False - - args = bindlet.args + args - exec_bind(uzbl, bind, *args) - uzbl.set_mode() - if not has_args: - bindlet.reset() - uzbl.clear_current() - - return True - - -def keycmd_update(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if t[MOD_CMD] or t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return - - bindlet.after() - - -def keycmd_exec(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if t[MOD_CMD] or not t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return uzbl.clear_keycmd() - - bindlet.after() - - -def modcmd_update(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if not t[MOD_CMD] or t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return - - bindlet.after() - - -def modcmd_exec(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if not t[MOD_CMD] or not t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return uzbl.clear_modcmd() - - bindlet.after() - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'BIND': parse_bind, - 'KEYCMD_EXEC': keycmd_exec, - 'KEYCMD_UPDATE': keycmd_update, - 'MODCMD_EXEC': modcmd_exec, - 'MODCMD_UPDATE': modcmd_update, - 'MODE_BIND': parse_mode_bind, - 'MODE_CHANGED': mode_changed, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'bind': bind, - 'mode_bind': mode_bind, - 'get_bindlet': get_bindlet, - }) diff --git a/examples/data/uzbl/plugins/cmd_expand.py b/examples/data/uzbl/plugins/cmd_expand.py deleted file mode 100644 index 3f6ae2b..0000000 --- a/examples/data/uzbl/plugins/cmd_expand.py +++ /dev/null @@ -1,42 +0,0 @@ -def escape(str): - for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]: - str = str.replace(char, (level * '\\') + char) - - return str - - -def cmd_expand(uzbl, cmd, args): - '''Exports a function that provides the following - expansions in any uzbl command string: - - %s = replace('%s', ' '.join(args)) - %r = replace('%r', "'%s'" % escaped(' '.join(args))) - %1 = replace('%1', arg[0]) - %2 = replace('%2', arg[1]) - %n = replace('%n', arg[n-1]) - ''' - - # Ensure (1) all string representable and (2) correct string encoding. - args = map(unicode, args) - - # Direct string replace. - if '%s' in cmd: - cmd = cmd.replace('%s', ' '.join(args)) - - # Escaped and quoted string replace. - if '%r' in cmd: - cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args))) - - # Arg index string replace. - for (index, arg) in enumerate(args): - index += 1 - if '%%%d' % index in cmd: - cmd = cmd.replace('%%%d' % index, unicode(arg)) - - return cmd - - -def init(uzbl): - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export('cmd_expand', cmd_expand) diff --git a/examples/data/uzbl/plugins/completion.py b/examples/data/uzbl/plugins/completion.py deleted file mode 100644 index 8cea203..0000000 --- a/examples/data/uzbl/plugins/completion.py +++ /dev/null @@ -1,206 +0,0 @@ -'''Keycmd completion.''' - -# A list of functions this plugin exports to be used via uzbl object. -__export__ = ['start_completion', 'get_completion_dict'] - -import re - -# Holds the per-instance completion dicts. -UZBLS = {} - -# Completion level -NONE, ONCE, LIST, COMPLETE = range(4) - -# Default instance dict. -DEFAULTS = {'completions': [], 'level': NONE, 'lock': False} - -# The reverse keyword finding re. -FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall - -# Formats -LIST_FORMAT = " %s " -ITEM_FORMAT = "%s%s" - - -def escape(str): - return str.replace("@", "\@") - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - # Make sure the config keys for all possible completions are known. - uzbl.send('dump_config_as_events') - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_completion_dict(uzbl): - '''Get data stored for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def get_incomplete_keyword(uzbl): - '''Gets the segment of the keycmd leading up to the cursor position and - uses a regular expression to search backwards finding parially completed - keywords or @variables. Returns a null string if the correct completion - conditions aren't met.''' - - keylet = uzbl.get_keylet() - left_segment = keylet.keycmd[:keylet.cursor] - partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip() - if partial.startswith('set '): - return ('@%s' % partial[4:].lstrip(), True) - - return (partial, False) - - -def stop_completion(uzbl, *args): - '''Stop command completion and return the level to NONE.''' - - d = get_completion_dict(uzbl) - d['level'] = NONE - uzbl.set('completion_list') - - -def complete_completion(uzbl, partial, hint, set_completion=False): - '''Inject the remaining porition of the keyword into the keycmd then stop - the completioning.''' - - if set_completion: - remainder = "%s = " % hint[len(partial):] - - else: - remainder = "%s " % hint[len(partial):] - - uzbl.inject_keycmd(remainder) - stop_completion(uzbl) - - -def partial_completion(uzbl, partial, hint): - '''Inject a common portion of the hints into the keycmd.''' - - remainder = hint[len(partial):] - uzbl.inject_keycmd(remainder) - - -def update_completion_list(uzbl, *args): - '''Checks if the user still has a partially completed keyword under his - cursor then update the completion hints list.''' - - partial = get_incomplete_keyword(uzbl)[0] - if not partial: - return stop_completion(uzbl) - - d = get_completion_dict(uzbl) - if d['level'] < LIST: - return - - hints = [h for h in d['completions'] if h.startswith(partial)] - if not hints: - return uzbl.set('completion_list') - - j = len(partial) - l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)] - uzbl.set('completion_list', LIST_FORMAT % ' '.join(l)) - - -def start_completion(uzbl, *args): - - d = get_completion_dict(uzbl) - if d['lock']: - return - - (partial, set_completion) = get_incomplete_keyword(uzbl) - if not partial: - return stop_completion(uzbl) - - if d['level'] < COMPLETE: - d['level'] += 1 - - hints = [h for h in d['completions'] if h.startswith(partial)] - if not hints: - return - - elif len(hints) == 1: - d['lock'] = True - complete_completion(uzbl, partial, hints[0], set_completion) - d['lock'] = False - return - - elif partial in hints and d['level'] == COMPLETE: - d['lock'] = True - complete_completion(uzbl, partial, partial, set_completion) - d['lock'] = False - return - - smalllen, smallest = sorted([(len(h), h) for h in hints])[0] - common = '' - for i in range(len(partial), smalllen): - char, same = smallest[i], True - for hint in hints: - if hint[i] != char: - same = False - break - - if not same: - break - - common += char - - if common: - d['lock'] = True - partial_completion(uzbl, partial, partial+common) - d['lock'] = False - - update_completion_list(uzbl) - - -def add_builtins(uzbl, args): - '''Pump the space delimited list of builtin commands into the - builtin list.''' - - completions = get_completion_dict(uzbl)['completions'] - builtins = filter(None, map(unicode.strip, args.split(" "))) - for builtin in builtins: - if builtin not in completions: - completions.append(builtin) - - -def add_config_key(uzbl, key, value): - '''Listen on the CONFIG_CHANGED event and add config keys to the variable - list for @var like expansion support.''' - - completions = get_completion_dict(uzbl)['completions'] - key = "@%s" % key - if key not in completions: - completions.append(key) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'BUILTINS': add_builtins, - 'CONFIG_CHANGED': add_config_key, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'KEYCMD_CLEARED': stop_completion, - 'KEYCMD_EXEC': stop_completion, - 'KEYCMD_UPDATE': update_completion_list, - 'START_COMPLETION': start_completion, - 'STOP_COMPLETION': stop_completion, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_completion_dict': get_completion_dict, - 'start_completion': start_completion, - }) diff --git a/examples/data/uzbl/plugins/config.py b/examples/data/uzbl/plugins/config.py deleted file mode 100644 index 4a848a3..0000000 --- a/examples/data/uzbl/plugins/config.py +++ /dev/null @@ -1,97 +0,0 @@ -import re -import types - -__export__ = ['set', 'get_config'] - -VALIDKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match -TYPECONVERT = {'int': int, 'float': float, 'str': unicode} - -UZBLS = {} - - -def escape(value): - '''A real escaping function may be required.''' - - return unicode(value) - - -def set(uzbl, key, value='', config=None, force=False): - '''Sends a: "set key = value" command to the uzbl instance. If force is - False then only send a set command if the values aren't equal.''' - - if type(value) == types.BooleanType: - value = int(value) - - else: - value = unicode(value) - - if not VALIDKEY(key): - raise KeyError("%r" % key) - - value = escape(value) - if '\n' in value: - value = value.replace("\n", "\\n") - - if not force: - if config is None: - config = get_config(uzbl) - - if key in config and config[key] == value: - return - - uzbl.send('set %s = %s' % (key, value)) - - -class ConfigDict(dict): - def __init__(self, uzbl): - self._uzbl = uzbl - - def __setitem__(self, key, value): - '''Makes "config[key] = value" a wrapper for the set function.''' - - set(self._uzbl, key, value, config=self) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = ConfigDict(uzbl) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del uzbl - - -def get_config(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def variable_set(uzbl, args): - config = get_config(uzbl) - - key, type, value = list(args.split(' ', 2) + ['',])[:3] - old = config[key] if key in config else None - value = TYPECONVERT[type](value) - - dict.__setitem__(config, key, value) - - if old != value: - uzbl.event("CONFIG_CHANGED", key, value) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'VARIABLE_SET': variable_set, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_config': get_config, - 'set': set, - }) diff --git a/examples/data/uzbl/plugins/keycmd.py b/examples/data/uzbl/plugins/keycmd.py deleted file mode 100644 index c119077..0000000 --- a/examples/data/uzbl/plugins/keycmd.py +++ /dev/null @@ -1,571 +0,0 @@ -import re - -# Hold the keylets. -UZBLS = {} - -# Keycmd format which includes the markup for the cursor. -KEYCMD_FORMAT = "%s%s%s" -MODCMD_FORMAT = " %s " - - -def escape(str): - for char in ['\\', '@']: - str = str.replace(char, '\\'+char) - - return str - - -def uzbl_escape(str): - return "@[%s]@" % escape(str) if str else '' - - -class Keylet(object): - '''Small per-instance object that tracks all the keys held and characters - typed.''' - - def __init__(self): - # Modcmd tracking - self.held = set() - self.ignored = set() - self.modcmd = '' - self.is_modcmd = False - - # Keycmd tracking - self.keycmd = '' - self.cursor = 0 - - self.modmaps = {} - self.ignores = {} - self.additions = {} - - # Keylet string repr cache. - self._repr_cache = None - - - def get_keycmd(self): - '''Get the keycmd-part of the keylet.''' - - return self.keycmd - - - def get_modcmd(self): - '''Get the modcmd-part of the keylet.''' - - if not self.is_modcmd: - return '' - - return ''.join(self.held) + self.modcmd - - - def modmap_key(self, key): - '''Make some obscure names for some keys friendlier.''' - - if key in self.modmaps: - return self.modmaps[key] - - elif key.endswith('_L') or key.endswith('_R'): - # Remove left-right discrimination and try again. - return self.modmap_key(key[:-2]) - - else: - return key - - - def find_addition(self, modkey): - '''Key has just been pressed, check if this key + the held list - results in a modkey addition. Return that addition and remove all - modkeys that created it.''' - - # Intersection of (held list + modkey) and additions. - added = (self.held | set([modkey])) & set(self.additions.keys()) - for key in added: - if key == modkey or modkey in self.additions[key]: - self.held -= self.additions[key] - return key - - # Held list + ignored list + modkey. - modkeys = self.held | self.ignored | set([modkey]) - for (key, value) in self.additions.items(): - if modkeys.issuperset(value): - self.held -= value - return key - - return modkey - - - def key_ignored(self, key): - '''Check if the given key is ignored by any ignore rules.''' - - for (glob, match) in self.ignores.items(): - if match(key): - return True - - return False - - - def __repr__(self): - '''Return a string representation of the keylet.''' - - if self._repr_cache: - return self._repr_cache - - l = [] - if self.is_modcmd: - l.append('modcmd=%r' % self.get_modcmd()) - - elif self.held: - l.append('held=%r' % ''.join(sorted(self.held))) - - if self.keycmd: - l.append('keycmd=%r' % self.get_keycmd()) - - self._repr_cache = '' % ', '.join(l) - return self._repr_cache - - -def add_modmap(uzbl, key, map): - '''Add modmaps. - - Examples: - set modmap = request MODMAP - @modmap - @modmap - ... - - Then: - @bind = - @bind x = - ... - - ''' - - assert len(key) - modmaps = get_keylet(uzbl).modmaps - - if key[0] == "<" and key[-1] == ">": - key = key[1:-1] - - modmaps[key] = map - uzbl.event("NEW_MODMAP", key, map) - - -def modmap_parse(uzbl, map): - '''Parse a modmap definiton.''' - - split = [s.strip() for s in map.split(' ') if s.split()] - - if not split or len(split) > 2: - raise Exception('Invalid modmap arugments: %r' % map) - - add_modmap(uzbl, *split) - - -def add_key_ignore(uzbl, glob): - '''Add an ignore definition. - - Examples: - set ignore_key = request IGNORE_KEY - @ignore_key - @ignore_key - ... - ''' - - assert len(glob) > 1 - ignores = get_keylet(uzbl).ignores - - glob = "<%s>" % glob.strip("<> ") - restr = glob.replace('*', '[^\s]*') - match = re.compile(restr).match - - ignores[glob] = match - uzbl.event('NEW_KEY_IGNORE', glob) - - -def add_modkey_addition(uzbl, modkeys, result): - '''Add a modkey addition definition. - - Examples: - set mod_addition = request MODKEY_ADDITION - @mod_addition - @mod_addition - @mod_addition - ... - - Then: - @bind = - @bind o = - ... - ''' - - additions = get_keylet(uzbl).additions - modkeys = set(modkeys) - - assert len(modkeys) and result and result not in modkeys - - for (existing_result, existing_modkeys) in additions.items(): - if existing_result != result: - assert modkeys != existing_modkeys - - additions[result] = modkeys - uzbl.event('NEW_MODKEY_ADDITION', modkeys, result) - - -def modkey_addition_parse(uzbl, modkeys): - '''Parse modkey addition definition.''' - - keys = filter(None, map(unicode.strip, modkeys.split(" "))) - keys = ['<%s>' % key.strip("<>") for key in keys if key.strip("<>")] - - assert len(keys) > 1 - add_modkey_addition(uzbl, keys[:-1], keys[-1]) - - -def add_instance(uzbl, *args): - '''Create the Keylet object for this uzbl instance.''' - - UZBLS[uzbl] = Keylet() - - -def del_instance(uzbl, *args): - '''Delete the Keylet object for this uzbl instance.''' - - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_keylet(uzbl): - '''Return the corresponding keylet for this uzbl instance.''' - - # Startup events are not correctly captured and sent over the uzbl socket - # yet so this line is needed because the INSTANCE_START event is lost. - if uzbl not in UZBLS: - add_instance(uzbl) - - keylet = UZBLS[uzbl] - keylet._repr_cache = False - return keylet - - -def clear_keycmd(uzbl): - '''Clear the keycmd for this uzbl instance.''' - - k = get_keylet(uzbl) - k.keycmd = '' - k.cursor = 0 - k._repr_cache = False - uzbl.set('keycmd') - uzbl.set('raw_keycmd') - uzbl.event('KEYCMD_CLEARED') - - -def clear_modcmd(uzbl, clear_held=False): - '''Clear the modcmd for this uzbl instance.''' - - k = get_keylet(uzbl) - k.modcmd = '' - k.is_modcmd = False - k._repr_cache = False - if clear_held: - k.ignored = set() - k.held = set() - - uzbl.set('modcmd') - uzbl.set('raw_modcmd') - uzbl.event('MODCMD_CLEARED') - - -def clear_current(uzbl): - '''Clear the modcmd if is_modcmd else clear keycmd.''' - - k = get_keylet(uzbl) - if k.is_modcmd: - clear_modcmd(uzbl) - - else: - clear_keycmd(uzbl) - - -def focus_changed(uzbl, *args): - '''Focus to the uzbl instance has now been lost which means all currently - held keys in the held list will not get a KEY_RELEASE event so clear the - entire held list.''' - - clear_modcmd(uzbl, clear_held=True) - - -def update_event(uzbl, k, execute=True): - '''Raise keycmd & modcmd update events.''' - - config = uzbl.get_config() - keycmd, modcmd = k.get_keycmd(), k.get_modcmd() - - if k.is_modcmd: - uzbl.event('MODCMD_UPDATE', k) - - else: - uzbl.event('KEYCMD_UPDATE', k) - - if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': - new_modcmd = k.get_modcmd() - if not new_modcmd: - uzbl.set('modcmd', config=config) - uzbl.set('raw_modcmd', config=config) - - elif new_modcmd == modcmd: - uzbl.set('raw_modcmd', escape(modcmd), config=config) - uzbl.set('modcmd', MODCMD_FORMAT % uzbl_escape(modcmd), - config=config) - - if 'keycmd_events' in config and config['keycmd_events'] != '1': - return - - new_keycmd = k.get_keycmd() - if not new_keycmd: - uzbl.set('keycmd', config=config) - uzbl.set('raw_keycmd', config=config) - - elif new_keycmd == keycmd: - # Generate the pango markup for the cursor in the keycmd. - curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' ' - chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]] - value = KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks)) - uzbl.set('keycmd', value, config=config) - uzbl.set('raw_keycmd', escape(keycmd), config=config) - - -def inject_str(str, index, inj): - '''Inject a string into string at at given index.''' - - return "%s%s%s" % (str[:index], inj, str[index:]) - - -def get_keylet_and_key(uzbl, key, add=True): - '''Return the keylet and apply any transformations to the key as defined - by the modmapping or modkey addition rules. Return None if the key is - ignored.''' - - keylet = get_keylet(uzbl) - key = keylet.modmap_key(key) - if len(key) == 1: - return (keylet, key) - - modkey = "<%s>" % key.strip("<>") - - if keylet.key_ignored(modkey): - if add: - keylet.ignored.add(modkey) - - elif modkey in keylet.ignored: - keylet.ignored.remove(modkey) - - modkey = keylet.find_addition(modkey) - - if keylet.key_ignored(modkey): - return (keylet, None) - - return (keylet, modkey) - - -def key_press(uzbl, key): - '''Handle KEY_PRESS events. Things done by this function include: - - 1. Ignore all shift key presses (shift can be detected by capital chars) - 3. In non-modcmd mode: - a. append char to keycmd - 4. If not in modcmd mode and a modkey was pressed set modcmd mode. - 5. If in modcmd mode the pressed key is added to the held keys list. - 6. Keycmd is updated and events raised if anything is changed.''' - - (k, key) = get_keylet_and_key(uzbl, key.strip()) - if not key: - return - - if key.lower() == '' and not k.held and k.keycmd: - k.keycmd = inject_str(k.keycmd, k.cursor, ' ') - k.cursor += 1 - - elif not k.held and len(key) == 1: - config = uzbl.get_config() - if 'keycmd_events' in config and config['keycmd_events'] != '1': - k.keycmd = '' - k.cursor = 0 - uzbl.set('keycmd', config=config) - uzbl.set('raw_keycmd', config=config) - return - - k.keycmd = inject_str(k.keycmd, k.cursor, key) - k.cursor += 1 - - elif len(key) > 1: - k.is_modcmd = True - if key not in k.held: - k.held.add(key) - - else: - k.is_modcmd = True - k.modcmd += key - - update_event(uzbl, k) - - -def key_release(uzbl, key): - '''Respond to KEY_RELEASE event. Things done by this function include: - - 1. Remove the key from the keylet held list. - 2. If in a mod-command then raise a MODCMD_EXEC. - 3. Check if any modkey is held, if so set modcmd mode. - 4. Update the keycmd uzbl variable if anything changed.''' - - (k, key) = get_keylet_and_key(uzbl, key.strip(), add=False) - - if key in k.held: - if k.is_modcmd: - uzbl.event('MODCMD_EXEC', k) - - k.held.remove(key) - clear_modcmd(uzbl) - - -def set_keycmd(uzbl, keycmd): - '''Allow setting of the keycmd externally.''' - - k = get_keylet(uzbl) - k.keycmd = keycmd - k._repr_cache = None - k.cursor = len(keycmd) - update_event(uzbl, k, False) - - -def inject_keycmd(uzbl, keycmd): - '''Allow injecting of a string into the keycmd at the cursor position.''' - - k = get_keylet(uzbl) - k.keycmd = inject_str(k.keycmd, k.cursor, keycmd) - k._repr_cache = None - k.cursor += len(keycmd) - update_event(uzbl, k, False) - - -def append_keycmd(uzbl, keycmd): - '''Allow appening of a string to the keycmd.''' - - k = get_keylet(uzbl) - k.keycmd += keycmd - k._repr_cache = None - k.cursor = len(k.keycmd) - update_event(uzbl, k, False) - - -def keycmd_strip_word(uzbl, sep): - ''' Removes the last word from the keycmd, similar to readline ^W ''' - - sep = sep or ' ' - k = get_keylet(uzbl) - if not k.keycmd: - return - - head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] - rfind = head.rfind(sep) - head = head[:rfind] if rfind + 1 else '' - k.keycmd = head + tail - k.cursor = len(head) - update_event(uzbl, k, False) - - -def keycmd_backspace(uzbl, *args): - '''Removes the character at the cursor position in the keycmd.''' - - k = get_keylet(uzbl) - if not k.keycmd: - return - - k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] - k.cursor -= 1 - update_event(uzbl, k, False) - - -def keycmd_delete(uzbl, *args): - '''Removes the character after the cursor position in the keycmd.''' - - k = get_keylet(uzbl) - if not k.keycmd: - return - - k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] - update_event(uzbl, k, False) - - -def keycmd_exec_current(uzbl, *args): - '''Raise a KEYCMD_EXEC with the current keylet and then clear the - keycmd.''' - - k = get_keylet(uzbl) - uzbl.event('KEYCMD_EXEC', k) - clear_keycmd(uzbl) - - -def set_cursor_pos(uzbl, index): - '''Allow setting of the cursor position externally. Supports negative - indexing and relative stepping with '+' and '-'.''' - - k = get_keylet(uzbl) - if index == '-': - cursor = k.cursor - 1 - - elif index == '+': - cursor = k.cursor + 1 - - else: - cursor = int(index.strip()) - if cursor < 0: - cursor = len(k.keycmd) + cursor + 1 - - if cursor < 0: - cursor = 0 - - if cursor > len(k.keycmd): - cursor = len(k.keycmd) - - k.cursor = cursor - update_event(uzbl, k, False) - - -def init(uzbl): - '''Connect handlers to uzbl events.''' - - # Event handling hooks. - uzbl.connect_dict({ - 'APPEND_KEYCMD': append_keycmd, - 'FOCUS_GAINED': focus_changed, - 'FOCUS_LOST': focus_changed, - 'IGNORE_KEY': add_key_ignore, - 'INJECT_KEYCMD': inject_keycmd, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'KEYCMD_BACKSPACE': keycmd_backspace, - 'KEYCMD_DELETE': keycmd_delete, - 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, - 'KEYCMD_STRIP_WORD': keycmd_strip_word, - 'KEY_PRESS': key_press, - 'KEY_RELEASE': key_release, - 'MODKEY_ADDITION': modkey_addition_parse, - 'MODMAP': modmap_parse, - 'SET_CURSOR_POS': set_cursor_pos, - 'SET_KEYCMD': set_keycmd, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'add_key_ignore': add_key_ignore, - 'add_modkey_addition': add_modkey_addition, - 'add_modmap': add_modmap, - 'append_keycmd': append_keycmd, - 'clear_current': clear_current, - 'clear_keycmd': clear_keycmd, - 'clear_modcmd': clear_modcmd, - 'get_keylet': get_keylet, - 'inject_keycmd': inject_keycmd, - 'set_cursor_pos': set_cursor_pos, - 'set_keycmd': set_keycmd, - }) diff --git a/examples/data/uzbl/plugins/mode.py b/examples/data/uzbl/plugins/mode.py deleted file mode 100644 index 54d865a..0000000 --- a/examples/data/uzbl/plugins/mode.py +++ /dev/null @@ -1,176 +0,0 @@ -import sys -import re - -__export__ = ['set_mode', 'get_mode', 'set_mode_config', 'get_mode_config'] - -UZBLS = {} - -DEFAULTS = { - 'mode': '', - 'modes': { - 'insert': { - 'forward_keys': True, - 'keycmd_events': False, - 'modcmd_updates': False, - 'mode_indicator': 'I'}, - 'command': { - 'forward_keys': False, - 'keycmd_events': True, - 'modcmd_updates': True, - 'mode_indicator': 'C'}}} - -FINDSPACES = re.compile("\s+") -VALID_KEY = re.compile("^[\w_]+$").match - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_mode_dict(uzbl): - '''Return the mode dict for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def get_mode_config(uzbl, mode): - '''Return the mode config for a given mode.''' - - modes = get_mode_dict(uzbl)['modes'] - if mode not in modes: - modes[mode] = {} - - return modes[mode] - - -def get_mode(uzbl): - return get_mode_dict(uzbl)['mode'] - - -def mode_changed(uzbl, mode): - '''The mode has just been changed, now set the per-mode config.''' - - if get_mode(uzbl) != mode: - return - - config = uzbl.get_config() - mode_config = get_mode_config(uzbl, mode) - for (key, value) in mode_config.items(): - uzbl.set(key, value, config=config) - - if 'mode_indicator' not in mode_config: - config['mode_indicator'] = mode - - uzbl.clear_keycmd() - uzbl.clear_modcmd() - - -def set_mode(uzbl, mode=None): - '''Set the mode and raise the MODE_CHANGED event if the mode has changed. - Fallback on the default mode if no mode argument was given and the default - mode is not null.''' - - config = uzbl.get_config() - mode_dict = get_mode_dict(uzbl) - if mode is None: - mode_dict['mode'] = '' - if 'default_mode' in config: - mode = config['default_mode'] - - else: - mode = 'command' - - if not VALID_KEY(mode): - raise KeyError("invalid mode name: %r" % mode) - - if 'mode' not in config or config['mode'] != mode: - config['mode'] = mode - - elif mode_dict['mode'] != mode: - mode_dict['mode'] = mode - uzbl.event("MODE_CHANGED", mode) - - -def config_changed(uzbl, key, value): - '''Check for mode related config changes.''' - - value = None if not value else value - if key == 'default_mode': - if not get_mode(uzbl): - set_mode(uzbl, value) - - elif key == 'mode': - set_mode(uzbl, value) - - -def set_mode_config(uzbl, mode, key, value): - '''Set mode specific configs. If the mode being modified is the current - mode then apply the changes on the go.''' - - assert VALID_KEY(mode) and VALID_KEY(key) - - mode_config = get_mode_config(uzbl, mode) - mode_config[key] = value - - if get_mode(uzbl) == mode: - uzbl.set(key, value) - - -def mode_config(uzbl, args): - '''Parse mode config events.''' - - split = map(unicode.strip, FINDSPACES.split(args.lstrip(), 1)) - if len(split) != 2: - raise SyntaxError('invalid mode config syntax: %r' % args) - - mode, set = split - split = map(unicode.strip, set.split('=', 1)) - if len(split) != 2: - raise SyntaxError('invalid set syntax: %r' % args) - - key, value = split - set_mode_config(uzbl, mode, key, value) - - -def toggle_modes(uzbl, modes): - '''Toggle or cycle between or through a list of modes.''' - - assert len(modes.strip()) - - modelist = filter(None, map(unicode.strip, modes.split(' '))) - mode = get_mode(uzbl) - - index = 0 - if mode in modelist: - index = (modelist.index(mode)+1) % len(modelist) - - set_mode(uzbl, modelist[index]) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'CONFIG_CHANGED': config_changed, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'MODE_CHANGED': mode_changed, - 'MODE_CONFIG': mode_config, - 'TOGGLE_MODES': toggle_modes, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_mode': get_mode, - 'get_mode_config': get_mode_config, - 'set_mode': set_mode, - 'set_mode_config': set_mode_config, - }) diff --git a/examples/data/uzbl/plugins/on_event.py b/examples/data/uzbl/plugins/on_event.py deleted file mode 100644 index b9c504a..0000000 --- a/examples/data/uzbl/plugins/on_event.py +++ /dev/null @@ -1,107 +0,0 @@ -'''Plugin provides arbitrary binding of uzbl events to uzbl commands. - -Formatting options: - %s = space separated string of the arguments - %r = escaped and quoted version of %s - %1 = argument 1 - %2 = argument 2 - %n = argument n - -Usage: - request ON_EVENT LINK_HOVER set selected_uri = $1 - --> LINK_HOVER http://uzbl.org/ - <-- set selected_uri = http://uzbl.org/ - - request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2 - --> CONFIG_CHANGED selected_uri http://uzbl.org/ - <-- print Config changed: selected_uri = http://uzbl.org/ -''' - -import sys -import re - -__export__ = ['get_on_events', 'on_event'] - -UZBLS = {} - - -def error(msg): - sys.stderr.write('on_event plugin: error: %s\n' % msg) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = {} - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_on_events(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def event_handler(uzbl, *args, **kargs): - '''This function handles all the events being watched by various - on_event definitions and responds accordingly.''' - - events = get_on_events(uzbl) - event = kargs['on_event'] - if event not in events: - return - - commands = events[event] - cmd_expand = uzbl.cmd_expand - for cmd in commands: - cmd = cmd_expand(cmd, args) - uzbl.send(cmd) - - -def on_event(uzbl, event, cmd): - '''Add a new event to watch and respond to.''' - - event = event.upper() - events = get_on_events(uzbl) - if event not in events: - uzbl.connect(event, event_handler, on_event=event) - events[event] = [] - - cmds = events[event] - if cmd not in cmds: - cmds.append(cmd) - - -def parse_on_event(uzbl, args): - '''Parse ON_EVENT events and pass them to the on_event function. - - Syntax: "event ON_EVENT commands".''' - - if not args: - return error("missing on_event arguments") - - split = args.split(' ', 1) - if len(split) != 2: - return error("invalid ON_EVENT syntax: %r" % args) - - event, cmd = split - on_event(uzbl, event, cmd) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'ON_EVENT': parse_on_event, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_on_events': get_on_events, - 'on_event': on_event, - }) diff --git a/examples/data/uzbl/plugins/plugin_template.py b/examples/data/uzbl/plugins/plugin_template.py deleted file mode 100644 index 565a999..0000000 --- a/examples/data/uzbl/plugins/plugin_template.py +++ /dev/null @@ -1,76 +0,0 @@ -'''Plugin template.''' - -# Holds the per-instance data dict. -UZBLS = {} - -# The default instance dict. -DEFAULTS = {} - - -def add_instance(uzbl, *args): - '''Add a new instance with default config options.''' - - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - '''Delete data stored for an instance.''' - - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_myplugin_dict(uzbl): - '''Get data stored for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def myplugin_function(uzbl, *args, **kargs): - '''Custom plugin function which is exported by the __export__ list at the - top of the file for use by other functions/callbacks.''' - - print "My plugin function arguments:", args, kargs - - # Get the per-instance data object. - data = get_myplugin_dict(uzbl) - - # Function logic goes here. - - -def myplugin_event_parser(uzbl, args): - '''Parses MYPLUGIN_EVENT raised by uzbl or another plugin.''' - - print "Got MYPLUGIN_EVENT with arguments: %r" % args - - # Parsing logic goes here. - - -def init(uzbl): - '''The main function of the plugin which is used to attach all the event - hooks that are going to be used throughout the plugins life. This function - is called each time a UzblInstance() object is created in the event - manager.''' - - # Make a dictionary comprising of {"EVENT_NAME": handler, ..} to the event - # handler stack: - uzbl.connect_dict({ - # event name function - 'INSTANCE_START': add_instance, - 'INSTANCE_EXIT': del_instance, - 'MYPLUGIN_EVENT': myplugin_event_parser, - }) - - # Or connect a handler to an event manually and supply additional optional - # arguments: - #uzbl.connect("MYOTHER_EVENT", myother_event_parser, True, limit=20) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.connect_dict({ - # external name function - 'myplugin_function': myplugin_function, - }) diff --git a/examples/data/uzbl/plugins/progress_bar.py b/examples/data/uzbl/plugins/progress_bar.py deleted file mode 100644 index 89ba175..0000000 --- a/examples/data/uzbl/plugins/progress_bar.py +++ /dev/null @@ -1,159 +0,0 @@ -import sys - -UZBLS = {} - -DEFAULTS = {'width': 8, - 'done': '=', - 'pending': '.', - 'format': '[%d%a%p]%c', - 'spinner': '-\\|/', - 'sprites': 'loading', - 'updates': 0, - 'progress': 100} - - -def error(msg): - sys.stderr.write("progress_bar plugin: error: %s\n" % msg) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_progress_config(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def update_progress(uzbl, prog=None): - '''Updates the progress_format variable on LOAD_PROGRESS update. - - The current substitution options are: - %d = done char * done - %p = pending char * remaining - %c = percent done - %i = int done - %s = -\|/ spinner - %t = percent pending - %o = int pending - %r = sprites - ''' - - prog_config = get_progress_config(uzbl) - config = uzbl.get_config() - - if prog is None: - prog = prog_config['progress'] - - else: - prog = int(prog) - prog_config['progress'] = prog - - prog_config['updates'] += 1 - format = prog_config['format'] - width = prog_config['width'] - - # Inflate the done and pending bars to stop the progress bar - # jumping around. - if '%c' in format or '%i' in format: - count = format.count('%c') + format.count('%i') - width += (3-len(str(prog))) * count - - if '%t' in format or '%o' in format: - count = format.count('%t') + format.count('%o') - width += (3-len(str(100-prog))) * count - - done = int(((prog/100.0)*width)+0.5) - pending = width - done - - if '%d' in format: - format = format.replace('%d', prog_config['done']*done) - - if '%p' in format: - format = format.replace('%p', prog_config['pending']*pending) - - if '%c' in format: - format = format.replace('%c', '%d%%' % prog) - - if '%i' in format: - format = format.replace('%i', '%d' % prog) - - if '%t' in format: - format = format.replace('%t', '%d%%' % (100-prog)) - - if '%o' in format: - format = format.replace('%o', '%d' % (100-prog)) - - if '%s' in format: - spinner = prog_config['spinner'] - spin = '-' if not spinner else spinner - index = 0 if prog == 100 else prog_config['updates'] % len(spin) - char = '\\\\' if spin[index] == '\\' else spin[index] - format = format.replace('%s', char) - - if '%r' in format: - sprites = prog_config['sprites'] - sprites = '-' if not sprites else sprites - index = int(((prog/100.0)*len(sprites))+0.5)-1 - sprite = '\\\\' if sprites[index] == '\\' else sprites[index] - format = format.replace('%r', sprite) - - if 'progress_format' not in config or config['progress_format'] != format: - config['progress_format'] = format - - -def progress_config(uzbl, args): - '''Parse PROGRESS_CONFIG events from the uzbl instance. - - Syntax: event PROGRESS_CONFIG = - ''' - - split = args.split('=', 1) - if len(split) != 2: - return error("invalid syntax: %r" % args) - - key, value = map(unicode.strip, split) - prog_config = get_progress_config(uzbl) - - if key not in prog_config: - return error("key error: %r" % args) - - if type(prog_config[key]) == type(1): - try: - value = int(value) - - except: - return error("invalid type: %r" % args) - - elif not value: - value = ' ' - - prog_config[key] = value - update_progress(uzbl) - - -def reset_progress(uzbl, args): - '''Reset the spinner counter, reset the progress int and re-draw the - progress bar on LOAD_COMMIT.''' - - prog_dict = get_progress_config(uzbl) - prog_dict['updates'] = prog_dict['progress'] = 0 - update_progress(uzbl) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'LOAD_COMMIT': reset_progress, - 'LOAD_PROGRESS': update_progress, - 'PROGRESS_CONFIG': progress_config, - }) diff --git a/examples/data/uzbl/scripts/cookies.sh b/examples/data/uzbl/scripts/cookies.sh deleted file mode 100755 index ee2ce51..0000000 --- a/examples/data/uzbl/scripts/cookies.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/sh - -set -n; - -# THIS IS EXPERIMENTAL AND COULD BE INSECURE !!!!!! - -# this is an example bash script of how you could manage your cookies. it is very raw and basic and not as good as uzbl-cookie-daemon -# we use the cookies.txt format (See http://kb.mozillazine.org/Cookies.txt) -# This is one textfile with entries like this: -# kb.mozillazine.org FALSE / FALSE 1146030396 wikiUserID 16993 -# domain alow-read-other-subdomains path http-required expiration name value -# you probably want your cookies config file in your $XDG_CONFIG_HOME ( eg $HOME/.config/uzbl/cookies) -# Note. in uzbl there is no strict definition on what a session is. it's YOUR job to clear cookies marked as end_session if you want to keep cookies only valid during a "session" -# MAYBE TODO: allow user to edit cookie before saving. this cannot be done with zenity :( -# TODO: different cookie paths per config (eg per group of uzbl instances) - -# TODO: correct implementation. -# see http://curl.haxx.se/rfc/cookie_spec.html -# http://en.wikipedia.org/wiki/HTTP_cookie - -# TODO : check expires= before sending. -# write sample script that cleans up cookies dir based on expires attribute. -# TODO: check uri against domain attribute. and path also. -# implement secure attribute. -# support blocking or not for 3rd parties -# http://kb.mozillazine.org/Cookies.txt -# don't always append cookies, sometimes we need to overwrite - -cookie_config=${XDG_CONFIG_HOME:-${HOME}/.config}/uzbl/cookies -[ "x$cookie_config" = x ] && exit 1 -[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/" ] &&\ -cookie_data=${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/cookies.txt || exit 1 - -notifier= -#notifier=notify-send -#notify_wrapper () { -# echo "$@" >> $HOME/cookielog -#} -#notifier=notifier_wrapper - -# if this variable is set, we will use it to inform you when and which cookies we store, and when/which we send. -# it's primarily used for debugging -notifier= -which zenity &>/dev/null || exit 2 - -# Example cookie: -# test_cookie=CheckForPermission; expires=Thu, 07-May-2009 19:17:55 GMT; path=/; domain=.doubleclick.net - -# uri=$6 -# uri=${uri/http:\/\/} # strip 'http://' part -# host=${uri/\/*/} -action=$8 # GET/PUT -shift -host=$9 -shift -path=$9 -shift -cookie=$9 - -field_domain=$host -field_path=$path -field_name= -field_value= -field_exp='end_session' - -notify() { - [ -n "$notifier" ] && $notifier "$@" -} - - -# FOR NOW LETS KEEP IT SIMPLE AND JUST ALWAYS PUT AND ALWAYS GET -parse_cookie() { - IFS=$';' - first_pair=1 - for pair in $cookie - do - if [ "x$first_pair" = x1 ] - then - field_name=${pair%%=*} - field_value=${pair#*=} - first_pair=0 - else - echo "$pair" | read -r pair #strip leading/trailing wite space - key=${pair%%=*} - val=${pair#*=} - [ "$key" == expires ] && field_exp=`date -u -d "$val" +'%s'` - # TODO: domain - [ "$key" == path ] && field_path=$val - fi - done - unset IFS -} - -# match cookies in cookies.txt against hostname and path -get_cookie() { - path_esc=${path//\//\\/} - search="^[^\t]*$host\t[^\t]*\t$path_esc" - cookie=`awk "/$search/" $cookie_data 2>/dev/null | tail -n 1` - if [ -z "$cookie" ] - then - notify "Get_cookie: search: $search in $cookie_data -> no result" - false - else - notify "Get_cookie: search: $search in $cookie_data -> result: $cookie" - echo "$cookie" | \ - read domain alow_read_other_subdomains path http_required expiration name \ - value; - cookie="$name=$value" - true - fi -} - -save_cookie() { - if parse_cookie - then - data="$field_domain\tFALSE\t$field_path\tFALSE\t$field_exp\t$field_name\t$field_value" - notify "save_cookie: adding $data to $cookie_data" - echo -e "$data" >> $cookie_data - else - notify "not saving a cookie. since we don't have policies yet, parse_cookie must have returned false. this is a bug" - fi -} - -[ "x$action" = xPUT ] && save_cookie -[ "x$action" = xGET ] && get_cookie && echo "$cookie" - -exit - - -# TODO: implement this later. -# $1 = section (TRUSTED or DENY) -# $2 =url -match() { - sed -n "/$1/,/^\$/p" $cookie_config 2>/dev/null | grep -q "^$host" -} - -fetch_cookie() { - cookie=`cat $cookie_data` -} - -store_cookie() { - echo $cookie > $cookie_data -} - -if match TRUSTED $host -then - [ "x$action" = xPUT ] && store_cookie $host - [ "x$action" = xGET ] && fetch_cookie && echo "$cookie" -elif ! match DENY $host -then - [ "x$action" = xPUT ] && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Accept this cookie from $host ?" --entry-text="$cookie"` && store_cookie $host - [ "x$action" = xGET ] && fetch_cookie && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Submit this cookie to $host ?" --entry-text="$cookie"` && echo $cookie -fi -exit 0 diff --git a/examples/data/uzbl/scripts/download.sh b/examples/data/uzbl/scripts/download.sh deleted file mode 100755 index 1c7d039..0000000 --- a/examples/data/uzbl/scripts/download.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -# just an example of how you could handle your downloads -# try some pattern matching on the uri to determine what we should do - -# Some sites block the default wget --user-agent.. -GET="wget --user-agent=Firefox" - -dest="$HOME" -url="$8" - -http_proxy="$9" -export http_proxy - -test "x$url" = "x" && { echo "you must supply a url! ($url)"; exit 1; } - -# only changes the dir for the $get sub process -if echo "$url" | grep -E '.*\.torrent' >/dev/null; -then - ( cd "$dest"; $GET "$url") -else - ( cd "$dest"; $GET "$url") -fi diff --git a/examples/data/uzbl/scripts/extedit.js b/examples/data/uzbl/scripts/extedit.js deleted file mode 100644 index 8ed346d..0000000 --- a/examples/data/uzbl/scripts/extedit.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Edit forms in external editor - * - * (c) 2009, Robert Manea - * utf8 functions are (c) by Webtoolkit.info (http://www.webtoolkit.info/) - * - * - * Installation: - * - Copy this script to $HOME/.local/share/uzbl/scripts - * - Add the following to $HOME/.config/uzbl/config: - * @bind E = script @scripts_dir/extedit.js - * - Set your preferred editor - * set editor = gvim - * - non-GUI editors - * set editor = xterm -e vim - * - * Usage: - * Select (click) an editable form, go to command mode and hit E - * -*/ - - -function utf8_decode ( str_data ) { - var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0; - - str_data += ''; - - while ( i < str_data.length ) { - c1 = str_data.charCodeAt(i); - if (c1 < 128) { - tmp_arr[ac++] = String.fromCharCode(c1); - i++; - } else if ((c1 > 191) && (c1 < 224)) { - c2 = str_data.charCodeAt(i+1); - tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); - i += 2; - } else { - c2 = str_data.charCodeAt(i+1); - c3 = str_data.charCodeAt(i+2); - tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); - i += 3; - } - } - - return tmp_arr.join(''); -} - - -function utf8_encode ( argString ) { - var string = (argString+''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n"); - - var utftext = ""; - var start, end; - var stringl = 0; - - start = end = 0; - stringl = string.length; - for (var n = 0; n < stringl; n++) { - var c1 = string.charCodeAt(n); - var enc = null; - - if (c1 < 128) { - end++; - } else if (c1 > 127 && c1 < 2048) { - enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128); - } else { - enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128); - } - if (enc !== null) { - if (end > start) { - utftext += string.substring(start, end); - } - utftext += enc; - start = end = n+1; - } - } - - if (end > start) { - utftext += string.substring(start, string.length); - } - - return utftext; -} - - -(function() { - var actelem = document.activeElement; - - if(actelem.type == 'text' || actelem.type == 'textarea') { - var editor = Uzbl.run("print @external_editor") || "gvim"; - var filename = Uzbl.run("print @(mktemp /tmp/uzbl_edit.XXXXXX)@"); - - if(actelem.value) - Uzbl.run("sh 'echo " + window.btoa(utf8_encode(actelem.value)) + " | base64 -d > " + filename + "'"); - - Uzbl.run("sync_sh '" + editor + " " + filename + "'"); - actelem.value = utf8_decode(window.atob(Uzbl.run("print @(base64 -w 0 " + filename + ")@"))); - - Uzbl.run("sh 'rm -f " + filename + "'"); - } - - })(); diff --git a/examples/data/uzbl/scripts/follow_Numbers.js b/examples/data/uzbl/scripts/follow_Numbers.js deleted file mode 100644 index 00b279e..0000000 --- a/examples/data/uzbl/scripts/follow_Numbers.js +++ /dev/null @@ -1,228 +0,0 @@ -/* This is the basic linkfollowing script. - * Its pretty stable, only using numbers to navigate. - * - * TODO: Some pages mess around a lot with the zIndex which - * lets some hints in the background. - * TODO: Some positions are not calculated correctly (mostly - * because of uber-fancy-designed-webpages. Basic HTML and CSS - * works good - * TODO: Still some links can't be followed/unexpected things - * happen. Blame some freaky webdesigners ;) - */ - -//Just some shortcuts and globals -var uzblid = 'uzbl_link_hint'; -var uzbldivid = uzblid + '_div_container'; -var doc = document; -var win = window; -var links = document.links; -var forms = document.forms; - -//Reset keycmd, modcmd and return to default mode. -function clearKeycmd() { Uzbl.run('set mode ='); } - -//Make onlick-links "clickable" -try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ - type: 'click' - }); - } - }; -} catch(e) {} -//Catch the ESC keypress to stop linkfollowing -function keyPressHandler(e) { - var kC = window.event ? event.keyCode: e.keyCode; - var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; - if (kC == Esc) { - removeAllHints(); - } -} -//Calculate element position to draw the hint -//Pretty accurate but on fails in some very fancy cases -function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; - var height = el.offsetHeight; - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return [up, left, width, height]; -} -//Calculate if an element is visible -function isVisible(el) { - if (el == doc) { - return true; - } - if (!el) { - return false; - } - if (!el.parentNode) { - return false; - } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); -} -//Calculate if an element is on the viewport. -function elementInViewport(el) { - offset = elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; - var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; -} -//Removes all hints/leftovers that might be generated -//by this script. -function removeAllHints() { - var elements = doc.getElementById(uzbldivid); - if (elements) { - elements.parentNode.removeChild(elements); - } -} -//Generate a hint for an element with the given label -//Here you can play around with the style of the hints! -function generateHint(el, label) { - var pos = elementPosition(el); - var hint = doc.createElement('div'); - hint.setAttribute('name', uzblid); - hint.innerText = label; - hint.style.display = 'inline'; - hint.style.backgroundColor = '#B9FF00'; - hint.style.border = '2px solid #4A6600'; - hint.style.color = 'black'; - hint.style.fontSize = '9px'; - hint.style.fontWeight = 'bold'; - hint.style.lineHeight = '9px'; - hint.style.margin = '0px'; - hint.style.padding = '1px'; - hint.style.position = 'absolute'; - hint.style.zIndex = '1000'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; - var img = el.getElementsByTagName('img'); - if (img.length > 0) { - hint.style.left = pos[1] + img[0].width / 2 + 'px'; - } - hint.style.textDecoration = 'none'; - hint.style.webkitBorderRadius = '6px'; - // Play around with this, pretty funny things to do :) - hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; - return hint; -} -//Here we choose what to do with an element if we -//want to "follow" it. On form elements we "select" -//or pass the focus, on links we try to perform a click, -//but at least set the href of the link. (needs some improvements) -function clickElem(item) { - removeAllHints(); - clearKeycmd(); - if (item) { - var name = item.tagName; - if (name == 'A') { - item.click(); - window.location = item.href; - } else if (name == 'INPUT') { - var type = item.getAttribute('type').toUpperCase(); - if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { - item.focus(); - item.select(); - } else { - item.click(); - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { - item.focus(); - item.select(); - } else { - item.click(); - window.location = item.href; - } - } -} -//Returns a list of all links (in this version -//just the elements itself, but in other versions, we -//add the label here. -function addLinks() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (isVisible(li) && elementInViewport(li)) { - res[0].push(li); - } - } - return res; -} -//Same as above, just for the form elements -function addFormElems() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { - res[0].push(el); - } - } - } - return res; -} -//Draw all hints for all elements passed. "len" is for -//the number of chars we should use to avoid collisions -function reDrawHints(elems, chars) { - removeAllHints(); - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - for (var i = 0; i < elems[0].length; i++) { - if (elems[0][i]) { - var label = elems[1][i].substring(chars); - var h = generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) { - document.body.appendChild(hintdiv); - } -} -//Put it all together -function followLinks(follow) { - var s = follow.split(''); - var linknr = parseInt(follow, 10); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = addLinks(); - var formelems = addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = (elems[0].length + '').length; - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { - clickElem(elems[0][linknr]); - } else { - for (var j = 0; j < elems[0].length; j++) { - var b = true; - var label = j + ''; - var n = label.length; - for (n; n < len; n++) { - label = '0' + label; - } - for (var k = 0; k < s.length; k++) { - b = b && label.charAt(k) == s[k]; - } - if (b) { - leftover[0].push(elems[0][j]); - leftover[1].push(label); - } - } - reDrawHints(leftover, s.length); - } -} -followLinks('%s'); diff --git a/examples/data/uzbl/scripts/follow_Numbers_Strings.js b/examples/data/uzbl/scripts/follow_Numbers_Strings.js deleted file mode 100644 index e50da5d..0000000 --- a/examples/data/uzbl/scripts/follow_Numbers_Strings.js +++ /dev/null @@ -1,212 +0,0 @@ -var uzblid = 'uzbl_link_hint'; -var uzbldivid = uzblid + '_div_container'; -var doc = document; -var win = window; -var links = document.links; -var forms = document.forms; - -//Reset keycmd, modcmd and return to default mode. -function clearKeycmd() { Uzbl.run('set mode ='); } - -try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ - type: 'click' - }); - } - }; -} catch(e) {} -function keyPressHandler(e) { - var kC = window.event ? event.keyCode: e.keyCode; - var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; - if (kC == Esc) { - removeAllHints(); - } -} -function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; - var height = el.offsetHeight; - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return [up, left, width, height]; -} -function isVisible(el) { - if (el == doc) { - return true; - } - if (!el) { - return false; - } - if (!el.parentNode) { - return false; - } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); -} -function elementInViewport(el) { - offset = elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; - var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; -} -function removeAllHints() { - var elements = doc.getElementById(uzbldivid); - if (elements) { - elements.parentNode.removeChild(elements); - } -} -function generateHint(el, label) { - var pos = elementPosition(el); - var hint = doc.createElement('div'); - hint.setAttribute('name', uzblid); - hint.innerText = label; - hint.style.display = 'inline'; - hint.style.backgroundColor = '#B9FF00'; - hint.style.border = '2px solid #4A6600'; - hint.style.color = 'black'; - hint.style.zIndex = '1000'; - hint.style.fontSize = '9px'; - hint.style.fontWeight = 'bold'; - hint.style.lineHeight = '9px'; - hint.style.margin = '0px'; - hint.style.padding = '1px'; - hint.style.position = 'absolute'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; - var img = el.getElementsByTagName('img'); - if (img.length > 0) { - hint.style.left = pos[1] + img[0].width / 2 + 'px'; - } - hint.style.textDecoration = 'none'; - hint.style.webkitBorderRadius = '6px'; - hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; - return hint; -} - -function clickElem(item) { - removeAllHints(); - clearKeycmd(); - if (item) { - var name = item.tagName; - if (name == 'A') { - item.click(); - window.location = item.href; - } else if (name == 'INPUT') { - var type = item.getAttribute('type').toUpperCase(); - if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { - item.focus(); - item.select(); - } else { - item.click(); - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { - item.focus(); - item.select(); - } else { - item.click(); - window.location = item.href; - } - } -} - -function addLinks() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (isVisible(li) && elementInViewport(li)) { - res[0].push(li); - res[1].push(li.innerText.toLowerCase()); - } - } - return res; -} -function addFormElems() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { - res[0].push(el); - if (el.getAttribute('value')) { - res[1].push(el.getAttribute('value').toLowerCase()); - } else { - res[1].push(el.getAttribute('name').toLowerCase()); - } - } - } - } - return res; -} -function reDrawHints(elems, len) { - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - hintdiv.style.opacity = '0.0'; - for (var i = 0; i < elems[0].length; i++) { - var label = i + ''; - var n = label.length; - for (n; n < len; n++) { - label = '0' + label; - } - if (elems[0][i]) { - var h = generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) { - document.body.appendChild(hintdiv); - hintdiv.style.opacity = '0.7' - } -} -function followLinks(follow) { - var s = follow.split(''); - var linknr = parseInt(follow, 10); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = addLinks(); - var formelems = addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = (elems[0].length + '').length; - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { - clickElem(elems[0][linknr]); - } else { - for (var j = 0; j < elems[0].length; j++) { - var b = true; - for (var k = 0; k < s.length; k++) { - b = b && elems[1][j].charAt(k) == s[k]; - } - if (!b) { - elems[0][j] = null; - elems[1][j] = null; - } else { - leftover[0].push(elems[0][j]); - leftover[1].push(elems[1][j]); - } - } - if (leftover[0].length == 1) { - clickElem(leftover[0][0]); - } else if (!oldDiv) { - if (linknr + 1 || s.length == 0) { - reDrawHints(elems, len); - } else { - reDrawHints(leftover, len); - } - } - } -} -followLinks('%s'); diff --git a/examples/data/uzbl/scripts/formfiller.pl b/examples/data/uzbl/scripts/formfiller.pl deleted file mode 100755 index 23da347..0000000 --- a/examples/data/uzbl/scripts/formfiller.pl +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/perl - -# a slightly more advanced form filler -# -# uses settings file like: $keydir/ -#TODO: fallback to $HOME/.local/share -# user arg 1: -# edit: force editing of the file (fetches if file is missing) -# load: fill forms from file (fetches if file is missing) -# new: fetch new file - -# usage example: -# bind LL = spawn /usr/share/uzbl/examples/scripts/formfiller.pl load -# bind LN = spawn /usr/share/uzbl/examples/scripts/formfiller.pl new -# bind LE = spawn /usr/share/uzbl/examples/scripts/formfiller.pl edit - -use strict; -use warnings; - -my $keydir = $ENV{XDG_CONFIG_HOME} . "/uzbl/forms"; -my ($config,$pid,$xid,$fifoname,$socket,$url,$title,$cmd) = @ARGV; -if (!defined $fifoname || $fifoname eq "") { die "No fifo"; } - -sub domain { - my ($url) = @_; - $url =~ s#http(s)?://([A-Za-z0-9\.-]+)(/.*)?#$2#; - return $url; -}; - -my $editor = "xterm -e vim"; -#my $editor = "gvim"; - -# ideally, there would be some way to ask uzbl for the html content instead of having to redownload it with -# Also, you may need to fake the user-agent on some sites (like facebook) - my $downloader = "curl -A 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.10) Gecko/2009042810 GranParadiso/3.0.10' "; -#my $downloader = "curl -s"; - -my @fields = ("type","name","value"); - -my %command; - -$command{load} = sub { - my ($domain) = @_; - my $filename = "$keydir/$domain"; - if (-e $filename){ - open(my $file, $filename) or die "Failed to open $filename: $!"; - my (@lines) = <$file>; - close($file); - $|++; - open(my $fifo, ">>", $fifoname) or die "Failed to open $fifoname: $!"; - foreach my $line (@lines) { - next if ($line =~ m/^#/); - my ($type,$name,$value) = ($line =~ /^\s*(\w+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*$/); - if ($type eq "checkbox") - { - printf $fifo 'js document.getElementsByName("%s")[0].checked = %s;', $name, $value; - } elsif ($type eq "submit") - { - printf $fifo 'js function fs (n) {try{n.submit()} catch (e){fs(n.parentNode)}}; fs(document.getElementsByName("%s")[0]);', $name; - } elsif ($type ne "") - { - printf $fifo 'js document.getElementsByName("%s")[0].value = "%s";', $name, $value; - } - print $fifo "\n"; - } - $|--; - } else { - $command{new}->($domain); - $command{edit}->($domain); - } -}; -$command{edit} = sub { - my ($domain) = @_; - my $file = "$keydir/$domain"; - if(-e $file){ - system ($editor, $file); - } else { - $command{new}->($domain); - } -}; -$command{new} = sub { - my ($domain) = @_; - my $filename = "$keydir/$domain"; - open (my $file,">>", $filename) or die "Failed to open $filename: $!"; - $|++; - print $file "# Make sure that there are no extra submits, since it may trigger the wrong one.\n"; - printf $file "#%-10s | %-10s | %s\n", @fields; - print $file "#------------------------------\n"; - my @data = `$downloader $url`; - foreach my $line (@data){ - if($line =~ m/].*?)>/i){ - $line =~ s/.*(].*?)>).*/$1/; - printf $file " %-10s | %-10s | %s\n", map { my ($r) = $line =~ /.*$_=["'](.*?)["']/;$r } @fields; - }; - }; - $|--; -}; - -$command{$cmd}->(domain($url)); diff --git a/examples/data/uzbl/scripts/formfiller.sh b/examples/data/uzbl/scripts/formfiller.sh deleted file mode 100755 index 10afaba..0000000 --- a/examples/data/uzbl/scripts/formfiller.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -# simple html form (eg for logins) filler (and manager) for uzbl. -# uses settings files like: $keydir/ -# files contain lines like: : - - -# user arg 1: -# edit: force editing the file (falls back to new if not found) -# new: start with a new file. -# load: try to load from file into form - -# something else (or empty): if file not available: new, otherwise load. - -keydir=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/forms -[ -d "`dirname $keydir`" ] || exit 1 -[ -d "$keydir" ] || mkdir "$keydir" - -editor=${VISUAL} -if [[ -z ${editor} ]]; then - #editor='gvim' - editor='urxvt -e vim' -fi - -config=$1; shift -pid=$1; shift -xid=$1; shift -fifo=$1; shift -socket=$1; shift -url=$1; shift -title=$1; shift -action=$1 - -[ -d $keydir ] || mkdir $keydir || exit 1 - -if [ "$action" != 'edit' -a "$action" != 'new' -a "$action" != 'load' ] -then - action=new - [[ -e $keydir/$domain ]] && action=load -elif [ "$action" == 'edit' ] && [[ ! -e $keydir/$domain ]] -then - action=new -fi -domain=$(echo $url | sed -re 's|(http\|https)+://([A-Za-z0-9\.]+)/.*|\2|') - - -#regex='s|.*.*|\1: |p' # sscj's first version, does not work on http://wiki.archlinux.org/index.php?title=Special:UserLogin&returnto=Main_Page - regex='s|.*> $fifo -else - if [ "$action" == 'new' ] - then - curl "$url" | grep ' $keydir/$domain - fi - [[ -e $keydir/$domain ]] || exit 3 #this should never happen, but you never know. - $editor $keydir/$domain #TODO: if user aborts save in editor, the file is already overwritten -fi diff --git a/examples/data/uzbl/scripts/hint.js b/examples/data/uzbl/scripts/hint.js deleted file mode 100644 index ec7f1e2..0000000 --- a/examples/data/uzbl/scripts/hint.js +++ /dev/null @@ -1,26 +0,0 @@ -for (var i=0; i < document.links.length; i++) { - var uzblid = 'uzbl_link_hint_'; - var li = document.links[i]; - var pre = document.getElementById(uzblid+i); - - if (pre) { - li.removeChild(pre); - } else { - var hint = document.createElement('div'); - hint.setAttribute('id',uzblid+i); - hint.innerHTML = i; - hint.style.display='inline'; - hint.style.lineHeight='90%'; - hint.style.backgroundColor='red'; - hint.style.color='white'; - hint.style.fontSize='small-xx'; - hint.style.fontWeight='light'; - hint.style.margin='0px'; - hint.style.padding='2px'; - hint.style.position='absolute'; - hint.style.textDecoration='none'; - hint.style.left=li.style.left; - hint.style.top=li.style.top; - li.insertAdjacentElement('afterBegin',hint); - } -} diff --git a/examples/data/uzbl/scripts/history.sh b/examples/data/uzbl/scripts/history.sh deleted file mode 100755 index 7c83aa6..0000000 --- a/examples/data/uzbl/scripts/history.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history -[ -d `dirname $file` ] || exit 1 -echo `date +'%Y-%m-%d %H:%M:%S'`" $6 $7" >> $file diff --git a/examples/data/uzbl/scripts/insert_bookmark.sh b/examples/data/uzbl/scripts/insert_bookmark.sh deleted file mode 100755 index c34e7db..0000000 --- a/examples/data/uzbl/scripts/insert_bookmark.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -[ -d "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl" ] || exit 1 -file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/bookmarks - -which zenity &>/dev/null || exit 2 - -entry=`zenity --entry --text="Add bookmark. add tags after the '\t', separated by spaces" --entry-text="$6 $7\t"` -exitstatus=$? -if [ $exitstatus -ne 0 ]; then exit $exitstatus; fi -url=`echo $entry | awk '{print $1}'` - -# TODO: check if already exists, if so, and tags are different: ask if you want to replace tags -echo "$entry" >/dev/null #for some reason we need this.. don't ask me why -echo -e "$entry" >> $file -true diff --git a/examples/data/uzbl/scripts/instance-select-wmii.sh b/examples/data/uzbl/scripts/instance-select-wmii.sh deleted file mode 100755 index 2bf13ba..0000000 --- a/examples/data/uzbl/scripts/instance-select-wmii.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh - - -# This script allows you to focus another uzbl window -# It considers all uzbl windows in the current tag -# you can select one from a list, or go to the next/previous one -# It does not change the layout (stacked/tiled/floating) nor does it -# changes the size or viewing mode of a uzbl window -# When your current uzbl window is maximized, the one you change to -# will be maximized as well. -# See http://www.uzbl.org/wiki/wmii for more info -# $1 must be one of 'list', 'next', 'prev' - -COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" - -if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch -else - DMENU="dmenu -i" -fi - -if [ "$1" == 'list' ] -then - list= - # get window id's of uzbl clients. we could also get the label in one shot but it's pretty tricky - for i in $(wmiir read /tag/sel/index | grep uzbl |cut -d ' ' -f2) - do - label=$(wmiir read /client/$i/label) - list="$list$i : $label\n" - done - window=$(echo -e "$list" | $DMENU $COLORS | cut -d ' ' -f1) - wmiir xwrite /tag/sel/ctl "select client $window" -elif [ "$1" == 'next' ] -then - current=$(wmiir read /client/sel/ctl | head -n 1) - # find the next uzbl window and focus it - next=$(wmiir read /tag/sel/index | grep -A 10000 " $current " | grep -m 1 uzbl | cut -d ' ' -f2) - if [ x"$next" != "x" ] - then - wmiir xwrite /tag/sel/ctl "select client $next" - fi -elif [ "$1" == 'prev' ] -then - current=$(wmiir read /client/sel/ctl | head -n 1) - prev=$(wmiir read /tag/sel/index | grep -B 10000 " $current " | tac | grep -m 1 uzbl | cut -d ' ' -f2) - if [ x"$prev" != "x" ] - then - wmiir xwrite /tag/sel/ctl "select client $prev" - fi -else - echo "\$1 not valid" >&2 - exit 2 -fi diff --git a/examples/data/uzbl/scripts/linkfollow.js b/examples/data/uzbl/scripts/linkfollow.js deleted file mode 100644 index 0eb629b..0000000 --- a/examples/data/uzbl/scripts/linkfollow.js +++ /dev/null @@ -1,269 +0,0 @@ -// link follower for uzbl -// requires http://github.com/DuClare/uzbl/commit/6c11777067bdb8aac09bba78d54caea04f85e059 -// -// first, it needs to be loaded before every time it is used. -// One way would be to use the load_commit_handler: -// set load_commit_handler = sh 'echo "script /usr/share/uzbl/examples/scripts/linkfollow.js" > "$4"' -// -// when script is loaded, it can be invoked with -// bind f* = js hints.set("%s", hints.open) -// bind f_ = js hints.follow("%s",hints.open) -// -// At the moment, it may be useful to have way of forcing uzbl to load the script -// bind :lf = script /usr/share/uzbl/examples/scripts/linkfollow.js -// -// The default style for the hints are pretty ugly, so it is recommended to add the following -// to config file -// set stylesheet_uri = /usr/share/uzbl/examples/data/style.css -// -// based on follow_Numbers.js -// -// TODO: fix styling for the first element -// TODO: emulate mouseover events when visiting some elements -// TODO: rewrite the element->action handling - - -function Hints(){ - - // Settings - //////////////////////////////////////////////////////////////////////////// - - // if set to true, you must explicitly call hints.follow(), otherwise it will - // follow the link if there is only one matching result - var requireReturn = true; - - // Case sensitivity flag - var matchCase = "i"; - - // For case sensitive matching, uncomment: - // var matchCase = ""; - - - var uzblid = 'uzbl_hint'; - var uzblclass = 'uzbl_highlight'; - var uzblclassfirst = 'uzbl_h_first'; - var doc = document; - var visible = []; - var hintdiv; - - this.set = hint; - this.follow = follow; - this.keyPressHandler = keyPressHandler; - - function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; var width = el.offsetWidth; - var height = el.offsetHeight; - - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return {up: up, left: left, width: width, height: height}; - } - - function elementInViewport(p) { - return (p.up < window.pageYOffset + window.innerHeight && - p.left < window.pageXOffset + window.innerWidth && - (p.up + p.height) > window.pageYOffset && - (p.left + p.width) > window.pageXOffset); - } - - function isVisible(el) { - if (el == doc) { return true; } - if (!el) { return false; } - if (!el.parentNode) { return false; } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); - } - - // the vimperator defaults minus the xhtml elements, since it gave DOM errors - var hintable = " //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select"; - - function Matcher(str){ - var numbers = str.replace(/[^\d]/g,""); - var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,matchCase)}); - this.test = test; - this.toString = toString; - this.numbers = numbers; - function matchAgainst(element){ - if(element.node.nodeName == "INPUT"){ - return element.node.value; - } else { - return element.node.textContent; - } - } - function test(element) { - // test all the regexp - var item = matchAgainst(element); - return words.every(function (regex) { return item.match(regex)}); - } - } - - function HintElement(node,pos){ - - this.node = node; - this.isHinted = false; - this.position = pos; - this.num = 0; - - this.addHint = function (labelNum) { - // TODO: fix uzblclassfirst - if(!this.isHinted){ - this.node.className += " " + uzblclass; - } - this.isHinted = true; - - // create hint - var hintNode = doc.createElement('div'); - hintNode.name = uzblid; - hintNode.innerText = labelNum; - hintNode.style.left = this.position.left + 'px'; - hintNode.style.top = this.position.up + 'px'; - hintNode.style.position = "absolute"; - doc.body.firstChild.appendChild(hintNode); - - } - this.removeHint = function(){ - if(this.isHinted){ - var s = (this.num)?uzblclassfirst:uzblclass; - this.node.className = this.node.className.replace(new RegExp(" "+s,"g"),""); - this.isHinted = false; - } - } - } - - function createHintDiv(){ - var hintdiv = doc.getElementById(uzblid); - if(hintdiv){ - hintdiv.parentNode.removeChild(hintdiv); - } - hintdiv = doc.createElement("div"); - hintdiv.setAttribute('id',uzblid); - doc.body.insertBefore(hintdiv,doc.body.firstChild); - return hintdiv; - } - - function init(){ - // WHAT? - doc.body.setAttribute("onkeyup","hints.keyPressHandler(event)"); - hintdiv = createHintDiv(); - visible = []; - - var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null); - for (var i = 0;i&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch - # show tags as well - goto=`$DMENU $COLORS < $file | awk '{print $1}'` -else - DMENU="dmenu -i" - # because they are all after each other, just show the url, not their tags. - goto=`awk '{print $1}' $file | $DMENU $COLORS` -fi - -#[ -n "$goto" ] && echo "uri $goto" > $4 -[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/uzbl/scripts/load_url_from_history.sh b/examples/data/uzbl/scripts/load_url_from_history.sh deleted file mode 100755 index 62e02ac..0000000 --- a/examples/data/uzbl/scripts/load_url_from_history.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -history_file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history -[ -r "$history_file" ] || exit 1 - -# choose from all entries, sorted and uniqued -# goto=`awk '{print $3}' $history_file | sort -u | dmenu -i` -COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" -if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]'; -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch - # choose an item in reverse order, showing also the date and page titles - # pick the last field from the first 3 fields. this way you can pick a url (prefixed with date & time) or type just a new url. - goto=`tac $history_file | $DMENU $COLORS | cut -d ' ' -f -3 | awk '{print $NF}'` -else - DMENU="dmenu -i" - # choose from all entries (no date or title), the first one being current url, and after that all others, sorted and uniqued, in ascending order - current=`tail -n 1 $history_file | awk '{print $3}'`; - goto=`(echo $current; awk '{print $3}' $history_file | grep -v "^$current\$" \ - | sort -u) | $DMENU $COLORS` -fi - -[ -n "$goto" ] && echo "uri $goto" > $4 -#[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/uzbl/scripts/scheme.py b/examples/data/uzbl/scripts/scheme.py deleted file mode 100755 index 0916466..0000000 --- a/examples/data/uzbl/scripts/scheme.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -import os, subprocess, sys, urlparse - -def detach_open(cmd): - # Thanks to the vast knowledge of Laurence Withers (lwithers) and this message: - # http://mail.python.org/pipermail/python-list/2006-November/587523.html - if not os.fork(): - null = os.open(os.devnull,os.O_WRONLY) - for i in range(3): os.dup2(null,i) - os.close(null) - subprocess.Popen(cmd) - print 'USED' - -if __name__ == '__main__': - uri = sys.argv[8] - u = urlparse.urlparse(uri) - if u.scheme == 'mailto': - detach_open(['xterm', '-e', 'mail', u.path]) - elif u.scheme == 'xmpp': - # Someone check for safe arguments to gajim-remote - detach_open(['gajim-remote', 'open_chat', uri]) - elif u.scheme == 'git': - detach_open(['git', 'clone', '--', uri], cwd=os.path.expanduser('~/src')) diff --git a/examples/data/uzbl/scripts/scroll-percentage.js b/examples/data/uzbl/scripts/scroll-percentage.js deleted file mode 100644 index c9a51aa..0000000 --- a/examples/data/uzbl/scripts/scroll-percentage.js +++ /dev/null @@ -1,68 +0,0 @@ -// VIM ruler style scroll message -(function() { - var run = Uzbl.run; - var update_message = function() { - var innerHeight = window.innerHeight; - var scrollY = window.scrollY; - var height = document.height; - var message; - - if (UzblZoom.type === "full") { - var zoom_level = UzblZoom.level; - innerHeight = Math.ceil(innerHeight * zoom_level); - scrollY = Math.ceil(scrollY * zoom_level); - height -= 1; - } - - if (! height) { - message = ""; - } - else if (height <= innerHeight) { - message = run("print @scroll_all_indicator") || "All"; - } - else if (scrollY === 0) { - message = run("print @scroll_top_indicator") || "Top"; - } - else if (scrollY + innerHeight >= height) { - message = run("print @scroll_bottom_indicator") || "Bot"; - } - else { - var percentage = Math.round(scrollY / (height - innerHeight) * 100); - message = percentage + "%"; - } - run("set scroll_message=" + message); - }; - - self.UzblZoom = { - get level() { - return Number(run("print @zoom_level")) || 1; - }, - set level(level) { - if (typeof level === "number" && level > 0) { - run("set zoom_level = " + level); - update_message(); - } - }, - get type() { - return run("print @zoom_type") || "text"; - }, - set type(type) { - if ((type === "text" || type === "full") && this.type != type) { - run("toggle_zoom_type"); - run("set zoom_type = " + type); - update_message(); - } - }, - toggle_type: function() { - this.type = (this.type === "text" ? "full" : "text"); - } - }; - - window.addEventListener("DOMContentLoaded", update_message, false); - window.addEventListener("load", update_message, false); - window.addEventListener("resize", update_message, false); - window.addEventListener("scroll", update_message, false); - update_message(); -})(); - -// vim: set noet ff=unix diff --git a/examples/data/uzbl/scripts/session.sh b/examples/data/uzbl/scripts/session.sh deleted file mode 100755 index 1059b5e..0000000 --- a/examples/data/uzbl/scripts/session.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh - -# Very simple session manager for uzbl-browser. When called with "endsession" as the -# argument, it'll backup $sessionfile, look for fifos in $fifodir and -# instruct each of them to store their current url in $sessionfile and -# terminate themselves. Run with "launch" as the argument and an instance of -# uzbl-browser will be launched for each stored url. "endinstance" is used internally -# and doesn't need to be called manually at any point. -# Add a line like 'bind quit = /path/to/session.sh endsession' to your config - -[ -d ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl ] || exit 1 -scriptfile=$0 # this script -sessionfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/browser-session # the file in which the "session" (i.e. urls) are stored -configfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/config # uzbl configuration file -UZBL="uzbl-browser -c $configfile" # add custom flags and whatever here. - -fifodir=/tmp # remember to change this if you instructed uzbl to put its fifos elsewhere -thisfifo="$4" -act="$8" -url="$6" - -if [ "$act." = "." ]; then - act="$1" -fi - - -case $act in - "launch" ) - urls=`cat $sessionfile` - if [ "$urls." = "." ]; then - $UZBL - else - for url in $urls; do - $UZBL --uri "$url" & - done - fi - exit 0 - ;; - - "endinstance" ) - if [ "$url" != "(null)" ]; then - echo "$url" >> $sessionfile; - fi - echo "exit" > "$thisfifo" - ;; - - "endsession" ) - mv "$sessionfile" "$sessionfile~" - for fifo in $fifodir/uzbl_fifo_*; do - if [ "$fifo" != "$thisfifo" ]; then - echo "spawn $scriptfile endinstance" > "$fifo" - fi - done - echo "spawn $scriptfile endinstance" > "$thisfifo" - ;; - - * ) echo "session manager: bad action" - echo "Usage: $scriptfile [COMMAND] where commands are:" - echo " launch - Restore a saved session or start a new one" - echo " endsession - Quit the running session. Must be called from uzbl" - ;; -esac diff --git a/examples/data/uzbl/scripts/uzbl-cookie-daemon b/examples/data/uzbl/scripts/uzbl-cookie-daemon deleted file mode 100755 index fde8b8e..0000000 --- a/examples/data/uzbl/scripts/uzbl-cookie-daemon +++ /dev/null @@ -1,664 +0,0 @@ -#!/usr/bin/env python - -# The Python Cookie Daemon for Uzbl. -# Copyright (c) 2009, Tom Adams -# Copyright (c) 2009, Dieter Plaetinck -# Copyright (c) 2009, Mason Larobina -# Copyright (c) 2009, Michael Fiano -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -''' -The Python Cookie Daemon -======================== - -This daemon is a re-write of the original cookies.py script found in uzbl's -master branch. This script provides more functionality than the original -cookies.py by adding numerous command line options to specify different cookie -jar locations, socket locations, verbose output, etc. This functionality is -very useful as it allows you to run multiple daemons at once serving cookies -to different groups of uzbl instances as required. - -Keeping up to date -================== - -Check the cookie daemon uzbl-wiki page for more information on where to -find the latest version of the cookie_daemon.py - - http://www.uzbl.org/wiki/cookie_daemon.py - -Command line options -==================== - -Use the following command to get a full list of the cookie_daemon.py command -line options: - - ./cookie_daemon.py --help - -Talking with uzbl -================= - -In order to get uzbl to talk to a running cookie daemon you add the following -to your uzbl config: - - set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket - -Or if you prefer using the $HOME variable: - - set cookie_handler = talk_to_socket $HOME/.cache/uzbl/cookie_daemon_socket - -Todo list -========= - - - Use a pid file to make force killing a running daemon possible. - -Reporting bugs / getting help -============================= - -The best way to report bugs and or get help with the cookie daemon is to -contact the maintainers it the #uzbl irc channel found on the Freenode IRC -network (irc.freenode.org). -''' - -import cookielib -import os -import sys -import urllib2 -import select -import socket -import time -import atexit -from traceback import print_exc -from signal import signal, SIGTERM -from optparse import OptionParser -from os.path import join - -try: - import cStringIO as StringIO - -except ImportError: - import StringIO - - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return join(os.environ['HOME'], default) - -# Setup xdg paths. -CACHE_DIR = join(xdghome('CACHE', '.cache/'), 'uzbl/') -DATA_DIR = join(xdghome('DATA', '.local/share/'), 'uzbl/') -CONFIG_DIR = join(xdghome('CONFIG', '.config/'), 'uzbl/') - -# Ensure data paths exist. -for path in [CACHE_DIR, DATA_DIR, CONFIG_DIR]: - if not os.path.exists(path): - os.makedirs(path) - -# Default config -config = { - - # Default cookie jar, whitelist, and daemon socket locations. - 'cookie_jar': join(DATA_DIR, 'cookies.txt'), - 'cookie_whitelist': join(CONFIG_DIR, 'cookie_whitelist'), - 'cookie_socket': join(CACHE_DIR, 'cookie_daemon_socket'), - - # Don't use a cookie whitelist policy by default. - 'use_whitelist': False, - - # Time out after x seconds of inactivity (set to 0 for never time out). - # WARNING: Do not use this option if you are manually launching the daemon. - 'daemon_timeout': 0, - - # Daemonise by default. - 'daemon_mode': True, - - # Optionally print helpful debugging messages to the terminal. - 'verbose': False, - -} # End of config dictionary. - - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - - -_SCRIPTNAME = os.path.basename(sys.argv[0]) -def echo(msg): - '''Prints only if the verbose flag has been set.''' - - if config['verbose']: - sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) - - -def error(msg): - '''Prints error message and exits.''' - - sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) - sys.exit(1) - - -def mkbasedir(filepath): - '''Create the base directories of the file in the file-path if the dirs - don't exist.''' - - dirname = os.path.dirname(filepath) - if not os.path.exists(dirname): - echo("creating dirs: %r" % dirname) - os.makedirs(dirname) - - -def daemon_running(cookie_socket): - '''Check if another process (hopefully a cookie_daemon.py) is listening - on the cookie daemon socket. If another process is found to be - listening on the socket exit the daemon immediately and leave the - socket alone. If the connect fails assume the socket has been abandoned - and delete it (to be re-created in the create socket function).''' - - if not os.path.exists(cookie_socket): - return False - - if os.path.isfile(cookie_socket): - raise Exception("regular file at %r is not a socket" % cookie_socket) - - - if os.path.isdir(cookie_socket): - raise Exception("directory at %r is not a socket" % cookie_socket) - - try: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - sock.connect(cookie_socket) - sock.close() - echo("detected daemon listening on %r" % cookie_socket) - return True - - except socket.error: - # Failed to connect to cookie_socket so assume it has been - # abandoned by another cookie daemon process. - if os.path.exists(cookie_socket): - echo("deleting abandoned socket at %r" % cookie_socket) - os.remove(cookie_socket) - - return False - - -def send_command(cookie_socket, cmd): - '''Send a command to a running cookie daemon.''' - - if not daemon_running(cookie_socket): - return False - - try: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - sock.connect(cookie_socket) - sock.send(cmd) - sock.close() - echo("sent command %r to %r" % (cmd, cookie_socket)) - return True - - except socket.error: - print_exc() - error("failed to send message %r to %r" % (cmd, cookie_socket)) - return False - - -def kill_daemon(cookie_socket): - '''Send the "EXIT" command to running cookie_daemon.''' - - if send_command(cookie_socket, "EXIT"): - # Now ensure the cookie_socket is cleaned up. - start = time.time() - while os.path.exists(cookie_socket): - time.sleep(0.1) - if (time.time() - start) > 5: - error("force deleting socket %r" % cookie_socket) - os.remove(cookie_socket) - return - - echo("stopped daemon listening on %r"% cookie_socket) - - else: - if os.path.exists(cookie_socket): - os.remove(cookie_socket) - echo("removed abandoned/broken socket %r" % cookie_socket) - - -def daemonize(): - '''Daemonize the process using the Stevens' double-fork magic.''' - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #1 failed") - sys.exit(1) - - os.chdir('/') - os.setsid() - os.umask(0) - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #2 failed") - sys.exit(1) - - sys.stdout.flush() - sys.stderr.flush() - - devnull = '/dev/null' - stdin = file(devnull, 'r') - stdout = file(devnull, 'a+') - stderr = file(devnull, 'a+', 0) - - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - -class CookieMonster: - '''The uzbl cookie daemon class.''' - - def __init__(self): - '''Initialise class variables.''' - - self.server_socket = None - self.jar = None - self.last_request = time.time() - self._running = False - - - def run(self): - '''Start the daemon.''' - - # The check healthy function will exit if another daemon is detected - # listening on the cookie socket and remove the abandoned socket if - # there isnt. - if os.path.exists(config['cookie_socket']): - if daemon_running(config['cookie_socket']): - sys.exit(1) - - # Create cookie daemon socket. - self.create_socket() - - # Daemonize process. - if config['daemon_mode']: - echo("entering daemon mode") - daemonize() - - # Register a function to cleanup on exit. - atexit.register(self.quit) - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) - - # Create cookie jar object from file. - self.open_cookie_jar() - - # Create a way to exit nested loops by setting a running flag. - self._running = True - - while self._running: - try: - # Enter main listen loop. - self.listen() - - except KeyboardInterrupt: - self._running = False - print - - except socket.error: - print_exc() - - except: - # Clean up - self.del_socket() - - # Raise exception - raise - - # Always delete the socket before calling create again. - self.del_socket() - # Create cookie daemon socket. - self.create_socket() - - - def load_whitelist(self): - '''Load the cookie jar whitelist policy.''' - - cookie_whitelist = config['cookie_whitelist'] - - if cookie_whitelist: - mkbasedir(cookie_whitelist) - - # Create cookie whitelist file if it does not exist. - if not os.path.exists(cookie_whitelist): - open(cookie_whitelist, 'w').close() - - # Read cookie whitelist file into list. - file = open(cookie_whitelist,'r') - domain_list = [line.rstrip('\n') for line in file] - file.close() - - # Define policy of allowed domains - policy = cookielib.DefaultCookiePolicy(allowed_domains=domain_list) - self.jar.set_policy(policy) - - # Save the last modified time of the whitelist. - self._whitelistmtime = os.stat(cookie_whitelist).st_mtime - - - def open_cookie_jar(self): - '''Open the cookie jar.''' - - cookie_jar = config['cookie_jar'] - cookie_whitelist = config['cookie_whitelist'] - - if cookie_jar: - mkbasedir(cookie_jar) - - # Create cookie jar object from file. - self.jar = cookielib.MozillaCookieJar(cookie_jar) - - # Load cookie whitelist policy. - if config['use_whitelist']: - self.load_whitelist() - - if cookie_jar: - try: - # Attempt to load cookies from the cookie jar. - self.jar.load(ignore_discard=True) - - # Ensure restrictive permissions are set on the cookie jar - # to prevent other users on the system from hi-jacking your - # authenticated sessions simply by copying your cookie jar. - os.chmod(cookie_jar, 0600) - - except: - pass - - - def reload_whitelist(self): - '''Reload the cookie whitelist.''' - - cookie_whitelist = config['cookie_whitelist'] - if os.path.exists(cookie_whitelist): - echo("reloading whitelist %r" % cookie_whitelist) - self.open_cookie_jar() - - - def create_socket(self): - '''Create AF_UNIX socket for communication with uzbl instances.''' - - cookie_socket = config['cookie_socket'] - mkbasedir(cookie_socket) - - self.server_socket = socket.socket(socket.AF_UNIX, - socket.SOCK_SEQPACKET) - - self.server_socket.bind(cookie_socket) - - # Set restrictive permissions on the cookie socket to prevent other - # users on the system from data-mining your cookies. - os.chmod(cookie_socket, 0600) - - - def listen(self): - '''Listen for incoming cookie PUT and GET requests.''' - - daemon_timeout = config['daemon_timeout'] - echo("listening on %r" % config['cookie_socket']) - - while self._running: - # This line tells the socket how many pending incoming connections - # to enqueue at once. Raising this number may or may not increase - # performance. - self.server_socket.listen(1) - - if bool(select.select([self.server_socket], [], [], 1)[0]): - client_socket, _ = self.server_socket.accept() - self.handle_request(client_socket) - self.last_request = time.time() - client_socket.close() - continue - - if daemon_timeout: - # Checks if the daemon has been idling for too long. - idle = time.time() - self.last_request - if idle > daemon_timeout: - self._running = False - - - def handle_request(self, client_socket): - '''Connection made, now to serve a cookie PUT or GET request.''' - - # Receive cookie request from client. - data = client_socket.recv(8192) - if not data: - return - - # Cookie argument list in packet is null separated. - argv = data.split("\0") - action = argv[0].upper().strip() - - # Catch the EXIT command sent to kill running daemons. - if action == "EXIT": - self._running = False - return - - # Catch whitelist RELOAD command. - elif action == "RELOAD": - self.reload_whitelist() - return - - # Return if command unknown. - elif action not in ['GET', 'PUT']: - error("unknown command %r." % argv) - return - - # Determine whether or not to print cookie data to terminal. - print_cookie = (config['verbose'] and not config['daemon_mode']) - if print_cookie: - print ' '.join(argv[:4]) - - uri = urllib2.urlparse.ParseResult( - scheme=argv[1], - netloc=argv[2], - path=argv[3], - params='', - query='', - fragment='').geturl() - - req = urllib2.Request(uri) - - if action == "GET": - self.jar.add_cookie_header(req) - if req.has_header('Cookie'): - cookie = req.get_header('Cookie') - client_socket.send(cookie) - if print_cookie: - print cookie - - else: - client_socket.send("\0") - - elif action == "PUT": - cookie = argv[4] if len(argv) > 3 else None - if print_cookie: - print cookie - - self.put_cookie(req, cookie) - - if print_cookie: - print - - - def put_cookie(self, req, cookie=None): - '''Put a cookie in the cookie jar.''' - - hdr = urllib2.httplib.HTTPMessage(\ - StringIO.StringIO('Set-Cookie: %s' % cookie)) - res = urllib2.addinfourl(StringIO.StringIO(), hdr, - req.get_full_url()) - self.jar.extract_cookies(res, req) - if config['cookie_jar']: - self.jar.save(ignore_discard=True) - - - def del_socket(self): - '''Remove the cookie_socket file on exit. In a way the cookie_socket - is the daemons pid file equivalent.''' - - if self.server_socket: - try: - self.server_socket.close() - - except: - pass - - self.server_socket = None - - cookie_socket = config['cookie_socket'] - if os.path.exists(cookie_socket): - echo("deleting socket %r" % cookie_socket) - os.remove(cookie_socket) - - - def quit(self): - '''Called on exit to make sure all loose ends are tied up.''' - - self.del_socket() - sys.exit(0) - - -def main(): - '''Main function.''' - - # Define command line parameters. - usage = "usage: %prog [options] {start|stop|restart|reload}" - parser = OptionParser(usage=usage) - parser.add_option('-n', '--no-daemon', dest='no_daemon', - action='store_true', help="don't daemonise the process.") - - parser.add_option('-v', '--verbose', dest="verbose", - action='store_true', help="print verbose output.") - - parser.add_option('-t', '--daemon-timeout', dest='daemon_timeout', - action="store", metavar="SECONDS", help="shutdown the daemon after x "\ - "seconds inactivity. WARNING: Do not use this when launching the "\ - "cookie daemon manually.") - - parser.add_option('-s', '--cookie-socket', dest="cookie_socket", - metavar="SOCKET", help="manually specify the socket location.") - - parser.add_option('-j', '--cookie-jar', dest='cookie_jar', - metavar="FILE", help="manually specify the cookie jar location.") - - parser.add_option('-m', '--memory', dest='memory', action='store_true', - help="store cookies in memory only - do not write to disk") - - parser.add_option('-u', '--use-whitelist', dest='usewhitelist', - action='store_true', help="use cookie whitelist policy") - - parser.add_option('-w', '--cookie-whitelist', dest='whitelist', - action='store', help="manually specify whitelist location", - metavar='FILE') - - # Parse the command line arguments. - (options, args) = parser.parse_args() - - expand = lambda p: os.path.realpath(os.path.expandvars(p)) - - initcommands = ['start', 'stop', 'restart', 'reload'] - for arg in args: - if arg not in initcommands: - error("unknown argument %r" % args[0]) - sys.exit(1) - - if len(args) > 1: - error("the daemon only accepts one {%s} action at a time." - % '|'.join(initcommands)) - sys.exit(1) - - if len(args): - action = args[0] - - else: - action = "start" - - if options.no_daemon: - config['daemon_mode'] = False - - if options.cookie_socket: - config['cookie_socket'] = expand(options.cookie_socket) - - if options.cookie_jar: - config['cookie_jar'] = expand(options.cookie_jar) - - if options.memory: - config['cookie_jar'] = None - - if options.whitelist: - config['cookie_whitelist'] = expand(options.whitelist) - - if options.whitelist or options.usewhitelist: - config['use_whitelist'] = True - - if options.daemon_timeout: - try: - config['daemon_timeout'] = int(options.daemon_timeout) - - except ValueError: - error("expected int argument for -t, --daemon-timeout") - - # Expand $VAR's in config keys that relate to paths. - for key in ['cookie_socket', 'cookie_jar', 'cookie_whitelist']: - if config[key]: - config[key] = os.path.expandvars(config[key]) - - if options.verbose: - config['verbose'] = True - import pprint - sys.stderr.write("%s\n" % pprint.pformat(config)) - - # It would be better if we didn't need to start this python process just - # to send a command to the socket, but unfortunately socat doesn't seem - # to support SEQPACKET. - if action == "reload": - send_command(config['cookie_socket'], "RELOAD") - - if action in ['stop', 'restart']: - kill_daemon(config['cookie_socket']) - - if action in ['start', 'restart']: - CookieMonster().run() - - -if __name__ == "__main__": - main() diff --git a/examples/data/uzbl/scripts/uzbl-event-manager b/examples/data/uzbl/scripts/uzbl-event-manager deleted file mode 100755 index b3fdb3c..0000000 --- a/examples/data/uzbl/scripts/uzbl-event-manager +++ /dev/null @@ -1,833 +0,0 @@ -#!/usr/bin/env python - -# Event Manager for Uzbl -# Copyright (c) 2009, Mason Larobina -# Copyright (c) 2009, Dieter Plaetinck -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -''' - -E V E N T _ M A N A G E R . P Y -=============================== - -Event manager for uzbl written in python. - -''' - -import imp -import os -import sys -import re -import socket -import pprint -import time -import atexit -from select import select -from signal import signal, SIGTERM -from optparse import OptionParser -from traceback import print_exc -from functools import partial - - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return os.path.join(os.environ['HOME'], default) - - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -# `make install` will put the correct value here for your system -PREFIX = '/usr/local/' - -# Setup xdg paths. -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') -CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') - -# Event manager config dictionary. This is not to be confused with the config -# dict that tracks variables in the uzbl instance. -CONFIG = { - 'verbose': False, - 'daemon_mode': True, - 'auto_close': False, - - 'plugins_load': [], - 'plugins_ignore': [], - - 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'), - os.path.join(PREFIX, 'share/uzbl/examples/data/uzbl/plugins/')], - - 'server_socket': os.path.join(CACHE_DIR, 'event_daemon'), - 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'), -} - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - - -# Define some globals. -SCRIPTNAME = os.path.basename(sys.argv[0]) -FINDSPACES = re.compile("\s+") - - -class ArgumentError(Exception): - pass - - -def echo(msg): - '''Prints only if the verbose flag has been set.''' - - if CONFIG['verbose']: - sys.stdout.write("%s: %s\n" % (SCRIPTNAME, msg)) - - -def error(msg): - '''Prints error messages to stderr.''' - - sys.stderr.write("%s: error: %s\n" % (SCRIPTNAME, msg)) - - -def counter(): - '''Generate unique object id's.''' - - i = 0 - while True: - i += 1 - yield i - - -def find_plugins(plugin_dirs): - '''Find all event manager plugins in the plugin dirs and return a - dictionary of {'plugin-name.py': '/full/path/to/plugin-name.py', ...}''' - - plugins = {} - - for plugin_dir in plugin_dirs: - plugin_dir = os.path.realpath(os.path.expandvars(plugin_dir)) - if not os.path.isdir(plugin_dir): - continue - - for filename in os.listdir(plugin_dir): - if not filename.lower().endswith('.py'): - continue - - path = os.path.join(plugin_dir, filename) - if not os.path.isfile(path): - continue - - if filename not in plugins: - plugins[filename] = plugin_dir - - return plugins - - -def load_plugins(plugin_dirs, load=None, ignore=None): - '''Load event manager plugins found in the plugin_dirs.''' - - load = [] if load is None else load - ignore = [] if ignore is None else ignore - - # Find the plugins in the plugin_dirs. - found = find_plugins(plugin_dirs) - - if load: - # Ignore anything not in the load list. - for plugin in found.keys(): - if plugin not in load: - del found[plugin] - - if ignore: - # Ignore anything in the ignore list. - for plugin in found.keys(): - if plugin in ignore: - del found[plugin] - - # Print plugin list to be loaded. - pprint.pprint(found) - - loaded = {} - # Load all found plugins into the loaded dict. - for (filename, plugin_dir) in found.items(): - name = filename[:-3] - info = imp.find_module(name, [plugin_dir]) - plugin = imp.load_module(name, *info) - loaded[(plugin_dir, filename)] = plugin - - return loaded - - -def daemonize(): - '''Daemonize the process using the Stevens' double-fork magic.''' - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #1 failed") - sys.exit(1) - - os.chdir('/') - os.setsid() - os.umask(0) - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #2 failed") - sys.exit(1) - - sys.stdout.flush() - sys.stderr.flush() - - devnull = '/dev/null' - stdin = file(devnull, 'r') - stdout = file(devnull, 'a+') - stderr = file(devnull, 'a+', 0) - - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - -def make_dirs(path): - '''Make all basedirs recursively as required.''' - - dirname = os.path.dirname(path) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - -def make_pid_file(pid_file): - '''Make pid file at given pid_file location.''' - - make_dirs(pid_file) - fileobj = open(pid_file, 'w') - fileobj.write('%d' % os.getpid()) - fileobj.close() - - -def del_pid_file(pid_file): - '''Delete pid file at given pid_file location.''' - - if os.path.isfile(pid_file): - os.remove(pid_file) - - -def get_pid(pid_file): - '''Read pid from pid_file.''' - - try: - fileobj = open(pid_file, 'r') - pid = int(fileobj.read()) - fileobj.close() - return pid - - except IOError, ValueError: - print_exc() - return None - - -def pid_running(pid): - '''Returns True if a process with the given pid is running.''' - - try: - os.kill(pid, 0) - - except OSError: - return False - - else: - return True - - -def term_process(pid): - '''Send a SIGTERM signal to the process with the given pid.''' - - if not pid_running(pid): - return False - - os.kill(pid, SIGTERM) - - start = time.time() - while True: - if not pid_running(pid): - return True - - if time.time() - start > 5: - raise OSError('failed to stop process with pid: %d' % pid) - - time.sleep(0.25) - - -def parse_msg(uzbl, msg): - '''Parse an incoming msg from a uzbl instance. All non-event messages - will be printed here and not be passed to the uzbl instance event - handler function.''' - - if not msg: - return - - cmd = FINDSPACES.split(msg, 3) - if not cmd or cmd[0] != 'EVENT': - # Not an event message. - print '---', msg - return - - while len(cmd) < 4: - cmd.append('') - - event, args = cmd[2], cmd[3] - if not event: - return - - try: - uzbl.event(event, args) - - except: - print_exc() - - -class EventHandler(object): - - nexthid = counter().next - - def __init__(self, event, handler, *args, **kargs): - if not callable(handler): - raise ArgumentError("EventHandler object requires a callable " - "object function for the handler argument not: %r" % handler) - - self.function = handler - self.args = args - self.kargs = kargs - self.event = event - self.hid = self.nexthid() - - - def __repr__(self): - args = ["event=%s" % self.event, "hid=%d" % self.hid, - "function=%r" % self.function] - - if self.args: - args.append("args=%r" % self.args) - - if self.kargs: - args.append("kargs=%r" % self.kargs) - - return "" % ', '.join(args) - - -class UzblInstance(object): - - # Give all plugins access to the main config dict. - config = CONFIG - - def __init__(self, parent, client_socket): - - # Internal variables. - self.exports = {} - self.handlers = {} - self.parent = parent - self.client_socket = client_socket - - self.depth = 0 - self.buffer = '' - self.pid = None - - # Call the init function in every plugin. The init function in each - # plugin is where that plugin connects functions to events and exports - # functions to the uzbl object. - for plugin in self.parent['plugins'].values(): - try: - plugin.init(self) - - except: - raise - - - def send(self, msg): - '''Send a command to the uzbl instance via the socket file.''' - - msg = msg.strip() - if self.client_socket: - print '%s<-- %s' % (' ' * self.depth, msg) - self.client_socket.send(("%s\n" % msg).encode('utf-8')) - - else: - print '%s!-- %s' % (' ' * self.depth, msg) - - - def export(self, name, function): - '''Export `function(uzbl, *args, ..)` inside a plugin to the uzbl - object like so `uzbl.function(*args, ..)`. This will allow other - plugins to call functions inside the current plugin (which is currently - calling this function) via the uzbl object.''' - - self.__dict__.__setitem__(name, partial(function, self)) - - - def export_dict(self, export_dict): - '''Export multiple (name, function)'s at once inside a dict of the - form `{name1: function1, name2: function2, ...}`.''' - - for (name, function) in export_dict.items(): - self.export(name, function) - - - def connect(self, event, handler, *args, **kargs): - '''Connect a uzbl event with a handler. Handlers can either be a - function or a uzbl command string.''' - - event = event.upper().strip() - assert event and ' ' not in event - - if event not in self.handlers.keys(): - self.handlers[event] = [] - - handlerobj = EventHandler(event, handler, *args, **kargs) - self.handlers[event].append(handlerobj) - print handlerobj - - - def connect_dict(self, connect_dict): - '''Connect a dictionary comprising of {"EVENT_NAME": handler, ..} to - the event handler stack. - - If you need to supply args or kargs to an event use the normal connect - function.''' - - for (event, handler) in connect_dict.items(): - self.connect(event, handler) - - - def remove_by_id(self, hid): - '''Remove connected event handler by unique handler id.''' - - for (event, handlers) in self.handlers.items(): - for handler in list(handlers): - if hid != handler.hid: - continue - - echo("removed %r" % handler) - handlers.remove(handler) - return - - echo('unable to find & remove handler with id: %d' % hid) - - - def remove(self, handler): - '''Remove connected event handler.''' - - for (event, handlers) in self.handlers.items(): - if handler in handlers: - echo("removed %r" % handler) - handlers.remove(handler) - return - - echo('unable to find & remove handler: %r' % handler) - - - def exec_handler(self, handler, *args, **kargs): - '''Execute event handler function.''' - - args += handler.args - kargs = dict(handler.kargs.items()+kargs.items()) - handler.function(self, *args, **kargs) - - - def event(self, event, *args, **kargs): - '''Raise an event.''' - - event = event.upper() - elems = [event,] - if args: elems.append(unicode(args)) - if kargs: elems.append(unicode(kargs)) - print "%s--> %s" % (' ' * self.depth, ' '.join(elems)) - - if event == "INSTANCE_START" and args: - self.pid = int(args[0]) - - if event not in self.handlers: - return - - for handler in self.handlers[event]: - self.depth += 1 - try: - self.exec_handler(handler, *args, **kargs) - - except: - print_exc() - - self.depth -= 1 - - - def close(self): - '''Close the client socket and clean up.''' - - try: - self.client_socket.close() - - except: - pass - - for (name, plugin) in self.parent['plugins'].items(): - if hasattr(plugin, 'cleanup'): - plugin.cleanup(self) - - -class UzblEventDaemon(dict): - def __init__(self): - - # Init variables and dict keys. - dict.__init__(self, {'uzbls': {}}) - self.running = None - self.server_socket = None - self.socket_location = None - - # Register that the event daemon server has started by creating the - # pid file. - make_pid_file(CONFIG['pid_file']) - - # Register a function to clean up the socket and pid file on exit. - atexit.register(self.quit) - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) - - # Load plugins, first-build of the plugins may be a costly operation. - self['plugins'] = load_plugins(CONFIG['plugin_dirs'], - CONFIG['plugins_load'], CONFIG['plugins_ignore']) - - - def _create_server_socket(self): - '''Create the event manager daemon socket for uzbl instance duplex - communication.''' - - server_socket = CONFIG['server_socket'] - server_socket = os.path.realpath(os.path.expandvars(server_socket)) - self.socket_location = server_socket - - # Delete socket if it exists. - if os.path.exists(server_socket): - os.remove(server_socket) - - self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.server_socket.bind(server_socket) - self.server_socket.listen(5) - - - def _close_server_socket(self): - '''Close and delete the server socket.''' - - try: - self.server_socket.close() - self.server_socket = None - - if os.path.exists(self.socket_location): - os.remove(self.socket_location) - - except: - pass - - - def run(self): - '''Main event daemon loop.''' - - # Create event daemon socket. - self._create_server_socket() - echo('listening on: %s' % self.socket_location) - - if CONFIG['daemon_mode']: - echo('entering daemon mode.') - daemonize() - # The pid has changed so update the pid file. - make_pid_file(CONFIG['pid_file']) - - # Now listen for incoming connections and or data. - self.listen() - - # Clean up. - self.quit() - - - def listen(self): - '''Accept incoming connections and constantly poll instance sockets - for incoming data.''' - - self.running = True - while self.running: - - sockets = [self.server_socket] + self['uzbls'].keys() - - reads, _, errors = select(sockets, [], sockets, 1) - - if self.server_socket in reads: - self.accept_connection() - reads.remove(self.server_socket) - - for client in reads: - self.read_socket(client) - - for client in errors: - error('Unknown error on socket: %r' % client) - self.close_connection(client) - - - def read_socket(self, client): - '''Read data from an instance socket and pass to the uzbl objects - event handler function.''' - - uzbl = self['uzbls'][client] - try: - raw = unicode(client.recv(8192), 'utf-8', 'ignore') - - except: - print_exc() - raw = None - - if not raw: - # Read null byte, close socket. - return self.close_connection(client) - - uzbl.buffer += raw - msgs = uzbl.buffer.split('\n') - uzbl.buffer = msgs.pop() - - for msg in msgs: - try: - parse_msg(uzbl, msg.strip()) - - except: - print_exc() - - - def accept_connection(self): - '''Accept incoming connection to the server socket.''' - - client_socket = self.server_socket.accept()[0] - - uzbl = UzblInstance(self, client_socket) - self['uzbls'][client_socket] = uzbl - - - def close_connection(self, client): - '''Clean up after instance close.''' - - try: - if client in self['uzbls']: - uzbl = self['uzbls'][client] - uzbl.close() - del self['uzbls'][client] - - except: - print_exc() - - if not len(self['uzbls']) and CONFIG['auto_close']: - echo('auto closing event manager.') - self.running = False - - - def quit(self): - '''Close all instance socket objects, server socket and delete the - pid file.''' - - echo('shutting down event manager.') - - for client in self['uzbls'].keys(): - self.close_connection(client) - - echo('unlinking: %r' % self.socket_location) - self._close_server_socket() - - echo('deleting pid file: %r' % CONFIG['pid_file']) - del_pid_file(CONFIG['pid_file']) - - -def stop_action(): - '''Stop the event manager daemon.''' - - pid_file = CONFIG['pid_file'] - if not os.path.isfile(pid_file): - return echo('no running daemon found.') - - echo('found pid file: %r' % pid_file) - pid = get_pid(pid_file) - if not pid_running(pid): - echo('no process with pid: %d' % pid) - return os.remove(pid_file) - - echo("terminating process with pid: %d" % pid) - term_process(pid) - if os.path.isfile(pid_file): - os.remove(pid_file) - - echo('stopped event daemon.') - - -def start_action(): - '''Start the event manager daemon.''' - - pid_file = CONFIG['pid_file'] - if os.path.isfile(pid_file): - echo('found pid file: %r' % pid_file) - pid = get_pid(pid_file) - if pid_running(pid): - return echo('event daemon already started with pid: %d' % pid) - - echo('no process with pid: %d' % pid) - os.remove(pid_file) - - echo('starting event manager.') - UzblEventDaemon().run() - - -def restart_action(): - '''Restart the event manager daemon.''' - - echo('restarting event manager daemon.') - stop_action() - start_action() - - -def list_action(): - '''List all the plugins being loaded by the event daemon.''' - - plugins = find_plugins(CONFIG['plugin_dirs']) - dirs = {} - - for (plugin, plugin_dir) in plugins.items(): - if plugin_dir not in dirs: - dirs[plugin_dir] = [] - - dirs[plugin_dir].append(plugin) - - for (index, (plugin_dir, plugin_list)) in enumerate(sorted(dirs.items())): - if index: - print - - print "%s:" % plugin_dir - for plugin in sorted(plugin_list): - print " %s" % plugin - - -if __name__ == "__main__": - USAGE = "usage: %prog [options] {start|stop|restart|list}" - PARSER = OptionParser(usage=USAGE) - PARSER.add_option('-v', '--verbose', dest='verbose', action="store_true", - help="print verbose output.") - - PARSER.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store", - metavar="DIRS", help="Specify plugin directories in the form of "\ - "'dir1:dir2:dir3'.") - - PARSER.add_option('-l', '--load-plugins', dest="load", action="store", - metavar="PLUGINS", help="comma separated list of plugins to load") - - PARSER.add_option('-i', '--ignore-plugins', dest="ignore", action="store", - metavar="PLUGINS", help="comma separated list of plugins to ignore") - - PARSER.add_option('-p', '--pid-file', dest='pid', action='store', - metavar='FILE', help="specify pid file location") - - PARSER.add_option('-s', '--server-socket', dest='socket', action='store', - metavar='SOCKET', help="specify the daemon socket location") - - PARSER.add_option('-n', '--no-daemon', dest="daemon", - action="store_true", help="don't enter daemon mode.") - - PARSER.add_option('-a', '--auto-close', dest='autoclose', - action='store_true', help='auto close after all instances disconnect.') - - (OPTIONS, ARGS) = PARSER.parse_args() - - # init like {start|stop|..} daemon actions dict. - DAEMON_ACTIONS = {'start': start_action, 'stop': stop_action, - 'restart': restart_action, 'list': list_action} - - if not ARGS: - ACTION = 'start' - - elif len(ARGS) == 1: - ACTION = ARGS[0] - if ACTION not in DAEMON_ACTIONS: - raise ArgumentError("unknown argument: %r" % ACTION) - - else: - raise ArgumentError("too many arguments: %r" % ARGS) - - # parse other flags & options. - if OPTIONS.verbose: - CONFIG['verbose'] = True - - if OPTIONS.plugin_dirs: - PLUGIN_DIRS = [] - for DIR in OPTIONS.plugin_dirs.split(':'): - if not DIR: - continue - - PLUGIN_DIRS.append(os.path.realpath(DIR)) - - CONFIG['plugin_dirs'] = PLUGIN_DIRS - echo("plugin search dirs: %r" % PLUGIN_DIRS) - - if OPTIONS.load and OPTIONS.ignore: - error("you can't load and ignore at the same time.") - sys.exit(1) - - elif OPTIONS.load: - LOAD = CONFIG['plugins_load'] - for PLUGIN in OPTIONS.load.split(','): - if PLUGIN.strip(): - LOAD.append(PLUGIN.strip()) - - echo('only loading plugin(s): %s' % ', '.join(LOAD)) - - elif OPTIONS.ignore: - IGNORE = CONFIG['plugins_ignore'] - for PLUGIN in OPTIONS.ignore.split(','): - if PLUGIN.strip(): - IGNORE.append(PLUGIN.strip()) - - echo('ignoring plugin(s): %s' % ', '.join(IGNORE)) - - if OPTIONS.autoclose: - CONFIG['auto_close'] = True - echo('will auto close.') - - if OPTIONS.pid: - CONFIG['pid_file'] = os.path.realpath(OPTIONS.pid) - echo("pid file location: %r" % CONFIG['pid_file']) - - if OPTIONS.socket: - CONFIG['server_socket'] = os.path.realpath(OPTIONS.socket) - echo("daemon socket location: %s" % CONFIG['server_socket']) - - if OPTIONS.daemon: - CONFIG['daemon_mode'] = False - - # Now {start|stop|...} - DAEMON_ACTIONS[ACTION]() diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed deleted file mode 100755 index 5d1a9f8..0000000 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ /dev/null @@ -1,1417 +0,0 @@ -#!/usr/bin/env python - -# Uzbl tabbing wrapper using a fifo socket interface -# Copyright (c) 2009, Tom Adams -# Copyright (c) 2009, Chris van Dijk -# Copyright (c) 2009, Mason Larobina -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# Author(s): -# Tom Adams -# Wrote the original uzbl_tabbed.py as a proof of concept. -# -# Chris van Dijk (quigybo) -# Made signifigant headway on the old uzbl_tabbing.py script on the -# uzbl wiki -# -# Mason Larobina -# Rewrite of the uzbl_tabbing.py script to use a fifo socket interface -# and inherit configuration options from the user's uzbl config. -# -# Contributor(s): -# mxey -# uzbl_config path now honors XDG_CONFIG_HOME if it exists. -# -# Romain Bignon -# Fix for session restoration code. -# -# Jake Probst -# Wrote a patch that overflows tabs in the tablist on to new lines when -# running of room. -# -# Devon Jones -# Fifo command bring_to_front which brings the gtk window to focus. -# -# Simon Lipp (sloonz) -# Various - - -# Dependencies: -# pygtk - python bindings for gtk. -# pango - python bindings needed for text rendering & layout in gtk widgets. -# pygobject - GLib's GObject bindings for python. -# -# Optional dependencies: -# simplejson - save uzbl_tabbed.py sessions & presets in json. -# -# Note: I haven't included version numbers with this dependency list because -# I've only ever tested uzbl_tabbed.py on the latest stable versions of these -# packages in Gentoo's portage. Package names may vary on different systems. - - -# Configuration: -# Because this version of uzbl_tabbed is able to inherit options from your main -# uzbl configuration file you may wish to configure uzbl tabbed from there. -# Here is a list of configuration options that can be customised and some -# example values for each: -# -# General tabbing options: -# show_tablist = 1 -# show_gtk_tabs = 0 -# tablist_top = 1 -# gtk_tab_pos = (top|left|bottom|right) -# gtk_refresh = 1000 -# switch_to_new_tabs = 1 -# capture_new_windows = 1 -# multiline_tabs = 1 -# -# Tab title options: -# tab_titles = 1 -# tab_indexes = 1 -# new_tab_title = Loading -# max_title_len = 50 -# show_ellipsis = 1 -# -# Session options: -# save_session = 1 -# json_session = 0 -# session_file = $HOME/.local/share/uzbl/session -# -# Inherited uzbl options: -# fifo_dir = /tmp -# socket_dir = /tmp -# icon_path = $HOME/.local/share/uzbl/uzbl.png -# status_background = #303030 -# -# Misc options: -# window_size = 800,800 -# verbose = 0 -# -# And uzbl_tabbed.py takes care of the actual binding of the commands via each -# instances fifo socket. -# -# Custom tab styling: -# tab_colours = foreground = "#888" background = "#303030" -# tab_text_colours = foreground = "#bbb" -# selected_tab = foreground = "#fff" -# selected_tab_text = foreground = "green" -# tab_indicate_https = 1 -# https_colours = foreground = "#888" -# https_text_colours = foreground = "#9c8e2d" -# selected_https = foreground = "#fff" -# selected_https_text = foreground = "gold" -# -# How these styling values are used are soley defined by the syling policy -# handler below (the function in the config section). So you can for example -# turn the tab text colour Firetruck-Red in the event "error" appears in the -# tab title or some other arbitrary event. You may wish to make a trusted -# hosts file and turn tab titles of tabs visiting trusted hosts purple. - - -# Issues: -# - new windows are not caught and opened in a new tab. -# - when uzbl_tabbed.py crashes it takes all the children with it. -# - when a new tab is opened when using gtk tabs the tab button itself -# grabs focus from its child for a few seconds. -# - when switch_to_new_tabs is not selected the notebook page is -# maintained but the new window grabs focus (try as I might to stop it). - - -# Todo: -# - add command line options to use a different session file, not use a -# session file and or open a uri on starup. -# - ellipsize individual tab titles when the tab-list becomes over-crowded -# - add "<" & ">" arrows to tablist to indicate that only a subset of the -# currently open tabs are being displayed on the tablist. -# - add the small tab-list display when both gtk tabs and text vim-like -# tablist are hidden (I.e. [ 1 2 3 4 5 ]) -# - check spelling. -# - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into -# the collective. Resistance is futile! - - -import pygtk -import gtk -import subprocess -import os -import re -import time -import getopt -import pango -import select -import sys -import gobject -import socket -import random -import hashlib -import atexit -import types - -from gobject import io_add_watch, source_remove, timeout_add, IO_IN, IO_HUP -from signal import signal, SIGTERM, SIGINT -from optparse import OptionParser, OptionGroup - - -pygtk.require('2.0') - -_SCRIPTNAME = os.path.basename(sys.argv[0]) -def error(msg): - sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return os.path.join(os.environ['HOME'], default) - -# Setup xdg paths. -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') - -# Ensure uzbl xdg paths exist -if not os.path.exists(DATA_DIR): - os.makedirs(DATA_DIR) - -# All of these settings can be inherited from your uzbl config file. -config = { - # Tab options - 'show_tablist': True, # Show text uzbl like statusbar tab-list - 'show_gtk_tabs': False, # Show gtk notebook tabs - 'tablist_top': True, # Display tab-list at top of window - 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) - 'gtk_refresh': 1000, # Tablist refresh millisecond interval - 'switch_to_new_tabs': True, # Upon opening a new tab switch to it - 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows - 'multiline_tabs': True, # Tabs overflow onto new tablist lines. - - # Tab title options - 'tab_titles': True, # Display tab titles (else only tab-nums) - 'tab_indexes': True, # Display tab nums (else only tab titles) - 'new_tab_title': 'Loading', # New tab title - 'max_title_len': 50, # Truncate title at n characters - 'show_ellipsis': True, # Show ellipsis when truncating titles - - # Session options - 'save_session': True, # Save session in file when quit - 'json_session': False, # Use json to save session. - 'saved_sessions_dir': os.path.join(DATA_DIR, 'sessions/'), - 'session_file': os.path.join(DATA_DIR, 'session'), - - # Inherited uzbl options - 'fifo_dir': '/tmp', # Path to look for uzbl fifo. - 'socket_dir': '/tmp', # Path to look for uzbl socket. - 'icon_path': os.path.join(DATA_DIR, 'uzbl.png'), - 'status_background': "#303030", # Default background for all panels. - - # Misc options - 'window_size': "800,800", # width,height in pixels. - 'verbose': False, # Print verbose output. - - # Add custom tab style definitions to be used by the tab colour policy - # handler here. Because these are added to the config dictionary like - # any other uzbl_tabbed configuration option remember that they can - # be superseeded from your main uzbl config file. - 'tab_colours': 'foreground = "#888" background = "#303030"', - 'tab_text_colours': 'foreground = "#bbb"', - 'selected_tab': 'foreground = "#fff"', - 'selected_tab_text': 'foreground = "green"', - 'tab_indicate_https': True, - 'https_colours': 'foreground = "#888"', - 'https_text_colours': 'foreground = "#9c8e2d"', - 'selected_https': 'foreground = "#fff"', - 'selected_https_text': 'foreground = "gold"', - -} # End of config dict. - -UZBL_TABBED_VARS = config.keys() - -# This is the tab style policy handler. Every time the tablist is updated -# this function is called to determine how to colourise that specific tab -# according the simple/complex rules as defined here. You may even wish to -# move this function into another python script and import it using: -# from mycustomtabbingconfig import colour_selector -# Remember to rename, delete or comment out this function if you do that. - -def colour_selector(tabindex, currentpage, uzbl): - '''Tablist styling policy handler. This function must return a tuple of - the form (tab style, text style).''' - - # Just as an example: - # if 'error' in uzbl.title: - # if tabindex == currentpage: - # return ('foreground="#fff"', 'foreground="red"') - # return ('foreground="#888"', 'foreground="red"') - - # Style tabs to indicate connected via https. - if config['tab_indicate_https'] and uzbl.uri.startswith("https://"): - if tabindex == currentpage: - return (config['selected_https'], config['selected_https_text']) - return (config['https_colours'], config['https_text_colours']) - - # Style to indicate selected. - if tabindex == currentpage: - return (config['selected_tab'], config['selected_tab_text']) - - # Default tab style. - return (config['tab_colours'], config['tab_text_colours']) - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def echo(msg): - if config['verbose']: - sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) - - -def counter(): - '''To infinity and beyond!''' - - i = 0 - while True: - i += 1 - yield i - - -def escape(s): - '''Replaces html markup in tab titles that screw around with pango.''' - - for (split, glue) in [('&','&'), ('<', '<'), ('>', '>')]: - s = s.replace(split, glue) - return s - - -class SocketClient: - '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance''' - - # List of UzblInstance objects not already linked with a SocketClient - instances_queue = {} - - def __init__(self, socket): - self._buffer = "" - self._socket = socket - self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\ - io_add_watch(socket, IO_HUP, self._socket_closed)] - self.uzbl = None - - - def _socket_recv(self, fd, condition): - '''Data available on socket, process it''' - - self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ? - return True - - - def _socket_closed(self, fd, condition): - '''Remote client exited''' - self.uzbl.close() - return False - - - def _feed(self, data): - '''An Uzbl instance sent some data, parse it''' - - self._buffer += data - if self.uzbl: - if "\n" in self._buffer: - cmds = self._buffer.split("\n") - - if cmds[-1]: # Last command has been received incomplete, don't process it - self._buffer, cmds = cmds[-1], cmds[:-1] - else: - self._buffer = "" - - for cmd in cmds: - if cmd: - self.uzbl.parse_command(cmd) - else: - name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M) - uzbl = self.instances_queue.get(name[0]) - if uzbl: - del self.instances_queue[name[0]] - self.uzbl = uzbl - self.uzbl.got_socket(self) - self._feed("") - - def send(self, data): - '''Child socket send function.''' - - self._socket.send(data + "\n") - - def close(self): - '''Close the connection''' - - if self._socket: - self._socket.close() - self._socket = None - map(source_remove, self._watchers) - self._watchers = [] - - -class UzblInstance: - '''Uzbl instance meta-data/meta-action object.''' - - def __init__(self, parent, tab, name, uri, title, switch): - - self.parent = parent - self.tab = tab - self.name = name - self.title = title - self.tabtitle = "" - self.uri = uri - self._client = None - self._switch = switch # Switch to tab after loading ? - self.title_changed() - - - def got_socket(self, client): - '''Uzbl instance is now connected''' - - self._client = client - self.parent.config_uzbl(self) - if self._switch: - tabid = self.parent.notebook.page_num(self.tab) - self.parent.goto_tab(tabid) - - - def title_changed(self, gtk_only = True): # GTK-only is for indexes - '''self.title has changed, update the tabs list''' - - tab_titles = config['tab_titles'] - tab_indexes = config['tab_indexes'] - show_ellipsis = config['show_ellipsis'] - max_title_len = config['max_title_len'] - - # Unicode heavy strings do not like being truncated/sliced so by - # re-encoding the string sliced of limbs are removed. - self.tabtitle = self.title[:max_title_len + int(show_ellipsis)] - if type(self.tabtitle) != types.UnicodeType: - self.tabtitle = unicode(self.tabtitle, 'utf-8', 'ignore') - - self.tabtitle = self.tabtitle.encode('utf-8', 'ignore').strip() - - if show_ellipsis and len(self.tabtitle) != len(self.title): - self.tabtitle += "\xe2\x80\xa6" - - gtk_tab_format = "%d %s" - index = self.parent.notebook.page_num(self.tab) - if tab_titles and tab_indexes: - self.parent.notebook.set_tab_label_text(self.tab, - gtk_tab_format % (index, self.tabtitle)) - elif tab_titles: - self.parent.notebook.set_tab_label_text(self.tab, self.tabtitle) - else: - self.parent.notebook.set_tab_label_text(self.tab, str(index)) - - # If instance is current tab, update window title - if index == self.parent.notebook.get_current_page(): - title_format = "%s - Uzbl Browser" - self.parent.window.set_title(title_format % self.title) - - # Non-GTK tabs - if not gtk_only: - self.parent.update_tablist() - - - def set(self, key, val): - ''' Send the SET command to Uzbl ''' - - if self._client: - self._client.send('set %s = %s') #TODO: escape chars ? - - - def exit(self): - ''' Ask the Uzbl instance to close ''' - - if self._client: - self._client.send('exit') - - - def parse_command(self, cmd): - ''' Parse event givent by the Uzbl instance ''' - - type, _, args = cmd.split(" ", 2) - if type == "EVENT": - type, args = args.split(" ", 1) - if type == "TITLE_CHANGED": - self.title = args - self.title_changed() - elif type == "VARIABLE_SET": - var, _, val = args.split(" ", 2) - try: - val = int(val) - except: - pass - - if var in UZBL_TABBED_VARS: - if config[var] != val: - config[var] = val - if var == "show_gtk_tabs": - self.parent.notebook.set_show_tabs(bool(val)) - elif var == "show_tablist" or var == "tablist_top": - self.parent.update_tablist_display() - elif var == "gtk_tab_pos": - self.parent.update_gtk_tab_pos() - elif var == "status_background": - col = gtk.gdk.color_parse(config['status_background']) - self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) - elif var == "tab_titles" or var == "tab_indexes": - for tab in self.parent.notebook: - self.parent.tabs[tab].title_changed(True) - - self.parent.update_tablist() - else: - config[var] = val - - if var == "uri": - self.uri = var - self.parent.update_tablist() - elif type == "NEW_TAB": - self.parent.new_tab(args) - elif type == "NEXT_TAB": - if args: - self.parent.next_tab(int(args)) - else: - self.parent.next_tab() - elif type == "PREV_TAB": - if args: - self.parent.prev_tab(int(args)) - else: - self.parent.prev_tab() - elif type == "GOTO_TAB": - self.parent.goto_tab(int(args)) - elif type == "FIRST_TAB": - self.parent.goto_tab(0) - elif type == "LAST_TAB": - self.parent.goto_tab(-1) - elif type == "PRESET_TABS": - self.parent.parse_command(["preset"] + args.split()) - elif type == "BRING_TO_FRONT": - self.parent.window.present() - elif type == "CLEAN_TABS": - self.parent.clean_slate() - elif type == "EXIT_ALL_TABS": - self.parent.quitrequest() - - - def close(self): - '''The remote instance exited''' - - if self._client: - self._client.close() - self._client = None - - -class UzblTabbed: - '''A tabbed version of uzbl using gtk.Notebook''' - - def __init__(self): - '''Create tablist, window and notebook.''' - - self._timers = {} - self._buffer = "" - self._killed = False - - # A list of the recently closed tabs - self._closed = [] - - # Holds metadata on the uzbl childen open. - self.tabs = {} - - # Uzbl sockets (socket => SocketClient) - self.clients = {} - - # Generates a unique id for uzbl socket filenames. - self.next_pid = counter().next - - # Create main window - self.window = gtk.Window() - try: - window_size = map(int, config['window_size'].split(',')) - self.window.set_default_size(*window_size) - - except: - error("Invalid value for default_size in config file.") - - self.window.set_title("Uzbl Browser") - self.window.set_border_width(0) - - # Set main window icon - icon_path = config['icon_path'] - if os.path.exists(icon_path): - self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) - - else: - icon_path = '/usr/share/uzbl/examples/data/uzbl/uzbl.png' - if os.path.exists(icon_path): - self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) - - # Attach main window event handlers - self.window.connect("delete-event", self.quitrequest) - - # Create tab list - vbox = gtk.VBox() - self.vbox = vbox - self.window.add(vbox) - ebox = gtk.EventBox() - self.ebox = ebox - self.tablist = gtk.Label() - - self.tablist.set_use_markup(True) - self.tablist.set_justify(gtk.JUSTIFY_LEFT) - self.tablist.set_line_wrap(False) - self.tablist.set_selectable(False) - self.tablist.set_padding(2,2) - self.tablist.set_alignment(0,0) - self.tablist.set_ellipsize(pango.ELLIPSIZE_END) - self.tablist.set_text(" ") - self.tablist.show() - ebox.add(self.tablist) - ebox.show() - bgcolor = gtk.gdk.color_parse(config['status_background']) - ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) - - # Create notebook - self.notebook = gtk.Notebook() - self.notebook.set_show_tabs(config['show_gtk_tabs']) - - # Set tab position - self.update_gtk_tab_pos() - - self.notebook.set_show_border(False) - self.notebook.set_scrollable(True) - self.notebook.set_border_width(0) - - self.notebook.connect("page-removed", self.tab_closed) - self.notebook.connect("switch-page", self.tab_changed) - self.notebook.connect("page-added", self.tab_opened) - - self.notebook.show() - vbox.pack_start(self.notebook, True, True, 0) - vbox.reorder_child(self.notebook, 1) - self.update_tablist_display() - - self.vbox.show() - self.window.show() - self.wid = self.notebook.window.xid - - # Store information about the applications fifo and socket. - fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid() - socket_filename = 'uzbltabbed_%d.socket' % os.getpid() - self._fifo = None - self._socket = None - self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename) - self.socket_path = os.path.join(config['socket_dir'], socket_filename) - - # Now initialise the fifo and the socket - self.init_fifo() - self.init_socket() - - # If we are using sessions then load the last one if it exists. - if config['save_session']: - self.load_session() - - - def run(self): - '''UzblTabbed main function that calls the gtk loop.''' - - if not self.clients and not SocketClient.instances_queue and not self.tabs: - self.new_tab() - - gtk_refresh = int(config['gtk_refresh']) - if gtk_refresh < 100: - gtk_refresh = 100 - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) - - # Catch keyboard interrupts - signal(SIGINT, lambda signum, stack_frame: self.terminate(SIGINT)) - - try: - gtk.main() - - except: - error("encounted error %r" % sys.exc_info()[1]) - - # Unlink fifo socket - self.unlink_fifo() - self.close_socket() - - # Attempt to close all uzbl instances nicely. - self.quitrequest() - - # Allow time for all the uzbl instances to quit. - time.sleep(1) - - raise - - - def terminate(self, termsig=None): - '''Handle termination signals and exit safely and cleanly.''' - - # Not required but at least it lets the user know what killed his - # browsing session. - if termsig == SIGTERM: - error("caught SIGTERM signal") - - elif termsig == SIGINT: - error("caught keyboard interrupt") - - else: - error("caught unknown signal") - - error("commencing infanticide!") - - # Sends the exit signal to all uzbl instances. - self.quitrequest() - - - def init_socket(self): - '''Create interprocess communication socket.''' - - def accept(sock, condition): - '''A new uzbl instance was created''' - - client, _ = sock.accept() - self.clients[client] = SocketClient(client) - - return True - - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.bind(self.socket_path) - sock.listen(1) - - # Add event handler for IO_IN event. - self._socket = (sock, io_add_watch(sock, IO_IN, accept)) - - echo("[socket] listening at %r" % self.socket_path) - - # Add atexit register to destroy the socket on program termination. - atexit.register(self.close_socket) - - - def close_socket(self): - '''Close the socket when closing the application''' - - if self._socket: - (fd, watcher) = self._socket - source_remove(watcher) - fd.close() - os.unlink(self.socket_path) - self._socket = None - - - def init_fifo(self): - '''Create interprocess communication fifo.''' - - if os.path.exists(self.fifo_path): - if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK): - os.mkfifo(self.fifo_path) - - else: - basedir = os.path.dirname(self.fifo_path) - if not os.path.exists(basedir): - os.makedirs(basedir) - - os.mkfifo(self.fifo_path) - - # Add event handlers for IO_IN & IO_HUP events. - self.setup_fifo_watchers() - - echo("[fifo] listening at %r" % self.fifo_path) - - # Add atexit register to destroy the fifo on program termination. - atexit.register(self.unlink_fifo) - - - def unlink_fifo(self): - '''Unlink the fifo socket. Note: This function is called automatically - on exit by an atexit register.''' - - # Make sure the fifo fd is closed. - self.close_fifo() - - # And unlink if the real fifo exists. - if os.path.exists(self.fifo_path): - os.unlink(self.fifo_path) - echo("unlinked %r" % self.fifo_path) - - - def close_fifo(self): - '''Remove all event handlers watching the fifo and close the fd.''' - - # Already closed - if self._fifo is None: return - - (fd, watchers) = self._fifo - os.close(fd) - - # Stop all gobject io watchers watching the fifo. - for gid in watchers: - source_remove(gid) - - self._fifo = None - - - def setup_fifo_watchers(self): - '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event - handlers.''' - - # Close currently open fifo fd and kill all watchers - self.close_fifo() - - fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK) - - # Add gobject io event handlers to the fifo socket. - watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\ - io_add_watch(fd, IO_HUP, self.main_fifo_hangup)] - - self._fifo = (fd, watchers) - - - def main_fifo_hangup(self, fd, cb_condition): - '''Handle main fifo socket hangups.''' - - # Close old fd, open new fifo socket and add io event handlers. - self.setup_fifo_watchers() - - # Kill the gobject event handler calling this handler function. - return False - - - def main_fifo_read(self, fd, cb_condition): - '''Read from main fifo socket.''' - - self._buffer = os.read(fd, 1024) - temp = self._buffer.split("\n") - self._buffer = temp.pop() - cmds = [s.strip().split() for s in temp if len(s.strip())] - - for cmd in cmds: - try: - #print cmd - self.parse_command(cmd) - - except: - error("parse_command: invalid command %s" % ' '.join(cmd)) - raise - - return True - - - def parse_command(self, cmd): - '''Parse instructions from uzbl child processes.''' - - # Commands ( [] = optional, {} = required ) - # new [uri] - # open new tab and head to optional uri. - # close [tab-num] - # close current tab or close via tab id. - # next [n-tabs] - # open next tab or n tabs down. Supports negative indexing. - # prev [n-tabs] - # open prev tab or n tabs down. Supports negative indexing. - # goto {tab-n} - # goto tab n. - # first - # goto first tab. - # last - # goto last tab. - # title {pid} {document-title} - # updates tablist title. - # uri {pid} {document-location} - # updates tablist uri - # bring_to_front - # brings the gtk window to focus. - # exit - # exits uzbl_tabbed.py - - if cmd[0] == "new": - if len(cmd) == 2: - self.new_tab(cmd[1]) - - else: - self.new_tab() - - elif cmd[0] == "newfromclip": - uri = subprocess.Popen(['xclip','-selection','clipboard','-o'],\ - stdout=subprocess.PIPE).communicate()[0] - if uri: - self.new_tab(uri) - - elif cmd[0] == "close": - if len(cmd) == 2: - self.close_tab(int(cmd[1])) - - else: - self.close_tab() - - elif cmd[0] == "next": - if len(cmd) == 2: - self.next_tab(int(cmd[1])) - - else: - self.next_tab() - - elif cmd[0] == "prev": - if len(cmd) == 2: - self.prev_tab(int(cmd[1])) - - else: - self.prev_tab() - - elif cmd[0] == "goto": - self.goto_tab(int(cmd[1])) - - elif cmd[0] == "first": - self.goto_tab(0) - - elif cmd[0] == "last": - self.goto_tab(-1) - - elif cmd[0] in ["title", "uri"]: - if len(cmd) > 2: - uzbl = self.get_tab_by_name(int(cmd[1])) - if uzbl: - old = getattr(uzbl, cmd[0]) - new = ' '.join(cmd[2:]) - setattr(uzbl, cmd[0], new) - if old != new: - self.update_tablist() - - else: - error("parse_command: no uzbl with name %r" % int(cmd[1])) - - elif cmd[0] == "preset": - if len(cmd) < 3: - error("parse_command: invalid preset command") - - elif cmd[1] == "save": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - self.save_session(path) - - elif cmd[1] == "load": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - self.load_session(path) - - elif cmd[1] == "del": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - if os.path.isfile(path): - os.remove(path) - - else: - error("parse_command: preset %r does not exist." % path) - - elif cmd[1] == "list": - uzbl = self.get_tab_by_name(int(cmd[2])) - if uzbl: - if not os.path.isdir(config['saved_sessions_dir']): - js = "js alert('No saved presets.');" - uzbl._client.send(js) - - else: - listdir = os.listdir(config['saved_sessions_dir']) - listdir = "\\n".join(listdir) - js = "js alert('Session presets:\\n\\n%s');" % listdir - uzbl._client.send(js) - - else: - error("parse_command: unknown tab name.") - - else: - error("parse_command: unknown parse command %r"\ - % ' '.join(cmd)) - - elif cmd[0] == "bring_to_front": - self.window.present() - - elif cmd[0] == "clean": - self.clean_slate() - - elif cmd[0] == "exit": - self.quitrequest() - - else: - error("parse_command: unknown command %r" % ' '.join(cmd)) - - - def get_tab_by_name(self, name): - '''Return uzbl instance by name.''' - - for (tab, uzbl) in self.tabs.items(): - if uzbl.name == name: - return uzbl - - return False - - - def new_tab(self, uri='', title='', switch=None): - '''Add a new tab to the notebook and start a new instance of uzbl. - Use the switch option to negate config['switch_to_new_tabs'] option - when you need to load multiple tabs at a time (I.e. like when - restoring a session from a file).''' - - tab = gtk.Socket() - tab.show() - self.notebook.append_page(tab) - sid = tab.get_id() - uri = uri.strip() - name = "%d-%d" % (os.getpid(), self.next_pid()) - - if switch is None: - switch = config['switch_to_new_tabs'] - - if not title: - title = config['new_tab_title'] - - cmd = ['uzbl-browser', '-n', name, '-s', str(sid), - '--connect-socket', self.socket_path, '--uri', uri] - subprocess.Popen(cmd) # TODO: do i need close_fds=True ? - - uzbl = UzblInstance(self, tab, name, uri, title, switch) - SocketClient.instances_queue[name] = uzbl - self.tabs[tab] = uzbl - - - def clean_slate(self): - '''Close all open tabs and open a fresh brand new one.''' - - self.new_tab() - tabs = self.tabs.keys() - for tab in list(self.notebook)[:-1]: - if tab not in tabs: continue - uzbl = self.tabs[tab] - uzbl.exit() - - - def config_uzbl(self, uzbl): - '''Send bind commands for tab new/close/next/prev to a uzbl - instance.''' - - # Set definitions here - # set(key, command back to fifo) - if config['capture_new_windows']: - uzbl.set("new_window", r'new $8') - - - def goto_tab(self, index): - '''Goto tab n (supports negative indexing).''' - - title_format = "%s - Uzbl Browser" - - tabs = list(self.notebook) - if 0 <= index < len(tabs): - self.notebook.set_current_page(index) - uzbl = self.tabs[self.notebook.get_nth_page(index)] - self.window.set_title(title_format % uzbl.title) - self.update_tablist() - return None - - try: - tab = tabs[index] - # Update index because index might have previously been a - # negative index. - index = tabs.index(tab) - self.notebook.set_current_page(index) - uzbl = self.tabs[self.notebook.get_nth_page(index)] - self.window.set_title(title_format % uzbl.title) - self.update_tablist() - - except IndexError: - pass - - - def next_tab(self, step=1): - '''Switch to next tab or n tabs right.''' - - if step < 1: - error("next_tab: invalid step %r" % step) - return None - - ntabs = self.notebook.get_n_pages() - tabn = (self.notebook.get_current_page() + step) % ntabs - self.goto_tab(tabn) - - - def prev_tab(self, step=1): - '''Switch to prev tab or n tabs left.''' - - if step < 1: - error("prev_tab: invalid step %r" % step) - return None - - ntabs = self.notebook.get_n_pages() - tabn = self.notebook.get_current_page() - step - while tabn < 0: tabn += ntabs - self.goto_tab(tabn) - - - def close_tab(self, tabn=None): - '''Closes current tab. Supports negative indexing.''' - - if tabn is None: - tabn = self.notebook.get_current_page() - - else: - try: - tab = list(self.notebook)[tabn] - - except IndexError: - error("close_tab: invalid index %r" % tabn) - return None - - self.notebook.remove_page(tabn) - - - def tab_opened(self, notebook, tab, index): - '''Called upon tab creation. Called by page-added signal.''' - - if config['switch_to_new_tabs']: - self.notebook.set_focus_child(tab) - - else: - oldindex = self.notebook.get_current_page() - oldtab = self.notebook.get_nth_page(oldindex) - self.notebook.set_focus_child(oldtab) - - - def tab_closed(self, notebook, tab, index): - '''Close the window if no tabs are left. Called by page-removed - signal.''' - - if tab in self.tabs.keys(): - uzbl = self.tabs[tab] - uzbl.close() - - self._closed.append((uzbl.uri, uzbl.title)) - self._closed = self._closed[-10:] - del self.tabs[tab] - - if self.notebook.get_n_pages() == 0: - if not self._killed and config['save_session']: - if os.path.exists(config['session_file']): - os.remove(config['session_file']) - - self.quit() - - for tab in self.notebook: - self.tabs[tab].title_changed(True) - self.update_tablist() - - return True - - - def tab_changed(self, notebook, page, index): - '''Refresh tab list. Called by switch-page signal.''' - - tab = self.notebook.get_nth_page(index) - self.notebook.set_focus_child(tab) - self.update_tablist(index) - return True - - - def update_tablist_display(self): - '''Called when show_tablist or tablist_top has changed''' - - if self.ebox in self.vbox.get_children(): - self.vbox.remove(self.ebox) - - if config['show_tablist']: - self.vbox.pack_start(self.ebox, False, False, 0) - if config['tablist_top']: - self.vbox.reorder_child(self.ebox, 0) - else: - self.vbox.reorder_child(self.ebox, 2) - - def update_gtk_tab_pos(self): - ''' Called when gtk_tab_pos has changed ''' - - allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, - 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} - if config['gtk_tab_pos'] in allposes.keys(): - self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) - - - def update_tablist(self, curpage=None): - '''Upate tablist status bar.''' - - if not config['show_tablist']: - return True - - tab_titles = config['tab_titles'] - tab_indexes = config['tab_indexes'] - multiline_tabs = config['multiline_tabs'] - - if multiline_tabs: - multiline = [] - - tabs = self.tabs.keys() - if curpage is None: - curpage = self.notebook.get_current_page() - - pango = "" - normal = (config['tab_colours'], config['tab_text_colours']) - selected = (config['selected_tab'], config['selected_tab_text']) - - if tab_titles and tab_indexes: - tab_format = " [ %(index)d %(title)s ] " - elif tab_titles: - tab_format = " [ %(title)s ] " - else: - tab_format = " [ %(index)d ] " - - for index, tab in enumerate(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - title = escape(uzbl.tabtitle) - - style = colour_selector(index, curpage, uzbl) - (tabc, textc) = style - - if multiline_tabs: - opango = pango - - pango += tab_format % locals() - - self.tablist.set_markup(pango) - listwidth = self.tablist.get_layout().get_pixel_size()[0] - winwidth = self.window.get_size()[0] - - if listwidth > (winwidth - 20): - multiline.append(opango) - pango = tab_format % locals() - else: - pango += tab_format % locals() - - if multiline_tabs: - multiline.append(pango) - self.tablist.set_markup(' '.join(multiline)) - - else: - self.tablist.set_markup(pango) - - return True - - - def save_session(self, session_file=None): - '''Save the current session to file for restoration on next load.''' - - strip = str.strip - - if session_file is None: - session_file = config['session_file'] - - tabs = self.tabs.keys() - state = [] - for tab in list(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - if not uzbl.uri: continue - state += [(uzbl.uri, uzbl.title),] - - session = {'curtab': self.notebook.get_current_page(), - 'tabs': state} - - if config['json_session']: - raw = json.dumps(session) - - else: - lines = ["curtab = %d" % session['curtab'],] - for (uri, title) in session['tabs']: - lines += ["%s\t%s" % (strip(uri), strip(title)),] - - raw = "\n".join(lines) - - if not os.path.isfile(session_file): - dirname = os.path.dirname(session_file) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - h = open(session_file, 'w') - h.write(raw) - h.close() - - - def load_session(self, session_file=None): - '''Load a saved session from file.''' - - default_path = False - strip = str.strip - json_session = config['json_session'] - delete_loaded = False - - if session_file is None: - default_path = True - delete_loaded = True - session_file = config['session_file'] - - if not os.path.isfile(session_file): - return False - - h = open(session_file, 'r') - raw = h.read() - h.close() - if json_session: - if sum([1 for s in raw.split("\n") if strip(s)]) != 1: - error("Warning: The session file %r does not look json. "\ - "Trying to load it as a non-json session file."\ - % session_file) - json_session = False - - if json_session: - try: - session = json.loads(raw) - curtab, tabs = session['curtab'], session['tabs'] - - except: - error("Failed to load jsonifed session from %r"\ - % session_file) - return None - - else: - tabs = [] - strip = str.strip - curtab, tabs = 0, [] - lines = [s for s in raw.split("\n") if strip(s)] - if len(lines) < 2: - error("Warning: The non-json session file %r looks invalid."\ - % session_file) - return None - - try: - for line in lines: - if line.startswith("curtab"): - curtab = int(line.split()[-1]) - - else: - uri, title = line.split("\t",1) - tabs += [(strip(uri), strip(title)),] - - except: - error("Warning: failed to load session file %r" % session_file) - return None - - session = {'curtab': curtab, 'tabs': tabs} - - # Now populate notebook with the loaded session. - for (index, (uri, title)) in enumerate(tabs): - self.new_tab(uri=uri, title=title, switch=(curtab==index)) - - # A saved session has been loaded now delete it. - if delete_loaded and os.path.exists(session_file): - os.remove(session_file) - - # There may be other state information in the session dict of use to - # other functions. Of course however the non-json session object is - # just a dummy object of no use to no one. - return session - - - def quitrequest(self, *args): - '''Attempt to close all uzbl instances nicely and exit.''' - - self._killed = True - - if config['save_session']: - if len(list(self.notebook)) > 1: - self.save_session() - - else: - # Notebook has one page open so delete the session file. - if os.path.isfile(config['session_file']): - os.remove(config['session_file']) - - for (tab, uzbl) in self.tabs.items(): - uzbl.exit() - - # Add a gobject timer to make sure the application force-quits after a - # reasonable period. Calling quit when all the tabs haven't had time to - # close should be a last resort. - timer = "force-quit" - timerid = timeout_add(5000, self.quit, timer) - self._timers[timer] = timerid - - - def quit(self, *args): - '''Cleanup and quit. Called by delete-event signal.''' - - # Close the fifo socket, remove any gobject io event handlers and - # delete socket. - self.unlink_fifo() - self.close_socket() - - # Remove all gobject timers that are still ticking. - for (timerid, gid) in self._timers.items(): - source_remove(gid) - del self._timers[timerid] - - try: - gtk.main_quit() - - except: - pass - - -if __name__ == "__main__": - - # Build command line parser - usage = "usage: %prog [OPTIONS] {URIS}..." - parser = OptionParser(usage=usage) - parser.add_option('-n', '--no-session', dest='nosession', - action='store_true', help="ignore session saving a loading.") - parser.add_option('-v', '--verbose', dest='verbose', - action='store_true', help='print verbose output.') - - # Parse command line options - (options, uris) = parser.parse_args() - - if options.nosession: - config['save_session'] = False - - if options.verbose: - config['verbose'] = True - - if config['json_session']: - try: - import simplejson as json - - except: - error("Warning: json_session set but cannot import the python "\ - "module simplejson. Fix: \"set json_session = 0\" or "\ - "install the simplejson python module to remove this warning.") - config['json_session'] = False - - if config['verbose']: - import pprint - sys.stderr.write("%s\n" % pprint.pformat(config)) - - uzbl = UzblTabbed() - - # All extra arguments given to uzbl_tabbed.py are interpreted as - # web-locations to opened in new tabs. - lasturi = len(uris)-1 - for (index,uri) in enumerate(uris): - uzbl.new_tab(uri, switch=(index==lasturi)) - - uzbl.run() diff --git a/examples/data/uzbl/scripts/uzblcat b/examples/data/uzbl/scripts/uzblcat deleted file mode 100755 index e955608..0000000 --- a/examples/data/uzbl/scripts/uzblcat +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python -# uzblcat - safely push html to uzbl -# See http://www.uzbl.org/wiki/html-mode - -from sys import stdin, stdout - -stdout.write("uri data:text/html,") -for line in stdin: - stdout.write(line[0:-1]) - -# vim: set noet ff=unix - diff --git a/examples/data/uzbl/style.css b/examples/data/uzbl/style.css deleted file mode 100644 index f9b111e..0000000 --- a/examples/data/uzbl/style.css +++ /dev/null @@ -1,25 +0,0 @@ -.uzbl_highlight { background-color: yellow;} -.uzbl_h_first { background-color: lightgreen;} - -.uzbl_follow { border-style: dotted; - border-width: thin; -} - -#uzbl_hint > div { - display: inline; - border: 2px solid #4a6600; - background-color: #b9ff00; - color: black; - font-size: 9px; - font-weight: bold; - line-height: 9px; - margin: 0px; - padding: 0px; - position: absolute; - z-index: 1000; - -webkit-border-radius: 6px; - text-decoration: none; - -wekit-transform: scale(1) rotate(0deg) translate(-6px,-5px); -} - -/* vim:set et ts=4: */ diff --git a/examples/data/uzbl/uzbl.png b/examples/data/uzbl/uzbl.png deleted file mode 100644 index 773ea84..0000000 Binary files a/examples/data/uzbl/uzbl.png and /dev/null differ diff --git a/sandbox/env.sh b/sandbox/env.sh index 0bf812a..122a7f2 100755 --- a/sandbox/env.sh +++ b/sandbox/env.sh @@ -5,8 +5,9 @@ # - executing limits scope of variables too much (even with exporting) # maybe we should spawn processes from here with an 'exec' at the end? -export XDG_DATA_HOME=./sandbox/examples/data -export XDG_CACHE_HOME=./sandbox/examples/cache -export XDG_CONFIG_HOME=./sandbox/examples/config -#export PATH="./sandbox/usr/local/share/uzbl/examples/data/uzbl/scripts/:$PATH" # needed when running uzbl-browser from here? don't think so.. +export HOME=./sandbox/home +export XDG_DATA_HOME=$HOME/.local/share +export XDG_CACHE_HOME=$HOME/.cache +export XDG_CONFIG_HOME=$HOME/.config +#export PATH="./sandbox/usr/local/share/uzbl/examples/data/scripts/:$PATH" # needed when running uzbl-browser from here? don't think so.. export PATH="./sandbox/usr/local/bin:$PATH" # needed to run uzbl-browser etc from here diff --git a/src/uzbl-browser b/src/uzbl-browser index d9b9752..8a7ab36 100755 --- a/src/uzbl-browser +++ b/src/uzbl-browser @@ -40,7 +40,7 @@ done # if no config exists yet in the recommended location, put the default (recommended) config there if [ ! -f $XDG_CONFIG_HOME/uzbl/config ] then - if ! cp $PREFIX/share/uzbl/examples/config/uzbl/config $XDG_CONFIG_HOME/uzbl/config + if ! cp $PREFIX/share/uzbl/examples/config/config $XDG_CONFIG_HOME/uzbl/config then echo "Could not copy default config to $XDG_CONFIG_HOME/uzbl/config" >&2 exit 3 -- cgit v1.2.3 From 91e081938617e24dea672949c58b8dbad8d19630 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Thu, 7 Jan 2010 21:27:39 +0100 Subject: explain npapi plugins --- docs/FAQ | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/FAQ b/docs/FAQ index 3294999..7587d70 100644 --- a/docs/FAQ +++ b/docs/FAQ @@ -109,8 +109,10 @@ we prefer being reasonably assured that things work as they are supposed to rath That's why we picked the Gtk variant of Webkit. Note that we do *not* depend on any Gnome libraries such as gconf. _That_ would be something worth complaining about :) -### Do you support flash? javascript? Ajax? Recent html/css/.. standards? +### Do you support flash? javascript? Ajax? Recent html/css/.. standards? Java/media plugins? Yes, Webkit takes care of all of that. Not that we like all of these, but you can use them if you want. +We use the NPAPI plugin architecture (just like mozilla, opera, etc) so just +install the plugins normally, and things should work. ### What's the difference between the socket file and the fifo? They both have advantages and disadvantages: -- cgit v1.2.3 From d84b07fd743b1460c0acede3889020165d193dfb Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Sat, 2 Jan 2010 01:18:10 +0100 Subject: eval_js: catch non-object (int, string,...) exceptions --- src/uzbl-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uzbl-core.c b/src/uzbl-core.c index 8a053f6..0acd0c5 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -1077,7 +1077,7 @@ eval_js(WebKitWebView * web_view, gchar *script, GString *result) { JSStringRelease(js_result_string); } - else if (js_exc && JSValueIsObject(context, js_exc)) { + else if (js_exc) { size_t size; JSStringRef prop, val; JSObjectRef exc = JSValueToObject(context, js_exc, NULL); -- cgit v1.2.3 From c8fb24d1069aa776bccdf3141adb9d3c3f6e8101 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Sat, 2 Jan 2010 00:32:19 +0100 Subject: add a scrollbars_visible option --- README | 1 + src/callbacks.c | 13 +++++++++++++ src/callbacks.h | 3 +++ src/uzbl-core.c | 1 + src/uzbl-core.h | 1 + 5 files changed, 19 insertions(+) diff --git a/README b/README index 217e6bf..45a3465 100644 --- a/README +++ b/README @@ -235,6 +235,7 @@ Besides the builtin variables you can also define your own ones and use them in - `fantasy_font_family` = "Pterra" - `serif_font_family` = serif (example "DejaVu Serif") - `sans_serif_font_family` = sans (example "DejaVu Sans") + - `scrollbars_visible`: set to 1 to have GTK scrollbars if the document doesn't fit into the window (defaults to 0) * Constants (not dumpable or writeable): diff --git a/src/callbacks.c b/src/callbacks.c index dab92c1..9130f5f 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -270,6 +270,19 @@ cmd_useragent() { } } +void +cmd_scrollbars_visibility() { + if(uzbl.gui.scrollbars_visible) { + uzbl.gui.bar_h = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (uzbl.gui.scrolled_win)); + uzbl.gui.bar_v = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (uzbl.gui.scrolled_win)); + } + else { + uzbl.gui.bar_v = gtk_range_get_adjustment (GTK_RANGE (uzbl.gui.scbar_v)); + uzbl.gui.bar_h = gtk_range_get_adjustment (GTK_RANGE (uzbl.gui.scbar_h)); + } + gtk_widget_set_scroll_adjustments (GTK_WIDGET (uzbl.gui.web_view), uzbl.gui.bar_h, uzbl.gui.bar_v); +} + /* requires webkit >=1.1.14 */ void cmd_view_source() { diff --git a/src/callbacks.h b/src/callbacks.h index 3f318f2..882ffa4 100644 --- a/src/callbacks.h +++ b/src/callbacks.h @@ -112,6 +112,9 @@ cmd_set_geometry(); void cmd_view_source(); +void +cmd_scrollbars_visibility(); + void cmd_load_start(); diff --git a/src/uzbl-core.c b/src/uzbl-core.c index 0acd0c5..ce1c77d 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -138,6 +138,7 @@ const struct var_name_to_ptr_t { { "default_encoding", PTR_V_STR(uzbl.behave.default_encoding, 1, cmd_default_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)}, /* constants (not dumpable or writeable) */ { "WEBKIT_MAJOR", PTR_C_INT(uzbl.info.webkit_major, NULL)}, diff --git a/src/uzbl-core.h b/src/uzbl-core.h index df9eb1a..70a383c 100644 --- a/src/uzbl-core.h +++ b/src/uzbl-core.h @@ -54,6 +54,7 @@ typedef struct { GtkScrollbar* scbar_h; // (These are still hidden) GtkAdjustment* bar_v; // Information about document length GtkAdjustment* bar_h; // and scrolling position + int scrollbars_visible; WebKitWebView* web_view; gchar* main_title; gchar* icon; -- cgit v1.2.3