diff options
author | David Keijser <keijser@gmail.com> | 2009-10-25 01:26:48 +0200 |
---|---|---|
committer | David Keijser <keijser@gmail.com> | 2009-10-25 01:26:48 +0200 |
commit | f5ca0aa5c05df75dd3268e85feb8947b92486aaf (patch) | |
tree | f89f52338bafdfcbaeb9d589de78659545219de9 | |
parent | fdca08d7d7ae0d6ec41cc23220f8cdd89785e370 (diff) | |
parent | 75c771ecf35605045dd7760269395ca276e8d989 (diff) |
Merge branch 'experimental' of git://github.com/Dieterbe/uzbl into prompt
Conflicts:
examples/data/uzbl/plugins/bind.py
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | AUTHORS | 2 | ||||
-rw-r--r-- | Makefile | 13 | ||||
-rw-r--r-- | README | 79 | ||||
-rw-r--r-- | callbacks.c | 120 | ||||
-rw-r--r-- | callbacks.h | 11 | ||||
-rw-r--r-- | events.c | 103 | ||||
-rw-r--r-- | events.h | 3 | ||||
-rw-r--r-- | examples/config/uzbl/config | 57 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/bind.py (renamed from examples/data/uzbl/scripts/plugins/bind.py) | 123 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/config.py (renamed from examples/data/uzbl/scripts/plugins/config.py) | 0 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/keycmd.py | 395 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/mode.py (renamed from examples/data/uzbl/scripts/plugins/mode.py) | 0 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/on_event.py (renamed from examples/data/uzbl/scripts/plugins/on_event.py) | 0 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/plugin_template.py (renamed from examples/data/uzbl/scripts/plugins/plugin_template.py) | 0 | ||||
-rw-r--r-- | examples/data/uzbl/plugins/progress_bar.py (renamed from examples/data/uzbl/scripts/plugins/progress_bar.py) | 0 | ||||
-rwxr-xr-x | examples/data/uzbl/scripts/event_manager.py | 871 | ||||
-rw-r--r-- | examples/data/uzbl/scripts/plugins/keycmd.py | 382 | ||||
-rw-r--r-- | tests/test-command.c | 265 | ||||
-rw-r--r-- | tests/test-expand.c | 25 | ||||
-rwxr-xr-x | uzbl-browser | 31 | ||||
-rw-r--r-- | uzbl-core.c | 558 | ||||
-rw-r--r-- | uzbl-core.h | 97 | ||||
-rwxr-xr-x | uzbl-daemon | 25 |
24 files changed, 2066 insertions, 1098 deletions
@@ -1,5 +1,5 @@ -uzbl -uzbl.o +uzbl-core +*.o *~ tags examples/data @@ -25,7 +25,7 @@ In alphabetical order: Chris Mason - code snippets such as basic cookie handler Chris van Dijk (quigybo) - work on uzbl_tabbed.py Damien Leon - misc - David Keijser - keycmd line editing patch for the keycmd plugin. + David Keijser - various C and python patches. Devon Jones <devon.jones@gmail.com> - uzbl_tabbed: bring_to_front Dieter Plaetinck (Dieter@be) <dieter AT plaetinck.be> - several contributions Dusan Popovic (dusanx) - many contributions to early uzbl @@ -37,14 +37,17 @@ uzbl-core: ${OBJ} @echo ... done. - uzbl-browser: uzbl-core PREFIX?=$(DESTDIR)/usr/local +# the 'tests' target can never be up to date +.PHONY: tests +force: + # When compiling unit tests, compile uzbl as a library first -tests: uzbl-core.o uzbl-events.o - $(CC) -DUZBL_LIBRARY -shared -Wl uzbl-core.o uzbl-events.o -o ./tests/libuzbl-core.so +tests: ${OBJ} force + $(CC) -shared -Wl ${OBJ} -o ./tests/libuzbl-core.so cd ./tests/; $(MAKE) test: uzbl-core @@ -58,8 +61,10 @@ test-dev: uzbl-core test-dev-browser: uzbl-browser XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/cookie_daemon.py start -nv & + XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./uzbl-daemon start -nv & XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./uzbl-browser --uri http://www.uzbl.org --verbose XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./examples/data/uzbl/scripts/cookie_daemon.py stop -v + XDG_DATA_HOME=./examples/data XDG_CACHE_HOME=./examples/cache XDG_CONFIG_HOME=./examples/config PATH="`pwd`:$$PATH" ./uzbl-daemon stop -v test-share: uzbl-core XDG_DATA_HOME=${PREFIX}/share/uzbl/examples/data XDG_CONFIG_HOME=${PREFIX}/share/uzbl/examples/config ./uzbl-core --uri http://www.uzbl.org --verbose @@ -84,6 +89,7 @@ install: all cp -rp examples $(PREFIX)/share/uzbl/ install -m755 uzbl-core $(PREFIX)/bin/uzbl-core install -m755 uzbl-browser $(PREFIX)/bin/uzbl-browser + install -m755 uzbl-daemon $(PREFIX)/bin/uzbl-daemon install -m644 AUTHORS $(PREFIX)/share/uzbl/docs install -m644 README $(PREFIX)/share/uzbl/docs @@ -91,4 +97,3 @@ install: all uninstall: rm -rf $(PREFIX)/bin/uzbl-* rm -rf $(PREFIX)/share/uzbl - @@ -84,8 +84,8 @@ If you made changes to the configuration at runtime, these are not pased on to t Uzbl-browser will read commands via standard input, named fifo pipe (if `fifo_dir` is set) and IPC socket (when `socket_dir` is set). For convenience, uzbl can also be instructed to read commands from a file on startup by using the `-c` option. Indeed, the config file is nothing more than a list of commands. -Each command starts with the name of the command, which must be the first thing on a line; preceding whitespace is not allowed. -A command is terminated by a newline. Empty lines and lines that start with the hash sign are ignored by the parser. Command names are always written in lowercase. +Each command starts with the name of the command or an uzbl variable that expands to it. A command is terminated by a newline. +Empty lines and lines that start with the hash sign are ignored by the parser. Command names are always written in lowercase. The following commands are recognized: @@ -164,6 +164,24 @@ The following commands are recognized: - update the contents of the status and title bars * `event <event_name> [event_details]` - send custom event +* menu_add <label> = <uzbl command> +* menu_link_add <label> = <uzbl command> +* menu_image_add <label> = <uzbl command> +* menu_editable_add <label> = <uzbl command> + - add a new entry "label" that will execute "uzbl command" to one of the right click context menus +* menu_separator <label> +* menu_link_separator <label> +* menu_image_separator <label> +* menu_editable_separator <label> + - adds a separator line to one of the right click context menus +* menu_remove <label> +* menu_link_remove <label> +* menu_image_remove <label> +* menu_editable_remove <label> + - removes the entry "label" from one of the right click context menus +* hardcopy + - open print dialog + ### VARIABLES AND CONSTANTS Uzbl has a lot of internal variables and constants. You can get the values (using the `print` command, see above), and for variables you can also change the value at @@ -429,74 +447,89 @@ for now we still use the handler code) Basically all events have this format: - EVENT EVENT_NAME [uzbl_instance_name] event_details + EVENT [uzbl_instance_name] EVENT_NAME event_details Reported events and their specific format: - on start uzbl will generate: - EVENT INSTANCE_START [uzbl_instance_name] process_id + EVENT [uzbl_instance_name] INSTANCE_START process_id - on exit: - EVENT INSTANCE_EXIT [uzbl_instance_name] process_id + EVENT [uzbl_instance_name] INSTANCE_EXIT process_id - whenever an uzbl variable is set: - EVENT VARIABLE_SET [uzbl_instance_name] variable_name str|int|float variable_value + EVENT [uzbl_instance_name] VARIABLE_SET variable_name str|int|float variable_value Note: str|int|float denote the type of variable_value - upon execution of an uzbl command: - EVENT COMMAND_EXECUTED [uzbl_instance_name] command_name optional_command_arguments + EVENT [uzbl_instance_name] COMMAND_EXECUTED command_name optional_command_arguments - when the size or position of the uzbl window changes: - EVENT GEOMETRY_CHANGED [uzbl_instance_name] WIDTHxHEIGHT+X_POSITION+Y_POSITION + EVENT [uzbl_instance_name] GEOMETRY_CHANGED WIDTHxHEIGHT+X_POSITION+Y_POSITION - when the fifo and/or the socket path is set or changed: - EVENT FIFO_SET [uzbl_instance_name] path_to_fifo - EVENT SOCKET_SET [uzbl_instance_name] path_to_socket + EVENT [uzbl_instance_name] FIFO_SET path_to_fifo + EVENT [uzbl_instance_name] SOCKET_SET path_to_socket - when a website is being loaded: - EVENT LOAD_COMMIT [uzbl_instance_name] uri - EVENT LOAD_START [uzbl_instance_name] uri - EVENT LOAD_FINISHED [uzbl_instance_name] uri - EVENT LOAD_ERROR [uzbl_instance_name] reason_of_error + EVENT [uzbl_instance_name] LOAD_COMMIT uri + EVENT [uzbl_instance_name] LOAD_START uri + EVENT [uzbl_instance_name] LOAD_FINISHED uri + EVENT [uzbl_instance_name] LOAD_ERROR reason_of_error - when the title of the uzbl window changes: - EVENT TITLE_CHANGED [uzbl_instance_name] title_name + EVENT [uzbl_instance_name] TITLE_CHANGED title_name - when content needs to be downloaded: - EVENT DOWNLOAD_REQUEST [uzbl_instance_name] download_uri + EVENT [uzbl_instance_name] DOWNLOAD_REQUEST download_uri - when you hover with the mouse over a link: - EVENT LINK_HOVER [uzbl_instance_name] uri + EVENT [uzbl_instance_name] LINK_HOVER uri + EVENT [uzbl_instance_name] LINK_UNHOVER uri - when you press or release a key: - EVENT KEY_PRESS [uzbl_instance_name] key_name - EVENT KEY_RELEASE [uzbl_instance_name] key_name + EVENT [uzbl_instance_name] KEY_PRESS key_name + EVENT [uzbl_instance_name] KEY_RELEASE key_name - when you select some text inside the uzbl window: - EVENT SELECTION_CHANGED [uzbl_instance_name] selected_text + EVENT [uzbl_instance_name] SELECTION_CHANGED selected_text - when a new uzbl window is created: - EVENT NEW_WINDOW [uzbl_instance_name] uri + EVENT [uzbl_instance_name] NEW_WINDOW uri - upon opening/closing of the webinspector window: - EVENT WEBINSPECTOR [uzbl_instance_name] open - EVENT WEBINSPECTOR [uzbl_instance_name] close + EVENT [uzbl_instance_name] WEBINSPECTOR open + EVENT [uzbl_instance_name] WEBINSPECTOR close + +- when the uzbl windows gained/lost keyboard focus + + EVENT [uzbl_instance_name] FOCUS_GAINED + EVENT [uzbl_instance_name] FOCUS_LOST + +- when a editable HTML is clicked + + EVENT [uzbl_instance_name] FORM_ACTIVE + +- when the document body or any non-editable element is clicked + + EVENT [uzbl_instance_name] ROOT_ACTIVE + ### COMMAND LINE ARGUMENTS diff --git a/callbacks.c b/callbacks.c index d65d09a..175f4a3 100644 --- a/callbacks.c +++ b/callbacks.c @@ -62,10 +62,7 @@ cmd_set_status() { void cmd_load_uri() { - GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); - g_array_append_val (a, uzbl.state.uri); - load_uri(uzbl.gui.web_view, a, NULL); - g_array_free (a, TRUE); + load_uri_imp (uzbl.state.uri); } void @@ -289,13 +286,11 @@ cmd_useragent() { } /* 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 toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result) { @@ -455,13 +450,33 @@ gboolean configure_event_cb(GtkWidget* window, GdkEventConfigure* event) { (void) window; (void) event; + gchar *lastgeo = NULL; + lastgeo = g_strdup(uzbl.gui.geometry); retrieve_geometry(); - send_event(GEOMETRY_CHANGED, uzbl.gui.geometry, NULL); + + 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 TRUE; +} + +gboolean key_press_cb (GtkWidget* window, GdkEventKey* event) { (void) window; @@ -482,6 +497,30 @@ key_release_cb (GtkWidget* window, GdkEventKey* event) { } gboolean +button_press_cb (GtkWidget* window, GdkEventButton* event) { + (void) window; + gint context; + + 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); + + /* left click */ + if(event->button == 1) { + context = get_click_context(); + + 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); + } + } + + return FALSE; +} + +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; @@ -591,3 +630,70 @@ download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) { 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()) == -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 index 16ff48a..72361ba 100644 --- a/callbacks.h +++ b/callbacks.h @@ -112,10 +112,8 @@ cmd_caret_browsing(); void cmd_set_geometry(); -/* void cmd_view_source(); -*/ void cmd_load_start(); @@ -188,3 +186,12 @@ create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer us 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 +focus_cb(GtkWidget* window, GdkEventFocus* event, void *ud); + @@ -35,7 +35,11 @@ const char *event_table[LAST_EVENT] = { "INSTANCE_START" , "INSTANCE_EXIT" , "LOAD_PROGRESS" , - "LINK_UNHOVER" + "LINK_UNHOVER" , + "FORM_ACTIVE" , + "ROOT_ACTIVE" , + "FOCUS_LOST" , + "FOCUS_GAINED" }; void @@ -52,54 +56,84 @@ void send_event_socket(GString *msg) { GError *error = NULL; GString *tmp; + GIOChannel *gio = NULL; GIOStatus ret = 0; gsize len; - guint i=0; - - if(uzbl.comm.socket_path && - uzbl.comm.clientchan && - uzbl.comm.clientchan->is_writeable) { + 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; ret = 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); + } + g_io_channel_flush(gio, &error); + } + } - if(uzbl.state.event_buffer) { - event_buffer_timeout(0); - - while(i < uzbl.state.event_buffer->len) { - tmp = g_ptr_array_index(uzbl.state.event_buffer, i++); - ret = g_io_channel_write_chars (uzbl.comm.clientchan, - tmp->str, tmp->len, - &len, &error); - /* is g_ptr_array_free(uzbl.state.event_buffer, TRUE) enough? */ - //g_string_free(tmp, TRUE); - if (ret == G_IO_STATUS_ERROR) { - g_warning ("Error sending event to socket: %s", error->message); + if(msg) { + while(!ret || + ret == G_IO_STATUS_AGAIN) { + 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); + } + g_io_channel_flush(gio, &error); } - g_io_channel_flush(uzbl.comm.clientchan, &error); } + } + if(uzbl.state.event_buffer) { g_ptr_array_free(uzbl.state.event_buffer, TRUE); uzbl.state.event_buffer = NULL; } - if(msg) { - while(!ret || - ret == G_IO_STATUS_AGAIN) { - ret = g_io_channel_write_chars (uzbl.comm.clientchan, - msg->str, msg->len, - &len, &error); - } - - if (ret == G_IO_STATUS_ERROR) { - g_warning ("Error sending event to socket: %s", error->message); - } - g_io_channel_flush(uzbl.comm.clientchan, &error); - } } /* buffer events until a socket is set and connected - * or a timeout is encountered + * 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++); + ret = 0; + + if(gio && gio->is_writeable && msg) { + while(!ret || ret == G_IO_STATUS_AGAIN) { + 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); + g_io_channel_flush(gio, &error); + } + } + } } void @@ -120,7 +154,7 @@ send_event(int type, const gchar *details, const gchar *custom_event) { /* expand shell vars */ if(details) { buf = g_strdup(details); - p_val = parseenv(g_strdup(buf ? g_strchug(buf) : " ")); + p_val = parseenv(buf ? g_strchug(buf) : " "); g_free(buf); } @@ -162,3 +196,4 @@ key_to_event(guint keyval, gint mode) { gdk_keyval_name(keyval), NULL); } + @@ -11,7 +11,8 @@ enum event_type { WEBINSPECTOR, NEW_WINDOW, SELECTION_CHANGED, VARIABLE_SET, FIFO_SET, SOCKET_SET, INSTANCE_START, INSTANCE_EXIT, LOAD_PROGRESS, - LINK_UNHOVER, + LINK_UNHOVER, FORM_ACTIVE, ROOT_ACTIVE, + FOCUS_LOST, FOCUS_GAINED, /* must be last entry */ LAST_EVENT diff --git a/examples/config/uzbl/config b/examples/config/uzbl/config index fae36a2..e5b3c6a 100644 --- a/examples/config/uzbl/config +++ b/examples/config/uzbl/config @@ -32,7 +32,7 @@ set scheme_handler = spawn @scripts_dir/scheme.py #set new_window = sh 'echo uri "$8" > $4' # open in same window set new_window = sh 'uzbl-browser -u $8' # equivalent to the default behaviour -# Load start handler +# Load start handlers @on_event LOAD_START @set_status <span foreground="khaki">wait</span> # Load commit handler @@ -42,6 +42,15 @@ set new_window = sh 'uzbl-browser -u $8' # equivalent to the default beh @on_event LOAD_FINISH @set_status <span foreground="gold">done</span> @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 + # Misc on_event handlers #@on_event CONFIG_CHANGED print Config changed: %1 = %2 @@ -52,12 +61,13 @@ set show_status = 1 set status_top = 0 set status_background = #303030 -set keycmd_style = weight="bold" foreground="red" +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 mode_section = <span background="khaki" foreground="black">[\@[\@mode_indicator]\@]</span> -set keycmd_section = [<span \@prompt_style>\@[\@keycmd_prompt]\@</span><span \@keycmd_style>\@keycmd</span>] +set keycmd_section = [<span \@prompt_style>\@[\@keycmd_prompt]\@</span><span \@modcmd_style>\@modcmd</span><span \@keycmd_style>\@keycmd</span>] set progress_section = <span foreground="#606060">\@[\@progress_format]\@</span> set uri_section = <span foreground="#99FF66">\@[\@uri]\@</span> set name_section = <span foreground="khaki">\@[\@NAME]\@</span> @@ -183,8 +193,41 @@ set formfiller = spawn @scripts_dir/formfiller @bind <Ctrl>b<name:>_<tags:>_ = sh 'echo -e "$6 $7 %s %s" >> $XDG_DATA_HOME/uzbl/bookmarks' # Multi-stage bindings with blank prompts (similar behaviour to emacs M-c M-s bindings?) -@bind <Ctrl>a<:>q = exit -@bind <Ctrl>a<:>h = uri http://uzbl.org/ +@bind <Ctrl>a<:><Ctrl>q = exit +@bind <Ctrl>a<:><Ctrl>h = uri http://uzbl.org/ + + +# === command editing configuration ========================================== + +# you'll want this at the very least +@bind <Return> = event KEYCMD_EXEC_CURRENT +@bind <Escape> = event @set_mode + +# basic searching +@bind <Home> = event SET_CURSOR_POS 0 +@bind <End> = event SET_CURSOR_POS -1 +@bind <Left> = event SET_CURSOR_POS - +@bind <Right> = event SET_CURSOR_POS + +@bind <BackSpace> = event KEYCMD_BACKSPACE +@bind <Delete> = event KEYCMD_DELETE + +# readline-ish bindings +@bind <Ctrl>w = event KEYCMD_STRIP_WORD +@bind <Ctrl>u = event SET_KEYCMD +@bind <Ctrl>a = event SET_CURSOR_POS 0 +@bind <Ctrl>e = event SET_CURSOR_POS -1 + + +# === 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 ===================================================== @@ -195,7 +238,7 @@ set insert = @mode_config insert set stack = @mode_config stack # Command mode config. -@command keycmd_style = weight="bold" foreground="red" +@command keycmd_style = foreground="red" @command status_background = #202020 @command mode_indicator = Cmd @@ -221,7 +264,7 @@ set default_mode = command set toggle_cmd_ins = @toggle_modes command insert @bind i = @toggle_cmd_ins -@bind <Ctrl>i = @toggle_cmd_ins +@bind <Ctrl><i> = @toggle_cmd_ins # === Post-load misc commands =============================================== diff --git a/examples/data/uzbl/scripts/plugins/bind.py b/examples/data/uzbl/plugins/bind.py index 16c8148..8cd0702 100644 --- a/examples/data/uzbl/scripts/plugins/bind.py +++ b/examples/data/uzbl/plugins/bind.py @@ -20,7 +20,7 @@ __export__ = ['bind', 'del_bind', 'del_bind_by_glob', 'get_binds'] UZBLS = {} # Commonly used regular expressions. -starts_with_mod = re.compile('^<([A-Z][A-Za-z0-9-_]+)>') +starts_with_mod = re.compile('^<([A-Z][A-Za-z0-9-_]*)>') find_prompts = re.compile('<([^:>]*):(\"[^>]*\"|)>').split # For accessing a bind glob stack. @@ -135,12 +135,13 @@ class Bind(object): nextbid = counter().next def __init__(self, glob, handler, *args, **kargs): - self.callable = iscallable(handler) + self.is_callable = iscallable(handler) + self._repr_cache = None if not glob: raise ArgumentError('glob cannot be blank') - if self.callable: + if self.is_callable: self.function = handler self.args = args self.kargs = kargs @@ -172,9 +173,8 @@ class Bind(object): msg = 'found null segment after first prompt: %r' % split raise BindParseError(msg) - self.stack = [] - - for glob in split[::3]: + stack = [] + for (index, glob) in enumerate(reversed(split[::3])): # Is the binding a MODCMD or KEYCMD: mod_cmd = ismodbind(glob) @@ -185,13 +185,28 @@ class Bind(object): has_args = True if glob[-1] in ['*', '_'] else False glob = glob[:-1] if has_args else glob - self.stack.append((mod_cmd, on_exec, has_args, glob)) + stack.append((mod_cmd, on_exec, has_args, glob, index)) + + self.stack = list(reversed(stack)) + self.is_global = len(self.stack) == 1 + + + 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.callable: + if self.is_callable: args.append('function=%r' % self.function) if self.args: args.append('args=%r' % self.args) @@ -204,7 +219,35 @@ class Bind(object): cmds = self.commands[0] if cmdlen == 1 else self.commands args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) - return '<Bind(%s)>' % ', '.join(args) + self._repr_cache = '<Bind(%s)>' % ', '.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 = [] + for cmd in bind.commands: + if '%s' in cmd: + if len(args) > 1: + for arg in args: + cmd = cmd.replace('%s', arg, 1) + + elif len(args) == 1: + cmd = cmd.replace('%s', args[0]) + + uzbl.send(cmd) def bind(uzbl, glob, handler, *args, **kargs): @@ -279,93 +322,101 @@ def filter_bind(uzbl, bind_dict, bind): def match_and_exec(uzbl, bind, depth, keycmd): bind_dict = get_bind_dict(uzbl) - mode_cmd, on_exec, has_args, glob = bind.stack[depth] + (mod_cmd, on_exec, has_args, glob, more) = bind[depth] if has_args: if not keycmd.startswith(glob): - filter_bind(uzbl, bind_dict, bind) + if not mod_cmd: + filter_bind(uzbl, bind_dict, bind) + return False args = [keycmd[len(glob):],] elif keycmd != glob: - filter_bind(uzbl, bind_dict, bind) + if not mod_cmd: + filter_bind(uzbl, bind_dict, bind) + return False else: args = [] - execindex = len(bind.stack)-1 - if execindex == depth == 0: - uzbl.exec_handler(bind, *args) + if bind.is_global or (not more and depth == 0): + exec_bind(uzbl, bind, *args) if not has_args: - uzbl.clear_keycmd() + uzbl.clear_current() return True - elif depth != execindex: + elif more: if bind_dict['depth'] == depth: - bind_dict['filter'] = [bind,] + globalcmds = [cmd for cmd in bind_dict['binds'] if cmd.is_global] + bind_dict['filter'] = [bind,] + globalcmds bind_dict['args'] += args bind_dict['depth'] = depth + 1 - else: - if bind not in bind_dict['filter']: - bind_dict['filter'].append(bind) + elif bind not in bind_dict['filter']: + bind_dict['filter'].append(bind) set_stack_mode(uzbl, bind.prompts[depth]) return False args = bind_dict['args'] + args - uzbl.exec_handler(bind, *args) - if on_exec: - uzbl.set_mode() + exec_bind(uzbl, bind, *args) + uzbl.set_mode() + if not has_args: + uzbl.clear_current() return True def keycmd_update(uzbl, keylet): depth = get_stack_depth(uzbl) - keycmd = keylet.to_string() + keycmd = keylet.get_keycmd() for bind in get_filtered_binds(uzbl): - t = bind.stack[depth] + t = bind[depth] if t[MOD_CMD] or t[ON_EXEC]: continue - match_and_exec(uzbl, bind, depth, keycmd) + if match_and_exec(uzbl, bind, depth, keycmd): + return def keycmd_exec(uzbl, keylet): depth = get_stack_depth(uzbl) - keycmd = keylet.to_string() + keycmd = keylet.get_keycmd() for bind in get_filtered_binds(uzbl): - t = bind.stack[depth] + t = bind[depth] if t[MOD_CMD] or not t[ON_EXEC]: continue - match_and_exec(uzbl, bind, depth, keycmd) + if match_and_exec(uzbl, bind, depth, keycmd): + return uzbl.clear_keycmd() def modcmd_update(uzbl, keylet): depth = get_stack_depth(uzbl) - keycmd = keylet.to_string() + keycmd = keylet.get_modcmd() for bind in get_filtered_binds(uzbl): - t = bind.stack[depth] + t = bind[depth] if not t[MOD_CMD] or t[ON_EXEC]: continue - match_and_exec(uzbl, bind, depth, keycmd) + if match_and_exec(uzbl, bind, depth, keycmd): + return def modcmd_exec(uzbl, keylet): depth = get_stack_depth(uzbl) - keycmd = keylet.to_string() + keycmd = keylet.get_modcmd() for bind in get_filtered_binds(uzbl): - t = bind.stack[depth] + t = bind[depth] if not t[MOD_CMD] or not t[ON_EXEC]: continue - match_and_exec(uzbl, bind, depth, keycmd) + if match_and_exec(uzbl, bind, depth, keycmd): + return uzbl.clear_modcmd() def init(uzbl): diff --git a/examples/data/uzbl/scripts/plugins/config.py b/examples/data/uzbl/plugins/config.py index 22803b4..22803b4 100644 --- a/examples/data/uzbl/scripts/plugins/config.py +++ b/examples/data/uzbl/plugins/config.py diff --git a/examples/data/uzbl/plugins/keycmd.py b/examples/data/uzbl/plugins/keycmd.py new file mode 100644 index 0000000..109bb38 --- /dev/null +++ b/examples/data/uzbl/plugins/keycmd.py @@ -0,0 +1,395 @@ +import re + +# Map these functions/variables in the plugins namespace to the uzbl object. +__export__ = ['clear_keycmd', 'set_keycmd', 'set_cursor_pos', 'get_keylet', + 'clear_current', 'clear_modcmd'] + +# Hold the keylets. +UZBLS = {} + +# Simple key names map. +SIMPLEKEYS = { + 'Control': 'Ctrl', + 'ISO_Left_Tab': 'Shift-Tab', + 'space':'Space', +} + +# Keycmd format which includes the markup for the cursor. +KEYCMD_FORMAT = "%s<span @cursor_style>%s</span>%s" + + +def uzbl_escape(str): + '''Prevent outgoing keycmd values from expanding inside the + status_format.''' + + if not str: + return '' + + for char in ['\\', '@']: + if char in str: + str = str.replace(char, '\\'+char) + + return "@[%s]@" % str + + +class Keylet(object): + '''Small per-instance object that tracks all the keys held and characters + typed.''' + + def __init__(self): + # Modcmd tracking + self.held = [] + self.modcmd = '' + self.is_modcmd = False + + # Keycmd tracking + self.keycmd = '' + self.cursor = 0 + + # 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(['<%s>' % key for key in self.held]) + self.modcmd + + + 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(['<%s>'%key for key in self.held])) + + if self.keycmd: + l.append('keycmd=%r' % self.get_keycmd()) + + self._repr_cache = '<Keylet(%s)>' % ', '.join(l) + return self._repr_cache + + +def make_simple(key): + '''Make some obscure names for some keys friendlier.''' + + # Remove left-right discrimination. + if key.endswith('_L') or key.endswith('_R'): + key = key[:-2] + + if key in SIMPLEKEYS: + key = SIMPLEKEYS[key] + + return key + + +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 + config = uzbl.get_config() + if 'keycmd' not in config or config['keycmd'] != '': + uzbl.set('keycmd', '') + uzbl.send('update_gui') + + uzbl.event('KEYCMD_CLEAR') + + +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.held = [] + + config = uzbl.get_config() + if 'modcmd' not in config or config['modcmd'] != '': + uzbl.set('modcmd', '') + uzbl.send('update_gui') + + uzbl.event('MODCMD_CLEAR') + + +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 or new_modcmd == modcmd: + uzbl.set('modcmd', uzbl_escape(new_modcmd)) + + if 'keycmd_events' in config and config['keycmd_events'] != '1': + return uzbl.send('update_gui') + + new_keycmd = k.get_keycmd() + if not new_keycmd or new_keycmd != keycmd: + uzbl.set('keycmd', '') + return uzbl.send('update_gui') + + + # 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:]] + uzbl.set('keycmd', KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks))) + uzbl.send('update_gui') + + +def inject_char(str, index, char): + '''Inject character into string at at given index.''' + + assert len(char) == 1 + return "%s%s%s" % (str[:index], char, str[index:]) + + +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.''' + + if key.startswith('Shift_'): + return + + if len(key) > 1: + key = make_simple(key) + + k = get_keylet(uzbl) + if key == 'Space' and not k.held and k.keycmd: + k.keycmd = inject_char(k.keycmd, k.cursor, ' ') + k.cursor += 1 + + elif not k.held and len(key) == 1: + config = uzbl.get_config() + if 'keycmd_events' not in config or config['keycmd_events'] == '1': + k.keycmd = inject_char(k.keycmd, k.cursor, key) + k.cursor += 1 + + elif k.keycmd: + k.keycmd = '' + k.cursor = 0 + + elif len(key) > 1: + k.is_modcmd = True + if key == 'Shift-Tab' and 'Tab' in k.held: + k.held.remove('Tab') + + if key not in k.held: + k.held.append(key) + k.held.sort() + + 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.''' + + if len(key) > 1: + key = make_simple(key) + + k = get_keylet(uzbl) + if key in ['Shift', 'Tab'] and 'Shift-Tab' in k.held: + key = 'Shift-Tab' + + elif key in ['Shift', 'Alt'] and 'Meta' in k.held: + key = 'Meta' + + if key in k.held: + if k.is_modcmd: + uzbl.event('MODCMD_EXEC', k) + + k.held.remove(key) + #k.is_modcmd = False + #k.modcmd = '' + #update_event(uzbl, k) + 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 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.''' + + connects = {'INSTANCE_START': add_instance, + 'INSTANCE_EXIT': del_instance, + 'KEY_PRESS': key_press, + 'KEY_RELEASE': key_release, + 'SET_KEYCMD': set_keycmd, + 'KEYCMD_STRIP_WORD': keycmd_strip_word, + 'KEYCMD_BACKSPACE': keycmd_backspace, + 'KEYCMD_DELETE': keycmd_delete, + 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, + 'SET_CURSOR_POS': set_cursor_pos, + 'FOCUS_LOST': focus_changed, + 'FOCUS_GAINED': focus_changed} + + uzbl.connect_dict(connects) diff --git a/examples/data/uzbl/scripts/plugins/mode.py b/examples/data/uzbl/plugins/mode.py index ad0d9a8..ad0d9a8 100644 --- a/examples/data/uzbl/scripts/plugins/mode.py +++ b/examples/data/uzbl/plugins/mode.py diff --git a/examples/data/uzbl/scripts/plugins/on_event.py b/examples/data/uzbl/plugins/on_event.py index 242f9b0..242f9b0 100644 --- a/examples/data/uzbl/scripts/plugins/on_event.py +++ b/examples/data/uzbl/plugins/on_event.py diff --git a/examples/data/uzbl/scripts/plugins/plugin_template.py b/examples/data/uzbl/plugins/plugin_template.py index 03cb748..03cb748 100644 --- a/examples/data/uzbl/scripts/plugins/plugin_template.py +++ b/examples/data/uzbl/plugins/plugin_template.py diff --git a/examples/data/uzbl/scripts/plugins/progress_bar.py b/examples/data/uzbl/plugins/progress_bar.py index b6fcb1b..b6fcb1b 100644 --- a/examples/data/uzbl/scripts/plugins/progress_bar.py +++ b/examples/data/uzbl/plugins/progress_bar.py diff --git a/examples/data/uzbl/scripts/event_manager.py b/examples/data/uzbl/scripts/event_manager.py index 2e84ded..391fb84 100755 --- a/examples/data/uzbl/scripts/event_manager.py +++ b/examples/data/uzbl/scripts/event_manager.py @@ -24,30 +24,19 @@ E V E N T _ M A N A G E R . P Y Event manager for uzbl written in python. -Usage -===== - - uzbl | $XDG_DATA_HOME/uzbl/scripts/event_manager.py - -Todo -==== - - - Command line options including supplying a list of plugins to load or not - load (default is load all plugins in the plugin_dir). - - Spell checking. - - ''' import imp import os import sys -import select import re import types 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 @@ -69,13 +58,21 @@ def xdghome(key, default): # Setup xdg paths. DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') +CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') # Config dict (NOT the same as the uzbl.config). config = { - 'verbose': False, - 'plugin_dir': "$XDG_DATA_HOME/uzbl/scripts/plugins/", - 'plugins_load': [], - 'plugins_ignore': [], + 'verbose': False, + 'daemon_mode': True, + + 'plugins_load': [], + 'plugins_ignore': [], + + 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'), + '/usr/local/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'), } @@ -122,223 +119,265 @@ def isiterable(obj): return hasattr(obj, "__iter__") -class PluginManager(dict): - def __init__(self): +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', ...}''' - plugin_dir = os.path.expandvars(config['plugin_dir']) - self.plugin_dir = os.path.realpath(plugin_dir) - if not os.path.exists(self.plugin_dir): - os.makedirs(self.plugin_dir) + plugins = {} - self.load_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 file in os.listdir(plugin_dir): + if not file.lower().endswith('.py'): + continue - def _find_all_plugins(self): - '''Find all python scripts in plugin dir and return a list of - locations and imp moduleinfo's.''' + path = os.path.join(plugin_dir, file) + if not os.path.isfile(path): + continue - dirlist = os.listdir(self.plugin_dir) - pythonfiles = filter(lambda s: s.endswith('.py'), dirlist) + if file not in plugins: + plugins[file] = plugin_dir - plugins = [] + return plugins - for filename in pythonfiles: - plugins.append(filename[:-3]) - return plugins +def load_plugins(plugin_dirs, load=[], ignore=[]): + '''Load event manager plugins found in the plugin_dirs.''' + # Find the plugins in the plugin_dirs. + found = find_plugins(plugin_dirs) - def _unload_plugin(self, name, remove_pyc=True): - '''Unload specific plugin and remove all waste in sys.modules + if load: + # Ignore anything not in the load list. + for plugin in found.keys(): + if plugin not in load: + del found[plugin] - Notice: manual manipulation of sys.modules is very un-pythonic but I - see no other way to make sure you have 100% unloaded the module. Also - this allows us to implement a reload plugins function.''' + if ignore: + # Ignore anything in the ignore list. + for plugin in found.keys(): + if plugin in ignore: + del found[plugin] - allmodules = sys.modules.keys() - allrefs = filter(lambda s: s.startswith("%s." % name), allmodules) + # Print plugin list to be loaded. + pprint.pprint(found) - for ref in allrefs: - del sys.modules[ref] + loaded = {} + # Load all found plugins into the loaded dict. + for (filename, dir) in found.items(): + name = filename[:-3] + info = imp.find_module(name, [dir,]) + plugin = imp.load_module(name, *info) + loaded[(dir, filename)] = plugin - if name in sys.modules.keys(): - del sys.modules[name] + return loaded - if name in self: - del self[name] - if remove_pyc: - pyc = os.path.join(self.plugin_dir, '%s.pyc' % name) - if os.path.exists(pyc): - os.remove(pyc) +def daemonize(): + '''Daemonize the process using the Stevens' double-fork magic.''' + try: + if os.fork(): + os._exit(0) - def load_plugins(self): + except OSError: + print_exc() + sys.stderr.write("fork #1 failed") + sys.exit(1) - if config['plugins_load']: - pluginlist = config['plugins_load'] + os.chdir('/') + os.setsid() + os.umask(0) - else: - pluginlist = self._find_all_plugins() - for name in config['plugins_ignore']: - if name in pluginlist: - pluginlist.remove(name) + try: + if os.fork(): + os._exit(0) - for name in pluginlist: - # Make sure the plugin isn't already loaded. - self._unload_plugin(name) + except OSError: + print_exc() + sys.stderr.write("fork #2 failed") + sys.exit(1) - try: - moduleinfo = imp.find_module(name, [self.plugin_dir,]) - plugin = imp.load_module(name, *moduleinfo) - self[name] = plugin + sys.stdout.flush() + sys.stderr.flush() - except: - raise + devnull = '/dev/null' + stdin = file(devnull, 'r') + stdout = file(devnull, 'a+') + stderr = file(devnull, 'a+', 0) - if self.keys(): - echo("loaded plugin(s): %s" % ', '.join(self.keys())) + os.dup2(stdin.fileno(), sys.stdin.fileno()) + os.dup2(stdout.fileno(), sys.stdout.fileno()) + os.dup2(stderr.fileno(), sys.stderr.fileno()) - def reload_plugins(self): - '''Unload all loaded plugins then run load_plugins() again. +def make_dirs(path): + '''Make all basedirs recursively as required.''' - IMPORTANT: It is crucial that the event handler be deleted if you - are going to unload any modules because there is now way to track - which module created wich handler.''' + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) - for plugin in self.keys(): - self._unload_plugin(plugin) - self.load_plugins() +def make_pid_file(pid_file): + '''Make pid file at given pid_file location.''' + make_dirs(pid_file) + file = open(pid_file, 'w') + file.write('%d' % os.getpid()) + file.close() -class CallPrepender(object): - '''Execution argument modifier. Takes (arg, function) then modifies the - function call: - -> function(*args, **kargs) -> function(arg, *args, **kargs) ->''' +def del_pid_file(pid_file): + '''Delete pid file at given pid_file location.''' - def __init__(self, uzbl, function): - self.function = function - self.uzbl = uzbl + if os.path.isfile(pid_file): + os.remove(pid_file) - def call(self, *args, **kargs): - return self.function(self.uzbl, *args, **kargs) +def get_pid(pid_file): + '''Read pid from pid_file.''' -class Handler(object): + try: + file = open(pid_file, 'r') + strpid = file.read() + file.close() + pid = int(strpid.strip()) + return pid - nexthid = counter().next + except: + print_exc() + return None - def __init__(self, event, handler, *args, **kargs): - self.callable = iscallable(handler) - if self.callable: - self.function = handler - self.args = args - self.kargs = kargs - elif kargs: - raise ArgumentError("cannot supply kargs with a uzbl command") +def pid_running(pid): + '''Returns True if a process with the given pid is running.''' - elif isiterable(handler): - self.commands = handler + try: + os.kill(pid, 0) - else: - self.commands = [handler,] + list(args) + 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 prepender(function, *pre_args): + '''Creates a wrapper around a callable object injecting a list of + arguments before the called arguments.''' + + locals = (function, pre_args) + def _prepender(*args, **kargs): + (function, pre_args) = locals + return function(*(pre_args + args), **kargs) + + return _prepender + + +class EventHandler(object): + + nexthid = counter().next + + def __init__(self, event, handler, *args, **kargs): + if not iscallable(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] - - if self.callable: - args.append("function=%r" % self.function) - if self.args: - args.append("args=%r" % self.args) + args = ["event=%s" % self.event, "hid=%d" % self.hid, + "function=%r" % self.function] - if self.kargs: - args.append("kargs=%r" % self.kargs) + if self.args: + args.append("args=%r" % self.args) - 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)) + if self.kargs: + args.append("kargs=%r" % self.kargs) return "<EventHandler(%s)>" % ', '.join(args) class UzblInstance(object): - '''Event manager for a uzbl instance.''' - - # Singleton plugin manager. - plugins = None - - def __init__(self): - '''Initialise event manager.''' + def __init__(self, parent, client_socket): - # Hold functions exported by plugins. + # Internal variables. self._exports = {} - self._running = None - self._buffer = '' - self._handlers = {} + self._parent = parent + self._client_socket = client_socket - # Variables needed for fifo & socket communication with uzbl. - self.uzbl_fifo = None - self.uzbl_socket = None - self._fifo_cmd_queue = [] - self._socket_cmd_queue = [] - self._socket = None - self.send = self._send_socket - - if not self.plugins: - self.plugins = PluginManager() + self.buffer = '' - # Call the init() function in every plugin which then setup their - # respective hooks (event handlers, binds or timers). + # Call the init() function in every plugin. Inside the init function + # is where the plugins insert the hooks into the event system. self._init_plugins() - def __getattribute__(self, name): + def __getattribute__(self, attr): '''Expose any exported functions before class functions.''' - if not name.startswith('_'): + if not attr.startswith('_'): exports = object.__getattribute__(self, '_exports') - if name in exports: - return exports[name] + if attr in exports: + return exports[attr] - return object.__getattribute__(self, name) + return object.__getattribute__(self, attr) def _init_plugins(self): '''Call the init() function in every plugin and expose all exposable functions in the plugins root namespace.''' + plugins = self._parent['plugins'] + # Map all plugin exports - for (name, plugin) in self.plugins.items(): + for (name, plugin) in plugins.items(): if not hasattr(plugin, '__export__'): continue for export in plugin.__export__: if export in self._exports: - orig = self._exports[export] - raise KeyError("already exported attribute: %r" % export) + raise KeyError("conflicting export: %r" % export) obj = getattr(plugin, export) if iscallable(obj): - # Wrap the function in the CallPrepender object to make - # the exposed functions act like instance methods. - obj = CallPrepender(self, obj).call + obj = prepender(obj, self) self._exports[export] = obj echo("exposed attribute(s): %s" % ', '.join(self._exports.keys())) # Now call the init function in all plugins. - for (name, plugin) in self.plugins.items(): + for (name, plugin) in plugins.items(): try: plugin.init(self) @@ -347,82 +386,15 @@ class UzblInstance(object): raise - def _init_uzbl_socket(self, uzbl_socket=None, timeout=None): - '''Store socket location and open socket connection to uzbl socket.''' - - if uzbl_socket is None: - uzbl_socket = self.uzbl_socket - - if not uzbl_socket: - error("no socket location.") - return - - if not os.path.exists(uzbl_socket): - if timeout is None: - error("uzbl socket doesn't exist: %r" % uzbl_socket) - return - - waitlimit = time.time() + timeout - echo("waiting for uzbl socket: %r" % uzbl_socket) - while not os.path.exists(uzbl_socket): - time.sleep(0.25) - if time.time() > waitlimit: - error("timed out waiting for socket: %r" % uzbl_socket) - return - - self.uzbl_socket = uzbl_socket - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(self.uzbl_socket) - self._socket = sock - - - def _close_socket(self): - '''Close the socket used for communication with the uzbl instance. - This function is normally called upon receiving the INSTANCE_EXIT - event.''' - - if self._socket: - self._socket.close() - - self.uzbl_socket = self._socket = None - - - def _flush(self): - '''Flush messages from the outgoing queue to the uzbl instance.''' - - if len(self._fifo_cmd_queue) and self.uzbl_fifo: - if os.path.exists(self.uzbl_fifo): - h = open(self.uzbl_fifo, 'w') - while len(self._fifo_cmd_queue): - msg = self._fifo_cmd_queue.pop(0) - print '<-- %s' % msg - h.write(("%s\n" % msg).encode('utf-8')) - - h.close() - - if len(self._socket_cmd_queue) and self.uzbl_socket: - if not self._socket and os.path.exists(self.uzbl_socket): - self._init_uzbl_socket() - - if self._socket: - while len(self._socket_cmd_queue): - msg = self._socket_cmd_queue.pop(0) - print '<-- %s' % msg - self._socket.send(("%s\n" % msg).encode('utf-8')) - - - def _send_fifo(self, msg): - '''Send a command to the uzbl instance via the fifo socket.''' - - self._fifo_cmd_queue.append(msg) - self._flush() - - - def _send_socket(self, msg): + def send(self, msg): '''Send a command to the uzbl instance via the socket file.''' - self._socket_cmd_queue.append(msg) - self._flush() + if self._client_socket: + print '<-- %s' % msg + self._client_socket.send(("%s\n" % msg).encode('utf-8')) + + else: + print '!-- %s' % msg def connect(self, event, handler, *args, **kargs): @@ -432,11 +404,9 @@ class UzblInstance(object): if event not in self._handlers.keys(): self._handlers[event] = [] - handler = Handler(event, handler, *args, **kargs) - self._handlers[event].append(handler) - - print handler - return handler + handlerobj = EventHandler(event, handler, *args, **kargs) + self._handlers[event].append(handlerobj) + print handlerobj def connect_dict(self, connect_dict): @@ -477,216 +447,362 @@ class UzblInstance(object): echo('unable to find & remove handler: %r' % handler) - def listen_from_fd(self, fd): - '''Polls for event messages from fd.''' + 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 a custom event.''' + + # Silence _printing_ of geo events while debugging. + if event != "GEOMETRY_CHANGED": + print "--> %s %s %s" % (event, args, '' if not kargs else kargs) + + if event not in self._handlers: + return + + for handler in self._handlers[event]: + try: + self.exec_handler(handler, *args, **kargs) + + except: + print_exc() + + + def close(self): + '''Close the client socket and clean up.''' try: - self._running = True - while self._running: - if select.select([fd,], [], [], 1)[0]: - self.read_from_fd(fd) - continue + self._client_socket.close() + + except: + pass - self._flush() + for (name, plugin) in self._parent['plugins'].items(): + if hasattr(plugin, 'cleanup'): + plugin.cleanup(self) - except KeyboardInterrupt: - print + del self._exports + del self._handlers + del self._client_socket + + +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: - #print_exc() - raise + pass - def read_from_fd(self, fd): - '''Reads event messages from a single fd.''' + def run(self): + '''Main event daemon loop.''' - raw = fd.readline() - if not raw: - # Read null byte (i.e. uzbl closed). - self._running = False - return + if config['daemon_mode']: + echo('entering daemon mode.') + daemonize() + # The pid has changed so update the pid file. + make_pid_file(config['pid_file']) - msg = raw.strip().split(' ', 3) + # Create event daemon socket. + self._create_server_socket() + echo('listening on: %s' % self.socket_location) - if not msg or msg[0] != "EVENT": - # Not an event message - print "---", raw.rstrip() - return + # Now listen for incoming connections and or data. + self.listen() - event, args = msg[1], msg[3] - self.handle_event(event, args) + # Clean up. + self.quit() - def listen_from_uzbl_socket(self, uzbl_socket): - '''Polls for event messages from a single uzbl socket.''' + def listen(self): + '''Accept incoming connections and constantly poll instance sockets + for incoming data.''' - self._init_uzbl_socket(uzbl_socket, 10) + self.running = True + while self.running: - if not self._socket: - error("failed to init socket: %r" % uzbl_socket) - return + sockets = [self.server_socket,] + self['uzbls'].keys() + + read, _, error = select(sockets, [], sockets, 1) + + if self.server_socket in read: + self.accept_connection() + read.remove(self.server_socket) + + for client in read: + self.read_socket(client) + + for client in error: + 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.''' - self._flush() try: - self._running = True - while self._running: - if select.select([self._socket], [], [], 1): - self.read_from_uzbl_socket() - continue + uzbl = self['uzbls'][client] + try: + raw = unicode(client.recv(8192), 'utf-8', 'ignore') - self._flush() + except: + print_exc() + raw = None - except KeyboardInterrupt: - print + 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: + self.parse_msg(uzbl, msg) except: - #print_exc() raise - def read_from_uzbl_socket(self): - '''Reads event messages from a uzbl socket.''' + def parse_msg(self, 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.''' - raw = unicode(self._socket.recv(8192), 'utf-8', 'ignore') - if not raw: - # Read null byte - self._running = False + msg = msg.strip() + if not msg: return - self._buffer += raw - msgs = self._buffer.split("\n") - self._buffer = msgs.pop() + cmd = _RE_FINDSPACES.split(msg, 3) + if not cmd or cmd[0] != 'EVENT': + # Not an event message. + print '---', msg + return - for msg in msgs: - msg = msg.rstrip() - if not msg: - continue + if len(cmd) < 4: + cmd.append('') - cmd = _RE_FINDSPACES.split(msg, 3) - if not cmd or cmd[0] != "EVENT": - # Not an event message - print msg.rstrip() - continue + event, args = cmd[2], cmd[3] - if len(cmd) < 4: - cmd.append('') + try: + uzbl.event(event, args) - event, args = cmd[2], cmd[3] - try: - self.handle_event(event, args) + except: + print_exc() - except: - #print_exc() - raise + def accept_connection(self): + '''Accept incoming connection to the server socket.''' - def handle_event(self, event, args): - '''Handle uzbl events internally before dispatch.''' + client_socket = self.server_socket.accept()[0] - if event == 'FIFO_SET': - self.uzbl_fifo = args - self._flush() + uzbl = UzblInstance(self, client_socket) + self['uzbls'][client_socket] = uzbl - elif event == 'SOCKET_SET': - if not self.uzbl_socket or not self._socket: - self._init_uzbl_socket(args) - self._flush() - elif event == 'INSTANCE_EXIT': - self._close_socket() - self._running = False - for (name, plugin) in self.plugins.items(): - if hasattr(plugin, "cleanup"): - plugin.cleanup(uzbl) + def close_connection(self, client): + '''Clean up after instance close.''' - # Now handle the event "publically". - self.event(event, args) + try: + if client not in self['uzbls']: + return + uzbl = self['uzbls'][client] + uzbl.close() + del self['uzbls'][client] - def exec_handler(self, handler, *args, **kargs): - '''Execute either the handler function or send the handlers uzbl - commands via the socket.''' + except: + print_exc() - if handler.callable: - args = args + handler.args - kargs = dict(handler.kargs.items()+kargs.items()) - handler.function(uzbl, *args, **kargs) - else: - if kargs: - raise ArgumentError('cannot supply kargs for uzbl commands') + def quit(self): + '''Close all instance socket objects, server socket and delete the + pid file.''' - for command in handler.commands: - if '%s' in command: - if len(args) > 1: - for arg in args: - command = command.replace('%s', arg, 1) + echo('shutting down event manager.') - elif len(args) == 1: - command = command.replace('%s', args[0]) + for client in self['uzbls'].keys(): + self.close_connection(client) - uzbl.send(command) + 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 event(self, event, *args, **kargs): - '''Raise a custom event.''' - # Silence _printing_ of geo events while still debugging. - if event != "GEOMETRY_CHANGED": - print "--> %s %s %s" % (event, args, '' if not kargs else kargs) +def stop(): + '''Stop the event manager daemon.''' - if event in self._handlers: - for handler in self._handlers[event]: - try: - self.exec_handler(handler, *args, **kargs) + pid_file = config['pid_file'] + if not os.path.isfile(pid_file): + return echo('no running daemon found.') - except: - #print_exc() - raise + 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) -if __name__ == "__main__": - #uzbl = UzblInstance().listen_from_fd(sys.stdin) + echo('stopped event daemon.') + + +def start(): + '''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(): + '''Restart the event manager daemon.''' + + echo('restarting event manager daemon.') + stop() + start() + + +def list_plugins(): + '''List all the plugins being loaded by the event daemon.''' - parser = OptionParser() - parser.add_option('-s', '--uzbl-socket', dest='socket', - action="store", metavar="SOCKET", - help="read event messages from uzbl socket.") + plugins = find_plugins(config['plugin_dirs']) + dirs = {} + for (plugin, dir) in plugins.items(): + if dir not in dirs: + dirs[dir] = [] + + dirs[dir].append(plugin) + + for (index, (dir, plugin_list)) in enumerate(sorted(dirs.items())): + if index: + print + + print "%s:" % 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-dir', dest='plugin_dir', action="store", - metavar="DIR", help="change plugin directory.") + 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('-p', '--load-plugins', dest="load", action="store", + 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('-l', '--list-plugins', dest='list', action='store_true', - help="list all the plugins in the plugin dir.") + 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.") (options, args) = parser.parse_args() - if len(args): - for arg in args: - error("unknown argument: %r" % arg) + # init like {start|stop|..} daemon control section. + daemon_controls = {'start': start, 'stop': stop, 'restart': restart, + 'list': list_plugins} + + if len(args) == 1: + action = args[0] + if action not in daemon_controls: + error('unknown action: %r' % action) + sys.exit(1) + + elif len(args) > 1: + error("too many arguments: %r" % args) + sys.exit(1) - raise ArgumentError + else: + action = 'start' + # parse other flags & options. if options.verbose: config['verbose'] = True - if options.plugin_dir: - plugin_dir = os.path.expandvars(options.plugin_dir) - if not os.path.isdir(plugin_dir): - error("%r is not a directory" % plugin_dir) - sys.exit(1) - - config['plugin_dir'] = plugin_dir - echo("changed plugin dir: %r" % plugin_dir) + if options.plugin_dirs: + plugin_dirs = map(str.strip, options.plugin_dirs.split(':')) + 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.") @@ -708,21 +824,16 @@ if __name__ == "__main__": echo('ignoring plugin(s): %s' % ', '.join(plugins_ignore)) + if options.pid: + config['pid_file'] = options.pid + echo("pid file location: %r" % config['pid_file']) - if options.list: - plugin_dir = os.path.expandvars(config['plugin_dir']) - if not os.path.isdir(plugin_dir): - error("not a directory: %r" % plugin_dir) - sys.exit(1) + if options.socket: + config['server_socket'] = options.socket + echo("daemon socket location: %s" % config['server_socket']) - dirlist = filter(lambda p: p.endswith('.py'), os.listdir(plugin_dir)) - print ', '.join([p[:-3] for p in dirlist]) + if options.daemon: + config['daemon_mode'] = False - else: - uzbl = UzblInstance() - if options.socket: - echo("listen from uzbl socket: %r" % options.socket) - uzbl.listen_from_uzbl_socket(options.socket) - - else: - uzbl.listen_from_fd(sys.stdin) + # Now {start|stop|...} + daemon_controls[action]() diff --git a/examples/data/uzbl/scripts/plugins/keycmd.py b/examples/data/uzbl/scripts/plugins/keycmd.py deleted file mode 100644 index 3dd6f37..0000000 --- a/examples/data/uzbl/scripts/plugins/keycmd.py +++ /dev/null @@ -1,382 +0,0 @@ -import re - -# Map these functions/variables in the plugins namespace to the uzbl object. -__export__ = ['clear_keycmd', 'set_keycmd', 'set_cursor_pos', 'get_keylet'] - -# Regular expression compile cache. -_RE_CACHE = {} - -# Hold the keylets. -UZBLS = {} - -# Simple key names map. -_SIMPLEKEYS = { - 'Control': 'Ctrl', - 'ISO_Left_Tab': 'Shift-Tab', - 'space':'Space', -} - -# Keycmd format which includes the markup for the cursor. -KEYCMD_FORMAT = "%s<span @cursor_style>%s</span>%s" - - -def escape(str): - '''Prevent outgoing keycmd values from expanding inside the - status_format.''' - - if not str: - return '' - - for char in ['\\', '@']: - if char in str: - str = str.replace(char, '\\'+char) - - return "@[%s]@" % str - - -def get_regex(regex): - '''Compiling regular expressions is a very time consuming so return a - pre-compiled regex match object if possible.''' - - if regex not in _RE_CACHE: - _RE_CACHE[regex] = re.compile(regex).match - - return _RE_CACHE[regex] - - -class Keylet(object): - '''Small per-instance object that tracks all the keys held and characters - typed.''' - - def __init__(self): - self.cmd = '' - self.cursor = 0 - self.held = [] - - # to_string() string building cache. - self._to_string = None - - self.modcmd = False - self.wasmod = False - - def __repr__(self): - return '<Keycmd(%r)>' % self.to_string() - - - def to_string(self): - '''Return a string representation of the keys held and pressed that - have been recorded.''' - - if self._to_string is not None: - # Return cached keycmd string. - return self._to_string - - if not self.held: - self._to_string = self.cmd - - else: - self._to_string = ''.join(['<%s>' % key for key in self.held]) - if self.cmd: - self._to_string += self.cmd - - return self._to_string - - - def match(self, regex): - '''See if the keycmd string matches the given regex.''' - - return bool(get_regex(regex)(self.to_string())) - - -def make_simple(key): - '''Make some obscure names for some keys friendlier.''' - - # Remove left-right discrimination. - if key.endswith('_L') or key.endswith('_R'): - key = key[:-2] - - if key in _SIMPLEKEYS: - key = _SIMPLEKEYS[key] - - return key - - -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._to_string = None - return keylet - - -def clear_keycmd(uzbl): - '''Clear the keycmd for this uzbl instance.''' - - k = get_keylet(uzbl) - k.cmd = '' - k.cursor = 0 - k._to_string = None - - if k.modcmd: - k.wasmod = True - - k.modcmd = False - config = uzbl.get_config() - if 'keycmd' not in config or config['keycmd'] != '': - config['keycmd'] = '' - - uzbl.event('KEYCMD_CLEAR') - - -def update_event(uzbl, k): - '''Raise keycmd & modcmd update events.''' - - config = uzbl.get_config() - if k.modcmd: - keycmd = k.to_string() - uzbl.event('MODCMD_UPDATE', k) - if keycmd != k.to_string(): - return - - if 'modcmd_updates' in config and config['modcmd_updates'] != '1': - return - - return uzbl.set('keycmd', escape(keycmd)) - - if 'keycmd_events' in config and config['keycmd_events'] != '1': - return - - keycmd = k.cmd - uzbl.event('KEYCMD_UPDATE', k) - if keycmd != k.cmd: - return - - if not k.cmd: - return uzbl.set('keycmd', '') - - # Generate the pango markup for the cursor in the keycmd. - if k.cursor < len(k.cmd): - cursor = k.cmd[k.cursor] - - else: - cursor = ' ' - - chunks = map(escape, [k.cmd[:k.cursor], cursor, k.cmd[k.cursor+1:]]) - uzbl.set('keycmd', KEYCMD_FORMAT % tuple(chunks)) - - -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) - 2. Re-enable modcmd var if the user presses another key with at least one - modkey still held from the previous modcmd (I.e. <Ctrl>+t, clear & - <Ctrl>+o without having to re-press <Ctrl>) - 3. In non-modcmd mode: - a. BackSpace deletes the character before the cursor position. - b. Delete deletes the character at the cursor position. - c. End moves the cursor to the end of the keycmd. - d. Home moves the cursor to the beginning of the keycmd. - e. Return raises a KEYCMD_EXEC event then clears the keycmd. - f. Escape clears the keycmd. - 4. If keycmd and held keys are both empty/null and a modkey was pressed - set modcmd mode. - 5. If in modcmd mode only mod keys are added to the held keys list. - 6. Keycmd is updated and events raised if anything is changed.''' - - if key.startswith('Shift_'): - return - - if len(key) > 1: - key = make_simple(key) - - k = get_keylet(uzbl) - cmdmod = False - if k.held and k.wasmod: - k.modcmd = True - k.wasmod = False - cmdmod = True - - if k.cmd and key == 'Space': - k.cmd = "%s %s" % (k.cmd[:k.cursor], k.cmd[k.cursor:]) - k.cursor += 1 - cmdmod = True - - elif not k.modcmd and k.cmd and key in ['BackSpace', 'Delete']: - if key == 'BackSpace' and k.cursor > 0: - k.cursor -= 1 - k.cmd = k.cmd[:k.cursor] + k.cmd[k.cursor+1:] - - elif key == 'Delete': - cmd = k.cmd - k.cmd = k.cmd[:k.cursor] + k.cmd[k.cursor+1:] - if k.cmd != cmd: - cmdmod = True - - if not k.cmd: - clear_keycmd(uzbl) - - elif key == 'BackSpace': - cmdmod = True - - elif not k.modcmd and key == 'Return': - if k.cmd: - uzbl.event('KEYCMD_EXEC', k) - - clear_keycmd(uzbl) - - elif not k.modcmd and key == 'Escape': - clear_keycmd(uzbl) - - elif not k.modcmd and k.cmd and key == 'Left': - if k.cursor > 0: - k.cursor -= 1 - cmdmod = True - - elif not k.modcmd and k.cmd and key == 'Right': - if k.cursor < len(k.cmd): - k.cursor += 1 - cmdmod = True - - elif not k.modcmd and k.cmd and key == 'End': - if k.cursor != len(k.cmd): - k.cursor = len(k.cmd) - cmdmod = True - - elif not k.modcmd and k.cmd and key == 'Home': - if k.cursor: - k.cursor = 0 - cmdmod = True - - elif not k.held and not k.cmd and len(key) > 1: - k.modcmd = True - k.held.append(key) - cmdmod = True - - elif k.modcmd: - cmdmod = True - if len(key) > 1: - if key == 'Shift-Tab' and 'Tab' in k.held: - k.held.remove('Tab') - - if key not in k.held: - k.held.append(key) - k.held.sort() - - else: - k.cmd = "%s%s%s" % (k.cmd[:k.cursor], key, k.cmd[k.cursor:]) - k.cursor += 1 - - else: - config = uzbl.get_config() - if 'keycmd_events' not in config or config['keycmd_events'] == '1': - if len(key) == 1: - cmdmod = True - k.cmd = "%s%s%s" % (k.cmd[:k.cursor], key, k.cmd[k.cursor:]) - k.cursor += 1 - - elif k.cmd: - cmdmod = True - k.cmd = '' - k.cursor = 0 - - if cmdmod: - 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 the key removed was a mod key and it was in a mod-command then - raise a MODCMD_EXEC event then clear the keycmd. - 3. Stop trying to restore mod-command status with wasmod if both the - keycmd and held list are empty/null. - 4. Update the keycmd uzbl variable if anything changed.''' - - if len(key) > 1: - key = make_simple(key) - - k = get_keylet(uzbl) - - cmdmod = False - if key in ['Shift', 'Tab'] and 'Shift-Tab' in k.held: - key = 'Shift-Tab' - - elif key in ['Shift', 'Alt'] and 'Meta' in k.held: - key = 'Meta' - - if key in k.held: - if k.modcmd: - uzbl.event('MODCMD_EXEC', k) - - k.held.remove(key) - clear_keycmd(uzbl) - - if not k.held and not k.cmd and k.wasmod: - k.wasmod = False - - if cmdmod: - update_event(uzbl, k) - - -def set_keycmd(uzbl, keycmd): - '''Allow setting of the keycmd externally.''' - - k = get_keylet(uzbl) - k.wasmod = k.modcmd = False - k._to_string = None - k.cmd = keycmd - k.cursor = len(keycmd) - update_event(uzbl, k) - - -def set_cursor_pos(uzbl, index): - '''Allow setting of the cursor position externally. Supports negative - indexing.''' - - cursor = int(index.strip()) - k = get_keylet(uzbl) - - if cursor < 0: - cursor = len(k.cmd) + cursor - - if cursor < 0: - cursor = 0 - - if cursor > len(k.cmd): - cursor = len(k.cmd) - - k.cursor = cursor - update_event(uzbl, k) - - -def init(uzbl): - '''Connect handlers to uzbl events.''' - - connects = {'INSTANCE_START': add_instance, - 'INSTANCE_EXIT': del_instance, - 'KEY_PRESS': key_press, - 'KEY_RELEASE': key_release, - 'SET_KEYCMD': set_keycmd, - 'SET_CURSOR_POS': set_cursor_pos} - - uzbl.connect_dict(connects) diff --git a/tests/test-command.c b/tests/test-command.c index 2a226b2..239803c 100644 --- a/tests/test-command.c +++ b/tests/test-command.c @@ -27,33 +27,274 @@ extern UzblCore uzbl; +#define INSTANCE_NAME "testing" + +#define ASSERT_EVENT(EF, STR) { read_event(ef); \ + g_assert_cmpstr("EVENT [" INSTANCE_NAME "] " STR "\n", ==, ef->event_buffer); } + +struct EventFixture +{ + /* uzbl's end of the socketpair */ + int uzbl_sock; + + /* the test framework's end of the socketpair */ + int test_sock; + char event_buffer[1024]; +}; + +void +read_event (struct EventFixture *ef) { + int r = read(ef->test_sock, ef->event_buffer, 1023); \ + ef->event_buffer[r] = 0; +} + +void +assert_no_event (struct EventFixture *ef) { + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(ef->test_sock, &rfds); + + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + /* check if there's any data waiting */ + int res = select(ef->test_sock + 1, &rfds, NULL, NULL, &timeout); + + if(res == 0) { + /* timeout expired, there was no event */ + + /* success */ + return; + } else if(res == -1) { + /* mechanical failure */ + perror("select():"); + assert(0); + } else { + /* there was an event. display it. */ + read_event(ef); + g_assert_cmpstr("", ==, ef->event_buffer); + } +} + +void +event_fixture_setup(struct EventFixture *ef, const void* data) +{ + (void) data; + + int socks[2]; + + /* make some sockets, fresh for every test */ + if(socketpair(AF_UNIX, SOCK_STREAM, 0, socks) == -1) + { + perror("socketpair() failed"); + g_assert(0); + } + + ef->uzbl_sock = socks[0]; + ef->test_sock = socks[1]; + + /* attach uzbl_sock to uzbl's event dispatcher. */ + uzbl.comm.socket_path = "/tmp/some-nonexistant-socket"; + + GIOChannel *iochan = g_io_channel_unix_new(ef->uzbl_sock); + g_io_channel_set_encoding(iochan, NULL, NULL); + + if(!uzbl.comm.connect_chan) + uzbl.comm.connect_chan = g_ptr_array_new(); + if(!uzbl.comm.client_chan) + uzbl.comm.client_chan = g_ptr_array_new(); + g_ptr_array_add(uzbl.comm.client_chan, (gpointer)iochan); +} + void -test_keycmd (void) { - add_binding("insert", "set insert_mode = 1"); - add_binding("command", "set insert_mode = 0"); +event_fixture_teardown(struct EventFixture *ef, const void *data) +{ + (void) data; - /* the 'keycmd' command */ - parse_command("keycmd", "insert", NULL); + /* there should be no events left waiting */ + assert_no_event(ef); - g_assert_cmpint(1, ==, uzbl.behave.forward_keys); - g_assert_cmpstr("", ==, uzbl.state.keycmd); + /* clean up the io channel we opened for uzbl */ + GIOChannel *iochan = g_ptr_array_index(uzbl.comm.client_chan, 0); + remove_socket_from_array(iochan); + + /* close the sockets so that nothing sticks around between tests */ + close(ef->uzbl_sock); + close(ef->test_sock); +} + +/* actual tests begin here */ + +void +test_event (struct EventFixture *ef, const void *data) { + (void) data; - /* setting the keycmd variable directly, equivalent to the 'keycmd' comand */ - set_var_value("keycmd", "command"); + parse_cmd_line("event", NULL); + assert_no_event(ef); - g_assert_cmpint(0, ==, uzbl.behave.forward_keys); - g_assert_cmpstr("", ==, uzbl.state.keycmd); + /* a simple event can be sent */ + parse_cmd_line("event event_type arg u ments", NULL); + ASSERT_EVENT(ef, "EVENT_TYPE arg u ments"); + + /* arguments to event should be expanded */ + parse_cmd_line("event event_type @(echo expansion)@ test", NULL); + ASSERT_EVENT(ef, "EVENT_TYPE expansion test"); + + /* "request" is just an alias for "event" */ + parse_cmd_line("request event_type arg u ments", NULL); + ASSERT_EVENT(ef, "EVENT_TYPE arg u ments"); +} + + +void +test_set_variable (struct EventFixture *ef, const void *data) { + (void) data; + + /* set a string */ + parse_cmd_line("set status_message = A Simple Testing Message", NULL); + ASSERT_EVENT(ef, "VARIABLE_SET status_message str A Simple Testing Message"); + g_assert_cmpstr("A Simple Testing Message", ==, uzbl.gui.sbar.msg); + + /* set an int */ + parse_cmd_line("set forward_keys = 0", NULL); + ASSERT_EVENT(ef, "VARIABLE_SET forward_keys int 0"); + g_assert_cmpint(0, ==, uzbl.behave.forward_keys); + + /* set a float */ + parse_cmd_line("set zoom_level = 0.25", NULL); + ASSERT_EVENT(ef, "VARIABLE_SET zoom_level float 0.250000"); + g_assert_cmpfloat(0.25, ==, uzbl.behave.zoom_level); + + /* set a constant int (nothing should happen) */ + int old_major = uzbl.info.webkit_major; + parse_cmd_line("set WEBKIT_MAJOR = 100", NULL); + assert_no_event(ef); + g_assert_cmpint(old_major, ==, uzbl.info.webkit_major); + + /* set a constant str (nothing should happen) */ + GString *old_arch = g_string_new(uzbl.info.arch); + parse_cmd_line("set ARCH_UZBL = A Lisp Machine", NULL); + assert_no_event(ef); + g_assert_cmpstr(g_string_free(old_arch, FALSE), ==, uzbl.info.arch); + + /* set a custom variable */ + parse_cmd_line("set nonexistant_variable = Some Value", NULL); + ASSERT_EVENT(ef, "VARIABLE_SET nonexistant_variable str Some Value"); + uzbl_cmdprop *c = g_hash_table_lookup(uzbl.comm.proto_var, "nonexistant_variable"); + g_assert_cmpstr("Some Value", ==, *c->ptr.s); + + /* set a custom variable with expansion */ + parse_cmd_line("set an_expanded_variable = Test @(echo expansion)@", NULL); + ASSERT_EVENT(ef, "VARIABLE_SET an_expanded_variable str Test expansion"); + c = g_hash_table_lookup(uzbl.comm.proto_var, "an_expanded_variable"); + g_assert_cmpstr("Test expansion", ==, *c->ptr.s); +} + +void +test_print (void) { + GString *result = g_string_new(""); + + /* a simple message can be returned as a result */ + parse_cmd_line("print A simple test", result); + g_assert_cmpstr("A simple test", ==, result->str); + + /* arguments to print should be expanded */ + parse_cmd_line("print A simple @(echo expansion)@ test", result); + g_assert_cmpstr("A simple expansion test", ==, result->str); + + g_string_free(result, TRUE); +} + +void +test_scroll (void) { + gtk_adjustment_set_lower(uzbl.gui.bar_v, 0); + gtk_adjustment_set_upper(uzbl.gui.bar_v, 100); + gtk_adjustment_set_page_size(uzbl.gui.bar_v, 5); + + /* scroll_end should scroll it to upper - page_size */ + parse_cmd_line("scroll_end", NULL); + g_assert_cmpfloat(gtk_adjustment_get_value(uzbl.gui.bar_v), ==, 95); + + /* scroll_begin should scroll it to lower */ + parse_cmd_line("scroll_begin", NULL); + g_assert_cmpfloat(gtk_adjustment_get_value(uzbl.gui.bar_v), ==, 0); + + /* scroll_vert can scroll by pixels */ + parse_cmd_line("scroll_vert 15", NULL); + g_assert_cmpfloat(gtk_adjustment_get_value(uzbl.gui.bar_v), ==, 15); + + parse_cmd_line("scroll_vert -10", NULL); + g_assert_cmpfloat(gtk_adjustment_get_value(uzbl.gui.bar_v), ==, 5); + + /* scroll_vert can scroll by a percentage of the page size */ + parse_cmd_line("scroll_vert 100%", NULL); + g_assert_cmpfloat(gtk_adjustment_get_value(uzbl.gui.bar_v), ==, 10); + + parse_cmd_line("scroll_vert -150%", NULL); + g_assert_cmpfloat(gtk_adjustment_get_value(uzbl.gui.bar_v), ==, 2.5); + + /* scroll_horz behaves basically the same way. */ +} + +void +test_toggle_status (void) { + g_assert(!uzbl.behave.show_status); + + /* status bar can be toggled on */ + parse_cmd_line("toggle_status", NULL); + g_assert(uzbl.behave.show_status); + + /* status bar can be toggled back off */ + parse_cmd_line("toggle_status", NULL); + g_assert(!uzbl.behave.show_status); +} + +void +test_sync_sh (void) { + parse_cmd_line("sync_sh 'echo Test echo.'", NULL); + g_assert_cmpstr("Test echo.\n", ==, uzbl.comm.sync_stdout); +} + +void +test_js (void) { + GString *result = g_string_new(""); + + /* simple javascript can be evaluated and returned */ + parse_cmd_line("js ('x' + 345).toUpperCase()", result); + g_assert_cmpstr("X345", ==, result->str); + + /* uzbl commands can be run from javascript */ + uzbl.gui.sbar.msg = "Test message"; + parse_cmd_line("js Uzbl.run('print @status_message').toUpperCase();", result); + g_assert_cmpstr("TEST MESSAGE", ==, result->str); + + g_string_free(result, TRUE); } int main (int argc, char *argv[]) { + /* set up tests */ g_type_init(); g_test_init(&argc, &argv, NULL); - g_test_add_func("/test-command/keycmd", test_keycmd); + g_test_add("/test-command/set-variable", struct EventFixture, NULL, event_fixture_setup, test_set_variable, event_fixture_teardown); + g_test_add("/test-command/event", struct EventFixture, NULL, event_fixture_setup, test_event, event_fixture_teardown); + + g_test_add_func("/test-command/print", test_print); + g_test_add_func("/test-command/scroll", test_scroll); + g_test_add_func("/test-command/toggle-status", test_toggle_status); + g_test_add_func("/test-command/sync-sh", test_sync_sh); + g_test_add_func("/test-command/js", test_js); + + /* set up uzbl */ initialize(argc, argv); + uzbl.state.instance_name = INSTANCE_NAME; + uzbl.behave.shell_cmd = "sh -c"; + return g_test_run(); } diff --git a/tests/test-expand.c b/tests/test-expand.c index f01e5c7..428ee23 100644 --- a/tests/test-expand.c +++ b/tests/test-expand.c @@ -52,14 +52,6 @@ test_LOAD_PROGRESS (void) { } void -test_LOAD_PROGRESSBAR (void) { - uzbl.gui.sbar.progress_w = 4; - progress_change_cb(NULL, 75, NULL); - - g_assert_cmpstr(expand("@LOAD_PROGRESSBAR", 0), ==, "===ยท"); -} - -void test_TITLE (void) { uzbl.gui.main_title = "Lorem Ipsum"; g_assert_cmpstr(expand("@TITLE", 0), ==, "Lorem Ipsum"); @@ -78,15 +70,6 @@ test_NAME (void) { } void -test_MODE (void) { - set_var_value("forward_keys", "0"); - g_assert_cmpstr(expand("@MODE", 0), ==, "C"); - - set_var_value("forward_keys", "1"); - g_assert_cmpstr(expand("@MODE", 0), ==, "I"); -} - -void test_status_message (void) { uzbl.gui.sbar.msg = "Hello from frosty Edmonton!"; g_assert_cmpstr(expand("@status_message", 0), ==, "Hello from frosty Edmonton!"); @@ -124,8 +107,7 @@ test_cmd_useragent_simple (void) { g_string_append(expected, itos(WEBKIT_MICRO_VERSION)); g_string_append(expected, ")"); - set_var_value("useragent", "Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO)"); - g_assert_cmpstr(uzbl.net.useragent, ==, g_string_free(expected, FALSE)); + g_assert_cmpstr(expand("Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO)", 0), ==, g_string_free(expected, FALSE)); } void @@ -157,8 +139,7 @@ test_cmd_useragent_full (void) { g_string_append(expected, uzbl.info.commit); g_string_append(expected, ")"); - set_var_value("useragent", "Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO) (@(uname -s)@ @(uname -n)@ @(uname -r)@ @(uname -v)@ @(uname -m)@ [@ARCH_UZBL]) (Commit @COMMIT)"); - g_assert_cmpstr(uzbl.net.useragent, ==, g_string_free(expected, FALSE)); + g_assert_cmpstr(expand("Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO) (@(uname -s)@ @(uname -n)@ @(uname -r)@ @(uname -v)@ @(uname -m)@ [@ARCH_UZBL]) (Commit @COMMIT)", 0), ==, g_string_free(expected, FALSE)); } void @@ -213,11 +194,9 @@ main (int argc, char *argv[]) { g_test_add_func("/test-expand/@status_message", test_status_message); g_test_add_func("/test-expand/@uri", test_uri); g_test_add_func("/test-expand/@LOAD_PROGRESS", test_LOAD_PROGRESS); - g_test_add_func("/test-expand/@LOAD_PROGRESSBAR", test_LOAD_PROGRESSBAR); g_test_add_func("/test-expand/@TITLE", test_TITLE); g_test_add_func("/test-expand/@SELECTED_URI", test_SELECTED_URI); g_test_add_func("/test-expand/@NAME", test_NAME); - g_test_add_func("/test-expand/@MODE", test_MODE); g_test_add_func("/test-expand/@WEBKIT_*", test_WEBKIT_VERSION); g_test_add_func("/test-expand/@ARCH_UZBL", test_ARCH_UZBL); g_test_add_func("/test-expand/@COMMIT", test_COMMIT); diff --git a/uzbl-browser b/uzbl-browser index 202db11..b1205ac 100755 --- a/uzbl-browser +++ b/uzbl-browser @@ -1,5 +1,5 @@ #!/bin/sh -# this script implements are more useful "browsing experience". +# this script implements are more useful "browsing experience". # We are assuming you want to use the event_manager.py and cookie_daemon.py. # So, you must have them in the appropriate place, and cookie_daemon_socket must be configured in the default location @@ -10,29 +10,30 @@ if [ -z "$XDG_DATA_HOME" ] then - XDG_DATA_HOME=$HOME/.local/share + export XDG_DATA_HOME=$HOME/.local/share fi if [ -z "$XDG_CACHE_HOME" ] then - XDG_CACHE_HOME=$HOME/.cache + export XDG_CACHE_HOME=$HOME/.cache fi if [ ! -S $XDG_CACHE_HOME/uzbl/cookie_daemon_socket ] then - $XDG_DATA_HOME/uzbl/scripts/cookie_daemon.py + if [ -f "$XDG_DATA_HOME/uzbl/scripts/cookie_daemon.py" ] + then + $XDG_DATA_HOME/uzbl/scripts/cookie_daemon.py + else + /usr/local/share/uzbl/examples/data/uzbl/scripts/cookie_daemon.py + fi fi +DAEMON_SOCKET=$XDG_CACHE_HOME/uzbl/event_daemon +DAEMON_PID=$XDG_CACHE_HOME/uzbl/event_daemon.pid -SOCKET_ID="$RANDOM$RANDOM" -SOCKET_DIR="/tmp" -SOCKET_PATH="$SOCKET_DIR/uzbl_socket_$SOCKET_ID" +#if [ -f "$DAEMON_PID" ] +#then + uzbl-daemon start +#fi -uzbl-core "$@" -n $SOCKET_ID & -$XDG_DATA_HOME/uzbl/scripts/event_manager.py -vs $SOCKET_PATH - -# TODO: make posix sh compliant. [ -S ] is said to not work. what about test -S ? -if [[ -S $SOCKETPATH ]] -then - rm $SOCKET_PATH -fi +uzbl-core "$@" --connect-socket $DAEMON_SOCKET 1>/dev/null diff --git a/uzbl-core.c b/uzbl-core.c index a4f096b..1480491 100644 --- a/uzbl-core.c +++ b/uzbl-core.c @@ -51,6 +51,8 @@ GOptionEntry entries[] = "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, + "Socket Name", "CSOCKET" }, { "geometry", 'g', 0, G_OPTION_ARG_STRING, &uzbl.gui.geometry, "Set window geometry (format: WIDTHxHEIGHT+-X+-Y)", "GEOMETRY" }, { "version", 'V', 0, G_OPTION_ARG_NONE, &uzbl.behave.print_version, @@ -68,18 +70,6 @@ XDG_Var XDG[] = }; /* associate command names to their properties */ -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; /* 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 } @@ -125,7 +115,7 @@ const struct var_name_to_ptr_t { { "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)}, + { "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)}, @@ -442,21 +432,22 @@ find_existing_file(gchar* path_list) { } +/* Returns a new string with environment $variables expanded */ gchar* -parseenv (char* string) { +parseenv (gchar* string) { extern char** environ; - gchar* tmpstr = NULL; + gchar* tmpstr = NULL, * out; int i = 0; + out = g_strdup(string); while (environ[i] != NULL) { gchar** env = g_strsplit (environ[i], "=", 2); gchar* envname = g_strconcat ("$", env[0], NULL); if (g_strrstr (string, envname) != NULL) { - tmpstr = g_strdup(string); - g_free (string); - string = str_replace(envname, env[1], tmpstr); + tmpstr = out; + out = str_replace(envname, env[1], out); g_free (tmpstr); } @@ -465,22 +456,9 @@ parseenv (char* string) { i++; } - return string; + return out; } -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 clean_up(void) { @@ -499,7 +477,36 @@ clean_up(void) { unlink (uzbl.comm.socket_path); } -/* --- SIGNAL HANDLER --- */ +gint +get_click_context() { + GUI *g = &uzbl.gui; + WebKitHitTestResult *ht; + guint context; + + if(!uzbl.state.last_button) + return -1; + + ht = webkit_web_view_get_hit_test_result(g->web_view, uzbl.state.last_button); + g_object_get(ht, "context", &context, NULL); + + return (gint)context; +} + +/* --- SIGNALS --- */ +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_sigterm(int s) { (void) s; @@ -583,43 +590,53 @@ VIEWFUNC(go_forward) /* -- 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_vert", {scroll_vert, 0} }, - { "scroll_horz", {scroll_horz, 0} }, - { "scroll_begin", {scroll_begin, 0} }, - { "scroll_end", {scroll_end, 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} }, - { "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} }, - /* a request is just semantic sugar to make things more obvious for - * the user, technically events and requests are the very same thing - */ - { "request", {event, TRUE} }, - { "update_gui", {update_gui, TRUE} } +{ /* key function no_split */ + { "back", {view_go_back, 0} }, + { "forward", {view_go_forward, 0} }, + { "scroll_vert", {scroll_vert, 0} }, + { "scroll_horz", {scroll_horz, 0} }, + { "scroll_begin", {scroll_begin, 0} }, + { "scroll_end", {scroll_end, 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} }, + { "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} }, + { "update_gui", {update_gui, 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} } }; void @@ -634,28 +651,6 @@ commands_hash(void) /* -- CORE FUNCTIONS -- */ -void -free_action(gpointer act) { - Action *action = (Action*)act; - g_free(action->name); - if (action->param) - g_free(action->param); - g_free(action); -} - -Action* -new_action(const gchar *name, const gchar *param) { - Action *action = g_new(Action, 1); - - action->name = g_strdup(name); - if (param) - action->param = g_strdup(param); - else - action->param = NULL; - - return action; -} - bool file_exists (const char * filename) { return (access(filename, F_OK) == 0); @@ -666,7 +661,7 @@ set_var(WebKitWebView *page, GArray *argv, GString *result) { (void) page; (void) result; gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2); if (split[0] != NULL) { - gchar *value = parseenv(g_strdup(split[1] ? g_strchug(split[1]) : " ")); + gchar *value = parseenv(split[1] ? g_strchug(split[1]) : " "); set_var_value(g_strstrip(split[0]), value); g_free(value); } @@ -681,6 +676,178 @@ update_gui(WebKitWebView *page, GArray *argv, GString *result) { } void +add_to_menu(GArray *argv, guint context) { + GUI *g = &uzbl.gui; + MenuItem *m; + gchar *item_cmd = NULL; + 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; @@ -708,6 +875,14 @@ print(WebKitWebView *page, GArray *argv, GString *result) { } void +hardcopy(WebKitWebView *page, GArray *argv, GString *result) { + (void) argv; + (void) result; + + webkit_web_frame_print(webkit_web_view_get_main_frame(page)); +} + +void act_dump_config() { dump_config(); } @@ -719,37 +894,8 @@ act_dump_config_as_events() { void load_uri (WebKitWebView *web_view, GArray *argv, GString *result) { - (void) result; - - if (argv_idx(argv, 0)) { - GString* newuri = g_string_new (argv_idx(argv, 0)); - if (g_strstr_len (argv_idx(argv, 0), 11, "javascript:") != NULL) { - run_js(web_view, argv, NULL); - return; - } - if (!soup_uri_new(argv_idx(argv, 0))) { - GString* fullpath = g_string_new (""); - if (g_path_is_absolute (newuri->str)) - 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 (web_view, newuri->str); - g_string_free (newuri, TRUE); - } + (void) web_view; (void) result; + load_uri_imp (argv_idx (argv, 0)); } /* Javascript*/ @@ -1014,7 +1160,8 @@ run_command (const gchar *command, const guint npre, const gchar **args, 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, + } else + result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &err); if (uzbl.state.verbose) { @@ -1259,6 +1406,7 @@ talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result) { 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; @@ -1286,6 +1434,14 @@ parse_command(const char *cmd, const char *param, GString *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 g_printerr ("command \"%s\" not understood. ignoring.\n", cmd); } @@ -1297,8 +1453,8 @@ move_statusbar() { !uzbl.gui.mainbar) return; - gtk_widget_ref(uzbl.gui.scrolled_win); - gtk_widget_ref(uzbl.gui.mainbar); + 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); @@ -1310,8 +1466,8 @@ move_statusbar() { 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); } - gtk_widget_unref(uzbl.gui.scrolled_win); - gtk_widget_unref(uzbl.gui.mainbar); + g_object_unref(uzbl.gui.scrolled_win); + g_object_unref(uzbl.gui.mainbar); gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); return; } @@ -1359,13 +1515,13 @@ set_var_value(const gchar *name, gchar *val) { } /* custom vars */ - c = malloc(sizeof(uzbl_cmdprop)); + 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 = malloc(sizeof(char *)); + c->ptr.s = g_malloc(sizeof(char *)); *c->ptr.s = buf; g_hash_table_insert(uzbl.comm.proto_var, g_strdup(name), (gpointer) c); @@ -1530,25 +1686,77 @@ create_stdin () { } 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.clientchan = g_io_channel_unix_new(clientsock))) { - g_io_channel_set_encoding(uzbl.comm.clientchan, NULL, NULL); - g_io_add_watch(uzbl.comm.clientchan, G_IO_IN|G_IO_HUP, - (GIOFunc) control_client_socket, uzbl.comm.clientchan); - /* replay buffered events */ - send_event_socket(NULL); - } + 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; @@ -1560,9 +1768,11 @@ control_client_socket(GIOChannel *clientchan) { 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; @@ -1680,6 +1890,7 @@ create_browser () { 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::title-changed", (GCallback)title_change_cb, NULL, "signal::selection-changed", (GCallback)selection_changed_cb, NULL, "signal::load-progress-changed", (GCallback)progress_change_cb, NULL, @@ -1693,6 +1904,9 @@ create_browser () { "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); } @@ -1716,12 +1930,17 @@ create_mainbar () { 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_window_set_wmclass(GTK_WINDOW(window), "uzbl", "uzbl"); 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), "destroy", G_CALLBACK (destroy_cb), NULL); + g_signal_connect (G_OBJECT (window), "configure-event", G_CALLBACK (configure_event_cb), NULL); return window; } @@ -1928,6 +2147,9 @@ settings_init () { 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); } @@ -2044,6 +2266,8 @@ void initialize(int argc, char *argv[]) { if (!g_thread_supported ()) g_thread_init (NULL); + gtk_init (&argc, &argv); + uzbl.state.executable_path = g_strdup(argv[0]); uzbl.state.selected_url = NULL; uzbl.state.searchtx = NULL; @@ -2083,29 +2307,58 @@ initialize(int argc, char *argv[]) { commands_hash (); create_var_to_name_hash(); + create_mainbar(); create_browser(); } +void +load_uri_imp(gchar *uri) { + GString* newuri; + if (g_strstr_len (uri, 11, "javascript:") != NULL) { + eval_js(uzbl.gui.web_view, uri, NULL); + return; + } + newuri = g_string_new (uri); + if (!soup_uri_new(uri)) { + GString* fullpath = g_string_new (""); + if (g_path_is_absolute (newuri->str)) + 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); - gtk_init (&argc, &argv); - uzbl.gui.scrolled_win = gtk_scrolled_window_new (NULL, NULL); - //main_window_ref = g_object_ref(scrolled_window); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (uzbl.gui.scrolled_win), - GTK_POLICY_NEVER, GTK_POLICY_NEVER); //todo: some sort of display of position/total length. like what emacs does + 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); - create_mainbar(); - /* 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); @@ -2121,6 +2374,12 @@ main (int argc, char* argv[]) { 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); @@ -2142,12 +2401,6 @@ main (int argc, char* argv[]) { printf("commit: %s\n", uzbl.info.commit); } - 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); - /* Check uzbl is in window mode before getting/setting geometry */ if (uzbl.gui.main_window) { if(uzbl.gui.geometry) @@ -2177,8 +2430,7 @@ main (int argc, char* argv[]) { if (uri_override) { set_var_value("uri", uri_override); g_free(uri_override); - } else if (uzbl.state.uri) - cmd_load_uri(); + } gtk_main (); clean_up(); diff --git a/uzbl-core.h b/uzbl-core.h index a7a8126..1bab39f 100644 --- a/uzbl-core.h +++ b/uzbl-core.h @@ -70,6 +70,9 @@ typedef struct { WebKitWebInspector *inspector; StatusBar sbar; + + /* custom context menu item */ + GPtrArray *menu_items; } GUI; @@ -82,7 +85,8 @@ typedef struct { GHashTable *proto_var; gchar *sync_stdout; - GIOChannel *clientchan; + GPtrArray *connect_chan; + GPtrArray *client_chan; } Communication; @@ -99,6 +103,8 @@ typedef struct { gchar* searchtx; gboolean verbose; GPtrArray *event_buffer; + gchar** connect_socket_names; + GdkEventButton *last_button; } State; @@ -205,17 +211,25 @@ extern UzblCore uzbl; typedef void sigfunc(int); -typedef struct { - char* name; - char* param; -} Action; - /* 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 * @@ -231,7 +245,7 @@ GArray* read_file_by_line (const gchar *path); gchar* -parseenv (char* string); +parseenv (gchar* string); void clean_up(void); @@ -242,23 +256,17 @@ catch_sigterm(int s); sigfunc * setup_signal(int signe, sigfunc *shandler); -gchar* -parseenv (char* string); - gboolean set_var_value(const gchar *name, gchar *val); void -print(WebKitWebView *page, GArray *argv, GString *result); +load_uri_imp(gchar *uri); void -commands_hash(void); +print(WebKitWebView *page, GArray *argv, GString *result); void -free_action(gpointer act); - -Action* -new_action(const gchar *name, const gchar *param); +commands_hash(void); bool file_exists (const char * filename); @@ -423,10 +431,67 @@ update_gui(WebKitWebView *page, GArray *argv, GString *result); 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); + 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-daemon b/uzbl-daemon new file mode 100755 index 0000000..d07beb2 --- /dev/null +++ b/uzbl-daemon @@ -0,0 +1,25 @@ +#!/bin/sh + +# TODO: Fix up the launcher to check the following paths in order to find the +# correct event_manager.py to run: +# 1. $XDG_DATA_HOME/uzbl/scripts/event_manager.py +# 2. /usr/local/share/uzbl/examples/data/uzbl/scripts/event_manager.py + +if [ -z "$XDG_DATA_HOME" ] +then + XDG_DATA_HOME=$HOME/.local/share +fi + +if [ -z "$XDG_CACHE_HOME" ] +then + XDG_CACHE_HOME=$HOME/.cache +fi + +if [ -f "$XDG_DATA_HOME/uzbl/scripts/event_manager.py" ] +then + EVENT_MANAGER=$XDG_DATA_HOME/uzbl/scripts/event_manager.py +else + EVENT_MANAGER=/usr/local/share/uzbl/examples/data/uzbl/scripts/event_manager.py +fi + +$EVENT_MANAGER -v "$@" |