From 7f8ef03cfd55c266de8b78bfa19e154e1e9047b6 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 11:50:21 +0100 Subject: move source files from project root into src directory. Makefile is a bit broken though --- src/callbacks.c | 724 +++++++++++++++ src/callbacks.h | 197 ++++ src/config.h | 10 + src/events.c | 206 +++++ src/events.h | 35 + src/inspector.c | 103 +++ src/inspector.h | 7 + src/uzbl-browser | 66 ++ src/uzbl-core.c | 2637 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/uzbl-core.h | 494 ++++++++++ 10 files changed, 4479 insertions(+) create mode 100644 src/callbacks.c create mode 100644 src/callbacks.h create mode 100644 src/config.h create mode 100644 src/events.c create mode 100644 src/events.h create mode 100644 src/inspector.c create mode 100644 src/inspector.h create mode 100755 src/uzbl-browser create mode 100644 src/uzbl-core.c create mode 100644 src/uzbl-core.h (limited to 'src') diff --git a/src/callbacks.c b/src/callbacks.c new file mode 100644 index 0000000..dab92c1 --- /dev/null +++ b/src/callbacks.c @@ -0,0 +1,724 @@ +/* + ** Callbacks + ** (c) 2009 by Robert Manea et al. +*/ + +#include "uzbl-core.h" +#include "callbacks.h" +#include "events.h" + + +void +set_proxy_url() { + SoupURI *suri; + + if(uzbl.net.proxy_url == NULL || *uzbl.net.proxy_url == ' ') { + soup_session_remove_feature_by_type(uzbl.net.soup_session, + (GType) SOUP_SESSION_PROXY_URI); + } + else { + suri = soup_uri_new(uzbl.net.proxy_url); + g_object_set(G_OBJECT(uzbl.net.soup_session), + SOUP_SESSION_PROXY_URI, + suri, NULL); + soup_uri_free(suri); + } + return; +} + +void +set_icon() { + if(file_exists(uzbl.gui.icon)) { + if (uzbl.gui.main_window) + gtk_window_set_icon_from_file (GTK_WINDOW (uzbl.gui.main_window), uzbl.gui.icon, NULL); + } else { + g_printerr ("Icon \"%s\" not found. ignoring.\n", uzbl.gui.icon); + } +} + +void +cmd_set_geometry() { + int ret=0, x=0, y=0; + unsigned int w=0, h=0; + if(uzbl.gui.geometry) { + if(uzbl.gui.geometry[0] == 'm') { /* m/maximize/maximized */ + gtk_window_maximize((GtkWindow *)(uzbl.gui.main_window)); + } else { + /* we used to use gtk_window_parse_geometry() but that didn't work how it was supposed to */ + ret = XParseGeometry(uzbl.gui.geometry, &x, &y, &w, &h); + if(ret & XValue) + gtk_window_move((GtkWindow *)uzbl.gui.main_window, x, y); + if(ret & WidthValue) + gtk_window_resize((GtkWindow *)uzbl.gui.main_window, w, h); + } + } + + /* update geometry var with the actual geometry + this is necessary as some WMs don't seem to honour + the above setting and we don't want to end up with + wrong geometry information + */ + retrieve_geometry(); +} + +void +cmd_set_status() { + if (!uzbl.behave.show_status) { + gtk_widget_hide(uzbl.gui.mainbar); + } else { + gtk_widget_show(uzbl.gui.mainbar); + } + update_title(); +} + +void +cmd_load_uri() { + load_uri_imp (uzbl.state.uri); +} + +void +cmd_max_conns() { + g_object_set(G_OBJECT(uzbl.net.soup_session), + SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL); +} + +void +cmd_max_conns_host() { + g_object_set(G_OBJECT(uzbl.net.soup_session), + SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL); +} + +void +cmd_http_debug() { + soup_session_remove_feature + (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger)); + /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */ + /*g_free(uzbl.net.soup_logger);*/ + + uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1); + soup_session_add_feature(uzbl.net.soup_session, + SOUP_SESSION_FEATURE(uzbl.net.soup_logger)); +} + +WebKitWebSettings* +view_settings() { + return webkit_web_view_get_settings(uzbl.gui.web_view); +} + +void +cmd_font_size() { + WebKitWebSettings *ws = view_settings(); + if (uzbl.behave.font_size > 0) { + g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.font_size, NULL); + } + + if (uzbl.behave.monospace_size > 0) { + g_object_set (G_OBJECT(ws), "default-monospace-font-size", + uzbl.behave.monospace_size, NULL); + } else { + g_object_set (G_OBJECT(ws), "default-monospace-font-size", + uzbl.behave.font_size, NULL); + } +} + +void +cmd_default_font_family() { + g_object_set (G_OBJECT(view_settings()), "default-font-family", + uzbl.behave.default_font_family, NULL); +} + +void +cmd_monospace_font_family() { + g_object_set (G_OBJECT(view_settings()), "monospace-font-family", + uzbl.behave.monospace_font_family, NULL); +} + +void +cmd_sans_serif_font_family() { + g_object_set (G_OBJECT(view_settings()), "sans_serif-font-family", + uzbl.behave.sans_serif_font_family, NULL); +} + +void +cmd_serif_font_family() { + g_object_set (G_OBJECT(view_settings()), "serif-font-family", + uzbl.behave.serif_font_family, NULL); +} + +void +cmd_cursive_font_family() { + g_object_set (G_OBJECT(view_settings()), "cursive-font-family", + uzbl.behave.cursive_font_family, NULL); +} + +void +cmd_fantasy_font_family() { + g_object_set (G_OBJECT(view_settings()), "fantasy-font-family", + uzbl.behave.fantasy_font_family, NULL); +} + +void +cmd_zoom_level() { + webkit_web_view_set_zoom_level (uzbl.gui.web_view, uzbl.behave.zoom_level); +} + +void +cmd_disable_plugins() { + g_object_set (G_OBJECT(view_settings()), "enable-plugins", + !uzbl.behave.disable_plugins, NULL); +} + +void +cmd_disable_scripts() { + g_object_set (G_OBJECT(view_settings()), "enable-scripts", + !uzbl.behave.disable_scripts, NULL); +} + +void +cmd_minimum_font_size() { + g_object_set (G_OBJECT(view_settings()), "minimum-font-size", + uzbl.behave.minimum_font_size, NULL); +} +void +cmd_autoload_img() { + g_object_set (G_OBJECT(view_settings()), "auto-load-images", + uzbl.behave.autoload_img, NULL); +} + + +void +cmd_autoshrink_img() { + g_object_set (G_OBJECT(view_settings()), "auto-shrink-images", + uzbl.behave.autoshrink_img, NULL); +} + + +void +cmd_enable_spellcheck() { + g_object_set (G_OBJECT(view_settings()), "enable-spell-checking", + uzbl.behave.enable_spellcheck, NULL); +} + +void +cmd_enable_private() { + g_object_set (G_OBJECT(view_settings()), "enable-private-browsing", + uzbl.behave.enable_private, NULL); +} + +void +cmd_print_bg() { + g_object_set (G_OBJECT(view_settings()), "print-backgrounds", + uzbl.behave.print_bg, NULL); +} + +void +cmd_style_uri() { + g_object_set (G_OBJECT(view_settings()), "user-stylesheet-uri", + uzbl.behave.style_uri, NULL); +} + +void +cmd_resizable_txt() { + g_object_set (G_OBJECT(view_settings()), "resizable-text-areas", + uzbl.behave.resizable_txt, NULL); +} + +void +cmd_default_encoding() { + g_object_set (G_OBJECT(view_settings()), "default-encoding", + uzbl.behave.default_encoding, NULL); +} + +void +cmd_enforce_96dpi() { + g_object_set (G_OBJECT(view_settings()), "enforce-96-dpi", + uzbl.behave.enforce_96dpi, NULL); +} + +void +cmd_caret_browsing() { + g_object_set (G_OBJECT(view_settings()), "enable-caret-browsing", + uzbl.behave.caret_browsing, NULL); +} + +void +cmd_fifo_dir() { + uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir); +} + +void +cmd_socket_dir() { + uzbl.behave.socket_dir = init_socket(uzbl.behave.socket_dir); +} + +void +cmd_inject_html() { + if(uzbl.behave.inject_html) { + webkit_web_view_load_html_string (uzbl.gui.web_view, + uzbl.behave.inject_html, NULL); + } +} + +void +cmd_useragent() { + if (*uzbl.net.useragent == ' ') { + g_free (uzbl.net.useragent); + uzbl.net.useragent = NULL; + } else { + g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, + uzbl.net.useragent, NULL); + } +} + +/* requires webkit >=1.1.14 */ +void +cmd_view_source() { + webkit_web_view_set_view_source_mode(uzbl.gui.web_view, + (gboolean) uzbl.behave.view_source); +} + +void +cmd_set_zoom_type () { + if(uzbl.behave.zoom_type) + webkit_web_view_set_full_content_zoom (uzbl.gui.web_view, TRUE); + else + webkit_web_view_set_full_content_zoom (uzbl.gui.web_view, FALSE); +} + +void +toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result) { + (void)argv; + (void)result; + + webkit_web_view_set_full_content_zoom (page, !webkit_web_view_get_full_content_zoom (page)); +} + +void +toggle_status_cb (WebKitWebView* page, GArray *argv, GString *result) { + (void)page; + (void)argv; + (void)result; + + if (uzbl.behave.show_status) { + gtk_widget_hide(uzbl.gui.mainbar); + } else { + gtk_widget_show(uzbl.gui.mainbar); + } + uzbl.behave.show_status = !uzbl.behave.show_status; + update_title(); +} + +void +link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) { + (void) page; + (void) title; + (void) data; + State *s = &uzbl.state; + + if(s->selected_url) { + if(s->last_selected_url) + g_free(s->last_selected_url); + s->last_selected_url = g_strdup(s->selected_url); + } + else { + if(s->last_selected_url) g_free(s->last_selected_url); + s->last_selected_url = NULL; + } + + g_free(s->selected_url); + s->selected_url = NULL; + + if (link) { + s->selected_url = g_strdup(link); + + if(s->last_selected_url && + g_strcmp0(s->selected_url, s->last_selected_url)) + send_event(LINK_UNHOVER, s->last_selected_url, NULL); + + send_event(LINK_HOVER, s->selected_url, NULL); + } + else if(s->last_selected_url) { + send_event(LINK_UNHOVER, s->last_selected_url, NULL); + } + + update_title(); +} + +void +title_change_cb (WebKitWebView* web_view, GParamSpec param_spec) { + (void) web_view; + (void) param_spec; + const gchar *title = webkit_web_view_get_title(web_view); + if (uzbl.gui.main_title) + g_free (uzbl.gui.main_title); + uzbl.gui.main_title = title ? g_strdup (title) : g_strdup ("(no title)"); + update_title(); + send_event(TITLE_CHANGED, uzbl.gui.main_title, NULL); +} + +void +progress_change_cb (WebKitWebView* page, gint progress, gpointer data) { + (void) page; + (void) data; + gchar *prg_str; + + prg_str = itos(progress); + send_event(LOAD_PROGRESS, prg_str, NULL); + g_free(prg_str); +} + +void +selection_changed_cb(WebKitWebView *webkitwebview, gpointer ud) { + (void)ud; + gchar *tmp; + + webkit_web_view_copy_clipboard(webkitwebview); + tmp = gtk_clipboard_wait_for_text(gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)); + send_event(SELECTION_CHANGED, tmp, NULL); + g_free(tmp); +} + +void +load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { + (void) page; + (void) data; + + send_event(LOAD_FINISH, webkit_web_frame_get_uri(frame), NULL); +} + +void +load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { + (void) page; + (void) frame; + (void) data; + + send_event(LOAD_START, uzbl.state.uri, NULL); +} + +void +load_error_cb (WebKitWebView* page, WebKitWebFrame* frame, gchar *uri, gpointer web_err, gpointer ud) { + (void) page; + (void) frame; + (void) ud; + GError *err = web_err; + gchar *details; + + details = g_strdup_printf("%s %d:%s", uri, err->code, err->message); + send_event(LOAD_ERROR, details, NULL); + g_free(details); +} + +void +load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { + (void) page; + (void) data; + g_free (uzbl.state.uri); + GString* newuri = g_string_new (webkit_web_frame_get_uri (frame)); + uzbl.state.uri = g_string_free (newuri, FALSE); + + send_event(LOAD_COMMIT, webkit_web_frame_get_uri (frame), NULL); +} + +void +destroy_cb (GtkWidget* widget, gpointer data) { + (void) widget; + (void) data; + gtk_main_quit (); +} + +gboolean +configure_event_cb(GtkWidget* window, GdkEventConfigure* event) { + (void) window; + (void) event; + gchar *lastgeo = NULL; + + lastgeo = g_strdup(uzbl.gui.geometry); + retrieve_geometry(); + + if(strcmp(lastgeo, uzbl.gui.geometry)) + send_event(GEOMETRY_CHANGED, uzbl.gui.geometry, NULL); + g_free(lastgeo); + + return FALSE; +} + +gboolean +focus_cb(GtkWidget* window, GdkEventFocus* event, void *ud) { + (void) window; + (void) event; + (void) ud; + + if(event->in) + send_event(FOCUS_GAINED, "", NULL); + else + send_event(FOCUS_LOST, "", NULL); + + return FALSE; +} + +gboolean +key_press_cb (GtkWidget* window, GdkEventKey* event) { + (void) window; + + if(event->type == GDK_KEY_PRESS) + key_to_event(event->keyval, GDK_KEY_PRESS); + + return uzbl.behave.forward_keys ? FALSE : TRUE; +} + +gboolean +key_release_cb (GtkWidget* window, GdkEventKey* event) { + (void) window; + + if(event->type == GDK_KEY_RELEASE) + key_to_event(event->keyval, GDK_KEY_RELEASE); + + return uzbl.behave.forward_keys ? FALSE : TRUE; +} + +gboolean +button_press_cb (GtkWidget* window, GdkEventButton* event) { + (void) window; + gint context; + gchar *details; + gboolean propagate = FALSE, + sendev = FALSE; + + if(event->type == GDK_BUTTON_PRESS) { + if(uzbl.state.last_button) + gdk_event_free((GdkEvent *)uzbl.state.last_button); + uzbl.state.last_button = (GdkEventButton *)gdk_event_copy((GdkEvent *)event); + + context = get_click_context(NULL); + /* left click */ + if(event->button == 1) { + if((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) + send_event(FORM_ACTIVE, "button1", NULL); + else if((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT)) + send_event(ROOT_ACTIVE, "button1", NULL); + } + else if(event->button == 2 && !(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) { + sendev = TRUE; + propagate = TRUE; + } + else if(event->button > 3) { + sendev = TRUE; + propagate = TRUE; + } + + if(sendev) { + details = g_strdup_printf("Button%d", event->button); + send_event(KEY_PRESS, details, NULL); + g_free(details); + } + } + + return propagate; +} + +gboolean +button_release_cb (GtkWidget* window, GdkEventButton* event) { + (void) window; + gint context; + gchar *details; + gboolean propagate = FALSE, + sendev = FALSE; + + context = get_click_context(NULL); + if(event->type == GDK_BUTTON_RELEASE) { + if(event->button == 2 && !(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) { + sendev = TRUE; + propagate = TRUE; + } + else if(event->button > 3) { + sendev = TRUE; + propagate = TRUE; + } + + if(sendev) { + details = g_strdup_printf("Button%d", event->button); + send_event(KEY_RELEASE, details, NULL); + g_free(details); + } + } + + return propagate; +} + +gboolean +navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { + (void) web_view; + (void) frame; + (void) navigation_action; + (void) user_data; + + const gchar* uri = webkit_network_request_get_uri (request); + gboolean decision_made = FALSE; + + if (uzbl.state.verbose) + printf("Navigation requested -> %s\n", uri); + + if (uzbl.behave.scheme_handler) { + GString *s = g_string_new (""); + g_string_printf(s, "'%s'", uri); + + run_handler(uzbl.behave.scheme_handler, s->str); + + if(uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) { + char *p = strchr(uzbl.comm.sync_stdout, '\n' ); + if ( p != NULL ) *p = '\0'; + if (!strcmp(uzbl.comm.sync_stdout, "USED")) { + webkit_web_policy_decision_ignore(policy_decision); + decision_made = TRUE; + } + } + if (uzbl.comm.sync_stdout) + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + g_string_free(s, TRUE); + } + if (!decision_made) + webkit_web_policy_decision_use(policy_decision); + + return TRUE; +} + +gboolean +new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { + (void) web_view; + (void) frame; + (void) navigation_action; + (void) policy_decision; + (void) user_data; + const gchar* uri = webkit_network_request_get_uri (request); + if (uzbl.state.verbose) + printf("New window requested -> %s \n", uri); + webkit_web_policy_decision_use(policy_decision); + send_event(NEW_WINDOW, uri, NULL); + return TRUE; +} + +gboolean +mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, gchar *mime_type, WebKitWebPolicyDecision *policy_decision, gpointer user_data) { + (void) frame; + (void) request; + (void) user_data; + + /* If we can display it, let's display it... */ + if (webkit_web_view_can_show_mime_type (web_view, mime_type)) { + webkit_web_policy_decision_use (policy_decision); + return TRUE; + } + + /* ...everything we can't display is downloaded */ + webkit_web_policy_decision_download (policy_decision); + return TRUE; +} + +/*@null@*/ WebKitWebView* +create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) { + (void) web_view; + (void) frame; + (void) user_data; + if (uzbl.state.selected_url != NULL) { + if (uzbl.state.verbose) + printf("\nNew web view -> %s\n",uzbl.state.selected_url); + new_window_load_uri(uzbl.state.selected_url); + } else { + if (uzbl.state.verbose) + printf("New web view -> %s\n","Nothing to open, exiting"); + } + return (NULL); +} + +gboolean +download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) { + (void) web_view; + (void) user_data; + if (uzbl.behave.download_handler) { + const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download); + if (uzbl.state.verbose) + printf("Download -> %s\n",uri); + + /* if urls not escaped, we may have to escape and quote uri before this call */ + GString *args = g_string_new(uri); + + if (uzbl.net.proxy_url) { + g_string_append_c(args, ' '); + g_string_append(args, uzbl.net.proxy_url); + } + + run_handler(uzbl.behave.download_handler, args->str); + + g_string_free(args, TRUE); + } + send_event(DOWNLOAD_REQ, webkit_download_get_uri ((WebKitDownload*)download), NULL); + return (FALSE); +} + +void +run_menu_command(GtkWidget *menu, const char *line) { + (void) menu; + + parse_cmd_line(line, NULL); +} + + +void +populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c) { + (void) v; + (void) c; + GUI *g = &uzbl.gui; + GtkWidget *item; + MenuItem *mi; + guint i=0; + gint context, hit=0; + + if(!g->menu_items) + return; + + /* check context */ + if((context = get_click_context(NULL)) == -1) + return; + + + for(i=0; i < uzbl.gui.menu_items->len; i++) { + hit = 0; + mi = g_ptr_array_index(uzbl.gui.menu_items, i); + + if((mi->context > WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) && + (context & mi->context)) { + if(mi->issep) { + item = gtk_separator_menu_item_new(); + gtk_menu_append(GTK_MENU(m), item); + gtk_widget_show(item); + } + else { + item = gtk_menu_item_new_with_label(mi->name); + g_signal_connect(item, "activate", + G_CALLBACK(run_menu_command), mi->cmd); + gtk_menu_append(GTK_MENU(m), item); + gtk_widget_show(item); + } + hit++; + } + + if((mi->context == WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) && + (context <= WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT) && + !hit) { + if(mi->issep) { + item = gtk_separator_menu_item_new(); + gtk_menu_append(GTK_MENU(m), item); + gtk_widget_show(item); + } + else { + item = gtk_menu_item_new_with_label(mi->name); + g_signal_connect(item, "activate", + G_CALLBACK(run_menu_command), mi->cmd); + gtk_menu_append(GTK_MENU(m), item); + gtk_widget_show(item); + } + } + } +} + diff --git a/src/callbacks.h b/src/callbacks.h new file mode 100644 index 0000000..3f318f2 --- /dev/null +++ b/src/callbacks.h @@ -0,0 +1,197 @@ +/* + ** Callbacks + ** (c) 2009 by Robert Manea et al. +*/ + +void +cmd_load_uri(); + +void +cmd_set_status(); + +void +set_proxy_url(); + +void +set_icon(); + +void +move_statusbar(); + +void +cmd_http_debug(); + +void +cmd_max_conns(); + +void +cmd_max_conns_host(); + +/* exported WebKitWebSettings properties */ +void +cmd_font_size(); + +void +cmd_default_font_family(); + +void +cmd_monospace_font_family(); + +void +cmd_sans_serif_font_family(); + +void +cmd_serif_font_family(); + +void +cmd_cursive_font_family(); + +void +cmd_fantasy_font_family(); + +void +cmd_zoom_level(); + +void +cmd_set_zoom_type(); + +void +cmd_disable_plugins(); + +void +cmd_disable_scripts(); + +void +cmd_minimum_font_size(); + +void +cmd_fifo_dir(); + +void +cmd_socket_dir(); + +void +cmd_useragent() ; + +void +cmd_autoload_img(); + +void +cmd_autoshrink_img(); + +void +cmd_enable_spellcheck(); + +void +cmd_enable_private(); + +void +cmd_print_bg(); + +void +cmd_style_uri(); + +void +cmd_resizable_txt(); + +void +cmd_default_encoding(); + +void +cmd_enforce_96dpi(); + +void +cmd_inject_html(); + +void +cmd_caret_browsing(); + +void +cmd_set_geometry(); + +void +cmd_view_source(); + +void +cmd_load_start(); + +WebKitWebSettings* +view_settings(); + +gboolean +download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data); + +void +toggle_zoom_type (WebKitWebView* page, GArray *argv, GString *result); + +void +toggle_status_cb (WebKitWebView* page, GArray *argv, GString *result); + +void +link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data); + +void +title_change_cb (WebKitWebView* web_view, GParamSpec param_spec); + +void +progress_change_cb (WebKitWebView* page, gint progress, gpointer data); + +void +load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); + +void +load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); + +void +load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); + +void +load_error_cb (WebKitWebView* page, WebKitWebFrame* frame, gchar *uri, gpointer web_err, gpointer ud); + +void +selection_changed_cb(WebKitWebView *webkitwebview, gpointer ud); + +void +destroy_cb (GtkWidget* widget, gpointer data); + +gboolean +configure_event_cb(GtkWidget* window, GdkEventConfigure* event); + +gboolean +key_press_cb (GtkWidget* window, GdkEventKey* event); + +gboolean +key_release_cb (GtkWidget* window, GdkEventKey* event); + +gboolean +navigation_decision_cb (WebKitWebView *web_view, WebKitWebFrame *frame, + WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision, gpointer user_data); + +gboolean +new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, + WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision, gpointer user_data); + +gboolean +mime_policy_cb(WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, + gchar *mime_type, WebKitWebPolicyDecision *policy_decision, gpointer user_data); + +/*@null@*/ WebKitWebView* +create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data); + +gboolean +download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data); + +void +populate_popup_cb(WebKitWebView *v, GtkMenu *m, void *c); + +gboolean +button_press_cb (GtkWidget* window, GdkEventButton* event); + +gboolean +button_release_cb (GtkWidget* window, GdkEventButton* event); + +gboolean +focus_cb(GtkWidget* window, GdkEventFocus* event, void *ud); + diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..e9b9a8e --- /dev/null +++ b/src/config.h @@ -0,0 +1,10 @@ +const struct { + /*@null@*/ char *command; +} default_config[] = { +{ "set status_format = \\@[\\@keycmd]\\@ \\@[\\@TITLE]\\@ - Uzbl browser"}, +{ "set title_format_long = \\@keycmd \\@TITLE - Uzbl browser <\\@NAME> > \\@SELECTED_URI"}, +{ "set title_format_short = \\@TITLE - Uzbl browser <\\@NAME>"}, +{ "set max_conns = 100"}, /* WebkitGTK default: 10 */ +{ "set max_conns_host = 6"}, /* WebkitGTK default: 2 */ +{ NULL } +}; diff --git a/src/events.c b/src/events.c new file mode 100644 index 0000000..acb554c --- /dev/null +++ b/src/events.c @@ -0,0 +1,206 @@ +/* + ** Uzbl event routines + ** (c) 2009 by Robert Manea +*/ + +#include "uzbl-core.h" +#include "events.h" + +UzblCore uzbl; + +/* Event id to name mapping + * Event names must be in the same + * order as in 'enum event_type' + * + * TODO: Add more useful events +*/ +const char *event_table[LAST_EVENT] = { + "LOAD_START" , + "LOAD_COMMIT" , + "LOAD_FINISH" , + "LOAD_ERROR" , + "KEY_PRESS" , + "KEY_RELEASE" , + "DOWNLOAD_REQUEST" , + "COMMAND_EXECUTED" , + "LINK_HOVER" , + "TITLE_CHANGED" , + "GEOMETRY_CHANGED" , + "WEBINSPECTOR" , + "NEW_WINDOW" , + "SELECTION_CHANGED", + "VARIABLE_SET" , + "FIFO_SET" , + "SOCKET_SET" , + "INSTANCE_START" , + "INSTANCE_EXIT" , + "LOAD_PROGRESS" , + "LINK_UNHOVER" , + "FORM_ACTIVE" , + "ROOT_ACTIVE" , + "FOCUS_LOST" , + "FOCUS_GAINED" , + "FILE_INCLUDED" , + "PLUG_CREATED" , + "COMMAND_ERROR" , + "BUILTINS" +}; + +void +event_buffer_timeout(guint sec) { + struct itimerval t; + memset(&t, 0, sizeof t); + t.it_value.tv_sec = sec; + t.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &t, NULL); +} + + +void +send_event_socket(GString *msg) { + GError *error = NULL; + GString *tmp; + GIOChannel *gio = NULL; + GIOStatus ret; + gsize len; + guint i=0, j=0; + + /* write to all --connect-socket sockets */ + if(uzbl.comm.connect_chan) { + while(i < uzbl.comm.connect_chan->len) { + gio = g_ptr_array_index(uzbl.comm.connect_chan, i++); + j=0; + + if(gio && gio->is_writeable) { + if(uzbl.state.event_buffer) { + event_buffer_timeout(0); + + /* replay buffered events */ + while(j < uzbl.state.event_buffer->len) { + tmp = g_ptr_array_index(uzbl.state.event_buffer, j++); + ret = g_io_channel_write_chars (gio, + tmp->str, tmp->len, + &len, &error); + + if (ret == G_IO_STATUS_ERROR) + g_warning ("Error sending event to socket: %s", error->message); + else + g_io_channel_flush(gio, &error); + } + } + + if(msg) { + ret = g_io_channel_write_chars (gio, + msg->str, msg->len, + &len, &error); + + if (ret == G_IO_STATUS_ERROR) + g_warning ("Error sending event to socket: %s", error->message); + else + g_io_channel_flush(gio, &error); + } + } + } + if(uzbl.state.event_buffer) { + g_ptr_array_free(uzbl.state.event_buffer, TRUE); + uzbl.state.event_buffer = NULL; + } + } + /* buffer events until a socket is set and connected + * or a timeout is encountered + */ + else { + if(!uzbl.state.event_buffer) + uzbl.state.event_buffer = g_ptr_array_new(); + g_ptr_array_add(uzbl.state.event_buffer, (gpointer)g_string_new(msg->str)); + } + + /* write to all client sockets */ + i=0; + if(msg && uzbl.comm.client_chan) { + while(i < uzbl.comm.client_chan->len) { + gio = g_ptr_array_index(uzbl.comm.client_chan, i++); + + if(gio && gio->is_writeable && msg) { + ret = g_io_channel_write_chars (gio, + msg->str, msg->len, + &len, &error); + + if (ret == G_IO_STATUS_ERROR) + g_warning ("Error sending event to socket: %s", error->message); + else + g_io_channel_flush(gio, &error); + } + } + } +} + +void +send_event_stdout(GString *msg) { + printf("%s", msg->str); + fflush(stdout); +} + +/* + * build event string and send over the supported interfaces + * custom_event == NULL indicates an internal event +*/ +void +send_event(int type, const gchar *details, const gchar *custom_event) { + GString *event_message = g_string_new(""); + gchar *buf, *p_val = NULL; + + /* expand shell vars */ + if(details) { + buf = g_strdup(details); + p_val = parseenv(buf ? g_strchug(buf) : " "); + g_free(buf); + } + + /* check for custom events */ + if(custom_event) { + g_string_printf(event_message, "EVENT [%s] %s %s\n", + uzbl.state.instance_name, custom_event, p_val); + } + /* check wether we support the internal event */ + else if(type < LAST_EVENT) { + g_string_printf(event_message, "EVENT [%s] %s %s\n", + uzbl.state.instance_name, event_table[type], p_val); + } + + if(event_message->str) { + if(uzbl.state.events_stdout) + send_event_stdout(event_message); + send_event_socket(event_message); + + g_string_free(event_message, TRUE); + } + g_free(p_val); +} + +/* Transform gdk key events to our own events */ +void +key_to_event(guint keyval, gint mode) { + gchar ucs[7]; + gint ulen; + guint32 ukval = gdk_keyval_to_unicode(keyval); + + /* check for printable unicode char */ + /* TODO: Pass the keyvals through a GtkIMContext so that + * we also get combining chars right + */ + if(g_unichar_isgraph(ukval)) { + ulen = g_unichar_to_utf8(ukval, ucs); + ucs[ulen] = 0; + + send_event(mode == GDK_KEY_PRESS ? KEY_PRESS : KEY_RELEASE, + ucs, NULL); + } + /* send keysym for non-printable chars */ + else { + send_event(mode == GDK_KEY_PRESS ? KEY_PRESS : KEY_RELEASE, + gdk_keyval_name(keyval), NULL); + } + +} + diff --git a/src/events.h b/src/events.h new file mode 100644 index 0000000..7b8f58b --- /dev/null +++ b/src/events.h @@ -0,0 +1,35 @@ +/* + ** Uzbl event routines + ** (c) 2009 by Robert Manea +*/ + +/* Event system */ +enum event_type { + LOAD_START, LOAD_COMMIT, LOAD_FINISH, LOAD_ERROR, + KEY_PRESS, KEY_RELEASE, DOWNLOAD_REQ, COMMAND_EXECUTED, + LINK_HOVER, TITLE_CHANGED, GEOMETRY_CHANGED, + WEBINSPECTOR, NEW_WINDOW, SELECTION_CHANGED, + VARIABLE_SET, FIFO_SET, SOCKET_SET, + INSTANCE_START, INSTANCE_EXIT, LOAD_PROGRESS, + LINK_UNHOVER, FORM_ACTIVE, ROOT_ACTIVE, + FOCUS_LOST, FOCUS_GAINED, FILE_INCLUDED, + PLUG_CREATED, COMMAND_ERROR, BUILTINS, + + /* must be last entry */ + LAST_EVENT +}; + +void +event_buffer_timeout(guint sec); + +void +send_event_socket(GString *msg); + +void +send_event_stdout(GString *msg); + +void +send_event(int type, const gchar *details, const gchar *custom_event); + +void +key_to_event(guint keyval, gint mode); diff --git a/src/inspector.c b/src/inspector.c new file mode 100644 index 0000000..de3dbcd --- /dev/null +++ b/src/inspector.c @@ -0,0 +1,103 @@ +/* + ** WebInspector + ** (c) 2009 by Robert Manea +*/ + +#include "uzbl-core.h" +#include "events.h" +#include "callbacks.h" + + +void +hide_window_cb(GtkWidget *widget, gpointer data) { + (void) data; + + gtk_widget_hide(widget); +} + +WebKitWebView* +create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){ + (void) data; + (void) page; + (void) web_inspector; + GtkWidget* scrolled_window; + GtkWidget* new_web_view; + GUI *g = &uzbl.gui; + + g->inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(G_OBJECT(g->inspector_window), "delete-event", + G_CALLBACK(hide_window_cb), NULL); + + gtk_window_set_title(GTK_WINDOW(g->inspector_window), "Uzbl WebInspector"); + gtk_window_set_default_size(GTK_WINDOW(g->inspector_window), 400, 300); + gtk_widget_show(g->inspector_window); + + scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(g->inspector_window), scrolled_window); + gtk_widget_show(scrolled_window); + + new_web_view = webkit_web_view_new(); + gtk_container_add(GTK_CONTAINER(scrolled_window), new_web_view); + + return WEBKIT_WEB_VIEW(new_web_view); +} + +gboolean +inspector_show_window_cb (WebKitWebInspector* inspector){ + (void) inspector; + gtk_widget_show(uzbl.gui.inspector_window); + + send_event(WEBINSPECTOR, "open", NULL); + return TRUE; +} + +/* TODO: Add variables and code to make use of these functions */ +gboolean +inspector_close_window_cb (WebKitWebInspector* inspector){ + (void) inspector; + send_event(WEBINSPECTOR, "close", NULL); + return TRUE; +} + +gboolean +inspector_attach_window_cb (WebKitWebInspector* inspector){ + (void) inspector; + return FALSE; +} + +gboolean +inspector_detach_window_cb (WebKitWebInspector* inspector){ + (void) inspector; + return FALSE; +} + +gboolean +inspector_uri_changed_cb (WebKitWebInspector* inspector){ + (void) inspector; + return FALSE; +} + +gboolean +inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){ + (void) inspector; + return FALSE; +} + +void +set_up_inspector() { + GUI *g = &uzbl.gui; + WebKitWebSettings *settings = view_settings(); + g_object_set(G_OBJECT(settings), "enable-developer-extras", TRUE, NULL); + + uzbl.gui.inspector = webkit_web_view_get_inspector(uzbl.gui.web_view); + g_signal_connect (G_OBJECT (g->inspector), "inspect-web-view", G_CALLBACK (create_inspector_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "show-window", G_CALLBACK (inspector_show_window_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "close-window", G_CALLBACK (inspector_close_window_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "attach-window", G_CALLBACK (inspector_attach_window_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "detach-window", G_CALLBACK (inspector_detach_window_cb), NULL); + g_signal_connect (G_OBJECT (g->inspector), "finished", G_CALLBACK (inspector_inspector_destroyed_cb), NULL); + + g_signal_connect (G_OBJECT (g->inspector), "notify::inspected-uri", G_CALLBACK (inspector_uri_changed_cb), NULL); +} diff --git a/src/inspector.h b/src/inspector.h new file mode 100644 index 0000000..57d0ca9 --- /dev/null +++ b/src/inspector.h @@ -0,0 +1,7 @@ +/* + ** WebInspector + ** (c) 2009 by Robert Manea +*/ + +void +set_up_inspector(); diff --git a/src/uzbl-browser b/src/uzbl-browser new file mode 100755 index 0000000..d9b9752 --- /dev/null +++ b/src/uzbl-browser @@ -0,0 +1,66 @@ +#!/bin/sh +# this script implements a more useful out-of-the-box "browsing experience". +# it does so by combining uzbl-core with a set of "recommended" tools and practices. +# see docs for more info +# If you want to customize the behavior of the cookie-daemon or similar helper tools, +# copy them to your $XDG_DATA_HOME/uzbl/scripts/, edit them and update $PATH + +# Also, we assume existence of fifo/socket == correctly functioning cookie_daemon/event_manager. +# Checking correct functioning of the daemons here would be too complex here, and it's not implemented in uzbl-core either. +# But this shouldn't cause much problems.. + +PREFIX=/usr/local +if [ -z "$XDG_DATA_HOME" ] +then + export XDG_DATA_HOME=$HOME/.local/share +fi + +if [ -z "$XDG_CACHE_HOME" ] +then + export XDG_CACHE_HOME=$HOME/.cache +fi + +if [ -z "$XDG_CONFIG_HOME" ] +then + export XDG_CONFIG_HOME=$HOME/.config +fi + +# assure the relevant directories exist. +for dir in $XDG_CACHE_HOME/uzbl $XDG_DATA_HOME/uzbl $XDG_CONFIG_HOME/uzbl +do + if [ ! -d $dir ] + then + if ! mkdir -p $dir + then + echo "could not create $dir" >&2 + exit 2 + fi + fi +done +# if no config exists yet in the recommended location, put the default (recommended) config there +if [ ! -f $XDG_CONFIG_HOME/uzbl/config ] +then + if ! cp $PREFIX/share/uzbl/examples/config/uzbl/config $XDG_CONFIG_HOME/uzbl/config + then + echo "Could not copy default config to $XDG_CONFIG_HOME/uzbl/config" >&2 + exit 3 + fi +fi + +# Uncomment this for a slight speedup at the expense of not having +# stale cookie daemon sockets cleaned up. +#if [ ! -S $XDG_CACHE_HOME/uzbl/cookie_daemon_socket ] +#then + # if you want to customize it, copy to your $XDG_DATA_HOME/uzbl/scripts/ and update $PATH + uzbl-cookie-daemon -v start +#fi + +DAEMON_SOCKET=$XDG_CACHE_HOME/uzbl/event_daemon +DAEMON_PID=${DAEMON_SOCKET}.pid + +#if [ -f "$DAEMON_PID" ] +#then + uzbl-event-manager -va start +#fi + +uzbl-core "$@" --connect-socket $DAEMON_SOCKET diff --git a/src/uzbl-core.c b/src/uzbl-core.c new file mode 100644 index 0000000..bc294b4 --- /dev/null +++ b/src/uzbl-core.c @@ -0,0 +1,2637 @@ +/* -*- c-basic-offset: 4; -*- */ +// Original code taken from the example webkit-gtk+ application. see notice below. +// Modified code is licensed under the GPL 3. See LICENSE file. + + +/* + * Copyright (C) 2006, 2007 Apple Inc. + * Copyright (C) 2007 Alp Toker + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "uzbl-core.h" +#include "callbacks.h" +#include "events.h" +#include "inspector.h" +#include "config.h" + +UzblCore uzbl; + +/* commandline arguments (set initial values for the state variables) */ +const +GOptionEntry entries[] = +{ + { "uri", 'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri, + "Uri to load at startup (equivalent to 'uzbl ' or 'set uri = URI' after uzbl has launched)", "URI" }, + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &uzbl.state.verbose, + "Whether to print all messages or just errors.", NULL }, + { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, + "Name of the current instance (defaults to Xorg window id or random for GtkSocket mode)", "NAME" }, + { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, + "Path to config file or '-' for stdin", "FILE" }, + { "socket", 's', 0, G_OPTION_ARG_INT, &uzbl.state.socket_id, + "Socket ID", "SOCKET" }, + { "connect-socket", 0, 0, G_OPTION_ARG_STRING_ARRAY, &uzbl.state.connect_socket_names, + "Connect to server socket", "CSOCKET" }, + { "print-events", 'p', 0, G_OPTION_ARG_NONE, &uzbl.state.events_stdout, + "Whether to print events to stdout.", NULL }, + { "geometry", 'g', 0, G_OPTION_ARG_STRING, &uzbl.gui.geometry, + "Set window geometry (format: WIDTHxHEIGHT+-X+-Y or maximized)", "GEOMETRY" }, + { "version", 'V', 0, G_OPTION_ARG_NONE, &uzbl.behave.print_version, + "Print the version and exit", NULL }, + { NULL, 0, 0, 0, NULL, NULL, NULL } +}; + +XDG_Var XDG[] = +{ + { "XDG_CONFIG_HOME", "~/.config" }, + { "XDG_DATA_HOME", "~/.local/share" }, + { "XDG_CACHE_HOME", "~/.cache" }, + { "XDG_CONFIG_DIRS", "/etc/xdg" }, + { "XDG_DATA_DIRS", "/usr/local/share/:/usr/share/" }, +}; + +/* abbreviations to help keep the table's width humane */ +#define PTR_V_STR(var, d, fun) { .ptr.s = &(var), .type = TYPE_STR, .dump = d, .writeable = 1, .func = fun } +#define PTR_V_INT(var, d, fun) { .ptr.i = (int*)&(var), .type = TYPE_INT, .dump = d, .writeable = 1, .func = fun } +#define PTR_V_FLOAT(var, d, fun) { .ptr.f = &(var), .type = TYPE_FLOAT, .dump = d, .writeable = 1, .func = fun } +#define PTR_C_STR(var, fun) { .ptr.s = &(var), .type = TYPE_STR, .dump = 0, .writeable = 0, .func = fun } +#define PTR_C_INT(var, fun) { .ptr.i = (int*)&(var), .type = TYPE_INT, .dump = 0, .writeable = 0, .func = fun } +#define PTR_C_FLOAT(var, fun) { .ptr.f = &(var), .type = TYPE_FLOAT, .dump = 0, .writeable = 0, .func = fun } + +const struct var_name_to_ptr_t { + const char *name; + uzbl_cmdprop cp; +} var_name_to_ptr[] = { +/* variable name pointer to variable in code dump callback function */ +/* ---------------------------------------------------------------------------------------------- */ + { "uri", PTR_V_STR(uzbl.state.uri, 1, cmd_load_uri)}, + { "verbose", PTR_V_INT(uzbl.state.verbose, 1, NULL)}, + { "print_events", PTR_V_INT(uzbl.state.events_stdout, 1, NULL)}, + { "inject_html", PTR_V_STR(uzbl.behave.inject_html, 0, cmd_inject_html)}, + { "geometry", PTR_V_STR(uzbl.gui.geometry, 1, cmd_set_geometry)}, + { "keycmd", PTR_V_STR(uzbl.state.keycmd, 1, NULL)}, + { "show_status", PTR_V_INT(uzbl.behave.show_status, 1, cmd_set_status)}, + { "status_top", PTR_V_INT(uzbl.behave.status_top, 1, move_statusbar)}, + { "status_format", PTR_V_STR(uzbl.behave.status_format, 1, NULL)}, + { "status_background", PTR_V_STR(uzbl.behave.status_background, 1, NULL)}, + { "title_format_long", PTR_V_STR(uzbl.behave.title_format_long, 1, NULL)}, + { "title_format_short", PTR_V_STR(uzbl.behave.title_format_short, 1, NULL)}, + { "icon", PTR_V_STR(uzbl.gui.icon, 1, set_icon)}, + { "forward_keys", PTR_V_INT(uzbl.behave.forward_keys, 1, NULL)}, + { "download_handler", PTR_V_STR(uzbl.behave.download_handler, 1, NULL)}, + { "cookie_handler", PTR_V_STR(uzbl.behave.cookie_handler, 1, NULL)}, + { "new_window", PTR_V_STR(uzbl.behave.new_window, 1, NULL)}, + { "scheme_handler", PTR_V_STR(uzbl.behave.scheme_handler, 1, NULL)}, + { "fifo_dir", PTR_V_STR(uzbl.behave.fifo_dir, 1, cmd_fifo_dir)}, + { "socket_dir", PTR_V_STR(uzbl.behave.socket_dir, 1, cmd_socket_dir)}, + { "http_debug", PTR_V_INT(uzbl.behave.http_debug, 1, cmd_http_debug)}, + { "shell_cmd", PTR_V_STR(uzbl.behave.shell_cmd, 1, NULL)}, + { "proxy_url", PTR_V_STR(uzbl.net.proxy_url, 1, set_proxy_url)}, + { "max_conns", PTR_V_INT(uzbl.net.max_conns, 1, cmd_max_conns)}, + { "max_conns_host", PTR_V_INT(uzbl.net.max_conns_host, 1, cmd_max_conns_host)}, + { "useragent", PTR_V_STR(uzbl.net.useragent, 1, cmd_useragent)}, + /* requires webkit >=1.1.14 */ + { "view_source", PTR_V_INT(uzbl.behave.view_source, 0, cmd_view_source)}, + + /* exported WebKitWebSettings properties */ + { "zoom_level", PTR_V_FLOAT(uzbl.behave.zoom_level, 1, cmd_zoom_level)}, + { "zoom_type", PTR_V_INT(uzbl.behave.zoom_type, 1, cmd_set_zoom_type)}, + { "font_size", PTR_V_INT(uzbl.behave.font_size, 1, cmd_font_size)}, + { "default_font_family", PTR_V_STR(uzbl.behave.default_font_family, 1, cmd_default_font_family)}, + { "monospace_font_family", PTR_V_STR(uzbl.behave.monospace_font_family, 1, cmd_monospace_font_family)}, + { "cursive_font_family", PTR_V_STR(uzbl.behave.cursive_font_family, 1, cmd_cursive_font_family)}, + { "sans_serif_font_family", PTR_V_STR(uzbl.behave.sans_serif_font_family, 1, cmd_sans_serif_font_family)}, + { "serif_font_family", PTR_V_STR(uzbl.behave.serif_font_family, 1, cmd_serif_font_family)}, + { "fantasy_font_family", PTR_V_STR(uzbl.behave.fantasy_font_family, 1, cmd_fantasy_font_family)}, + { "monospace_size", PTR_V_INT(uzbl.behave.monospace_size, 1, cmd_font_size)}, + { "minimum_font_size", PTR_V_INT(uzbl.behave.minimum_font_size, 1, cmd_minimum_font_size)}, + { "disable_plugins", PTR_V_INT(uzbl.behave.disable_plugins, 1, cmd_disable_plugins)}, + { "disable_scripts", PTR_V_INT(uzbl.behave.disable_scripts, 1, cmd_disable_scripts)}, + { "autoload_images", PTR_V_INT(uzbl.behave.autoload_img, 1, cmd_autoload_img)}, + { "autoshrink_images", PTR_V_INT(uzbl.behave.autoshrink_img, 1, cmd_autoshrink_img)}, + { "enable_spellcheck", PTR_V_INT(uzbl.behave.enable_spellcheck, 1, cmd_enable_spellcheck)}, + { "enable_private", PTR_V_INT(uzbl.behave.enable_private, 1, cmd_enable_private)}, + { "print_backgrounds", PTR_V_INT(uzbl.behave.print_bg, 1, cmd_print_bg)}, + { "stylesheet_uri", PTR_V_STR(uzbl.behave.style_uri, 1, cmd_style_uri)}, + { "resizable_text_areas", PTR_V_INT(uzbl.behave.resizable_txt, 1, cmd_resizable_txt)}, + { "default_encoding", PTR_V_STR(uzbl.behave.default_encoding, 1, cmd_default_encoding)}, + { "enforce_96_dpi", PTR_V_INT(uzbl.behave.enforce_96dpi, 1, cmd_enforce_96dpi)}, + { "caret_browsing", PTR_V_INT(uzbl.behave.caret_browsing, 1, cmd_caret_browsing)}, + + /* constants (not dumpable or writeable) */ + { "WEBKIT_MAJOR", PTR_C_INT(uzbl.info.webkit_major, NULL)}, + { "WEBKIT_MINOR", PTR_C_INT(uzbl.info.webkit_minor, NULL)}, + { "WEBKIT_MICRO", PTR_C_INT(uzbl.info.webkit_micro, NULL)}, + { "ARCH_UZBL", PTR_C_STR(uzbl.info.arch, NULL)}, + { "COMMIT", PTR_C_STR(uzbl.info.commit, NULL)}, + { "TITLE", PTR_C_STR(uzbl.gui.main_title, NULL)}, + { "SELECTED_URI", PTR_C_STR(uzbl.state.selected_url, NULL)}, + { "NAME", PTR_C_STR(uzbl.state.instance_name, NULL)}, + { "PID", PTR_C_STR(uzbl.info.pid_str, NULL)}, + + { NULL, {.ptr.s = NULL, .type = TYPE_INT, .dump = 0, .writeable = 0, .func = NULL}} +}; + +/* construct a hash from the var_name_to_ptr array for quick access */ +void +create_var_to_name_hash() { + const struct var_name_to_ptr_t *n2v_p = var_name_to_ptr; + uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal); + while(n2v_p->name) { + g_hash_table_insert(uzbl.comm.proto_var, + (gpointer) n2v_p->name, + (gpointer) &n2v_p->cp); + n2v_p++; + } +} + + +/* --- UTILITY FUNCTIONS --- */ +enum exp_type {EXP_ERR, EXP_SIMPLE_VAR, EXP_BRACED_VAR, EXP_EXPR, EXP_JS, EXP_ESCAPE}; +enum exp_type +get_exp_type(const gchar *s) { + /* variables */ + if(*(s+1) == '(') + return EXP_EXPR; + else if(*(s+1) == '{') + return EXP_BRACED_VAR; + else if(*(s+1) == '<') + return EXP_JS; + else if(*(s+1) == '[') + return EXP_ESCAPE; + else + return EXP_SIMPLE_VAR; + + /*@notreached@*/ +return EXP_ERR; +} + +/* + * recurse == 1: don't expand '@(command)@' + * recurse == 2: don't expand '@@' +*/ +gchar * +expand(const char *s, guint recurse) { + uzbl_cmdprop *c; + enum exp_type etype; + char *end_simple_var = "^°!\"§$%&/()=?'`'+~*'#-.:,;@<>| \\{}[]¹²³¼½"; + char *ret = NULL; + char *vend = NULL; + GError *err = NULL; + gchar *cmd_stdout = NULL; + gchar *mycmd = NULL; + GString *buf = g_string_new(""); + GString *js_ret = g_string_new(""); + + while(s && *s) { + switch(*s) { + case '\\': + g_string_append_c(buf, *++s); + s++; + break; + + case '@': + etype = get_exp_type(s); + s++; + + switch(etype) { + case EXP_SIMPLE_VAR: + vend = strpbrk(s, end_simple_var); + if(!vend) vend = strchr(s, '\0'); + break; + case EXP_BRACED_VAR: + s++; + vend = strchr(s, '}'); + if(!vend) vend = strchr(s, '\0'); + break; + case EXP_EXPR: + s++; + vend = strstr(s, ")@"); + if(!vend) vend = strchr(s, '\0'); + break; + case EXP_JS: + s++; + vend = strstr(s, ">@"); + if(!vend) vend = strchr(s, '\0'); + break; + case EXP_ESCAPE: + s++; + vend = strstr(s, "]@"); + if(!vend) vend = strchr(s, '\0'); + break; + /*@notreached@*/ + case EXP_ERR: + break; + } + assert(vend); + + ret = g_strndup(s, vend-s); + + if(etype == EXP_SIMPLE_VAR || + etype == EXP_BRACED_VAR) { + if( (c = g_hash_table_lookup(uzbl.comm.proto_var, ret)) ) { + if(c->type == TYPE_STR && *c->ptr.s != NULL) { + g_string_append(buf, (gchar *)*c->ptr.s); + } + else if(c->type == TYPE_INT) { + g_string_append_printf(buf, "%d", *c->ptr.i); + } + else if(c->type == TYPE_FLOAT) { + g_string_append_printf(buf, "%f", *c->ptr.f); + } + } + + if(etype == EXP_SIMPLE_VAR) + s = vend; + else + s = vend+1; + } + else if(recurse != 1 && + etype == EXP_EXPR) { + + /* execute program directly */ + if(ret[0] == '+') { + mycmd = expand(ret+1, 1); + g_spawn_command_line_sync(mycmd, &cmd_stdout, NULL, NULL, &err); + g_free(mycmd); + } + /* execute program through shell, quote it first */ + else { + mycmd = expand(ret, 1); + gchar *quoted = g_shell_quote(mycmd); + gchar *tmp = g_strdup_printf("%s %s", + uzbl.behave.shell_cmd?uzbl.behave.shell_cmd:"/bin/sh -c", + quoted); + g_spawn_command_line_sync(tmp, &cmd_stdout, NULL, NULL, &err); + g_free(mycmd); + g_free(quoted); + g_free(tmp); + } + + if (err) { + g_printerr("error on running command: %s\n", err->message); + g_error_free (err); + } + else if (*cmd_stdout) { + size_t len = strlen(cmd_stdout); + + if(len > 0 && cmd_stdout[len-1] == '\n') + cmd_stdout[--len] = '\0'; /* strip trailing newline */ + + g_string_append(buf, cmd_stdout); + g_free(cmd_stdout); + } + s = vend+2; + } + else if(recurse != 2 && + etype == EXP_JS) { + + /* read JS from file */ + if(ret[0] == '+') { + GArray *tmp = g_array_new(TRUE, FALSE, sizeof(gchar *)); + mycmd = expand(ret+1, 2); + g_array_append_val(tmp, mycmd); + + run_external_js(uzbl.gui.web_view, tmp, js_ret); + g_array_free(tmp, TRUE); + } + /* JS from string */ + else { + mycmd = expand(ret, 2); + eval_js(uzbl.gui.web_view, mycmd, js_ret); + g_free(mycmd); + } + + if(js_ret->str) { + g_string_append(buf, js_ret->str); + g_string_free(js_ret, TRUE); + js_ret = g_string_new(""); + } + s = vend+2; + } + else if(etype == EXP_ESCAPE) { + mycmd = expand(ret, 0); + char *escaped = g_markup_escape_text(mycmd, strlen(mycmd)); + + g_string_append(buf, escaped); + + g_free(escaped); + g_free(mycmd); + s = vend+2; + } + + g_free(ret); + ret = NULL; + break; + + default: + g_string_append_c(buf, *s); + s++; + break; + } + } + g_string_free(js_ret, TRUE); + return g_string_free(buf, FALSE); +} + +char * +itos(int val) { + char tmp[20]; + + snprintf(tmp, sizeof(tmp), "%i", val); + return g_strdup(tmp); +} + +gchar* +strfree(gchar *str) { + g_free(str); + return NULL; +} + +gchar* +argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); } + +char * +str_replace (const char* search, const char* replace, const char* string) { + gchar **buf; + char *ret; + + if(!string) + return NULL; + + buf = g_strsplit (string, search, -1); + ret = g_strjoinv (replace, buf); + g_strfreev(buf); + + return ret; +} + +GArray* +read_file_by_line (const gchar *path) { + GIOChannel *chan = NULL; + gchar *readbuf = NULL; + gsize len; + GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*)); + int i = 0; + + chan = g_io_channel_new_file(path, "r", NULL); + if (chan) { + while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) { + const gchar* val = g_strdup (readbuf); + g_array_append_val (lines, val); + g_free (readbuf); + i ++; + } + + g_io_channel_unref (chan); + } else { + gchar *tmp = g_strdup_printf("File %s can not be read.", path); + send_event(COMMAND_ERROR, tmp, NULL); + g_free(tmp); + } + + return lines; +} + +/* search a PATH style string for an existing file+path combination */ +gchar* +find_existing_file(gchar* path_list) { + int i=0; + int cnt; + gchar **split; + gchar *tmp = NULL; + gchar *executable; + + if(!path_list) + return NULL; + + split = g_strsplit(path_list, ":", 0); + while(split[i]) + i++; + + if(i<=1) { + tmp = g_strdup(split[0]); + g_strfreev(split); + return tmp; + } + else + cnt = i-1; + + i=0; + tmp = g_strdup(split[cnt]); + g_strstrip(tmp); + if(tmp[0] == '/') + executable = g_strdup_printf("%s", tmp+1); + else + executable = g_strdup(tmp); + g_free(tmp); + + while(iweb_view, uzbl.state.last_button); + g_object_get(ht, "context", &context, NULL); + + return (gint)context; +} + +/* --- SIGNALS --- */ +int sigs[] = {SIGTERM, SIGINT, SIGSEGV, SIGILL, SIGFPE, SIGQUIT, SIGALRM, 0}; + +sigfunc* +setup_signal(int signr, sigfunc *shandler) { + struct sigaction nh, oh; + + nh.sa_handler = shandler; + sigemptyset(&nh.sa_mask); + nh.sa_flags = 0; + + if(sigaction(signr, &nh, &oh) < 0) + return SIG_ERR; + + return NULL; +} + +void +catch_signal(int s) { + if(s == SIGTERM || + s == SIGINT || + s == SIGILL || + s == SIGFPE || + s == SIGQUIT) { + clean_up(); + exit(EXIT_SUCCESS); + } + else if(s == SIGSEGV) { + clean_up(); + fprintf(stderr, "Program aborted, segmentation fault!\nAttempting to clean up...\n"); + exit(EXIT_FAILURE); + } + else if(s == SIGALRM && uzbl.state.event_buffer) { + g_ptr_array_free(uzbl.state.event_buffer, TRUE); + uzbl.state.event_buffer = NULL; + } +} + +/* scroll a bar in a given direction */ +void +scroll (GtkAdjustment* bar, gchar *amount_str) { + gchar *end; + gdouble max_value; + + gdouble page_size = gtk_adjustment_get_page_size(bar); + gdouble value = gtk_adjustment_get_value(bar); + gdouble amount = g_ascii_strtod(amount_str, &end); + + if (*end == '%') + value += page_size * amount * 0.01; + else + value += amount; + + max_value = gtk_adjustment_get_upper(bar) - page_size; + + if (value > max_value) + value = max_value; /* don't scroll past the end of the page */ + + gtk_adjustment_set_value (bar, value); +} + +/* + * scroll vertical 20 + * scroll vertical 20% + * scroll vertical -40 + * scroll vertical begin + * scroll vertical end + * scroll horizontal 10 + * scroll horizontal -500 + * scroll horizontal begin + * scroll horizontal end + */ +void +scroll_cmd(WebKitWebView* page, GArray *argv, GString *result) { + (void) page; (void) result; + gchar *direction = g_array_index(argv, gchar*, 0); + gchar *argv1 = g_array_index(argv, gchar*, 1); + + if (g_strcmp0(direction, "horizontal") == 0) + { + if (g_strcmp0(argv1, "begin") == 0) + gtk_adjustment_set_value(uzbl.gui.bar_h, gtk_adjustment_get_lower(uzbl.gui.bar_h)); + else if (g_strcmp0(argv1, "end") == 0) + gtk_adjustment_set_value (uzbl.gui.bar_h, gtk_adjustment_get_upper(uzbl.gui.bar_h) - + gtk_adjustment_get_page_size(uzbl.gui.bar_h)); + else + scroll(uzbl.gui.bar_h, argv1); + } + else if (g_strcmp0(direction, "vertical") == 0) + { + if (g_strcmp0(argv1, "begin") == 0) + gtk_adjustment_set_value(uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v)); + else if (g_strcmp0(argv1, "end") == 0) + gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) - + gtk_adjustment_get_page_size(uzbl.gui.bar_v)); + else + scroll(uzbl.gui.bar_v, argv1); + } + else + if(uzbl.state.verbose) + puts("Unrecognized scroll format"); +} + + +/* VIEW funcs (little webkit wrappers) */ +#define VIEWFUNC(name) void view_##name(WebKitWebView *page, GArray *argv, GString *result){(void)argv; (void)result; webkit_web_view_##name(page);} +VIEWFUNC(reload) +VIEWFUNC(reload_bypass_cache) +VIEWFUNC(stop_loading) +VIEWFUNC(zoom_in) +VIEWFUNC(zoom_out) +VIEWFUNC(go_back) +VIEWFUNC(go_forward) +#undef VIEWFUNC + +/* -- command to callback/function map for things we cannot attach to any signals */ +struct {const char *key; CommandInfo value;} cmdlist[] = +{ /* key function no_split */ + { "back", {view_go_back, 0} }, + { "forward", {view_go_forward, 0} }, + { "scroll", {scroll_cmd, 0} }, + { "reload", {view_reload, 0}, }, + { "reload_ign_cache", {view_reload_bypass_cache, 0} }, + { "stop", {view_stop_loading, 0}, }, + { "zoom_in", {view_zoom_in, 0}, }, //Can crash (when max zoom reached?). + { "zoom_out", {view_zoom_out, 0}, }, + { "toggle_zoom_type", {toggle_zoom_type, 0}, }, + { "uri", {load_uri, TRUE} }, + { "js", {run_js, TRUE} }, + { "script", {run_external_js, 0} }, + { "toggle_status", {toggle_status_cb, 0} }, + { "spawn", {spawn, 0} }, + { "sync_spawn", {spawn_sync, 0} }, // needed for cookie handler + { "sh", {spawn_sh, 0} }, + { "sync_sh", {spawn_sh_sync, 0} }, // needed for cookie handler + { "talk_to_socket", {talk_to_socket, 0} }, + { "exit", {close_uzbl, 0} }, + { "search", {search_forward_text, TRUE} }, + { "search_reverse", {search_reverse_text, TRUE} }, + { "search_clear", {search_clear, TRUE} }, + { "dehilight", {dehilight, 0} }, + { "set", {set_var, TRUE} }, + { "dump_config", {act_dump_config, 0} }, + { "dump_config_as_events", {act_dump_config_as_events, 0} }, + { "chain", {chain, 0} }, + { "print", {print, TRUE} }, + { "event", {event, TRUE} }, + { "request", {event, TRUE} }, + { "menu_add", {menu_add, TRUE} }, + { "menu_link_add", {menu_add_link, TRUE} }, + { "menu_image_add", {menu_add_image, TRUE} }, + { "menu_editable_add", {menu_add_edit, TRUE} }, + { "menu_separator", {menu_add_separator, TRUE} }, + { "menu_link_separator", {menu_add_separator_link, TRUE} }, + { "menu_image_separator", {menu_add_separator_image, TRUE}}, + { "menu_editable_separator", {menu_add_separator_edit, TRUE} }, + { "menu_remove", {menu_remove, TRUE} }, + { "menu_link_remove", {menu_remove_link, TRUE} }, + { "menu_image_remove", {menu_remove_image, TRUE} }, + { "menu_editable_remove", {menu_remove_edit, TRUE} }, + { "hardcopy", {hardcopy, TRUE} }, + { "include", {include, TRUE} } +}; + +void +commands_hash(void) +{ + unsigned int i; + uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal); + + for (i = 0; i < LENGTH(cmdlist); i++) + g_hash_table_insert(uzbl.behave.commands, (gpointer) cmdlist[i].key, &cmdlist[i].value); +} + +void +builtins() { + unsigned int i, + len = LENGTH(cmdlist); + GString *command_list = g_string_new(""); + + for (i = 0; i < len; i++) { + g_string_append(command_list, cmdlist[i].key); + g_string_append_c(command_list, ' '); + } + + send_event(BUILTINS, command_list->str, NULL); + g_string_free(command_list, TRUE); +} + +/* -- CORE FUNCTIONS -- */ + +bool +file_exists (const char * filename) { + return (access(filename, F_OK) == 0); +} + +void +set_var(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + + if(!argv_idx(argv, 0)) + return; + + gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2); + if (split[0] != NULL) { + gchar *value = parseenv(split[1] ? g_strchug(split[1]) : " "); + set_var_value(g_strstrip(split[0]), value); + g_free(value); + } + g_strfreev(split); +} + +void +add_to_menu(GArray *argv, guint context) { + GUI *g = &uzbl.gui; + MenuItem *m; + gchar *item_cmd = NULL; + + if(!argv_idx(argv, 0)) + return; + + gchar **split = g_strsplit(argv_idx(argv, 0), "=", 2); + if(!g->menu_items) + g->menu_items = g_ptr_array_new(); + + if(split[1]) + item_cmd = g_strdup(split[1]); + + if(split[0]) { + m = malloc(sizeof(MenuItem)); + m->name = g_strdup(split[0]); + m->cmd = g_strdup(item_cmd?item_cmd:""); + m->context = context; + m->issep = FALSE; + g_ptr_array_add(g->menu_items, m); + } + else + g_free(item_cmd); + + g_strfreev(split); +} + +void +menu_add(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); + +} + +void +menu_add_link(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); +} + +void +menu_add_image(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); +} + +void +menu_add_edit(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); +} + +void +add_separator_to_menu(GArray *argv, guint context) { + GUI *g = &uzbl.gui; + MenuItem *m; + gchar *sep_name; + + if(!g->menu_items) + g->menu_items = g_ptr_array_new(); + + if(!argv_idx(argv, 0)) + return; + else + sep_name = argv_idx(argv, 0); + + m = malloc(sizeof(MenuItem)); + m->name = g_strdup(sep_name); + m->cmd = NULL; + m->context = context; + m->issep = TRUE; + g_ptr_array_add(g->menu_items, m); +} + +void +menu_add_separator(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); +} + +void +menu_add_separator_link(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); +} +void +menu_add_separator_image(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); +} + +void +menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + add_separator_to_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); +} + +void +remove_from_menu(GArray *argv, guint context) { + GUI *g = &uzbl.gui; + MenuItem *mi; + gchar *name = NULL; + guint i=0; + + if(!g->menu_items) + return; + + if(!argv_idx(argv, 0)) + return; + else + name = argv_idx(argv, 0); + + for(i=0; i < g->menu_items->len; i++) { + mi = g_ptr_array_index(g->menu_items, i); + + if((context == mi->context) && !strcmp(name, mi->name)) { + g_free(mi->name); + g_free(mi->cmd); + g_free(mi); + g_ptr_array_remove_index(g->menu_items, i); + } + } +} + +void +menu_remove(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT); +} + +void +menu_remove_link(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK); +} + +void +menu_remove_image(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE); +} + +void +menu_remove_edit(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + + remove_from_menu(argv, WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE); +} + +void +event(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + GString *event_name; + gchar **split = NULL; + + if(!argv_idx(argv, 0)) + return; + + split = g_strsplit(argv_idx(argv, 0), " ", 2); + if(split[0]) + event_name = g_string_ascii_up(g_string_new(split[0])); + else + return; + + send_event(0, split[1]?split[1]:"", event_name->str); + + g_string_free(event_name, TRUE); + g_strfreev(split); +} + +void +print(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + gchar* buf; + + buf = expand(argv_idx(argv, 0), 0); + g_string_assign(result, buf); + g_free(buf); +} + +void +hardcopy(WebKitWebView *page, GArray *argv, GString *result) { + (void) argv; + (void) result; + + webkit_web_frame_print(webkit_web_view_get_main_frame(page)); +} + +void +include(WebKitWebView *page, GArray *argv, GString *result) { + (void) page; + (void) result; + gchar *pe = NULL, + *path = NULL, + *line; + int i=0; + + if(!argv_idx(argv, 0)) + return; + + pe = parseenv(argv_idx(argv, 0)); + if((path = find_existing_file(pe))) { + GArray* lines = read_file_by_line(path); + + while ((line = g_array_index(lines, gchar*, i))) { + parse_cmd_line (line, NULL); + i++; + g_free (line); + } + g_array_free (lines, TRUE); + + send_event(FILE_INCLUDED, path, NULL); + g_free(path); + } + g_free(pe); +} + +void +act_dump_config() { + dump_config(); +} + +void +act_dump_config_as_events() { + dump_config_as_events(); +} + +void +load_uri (WebKitWebView *web_view, GArray *argv, GString *result) { + (void) web_view; (void) result; + load_uri_imp (argv_idx (argv, 0)); +} + +/* Javascript*/ + +JSValueRef +js_run_command (JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, + size_t argumentCount, const JSValueRef arguments[], + JSValueRef* exception) { + (void) function; + (void) thisObject; + (void) exception; + + JSStringRef js_result_string; + GString *result = g_string_new(""); + + if (argumentCount >= 1) { + JSStringRef arg = JSValueToStringCopy(ctx, arguments[0], NULL); + size_t arg_size = JSStringGetMaximumUTF8CStringSize(arg); + char ctl_line[arg_size]; + JSStringGetUTF8CString(arg, ctl_line, arg_size); + + parse_cmd_line(ctl_line, result); + + JSStringRelease(arg); + } + js_result_string = JSStringCreateWithUTF8CString(result->str); + + g_string_free(result, TRUE); + + return JSValueMakeString(ctx, js_result_string); +} + +JSStaticFunction js_static_functions[] = { + {"run", js_run_command, kJSPropertyAttributeNone}, +}; + +void +js_init() { + /* This function creates the class and its definition, only once */ + if (!uzbl.js.initialized) { + /* it would be pretty cool to make this dynamic */ + uzbl.js.classdef = kJSClassDefinitionEmpty; + uzbl.js.classdef.staticFunctions = js_static_functions; + + uzbl.js.classref = JSClassCreate(&uzbl.js.classdef); + } +} + + +void +eval_js(WebKitWebView * web_view, gchar *script, GString *result) { + WebKitWebFrame *frame; + JSGlobalContextRef context; + JSObjectRef globalobject; + JSStringRef var_name; + + JSStringRef js_script; + JSValueRef js_result; + JSValueRef js_exc = NULL; + JSStringRef js_result_string; + size_t js_result_size; + + js_init(); + + frame = webkit_web_view_get_main_frame(WEBKIT_WEB_VIEW(web_view)); + context = webkit_web_frame_get_global_context(frame); + globalobject = JSContextGetGlobalObject(context); + + /* uzbl javascript namespace */ + var_name = JSStringCreateWithUTF8CString("Uzbl"); + JSObjectSetProperty(context, globalobject, var_name, + JSObjectMake(context, uzbl.js.classref, NULL), + kJSClassAttributeNone, NULL); + + /* evaluate the script and get return value*/ + js_script = JSStringCreateWithUTF8CString(script); + js_result = JSEvaluateScript(context, js_script, globalobject, NULL, 0, &js_exc); + if (js_result && !JSValueIsUndefined(context, js_result)) { + js_result_string = JSValueToStringCopy(context, js_result, NULL); + js_result_size = JSStringGetMaximumUTF8CStringSize(js_result_string); + + if (js_result_size) { + char js_result_utf8[js_result_size]; + JSStringGetUTF8CString(js_result_string, js_result_utf8, js_result_size); + g_string_assign(result, js_result_utf8); + } + + JSStringRelease(js_result_string); + } + else if (js_exc && JSValueIsObject(context, js_exc)) { + size_t size; + JSStringRef prop, val; + JSObjectRef exc = JSValueToObject(context, js_exc, NULL); + + printf("Exception occured while executing script:\n"); + + /* Print line */ + prop = JSStringCreateWithUTF8CString("line"); + val = JSValueToStringCopy(context, JSObjectGetProperty(context, exc, prop, NULL), NULL); + size = JSStringGetMaximumUTF8CStringSize(val); + if(size) { + char cstr[size]; + JSStringGetUTF8CString(val, cstr, size); + printf("At line %s: ", cstr); + } + JSStringRelease(prop); + JSStringRelease(val); + + /* Print message */ + val = JSValueToStringCopy(context, exc, NULL); + size = JSStringGetMaximumUTF8CStringSize(val); + if(size) { + char cstr[size]; + JSStringGetUTF8CString(val, cstr, size); + printf("%s\n", cstr); + } + JSStringRelease(val); + } + + /* cleanup */ + JSObjectDeleteProperty(context, globalobject, var_name, NULL); + + JSStringRelease(var_name); + JSStringRelease(js_script); +} + +void +run_js (WebKitWebView * web_view, GArray *argv, GString *result) { + if (argv_idx(argv, 0)) + eval_js(web_view, argv_idx(argv, 0), result); +} + +void +run_external_js (WebKitWebView * web_view, GArray *argv, GString *result) { + (void) result; + gchar *path = NULL; + + if (argv_idx(argv, 0) && + ((path = find_existing_file(argv_idx(argv, 0)))) ) { + GArray* lines = read_file_by_line (path); + gchar* js = NULL; + int i = 0; + gchar* line; + + while ((line = g_array_index(lines, gchar*, i))) { + if (js == NULL) { + js = g_strdup (line); + } else { + gchar* newjs = g_strconcat (js, line, NULL); + js = newjs; + } + i ++; + g_free (line); + } + + if (uzbl.state.verbose) + printf ("External JavaScript file %s loaded\n", argv_idx(argv, 0)); + + gchar* newjs = str_replace("%s", argv_idx (argv, 1)?argv_idx (argv, 1):"", js); + g_free (js); + js = newjs; + + eval_js (web_view, js, result); + g_free (js); + g_array_free (lines, TRUE); + g_free(path); + } +} + +void +search_text (WebKitWebView *page, GArray *argv, const gboolean forward) { + if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0')) { + if (g_strcmp0 (uzbl.state.searchtx, argv_idx(argv, 0)) != 0) { + webkit_web_view_unmark_text_matches (page); + webkit_web_view_mark_text_matches (page, argv_idx(argv, 0), FALSE, 0); + uzbl.state.searchtx = g_strdup(argv_idx(argv, 0)); + } + } + + + if (uzbl.state.searchtx) { + if (uzbl.state.verbose) + printf ("Searching: %s\n", uzbl.state.searchtx); + webkit_web_view_set_highlight_text_matches (page, TRUE); + webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE); + } +} + +void +search_clear(WebKitWebView *page, GArray *argv, GString *result) { + (void) argv; + (void) result; + + webkit_web_view_unmark_text_matches (page); + if(uzbl.state.searchtx) { + g_free(uzbl.state.searchtx); + uzbl.state.searchtx = NULL; + } +} + +void +search_forward_text (WebKitWebView *page, GArray *argv, GString *result) { + (void) result; + search_text(page, argv, TRUE); +} + +void +search_reverse_text (WebKitWebView *page, GArray *argv, GString *result) { + (void) result; + search_text(page, argv, FALSE); +} + +void +dehilight (WebKitWebView *page, GArray *argv, GString *result) { + (void) argv; (void) result; + webkit_web_view_set_highlight_text_matches (page, FALSE); +} + + +void +new_window_load_uri (const gchar * uri) { + if (uzbl.behave.new_window) { + GString *s = g_string_new (""); + g_string_printf(s, "'%s'", uri); + run_handler(uzbl.behave.new_window, s->str); + send_event(NEW_WINDOW, s->str, NULL); + return; + } + GString* to_execute = g_string_new (""); + g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri); + int i; + for (i = 0; entries[i].long_name != NULL; i++) { + if ((entries[i].arg == G_OPTION_ARG_STRING) && + !strcmp(entries[i].long_name,"uri") && + !strcmp(entries[i].long_name,"name")) { + gchar** str = (gchar**)entries[i].arg_data; + if (*str!=NULL) + g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str); + } + else if(entries[i].arg == G_OPTION_ARG_STRING_ARRAY) { + int j; + gchar **str = *((gchar ***)entries[i].arg_data); + for(j=0; str[j]; j++) + g_string_append_printf(to_execute, " --%s '%s'", entries[i].long_name, str[j]); + } + } + if (uzbl.state.verbose) + printf("\n%s\n", to_execute->str); + g_spawn_command_line_async (to_execute->str, NULL); + /* TODO: should we just report the uri as event detail? */ + send_event(NEW_WINDOW, to_execute->str, NULL); + g_string_free (to_execute, TRUE); +} + +void +chain (WebKitWebView *page, GArray *argv, GString *result) { + (void) page; (void) result; + gchar *a = NULL; + gchar **parts = NULL; + guint i = 0; + while ((a = argv_idx(argv, i++))) { + parts = g_strsplit (a, " ", 2); + if (parts[0]) + parse_command(parts[0], parts[1], result); + g_strfreev (parts); + } +} + +void +close_uzbl (WebKitWebView *page, GArray *argv, GString *result) { + (void)page; + (void)argv; + (void)result; + gtk_main_quit (); +} + +void +sharg_append(GArray *a, const gchar *str) { + const gchar *s = (str ? str : ""); + g_array_append_val(a, s); +} + +// make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc) +gboolean +run_command (const gchar *command, const guint npre, const gchar **args, + const gboolean sync, char **output_stdout) { + //command [args] + GError *err = NULL; + + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + gchar *pid = itos(getpid()); + gchar *xwin = itos(uzbl.xwin); + guint i; + sharg_append(a, command); + for (i = 0; i < npre; i++) /* add n args before the default vars */ + sharg_append(a, args[i]); + sharg_append(a, uzbl.state.config_file); + sharg_append(a, pid); + sharg_append(a, xwin); + sharg_append(a, uzbl.comm.fifo_path); + sharg_append(a, uzbl.comm.socket_path); + sharg_append(a, uzbl.state.uri); + sharg_append(a, uzbl.gui.main_title); + + for (i = npre; i < g_strv_length((gchar**)args); i++) + sharg_append(a, args[i]); + + gboolean result; + if (sync) { + if (*output_stdout) *output_stdout = strfree(*output_stdout); + + result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, output_stdout, NULL, NULL, &err); + } else + result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, &err); + + if (uzbl.state.verbose) { + GString *s = g_string_new("spawned:"); + for (i = 0; i < (a->len); i++) { + gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i)); + g_string_append_printf(s, " %s", qarg); + g_free (qarg); + } + g_string_append_printf(s, " -- result: %s", (result ? "true" : "false")); + printf("%s\n", s->str); + g_string_free(s, TRUE); + if(output_stdout) { + printf("Stdout: %s\n", *output_stdout); + } + } + if (err) { + g_printerr("error on run_command: %s\n", err->message); + g_error_free (err); + } + g_free (pid); + g_free (xwin); + g_array_free (a, TRUE); + return result; +} + +/*@null@*/ gchar** +split_quoted(const gchar* src, const gboolean unquote) { + /* split on unquoted space, return array of strings; + remove a layer of quotes and backslashes if unquote */ + if (!src) return NULL; + + gboolean dq = FALSE; + gboolean sq = FALSE; + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + GString *s = g_string_new (""); + const gchar *p; + gchar **ret; + gchar *dup; + for (p = src; *p != '\0'; p++) { + if ((*p == '\\') && unquote) g_string_append_c(s, *++p); + else if (*p == '\\') { g_string_append_c(s, *p++); + g_string_append_c(s, *p); } + else if ((*p == '"') && unquote && !sq) dq = !dq; + else if (*p == '"' && !sq) { g_string_append_c(s, *p); + dq = !dq; } + else if ((*p == '\'') && unquote && !dq) sq = !sq; + else if (*p == '\'' && !dq) { g_string_append_c(s, *p); + sq = ! sq; } + else if ((*p == ' ') && !dq && !sq) { + dup = g_strdup(s->str); + g_array_append_val(a, dup); + g_string_truncate(s, 0); + } else g_string_append_c(s, *p); + } + dup = g_strdup(s->str); + g_array_append_val(a, dup); + ret = (gchar**)a->data; + g_array_free (a, FALSE); + g_string_free (s, TRUE); + return ret; +} + +void +spawn(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + gchar *path = NULL; + + //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after + if (argv_idx(argv, 0) && + ((path = find_existing_file(argv_idx(argv, 0)))) ) { + run_command(path, 0, + ((const gchar **) (argv->data + sizeof(gchar*))), + FALSE, NULL); + g_free(path); + } +} + +void +spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + gchar *path = NULL; + + if (argv_idx(argv, 0) && + ((path = find_existing_file(argv_idx(argv, 0)))) ) { + run_command(path, 0, + ((const gchar **) (argv->data + sizeof(gchar*))), + TRUE, &uzbl.comm.sync_stdout); + g_free(path); + } +} + +void +spawn_sh(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + if (!uzbl.behave.shell_cmd) { + g_printerr ("spawn_sh: shell_cmd is not set!\n"); + return; + } + + guint i; + gchar *spacer = g_strdup(""); + g_array_insert_val(argv, 1, spacer); + gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE); + + for (i = 1; i < g_strv_length(cmd); i++) + g_array_prepend_val(argv, cmd[i]); + + if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL); + g_free (spacer); + g_strfreev (cmd); +} + +void +spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + if (!uzbl.behave.shell_cmd) { + g_printerr ("spawn_sh_sync: shell_cmd is not set!\n"); + return; + } + + guint i; + gchar *spacer = g_strdup(""); + g_array_insert_val(argv, 1, spacer); + gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE); + + for (i = 1; i < g_strv_length(cmd); i++) + g_array_prepend_val(argv, cmd[i]); + + if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, + TRUE, &uzbl.comm.sync_stdout); + g_free (spacer); + g_strfreev (cmd); +} + +void +talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result) { + (void)web_view; (void)result; + + int fd, len; + struct sockaddr_un sa; + char* sockpath; + ssize_t ret; + struct pollfd pfd; + struct iovec* iov; + guint i; + + if(uzbl.comm.sync_stdout) uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + /* This function could be optimised by storing a hash table of socket paths + and associated connected file descriptors rather than closing and + re-opening for every call. Also we could launch a script if socket connect + fails. */ + + /* First element argv[0] is path to socket. Following elements are tokens to + write to the socket. We write them as a single packet with each token + separated by an ASCII nul (\0). */ + if(argv->len < 2) { + g_printerr("talk_to_socket called with only %d args (need at least two).\n", + (int)argv->len); + return; + } + + /* copy socket path, null terminate result */ + sockpath = g_array_index(argv, char*, 0); + g_strlcpy(sa.sun_path, sockpath, sizeof(sa.sun_path)); + sa.sun_family = AF_UNIX; + + /* create socket file descriptor and connect it to path */ + fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if(fd == -1) { + g_printerr("talk_to_socket: creating socket failed (%s)\n", strerror(errno)); + return; + } + if(connect(fd, (struct sockaddr*)&sa, sizeof(sa))) { + g_printerr("talk_to_socket: connect failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* build request vector */ + iov = g_malloc(sizeof(struct iovec) * (argv->len - 1)); + if(!iov) { + g_printerr("talk_to_socket: unable to allocated memory for token vector\n"); + close(fd); + return; + } + for(i = 1; i < argv->len; ++i) { + iov[i - 1].iov_base = g_array_index(argv, char*, i); + iov[i - 1].iov_len = strlen(iov[i - 1].iov_base) + 1; /* string plus \0 */ + } + + /* write request */ + ret = writev(fd, iov, argv->len - 1); + g_free(iov); + if(ret == -1) { + g_printerr("talk_to_socket: write failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* wait for a response, with a 500ms timeout */ + pfd.fd = fd; + pfd.events = POLLIN; + while(1) { + ret = poll(&pfd, 1, 500); + if(ret == 1) break; + if(ret == 0) errno = ETIMEDOUT; + if(errno == EINTR) continue; + g_printerr("talk_to_socket: poll failed while waiting for input (%s)\n", + strerror(errno)); + close(fd); + return; + } + + /* get length of response */ + if(ioctl(fd, FIONREAD, &len) == -1) { + g_printerr("talk_to_socket: cannot find daemon response length, " + "ioctl failed (%s)\n", strerror(errno)); + close(fd); + return; + } + + /* if there is a response, read it */ + if(len) { + uzbl.comm.sync_stdout = g_malloc(len + 1); + if(!uzbl.comm.sync_stdout) { + g_printerr("talk_to_socket: failed to allocate %d bytes\n", len); + close(fd); + return; + } + uzbl.comm.sync_stdout[len] = 0; /* ensure result is null terminated */ + + ret = read(fd, uzbl.comm.sync_stdout, len); + if(ret == -1) { + g_printerr("talk_to_socket: failed to read from socket (%s)\n", + strerror(errno)); + close(fd); + return; + } + } + + /* clean up */ + close(fd); + return; +} + +void +parse_command(const char *cmd, const char *param, GString *result) { + CommandInfo *c; + GString *tmp = g_string_new(""); + + if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) { + guint i; + gchar **par = split_quoted(param, TRUE); + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + + if (c->no_split) { /* don't split */ + sharg_append(a, param); + } else if (par) { + for (i = 0; i < g_strv_length(par); i++) + sharg_append(a, par[i]); + } + + if (result == NULL) { + GString *result_print = g_string_new(""); + + c->function(uzbl.gui.web_view, a, result_print); + if (result_print->len) + printf("%*s\n", (int)result_print->len, result_print->str); + + g_string_free(result_print, TRUE); + } else { + c->function(uzbl.gui.web_view, a, result); + } + g_strfreev (par); + g_array_free (a, TRUE); + + if(strcmp("set", cmd) && + strcmp("event", cmd) && + strcmp("request", cmd)) { + g_string_printf(tmp, "%s %s", cmd, param?param:""); + send_event(COMMAND_EXECUTED, tmp->str, NULL); + g_string_free(tmp, TRUE); + } + } + else { + gchar *tmp = g_strdup_printf("%s %s", cmd, param?param:""); + send_event(COMMAND_ERROR, tmp, NULL); + g_free(tmp); + } +} + + +void +move_statusbar() { + if (!uzbl.gui.scrolled_win && + !uzbl.gui.mainbar) + return; + + g_object_ref(uzbl.gui.scrolled_win); + g_object_ref(uzbl.gui.mainbar); + gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win); + gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar); + + if(uzbl.behave.status_top) { + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); + } + else { + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); + } + g_object_unref(uzbl.gui.scrolled_win); + g_object_unref(uzbl.gui.mainbar); + gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); + return; +} + +gboolean +set_var_value(const gchar *name, gchar *val) { + uzbl_cmdprop *c = NULL; + char *endp = NULL; + char *buf = NULL; + char *invalid_chars = "^°!\"§$%&/()=?'`'+~*'#-.:,;@<>| \\{}[]¹²³¼½"; + GString *msg; + + if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) { + if(!c->writeable) return FALSE; + + msg = g_string_new(name); + + /* check for the variable type */ + if (c->type == TYPE_STR) { + buf = g_strdup(val); + g_free(*c->ptr.s); + *c->ptr.s = buf; + g_string_append_printf(msg, " str %s", buf); + + } else if(c->type == TYPE_INT) { + *c->ptr.i = (int)strtoul(val, &endp, 10); + g_string_append_printf(msg, " int %d", *c->ptr.i); + + } else if (c->type == TYPE_FLOAT) { + *c->ptr.f = strtod(val, &endp); + g_string_append_printf(msg, " float %f", *c->ptr.f); + } + + send_event(VARIABLE_SET, msg->str, NULL); + g_string_free(msg,TRUE); + + /* invoke a command specific function */ + if(c->func) c->func(); + } else { + /* check wether name violates our naming scheme */ + if(strpbrk(name, invalid_chars)) { + if (uzbl.state.verbose) + printf("Invalid variable name\n"); + return FALSE; + } + + /* custom vars */ + c = g_malloc(sizeof(uzbl_cmdprop)); + c->type = TYPE_STR; + c->dump = 0; + c->func = NULL; + c->writeable = 1; + buf = g_strdup(val); + c->ptr.s = g_malloc(sizeof(char *)); + *c->ptr.s = buf; + g_hash_table_insert(uzbl.comm.proto_var, + g_strdup(name), (gpointer) c); + + msg = g_string_new(name); + g_string_append_printf(msg, " str %s", buf); + send_event(VARIABLE_SET, msg->str, NULL); + g_string_free(msg,TRUE); + } + update_title(); + return TRUE; +} + +void +parse_cmd_line(const char *ctl_line, GString *result) { + size_t len=0; + gchar *ctlstrip = NULL; + gchar *exp_line = NULL; + gchar *work_string = NULL; + + work_string = g_strdup(ctl_line); + + /* strip trailing newline */ + len = strlen(ctl_line); + if (work_string[len - 1] == '\n') + ctlstrip = g_strndup(work_string, len - 1); + else + ctlstrip = g_strdup(work_string); + g_free(work_string); + + if( strcmp(g_strchug(ctlstrip), "") && + strcmp(exp_line = expand(ctlstrip, 0), "") + ) { + /* ignore comments */ + if((exp_line[0] == '#')) + ; + + /* parse a command */ + else { + gchar **tokens = NULL; + + tokens = g_strsplit(exp_line, " ", 2); + parse_command(tokens[0], tokens[1], result); + g_strfreev(tokens); + } + g_free(exp_line); + } + + g_free(ctlstrip); +} + + +/*@null@*/ gchar* +build_stream_name(int type, const gchar* dir) { + State *s = &uzbl.state; + gchar *str = NULL; + + if (type == FIFO) { + str = g_strdup_printf + ("%s/uzbl_fifo_%s", dir, s->instance_name); + } else if (type == SOCKET) { + str = g_strdup_printf + ("%s/uzbl_socket_%s", dir, s->instance_name); + } + return str; +} + +gboolean +control_fifo(GIOChannel *gio, GIOCondition condition) { + if (uzbl.state.verbose) + printf("triggered\n"); + gchar *ctl_line; + GIOStatus ret; + GError *err = NULL; + + if (condition & G_IO_HUP) + g_error ("Fifo: Read end of pipe died!\n"); + + if(!gio) + g_error ("Fifo: GIOChannel broke\n"); + + ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err); + if (ret == G_IO_STATUS_ERROR) { + g_error ("Fifo: Error reading: %s\n", err->message); + g_error_free (err); + } + + parse_cmd_line(ctl_line, NULL); + g_free(ctl_line); + + return TRUE; +} + +/*@null@*/ gchar* +init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */ + if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */ + if (unlink(uzbl.comm.fifo_path) == -1) + g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path); + g_free(uzbl.comm.fifo_path); + uzbl.comm.fifo_path = NULL; + } + + GIOChannel *chan = NULL; + GError *error = NULL; + gchar *path = build_stream_name(FIFO, dir); + + if (!file_exists(path)) { + if (mkfifo (path, 0666) == 0) { + // we don't really need to write to the file, but if we open the file as 'r' we will block here, waiting for a writer to open the file. + chan = g_io_channel_new_file(path, "r+", &error); + if (chan) { + if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) { + if (uzbl.state.verbose) + printf ("init_fifo: created successfully as %s\n", path); + send_event(FIFO_SET, path, NULL); + uzbl.comm.fifo_path = path; + return dir; + } else g_warning ("init_fifo: could not add watch on %s\n", path); + } else g_warning ("init_fifo: can't open: %s\n", error->message); + } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno)); + } else g_warning ("init_fifo: can't create %s: file exists\n", path); + + /* if we got this far, there was an error; cleanup */ + if (error) g_error_free (error); + g_free(dir); + g_free(path); + return NULL; +} + +gboolean +control_stdin(GIOChannel *gio, GIOCondition condition) { + (void) condition; + gchar *ctl_line = NULL; + GIOStatus ret; + + ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL); + if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) ) + return FALSE; + + parse_cmd_line(ctl_line, NULL); + g_free(ctl_line); + + return TRUE; +} + +void +create_stdin () { + GIOChannel *chan = NULL; + GError *error = NULL; + + chan = g_io_channel_unix_new(fileno(stdin)); + if (chan) { + if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) { + g_error ("Stdin: could not add watch\n"); + } else { + if (uzbl.state.verbose) + printf ("Stdin: watch added successfully\n"); + } + } else { + g_error ("Stdin: Error while opening: %s\n", error->message); + } + if (error) g_error_free (error); +} + +gboolean +remove_socket_from_array(GIOChannel *chan) { + gboolean ret = 0; + + /* TODO: Do wee need to manually free the IO channel or is this + * happening implicitly on unref? + */ + ret = g_ptr_array_remove_fast(uzbl.comm.connect_chan, chan); + if(!ret) + ret = g_ptr_array_remove_fast(uzbl.comm.client_chan, chan); + + return ret; +} + +gboolean +control_socket(GIOChannel *chan) { + struct sockaddr_un remote; + unsigned int t = sizeof(remote); + GIOChannel *iochan; + int clientsock; + + clientsock = accept (g_io_channel_unix_get_fd(chan), + (struct sockaddr *) &remote, &t); + + if(!uzbl.comm.client_chan) + uzbl.comm.client_chan = g_ptr_array_new(); + + if ((iochan = g_io_channel_unix_new(clientsock))) { + g_io_channel_set_encoding(iochan, NULL, NULL); + g_io_add_watch(iochan, G_IO_IN|G_IO_HUP, + (GIOFunc) control_client_socket, iochan); + g_ptr_array_add(uzbl.comm.client_chan, (gpointer)iochan); + } + return TRUE; +} + +void +init_connect_socket() { + int sockfd, replay = 0; + struct sockaddr_un local; + GIOChannel *chan; + gchar **name = NULL; + + if(!uzbl.comm.connect_chan) + uzbl.comm.connect_chan = g_ptr_array_new(); + + name = uzbl.state.connect_socket_names; + + while(name && *name) { + sockfd = socket (AF_UNIX, SOCK_STREAM, 0); + local.sun_family = AF_UNIX; + strcpy (local.sun_path, *name); + + if(!connect(sockfd, (struct sockaddr *) &local, sizeof(local))) { + if ((chan = g_io_channel_unix_new(sockfd))) { + g_io_channel_set_encoding(chan, NULL, NULL); + g_io_add_watch(chan, G_IO_IN|G_IO_HUP, + (GIOFunc) control_client_socket, chan); + g_ptr_array_add(uzbl.comm.connect_chan, (gpointer)chan); + replay++; + } + } + else + g_warning("Error connecting to socket: %s\n", *name); + name++; + } + + /* replay buffered events */ + if(replay) + send_event_socket(NULL); +} + +gboolean +control_client_socket(GIOChannel *clientchan) { + char *ctl_line; + GString *result = g_string_new(""); + GError *error = NULL; + GIOStatus ret; + gsize len; + + ret = g_io_channel_read_line(clientchan, &ctl_line, &len, NULL, &error); + if (ret == G_IO_STATUS_ERROR) { + g_warning ("Error reading: %s\n", error->message); + remove_socket_from_array(clientchan); + g_io_channel_shutdown(clientchan, TRUE, &error); + return FALSE; + } else if (ret == G_IO_STATUS_EOF) { + remove_socket_from_array(clientchan); + /* shutdown and remove channel watch from main loop */ + g_io_channel_shutdown(clientchan, TRUE, &error); + return FALSE; + } + + if (ctl_line) { + parse_cmd_line (ctl_line, result); + g_string_append_c(result, '\n'); + ret = g_io_channel_write_chars (clientchan, result->str, result->len, + &len, &error); + if (ret == G_IO_STATUS_ERROR) { + g_warning ("Error writing: %s", error->message); + } + g_io_channel_flush(clientchan, &error); + } + + if (error) g_error_free (error); + g_string_free(result, TRUE); + g_free(ctl_line); + return TRUE; +} + +/*@null@*/ gchar* +init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */ + if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */ + if (unlink(uzbl.comm.socket_path) == -1) + g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path); + g_free(uzbl.comm.socket_path); + uzbl.comm.socket_path = NULL; + } + + if (*dir == ' ') { + g_free(dir); + return NULL; + } + + GIOChannel *chan = NULL; + int sock, len; + struct sockaddr_un local; + gchar *path = build_stream_name(SOCKET, dir); + + sock = socket (AF_UNIX, SOCK_STREAM, 0); + + local.sun_family = AF_UNIX; + strcpy (local.sun_path, path); + unlink (local.sun_path); + + len = strlen (local.sun_path) + sizeof (local.sun_family); + if (bind (sock, (struct sockaddr *) &local, len) != -1) { + if (uzbl.state.verbose) + printf ("init_socket: opened in %s\n", path); + listen (sock, 5); + + if( (chan = g_io_channel_unix_new(sock)) ) { + g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan); + uzbl.comm.socket_path = path; + send_event(SOCKET_SET, path, NULL); + return dir; + } + } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno)); + + /* if we got this far, there was an error; cleanup */ + g_free(path); + g_free(dir); + return NULL; +} + +/* + NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state + it will probably improve performance if we would "cache" the processed variant, but for now it works well enough... +*/ +// this function may be called very early when the templates are not set (yet), hence the checks +void +update_title (void) { + Behaviour *b = &uzbl.behave; + gchar *parsed; + + if (b->show_status) { + if (b->title_format_short) { + parsed = expand(b->title_format_short, 0); + if (uzbl.gui.main_window) + gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed); + g_free(parsed); + } + if (b->status_format) { + parsed = expand(b->status_format, 0); + gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed); + g_free(parsed); + } + if (b->status_background) { + GdkColor color; + gdk_color_parse (b->status_background, &color); + //labels and hboxes do not draw their own background. applying this on the vbox/main_window is ok as the statusbar is the only affected widget. (if not, we could also use GtkEventBox) + if (uzbl.gui.main_window) + gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color); + else if (uzbl.gui.plug) + gtk_widget_modify_bg (GTK_WIDGET(uzbl.gui.plug), GTK_STATE_NORMAL, &color); + } + } else { + if (b->title_format_long) { + parsed = expand(b->title_format_long, 0); + if (uzbl.gui.main_window) + gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed); + g_free(parsed); + } + } +} + +void +create_browser () { + GUI *g = &uzbl.gui; + + g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ()); + + g_object_connect((GObject*)g->web_view, + "signal::key-press-event", (GCallback)key_press_cb, NULL, + "signal::key-release-event", (GCallback)key_release_cb, NULL, + "signal::button-press-event", (GCallback)button_press_cb, NULL, + "signal::button-release-event", (GCallback)button_release_cb, NULL, + "signal::title-changed", (GCallback)title_change_cb, NULL, + "signal::selection-changed", (GCallback)selection_changed_cb, NULL, + "signal::load-progress-changed", (GCallback)progress_change_cb, NULL, + "signal::load-committed", (GCallback)load_commit_cb, NULL, + "signal::load-started", (GCallback)load_start_cb, NULL, + "signal::load-finished", (GCallback)load_finish_cb, NULL, + "signal::load-error", (GCallback)load_error_cb, NULL, + "signal::hovering-over-link", (GCallback)link_hover_cb, NULL, + "signal::navigation-policy-decision-requested", (GCallback)navigation_decision_cb, NULL, + "signal::new-window-policy-decision-requested", (GCallback)new_window_cb, NULL, + "signal::download-requested", (GCallback)download_cb, NULL, + "signal::create-web-view", (GCallback)create_web_view_cb, NULL, + "signal::mime-type-policy-decision-requested", (GCallback)mime_policy_cb, NULL, + "signal::populate-popup", (GCallback)populate_popup_cb, NULL, + "signal::focus-in-event", (GCallback)focus_cb, NULL, + "signal::focus-out-event", (GCallback)focus_cb, NULL, + NULL); +} + +GtkWidget* +create_mainbar () { + GUI *g = &uzbl.gui; + + g->mainbar = gtk_hbox_new (FALSE, 0); + g->mainbar_label = gtk_label_new (""); + gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE); + gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0); + gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2); + gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0); + + g_object_connect((GObject*)g->mainbar, + "signal::key-press-event", (GCallback)key_press_cb, NULL, + "signal::key-release-event", (GCallback)key_release_cb, NULL, + NULL); + + return g->mainbar; +} + + +GtkWidget* +create_window () { + GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); + gtk_widget_set_name (window, "Uzbl browser"); + + g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL); + g_signal_connect (G_OBJECT (window), "configure-event", G_CALLBACK (configure_event_cb), NULL); + + return window; +} + +GtkPlug* +create_plug () { + GtkPlug* plug = GTK_PLUG (gtk_plug_new (uzbl.state.socket_id)); + g_signal_connect (G_OBJECT (plug), "destroy", G_CALLBACK (destroy_cb), NULL); + g_signal_connect (G_OBJECT (plug), "key-press-event", G_CALLBACK (key_press_cb), NULL); + g_signal_connect (G_OBJECT (plug), "key-release-event", G_CALLBACK (key_release_cb), NULL); + + return plug; +} + + +gchar** +inject_handler_args(const gchar *actname, const gchar *origargs, const gchar *newargs) { + /* + If actname is one that calls an external command, this function will inject + newargs in front of the user-provided args in that command line. They will + come become after the body of the script (in sh) or after the name of + the command to execute (in spawn). + i.e. sh becomes sh and + spawn becomes spawn . + + The return value consist of two strings: the action (sh, ...) and its args. + + If act is not one that calls an external command, then the given action merely + gets duplicated. + */ + GArray *rets = g_array_new(TRUE, FALSE, sizeof(gchar*)); + /* Arrr! Here be memory leaks */ + gchar *actdup = g_strdup(actname); + g_array_append_val(rets, actdup); + + if ((g_strcmp0(actname, "spawn") == 0) || + (g_strcmp0(actname, "sh") == 0) || + (g_strcmp0(actname, "sync_spawn") == 0) || + (g_strcmp0(actname, "sync_sh") == 0) || + (g_strcmp0(actname, "talk_to_socket") == 0)) { + guint i; + GString *a = g_string_new(""); + gchar **spawnparts = split_quoted(origargs, FALSE); + g_string_append_printf(a, "%s", spawnparts[0]); /* sh body or script name */ + if (newargs) g_string_append_printf(a, " %s", newargs); /* handler args */ + + for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */ + if (spawnparts[i]) g_string_append_printf(a, " %s", spawnparts[i]); + + g_array_append_val(rets, a->str); + g_string_free(a, FALSE); + g_strfreev(spawnparts); + } else { + gchar *origdup = g_strdup(origargs); + g_array_append_val(rets, origdup); + } + return (gchar**)g_array_free(rets, FALSE); +} + +void +run_handler (const gchar *act, const gchar *args) { + /* Consider this code a temporary hack to make the handlers usable. + In practice, all this splicing, injection, and reconstruction is + inefficient, annoying and hard to manage. Potential pitfalls arise + when the handler specific args 1) are not quoted (the handler + callbacks should take care of this) 2) are quoted but interfere + with the users' own quotation. A more ideal solution is + to refactor parse_command so that it doesn't just take a string + and execute it; rather than that, we should have a function which + returns the argument vector parsed from the string. This vector + could be modified (e.g. insert additional args into it) before + passing it to the next function that actually executes it. Though + it still isn't perfect for chain actions.. will reconsider & re- + factor when I have the time. -duc */ + + if (!act) return; + char **parts = g_strsplit(act, " ", 2); + if (!parts || !parts[0]) return; + if (g_strcmp0(parts[0], "chain") == 0) { + GString *newargs = g_string_new(""); + gchar **chainparts = split_quoted(parts[1], FALSE); + + /* for every argument in the chain, inject the handler args + and make sure the new parts are wrapped in quotes */ + gchar **cp = chainparts; + gchar quot = '\''; + gchar *quotless = NULL; + gchar **spliced_quotless = NULL; // sigh -_-; + gchar **inpart = NULL; + + while (*cp) { + if ((**cp == '\'') || (**cp == '\"')) { /* strip old quotes */ + quot = **cp; + quotless = g_strndup(&(*cp)[1], strlen(*cp) - 2); + } else quotless = g_strdup(*cp); + + spliced_quotless = g_strsplit(quotless, " ", 2); + inpart = inject_handler_args(spliced_quotless[0], spliced_quotless[1], args); + g_strfreev(spliced_quotless); + + g_string_append_printf(newargs, " %c%s %s%c", quot, inpart[0], inpart[1], quot); + g_free(quotless); + g_strfreev(inpart); + cp++; + } + + parse_command(parts[0], &(newargs->str[1]), NULL); + g_string_free(newargs, TRUE); + g_strfreev(chainparts); + + } else { + gchar **inparts; + gchar *inparts_[2]; + if (parts[1]) { + /* expand the user-specified arguments */ + gchar* expanded = expand(parts[1], 0); + inparts = inject_handler_args(parts[0], expanded, args); + g_free(expanded); + } else { + inparts_[0] = parts[0]; + inparts_[1] = g_strdup(args); + inparts = inparts_; + } + + parse_command(inparts[0], inparts[1], NULL); + + if (inparts != inparts_) { + g_free(inparts[0]); + g_free(inparts[1]); + } else + g_free(inparts[1]); + } + g_strfreev(parts); +} + +/*@null@*/ gchar* +get_xdg_var (XDG_Var xdg) { + const gchar* actual_value = getenv (xdg.environmental); + const gchar* home = getenv ("HOME"); + gchar* return_value; + + if (! actual_value || strcmp (actual_value, "") == 0) { + if (xdg.default_value) { + return_value = str_replace ("~", home, xdg.default_value); + } else { + return_value = NULL; + } + } else { + return_value = str_replace("~", home, actual_value); + } + + return return_value; +} + +/*@null@*/ gchar* +find_xdg_file (int xdg_type, const char* filename) { + /* xdg_type = 0 => config + xdg_type = 1 => data + xdg_type = 2 => cache*/ + + gchar* xdgv = get_xdg_var (XDG[xdg_type]); + gchar* temporary_file = g_strconcat (xdgv, filename, NULL); + g_free (xdgv); + + gchar* temporary_string; + char* saveptr; + char* buf; + + if (! file_exists (temporary_file) && xdg_type != 2) { + buf = get_xdg_var (XDG[3 + xdg_type]); + temporary_string = (char *) strtok_r (buf, ":", &saveptr); + g_free(buf); + + while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) { + g_free (temporary_file); + temporary_file = g_strconcat (temporary_string, filename, NULL); + } + } + + //g_free (temporary_string); - segfaults. + + if (file_exists (temporary_file)) { + return temporary_file; + } else { + g_free(temporary_file); + return NULL; + } +} +void +settings_init () { + State *s = &uzbl.state; + Network *n = &uzbl.net; + + int i; + for (i = 0; default_config[i].command != NULL; i++) { + parse_cmd_line(default_config[i].command, NULL); + } + + if (g_strcmp0(s->config_file, "-") == 0) { + s->config_file = NULL; + create_stdin(); + } + + else if (!s->config_file) { + s->config_file = find_xdg_file (0, "/uzbl/config"); + } + + if (s->config_file) { + GArray* lines = read_file_by_line (s->config_file); + int i = 0; + gchar* line; + + while ((line = g_array_index(lines, gchar*, i))) { + parse_cmd_line (line, NULL); + i ++; + g_free (line); + } + g_array_free (lines, TRUE); + } else { + if (uzbl.state.verbose) + printf ("No configuration file loaded.\n"); + } + + if(s->connect_socket_names) + init_connect_socket(); + + g_signal_connect_after(n->soup_session, "request-started", G_CALLBACK(handle_cookies), NULL); +} + +void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){ + (void) session; + (void) user_data; + //if (!uzbl.behave.cookie_handler) + // return; + + soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL); + GString *s = g_string_new (""); + SoupURI * soup_uri = soup_message_get_uri(msg); + g_string_printf(s, "GET '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path); + if(uzbl.behave.cookie_handler) + run_handler(uzbl.behave.cookie_handler, s->str); + + if(uzbl.behave.cookie_handler && + uzbl.comm.sync_stdout && strcmp (uzbl.comm.sync_stdout, "") != 0) { + char *p = strchr(uzbl.comm.sync_stdout, '\n' ); + if ( p != NULL ) *p = '\0'; + soup_message_headers_replace (msg->request_headers, "Cookie", (const char *) uzbl.comm.sync_stdout); + + } + if (uzbl.comm.sync_stdout) + uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout); + + g_string_free(s, TRUE); +} + +void +save_cookies (SoupMessage *msg, gpointer user_data){ + (void) user_data; + GSList *ck; + char *cookie; + for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){ + cookie = soup_cookie_to_set_cookie_header(ck->data); + SoupURI * soup_uri = soup_message_get_uri(msg); + GString *s = g_string_new (""); + g_string_printf(s, "PUT '%s' '%s' '%s' '%s'", soup_uri->scheme, soup_uri->host, soup_uri->path, cookie); + run_handler(uzbl.behave.cookie_handler, s->str); + g_free (cookie); + g_string_free(s, TRUE); + } + g_slist_free(ck); +} + +void +dump_var_hash(gpointer k, gpointer v, gpointer ud) { + (void) ud; + uzbl_cmdprop *c = v; + + if(!c->dump) + return; + + if(c->type == TYPE_STR) + printf("set %s = %s\n", (char *)k, *c->ptr.s ? *c->ptr.s : " "); + else if(c->type == TYPE_INT) + printf("set %s = %d\n", (char *)k, *c->ptr.i); + else if(c->type == TYPE_FLOAT) + printf("set %s = %f\n", (char *)k, *c->ptr.f); +} + +void +dump_config() { + g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash, NULL); +} + +void +dump_var_hash_as_event(gpointer k, gpointer v, gpointer ud) { + (void) ud; + uzbl_cmdprop *c = v; + GString *msg; + + if(!c->dump) + return; + + /* check for the variable type */ + msg = g_string_new((char *)k); + if (c->type == TYPE_STR) { + g_string_append_printf(msg, " str %s", *c->ptr.s ? *c->ptr.s : " "); + } else if(c->type == TYPE_INT) { + g_string_append_printf(msg, " int %d", *c->ptr.i); + } else if (c->type == TYPE_FLOAT) { + g_string_append_printf(msg, " float %f", *c->ptr.f); + } + + send_event(VARIABLE_SET, msg->str, NULL); + g_string_free(msg, TRUE); +} + +void +dump_config_as_events() { + g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash_as_event, NULL); +} + +void +retrieve_geometry() { + int w, h, x, y; + GString *buf = g_string_new(""); + + gtk_window_get_size(GTK_WINDOW(uzbl.gui.main_window), &w, &h); + gtk_window_get_position(GTK_WINDOW(uzbl.gui.main_window), &x, &y); + + g_string_printf(buf, "%dx%d+%d+%d", w, h, x, y); + + if(uzbl.gui.geometry) + g_free(uzbl.gui.geometry); + uzbl.gui.geometry = g_string_free(buf, FALSE); +} + +/* set up gtk, gobject, variable defaults and other things that tests and other + * external applications need to do anyhow */ +void +initialize(int argc, char *argv[]) { + int i; + + for(i=0; istr)) + g_string_assign (fullpath, newuri->str); + else { + gchar* wd; + wd = g_get_current_dir (); + g_string_assign (fullpath, g_build_filename (wd, newuri->str, NULL)); + free(wd); + } + struct stat stat_result; + if (! g_stat(fullpath->str, &stat_result)) { + g_string_prepend (fullpath, "file://"); + g_string_assign (newuri, fullpath->str); + } + else + g_string_prepend (newuri, "http://"); + g_string_free (fullpath, TRUE); + } + /* if we do handle cookies, ask our handler for them */ + webkit_web_view_load_uri (uzbl.gui.web_view, newuri->str); + g_string_free (newuri, TRUE); +} + + +#ifndef UZBL_LIBRARY +/** -- MAIN -- **/ +int +main (int argc, char* argv[]) { + initialize(argc, argv); + + uzbl.gui.scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (uzbl.gui.scrolled_win), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + + gtk_container_add (GTK_CONTAINER (uzbl.gui.scrolled_win), + GTK_WIDGET (uzbl.gui.web_view)); + + uzbl.gui.vbox = gtk_vbox_new (FALSE, 0); + + /* initial packing */ + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0); + + if (uzbl.state.plug_mode) { + uzbl.gui.plug = create_plug (); + gtk_container_add (GTK_CONTAINER (uzbl.gui.plug), uzbl.gui.vbox); + gtk_widget_show_all (GTK_WIDGET (uzbl.gui.plug)); + /* in xembed mode the window has no unique id and thus + * socket/fifo names aren't unique either. + * we use a custom randomizer to create a random id + */ + struct timeval tv; + gettimeofday(&tv, NULL); + srand((unsigned int)tv.tv_sec*tv.tv_usec); + uzbl.xwin = rand(); + } else { + uzbl.gui.main_window = create_window (); + gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox); + gtk_widget_show_all (uzbl.gui.main_window); + uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window); + } + + uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL); + uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v); + uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL); + uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h); + gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v); + + if(!uzbl.state.instance_name) + uzbl.state.instance_name = itos((int)uzbl.xwin); + + GString *tmp = g_string_new(""); + g_string_printf(tmp, "%d", getpid()); + uzbl.info.pid_str = g_string_free(tmp, FALSE); + send_event(INSTANCE_START, uzbl.info.pid_str, NULL); + + if(uzbl.state.plug_mode) { + char *t = itos(gtk_plug_get_id(uzbl.gui.plug)); + send_event(PLUG_CREATED, t, NULL); + g_free(t); + } + + /* generate an event with a list of built in commands */ + builtins(); + + gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view)); + + if (uzbl.state.verbose) { + printf("Uzbl start location: %s\n", argv[0]); + if (uzbl.state.socket_id) + printf("plug_id %i\n", gtk_plug_get_id(uzbl.gui.plug)); + else + printf("window_id %i\n",(int) uzbl.xwin); + printf("pid %i\n", getpid ()); + printf("name: %s\n", uzbl.state.instance_name); + printf("commit: %s\n", uzbl.info.commit); + } + + /* Check uzbl is in window mode before getting/setting geometry */ + if (uzbl.gui.main_window) { + if(uzbl.gui.geometry) + cmd_set_geometry(); + else + retrieve_geometry(); + } + + gchar *uri_override = (uzbl.state.uri ? g_strdup(uzbl.state.uri) : NULL); + if (argc > 1 && !uzbl.state.uri) + uri_override = g_strdup(argv[1]); + gboolean verbose_override = uzbl.state.verbose; + + settings_init (); + + if (!uzbl.behave.show_status) + gtk_widget_hide(uzbl.gui.mainbar); + else + update_title(); + + /* WebInspector */ + set_up_inspector(); + + if (verbose_override > uzbl.state.verbose) + uzbl.state.verbose = verbose_override; + + if (uri_override) { + set_var_value("uri", uri_override); + g_free(uri_override); + } + + gtk_main (); + clean_up(); + + return EXIT_SUCCESS; +} +#endif + +/* vi: set et ts=4: */ diff --git a/src/uzbl-core.h b/src/uzbl-core.h new file mode 100644 index 0000000..df9eb1a --- /dev/null +++ b/src/uzbl-core.h @@ -0,0 +1,494 @@ +/* -*- c-basic-offset: 4; -*- + + * See LICENSE for license details + * + * Changelog: + * --------- + * + * (c) 2009 by Robert Manea + * - introduced struct concept + * + */ + +#define _POSIX_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LENGTH(x) (sizeof x / sizeof x[0]) + +/* gui elements */ +typedef struct { + GtkWidget* main_window; + gchar* geometry; + GtkPlug* plug; + GtkWidget* scrolled_win; + GtkWidget* vbox; + GtkWidget* mainbar; + GtkWidget* mainbar_label; + GtkScrollbar* scbar_v; // Horizontal and Vertical Scrollbar + GtkScrollbar* scbar_h; // (These are still hidden) + GtkAdjustment* bar_v; // Information about document length + GtkAdjustment* bar_h; // and scrolling position + WebKitWebView* web_view; + gchar* main_title; + gchar* icon; + + /* WebInspector */ + GtkWidget *inspector_window; + WebKitWebInspector *inspector; + + /* custom context menu item */ + GPtrArray *menu_items; +} GUI; + + +/* external communication*/ +enum { FIFO, SOCKET}; +typedef struct { + gchar *fifo_path; + gchar *socket_path; + /* stores (key)"variable name" -> (value)"pointer to var*/ + GHashTable *proto_var; + + gchar *sync_stdout; + GPtrArray *connect_chan; + GPtrArray *client_chan; +} Communication; + + +/* internal state */ +typedef struct { + gchar *uri; + gchar *config_file; + int socket_id; + char *instance_name; + gchar *selected_url; + gchar *last_selected_url; + gchar *executable_path; + gchar* keycmd; + gchar* searchtx; + gboolean verbose; + gboolean events_stdout; + GPtrArray *event_buffer; + gchar** connect_socket_names; + GdkEventButton *last_button; + gboolean plug_mode; +} State; + + +/* networking */ +typedef struct { + SoupSession *soup_session; + SoupLogger *soup_logger; + char *proxy_url; + char *useragent; + gint max_conns; + gint max_conns_host; +} Network; + + +/* behaviour */ +typedef struct { + gchar* status_format; + gchar* title_format_short; + gchar* title_format_long; + gchar* status_background; + gchar* fifo_dir; + gchar* socket_dir; + gchar* download_handler; + gchar* cookie_handler; + gchar* new_window; + gchar* default_font_family; + gchar* monospace_font_family; + gchar* sans_serif_font_family; + gchar* serif_font_family; + gchar* fantasy_font_family; + gchar* cursive_font_family; + gchar* scheme_handler; + gboolean show_status; + gboolean forward_keys; + gboolean status_top; + guint modmask; + guint http_debug; + gchar* shell_cmd; + guint view_source; + /* WebKitWebSettings exports */ + guint font_size; + guint monospace_size; + guint minimum_font_size; + gfloat zoom_level; + gboolean zoom_type; + guint disable_plugins; + guint disable_scripts; + guint autoload_img; + guint autoshrink_img; + guint enable_spellcheck; + guint enable_private; + guint print_bg; + gchar* style_uri; + guint resizable_txt; + gchar* default_encoding; + guint enforce_96dpi; + gchar *inject_html; + guint caret_browsing; + guint mode; + gchar* base_url; + gboolean print_version; + + /* command list: (key)name -> (value)Command */ + /* command list: (key)name -> (value)Command */ + GHashTable* commands; + /* event lookup: (key)event_id -> (value)event_name */ + GHashTable *event_lookup; +} Behaviour; + +/* javascript */ +typedef struct { + gboolean initialized; + JSClassDefinition classdef; + JSClassRef classref; +} Javascript; + +/* static information */ +typedef struct { + int webkit_major; + int webkit_minor; + int webkit_micro; + gchar *arch; + gchar *commit; + gchar *pid_str; +} Info; + +/* main uzbl data structure */ +typedef struct { + GUI gui; + State state; + Network net; + Behaviour behave; + Communication comm; + Javascript js; + Info info; + + Window xwin; +} UzblCore; + +/* Main Uzbl object */ +extern UzblCore uzbl; + +typedef void sigfunc(int); + +/* XDG Stuff */ +typedef struct { + gchar* environmental; + gchar* default_value; +} XDG_Var; + +/* uzbl variables */ +enum ptr_type {TYPE_INT, TYPE_STR, TYPE_FLOAT}; +typedef struct { + enum ptr_type type; + union { + int *i; + float *f; + gchar **s; + } ptr; + int dump; + int writeable; + /*@null@*/ void (*func)(void); +} uzbl_cmdprop; + +/* Functions */ +char * +itos(int val); + +char * +str_replace (const char* search, const char* replace, const char* string); + +gchar* +strfree(gchar *str); + +GArray* +read_file_by_line (const gchar *path); + +gchar* +parseenv (gchar* string); + +void +clean_up(void); + +void +catch_sigterm(int s); + +sigfunc * +setup_signal(int signe, sigfunc *shandler); + +gboolean +set_var_value(const gchar *name, gchar *val); + +void +load_uri_imp(gchar *uri); + +void +print(WebKitWebView *page, GArray *argv, GString *result); + +void +commands_hash(void); + +bool +file_exists (const char * filename); + +void +set_keycmd(); + +void +load_uri (WebKitWebView * web_view, GArray *argv, GString *result); + +void +new_window_load_uri (const gchar * uri); + +void +chain (WebKitWebView *page, GArray *argv, GString *result); + +void +close_uzbl (WebKitWebView *page, GArray *argv, GString *result); + +gboolean +run_command(const gchar *command, const guint npre, + const gchar **args, const gboolean sync, char **output_stdout); + +void +talk_to_socket(WebKitWebView *web_view, GArray *argv, GString *result); + +void +spawn(WebKitWebView *web_view, GArray *argv, GString *result); + +void +spawn_sh(WebKitWebView *web_view, GArray *argv, GString *result); + +void +spawn_sync(WebKitWebView *web_view, GArray *argv, GString *result); + +void +spawn_sh_sync(WebKitWebView *web_view, GArray *argv, GString *result); + +void +parse_command(const char *cmd, const char *param, GString *result); + +void +parse_cmd_line(const char *ctl_line, GString *result); + +/*@null@*/ gchar* +build_stream_name(int type, const gchar *dir); + +gboolean +control_fifo(GIOChannel *gio, GIOCondition condition); + +/*@null@*/ gchar* +init_fifo(gchar *dir); + +gboolean +control_stdin(GIOChannel *gio, GIOCondition condition); + +void +create_stdin(); + +/*@null@*/ gchar* +init_socket(gchar *dir); + +gboolean +control_socket(GIOChannel *chan); + +gboolean +control_client_socket(GIOChannel *chan); + +void +update_title (void); + +gboolean +key_press_cb (GtkWidget* window, GdkEventKey* event); + +gboolean +key_release_cb (GtkWidget* window, GdkEventKey* event); + +void +run_keycmd(const gboolean key_ret); + +void +initialize (int argc, char *argv[]); + +void +create_browser (); + +GtkWidget* +create_mainbar (); + +GtkWidget* +create_window (); + +GtkPlug* +create_plug (); + +void +run_handler (const gchar *act, const gchar *args); + +/*@null@*/ gchar* +get_xdg_var (XDG_Var xdg); + +/*@null@*/ gchar* +find_xdg_file (int xdg_type, const char* filename); + +void +settings_init (); + +void +search_text (WebKitWebView *page, GArray *argv, const gboolean forward); + +void +search_forward_text (WebKitWebView *page, GArray *argv, GString *result); + +void +search_reverse_text (WebKitWebView *page, GArray *argv, GString *result); + +void +search_clear(WebKitWebView *page, GArray *argv, GString *result); + +void +dehilight (WebKitWebView *page, GArray *argv, GString *result); + +void +run_js (WebKitWebView * web_view, GArray *argv, GString *result); + +void +run_external_js (WebKitWebView * web_view, GArray *argv, GString *result); + +void +eval_js(WebKitWebView * web_view, gchar *script, GString *result); + +void handle_cookies (SoupSession *session, + SoupMessage *msg, + gpointer user_data); +void +save_cookies (SoupMessage *msg, gpointer user_data); + +void +set_var(WebKitWebView *page, GArray *argv, GString *result); + +void +act_dump_config(); + +void +act_dump_config_as_events(); + +void +dump_var_hash(gpointer k, gpointer v, gpointer ud); + +void +dump_key_hash(gpointer k, gpointer v, gpointer ud); + +void +dump_config(); + +void +dump_config_as_events(); + +void +retrieve_geometry(); + +void +event(WebKitWebView *page, GArray *argv, GString *result); + +void +init_connect_socket(); + +gboolean +remove_socket_from_array(GIOChannel *chan); + +void +menu_add(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_link(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_image(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_edit(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_separator(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_separator_link(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_separator_image(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_add_separator_edit(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_remove(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_remove_link(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_remove_image(WebKitWebView *page, GArray *argv, GString *result); + +void +menu_remove_edit(WebKitWebView *page, GArray *argv, GString *result); + +gint +get_click_context(); + +void +hardcopy(WebKitWebView *page, GArray *argv, GString *result); + +void +include(WebKitWebView *page, GArray *argv, GString *result); + +void +builtins(); + +typedef void (*Command)(WebKitWebView*, GArray *argv, GString *result); + +typedef struct { + Command function; + gboolean no_split; +} CommandInfo; + +typedef struct { + gchar *name; + gchar *cmd; + gboolean issep; + guint context; +} MenuItem; + + +/* vi: set et ts=4: */ -- cgit v1.2.3 From 9dd1370d0b7cd876f004f7a822b0357039252184 Mon Sep 17 00:00:00 2001 From: Dieter Plaetinck Date: Sat, 2 Jan 2010 20:11:15 +0100 Subject: remove the 'uzbl' subdirectory in examples/*/, since the sandbox they are no longer needed + update paths everywhere + remove examples/config/enchant (also not needed since sandbox) + bugfix: set /home/dieter in sandbox --- .gitignore | 1 - Makefile | 18 +- examples/config/config | 376 ++++++ examples/config/cookies | 22 + examples/config/uzbl/config | 376 ------ examples/config/uzbl/cookies | 22 - examples/data/bookmarks | 4 + examples/data/forms/bbs.archlinux.org | 5 + examples/data/plugins/bind.py | 521 +++++++ examples/data/plugins/cmd_expand.py | 42 + examples/data/plugins/completion.py | 206 +++ examples/data/plugins/config.py | 97 ++ examples/data/plugins/keycmd.py | 571 ++++++++ examples/data/plugins/mode.py | 176 +++ examples/data/plugins/on_event.py | 107 ++ examples/data/plugins/plugin_template.py | 76 ++ examples/data/plugins/progress_bar.py | 159 +++ examples/data/scripts/cookies.sh | 154 +++ examples/data/scripts/download.sh | 22 + examples/data/scripts/extedit.js | 102 ++ examples/data/scripts/follow_Numbers.js | 228 ++++ examples/data/scripts/follow_Numbers_Strings.js | 212 +++ examples/data/scripts/formfiller.pl | 99 ++ examples/data/scripts/formfiller.sh | 62 + examples/data/scripts/hint.js | 26 + examples/data/scripts/history.sh | 5 + examples/data/scripts/insert_bookmark.sh | 16 + examples/data/scripts/instance-select-wmii.sh | 54 + examples/data/scripts/linkfollow.js | 269 ++++ examples/data/scripts/load_url_from_bookmarks.sh | 20 + examples/data/scripts/load_url_from_history.sh | 24 + examples/data/scripts/scheme.py | 24 + examples/data/scripts/scroll-percentage.js | 68 + examples/data/scripts/session.sh | 62 + examples/data/scripts/uzbl-cookie-daemon | 664 +++++++++ examples/data/scripts/uzbl-event-manager | 833 ++++++++++++ examples/data/scripts/uzbl-tabbed | 1417 ++++++++++++++++++++ examples/data/scripts/uzblcat | 12 + examples/data/style.css | 25 + examples/data/uzbl.png | Bin 0 -> 2185 bytes examples/data/uzbl/bookmarks | 4 - examples/data/uzbl/forms/bbs.archlinux.org | 5 - examples/data/uzbl/plugins/bind.py | 521 ------- examples/data/uzbl/plugins/cmd_expand.py | 42 - examples/data/uzbl/plugins/completion.py | 206 --- examples/data/uzbl/plugins/config.py | 97 -- examples/data/uzbl/plugins/keycmd.py | 571 -------- examples/data/uzbl/plugins/mode.py | 176 --- examples/data/uzbl/plugins/on_event.py | 107 -- examples/data/uzbl/plugins/plugin_template.py | 76 -- examples/data/uzbl/plugins/progress_bar.py | 159 --- examples/data/uzbl/scripts/cookies.sh | 154 --- examples/data/uzbl/scripts/download.sh | 22 - examples/data/uzbl/scripts/extedit.js | 102 -- examples/data/uzbl/scripts/follow_Numbers.js | 228 ---- .../data/uzbl/scripts/follow_Numbers_Strings.js | 212 --- examples/data/uzbl/scripts/formfiller.pl | 99 -- examples/data/uzbl/scripts/formfiller.sh | 62 - examples/data/uzbl/scripts/hint.js | 26 - examples/data/uzbl/scripts/history.sh | 5 - examples/data/uzbl/scripts/insert_bookmark.sh | 16 - examples/data/uzbl/scripts/instance-select-wmii.sh | 54 - examples/data/uzbl/scripts/linkfollow.js | 269 ---- .../data/uzbl/scripts/load_url_from_bookmarks.sh | 20 - .../data/uzbl/scripts/load_url_from_history.sh | 24 - examples/data/uzbl/scripts/scheme.py | 24 - examples/data/uzbl/scripts/scroll-percentage.js | 68 - examples/data/uzbl/scripts/session.sh | 62 - examples/data/uzbl/scripts/uzbl-cookie-daemon | 664 --------- examples/data/uzbl/scripts/uzbl-event-manager | 833 ------------ examples/data/uzbl/scripts/uzbl-tabbed | 1417 -------------------- examples/data/uzbl/scripts/uzblcat | 12 - examples/data/uzbl/style.css | 25 - examples/data/uzbl/uzbl.png | Bin 2185 -> 0 bytes sandbox/env.sh | 9 +- src/uzbl-browser | 2 +- 76 files changed, 6780 insertions(+), 6770 deletions(-) create mode 100644 examples/config/config create mode 100644 examples/config/cookies delete mode 100644 examples/config/uzbl/config delete mode 100644 examples/config/uzbl/cookies create mode 100644 examples/data/bookmarks create mode 100644 examples/data/forms/bbs.archlinux.org create mode 100644 examples/data/plugins/bind.py create mode 100644 examples/data/plugins/cmd_expand.py create mode 100644 examples/data/plugins/completion.py create mode 100644 examples/data/plugins/config.py create mode 100644 examples/data/plugins/keycmd.py create mode 100644 examples/data/plugins/mode.py create mode 100644 examples/data/plugins/on_event.py create mode 100644 examples/data/plugins/plugin_template.py create mode 100644 examples/data/plugins/progress_bar.py create mode 100755 examples/data/scripts/cookies.sh create mode 100755 examples/data/scripts/download.sh create mode 100644 examples/data/scripts/extedit.js create mode 100644 examples/data/scripts/follow_Numbers.js create mode 100644 examples/data/scripts/follow_Numbers_Strings.js create mode 100755 examples/data/scripts/formfiller.pl create mode 100755 examples/data/scripts/formfiller.sh create mode 100644 examples/data/scripts/hint.js create mode 100755 examples/data/scripts/history.sh create mode 100755 examples/data/scripts/insert_bookmark.sh create mode 100755 examples/data/scripts/instance-select-wmii.sh create mode 100644 examples/data/scripts/linkfollow.js create mode 100755 examples/data/scripts/load_url_from_bookmarks.sh create mode 100755 examples/data/scripts/load_url_from_history.sh create mode 100755 examples/data/scripts/scheme.py create mode 100644 examples/data/scripts/scroll-percentage.js create mode 100755 examples/data/scripts/session.sh create mode 100755 examples/data/scripts/uzbl-cookie-daemon create mode 100755 examples/data/scripts/uzbl-event-manager create mode 100755 examples/data/scripts/uzbl-tabbed create mode 100755 examples/data/scripts/uzblcat create mode 100644 examples/data/style.css create mode 100644 examples/data/uzbl.png delete mode 100644 examples/data/uzbl/bookmarks delete mode 100644 examples/data/uzbl/forms/bbs.archlinux.org delete mode 100644 examples/data/uzbl/plugins/bind.py delete mode 100644 examples/data/uzbl/plugins/cmd_expand.py delete mode 100644 examples/data/uzbl/plugins/completion.py delete mode 100644 examples/data/uzbl/plugins/config.py delete mode 100644 examples/data/uzbl/plugins/keycmd.py delete mode 100644 examples/data/uzbl/plugins/mode.py delete mode 100644 examples/data/uzbl/plugins/on_event.py delete mode 100644 examples/data/uzbl/plugins/plugin_template.py delete mode 100644 examples/data/uzbl/plugins/progress_bar.py delete mode 100755 examples/data/uzbl/scripts/cookies.sh delete mode 100755 examples/data/uzbl/scripts/download.sh delete mode 100644 examples/data/uzbl/scripts/extedit.js delete mode 100644 examples/data/uzbl/scripts/follow_Numbers.js delete mode 100644 examples/data/uzbl/scripts/follow_Numbers_Strings.js delete mode 100755 examples/data/uzbl/scripts/formfiller.pl delete mode 100755 examples/data/uzbl/scripts/formfiller.sh delete mode 100644 examples/data/uzbl/scripts/hint.js delete mode 100755 examples/data/uzbl/scripts/history.sh delete mode 100755 examples/data/uzbl/scripts/insert_bookmark.sh delete mode 100755 examples/data/uzbl/scripts/instance-select-wmii.sh delete mode 100644 examples/data/uzbl/scripts/linkfollow.js delete mode 100755 examples/data/uzbl/scripts/load_url_from_bookmarks.sh delete mode 100755 examples/data/uzbl/scripts/load_url_from_history.sh delete mode 100755 examples/data/uzbl/scripts/scheme.py delete mode 100644 examples/data/uzbl/scripts/scroll-percentage.js delete mode 100755 examples/data/uzbl/scripts/session.sh delete mode 100755 examples/data/uzbl/scripts/uzbl-cookie-daemon delete mode 100755 examples/data/uzbl/scripts/uzbl-event-manager delete mode 100755 examples/data/uzbl/scripts/uzbl-tabbed delete mode 100755 examples/data/uzbl/scripts/uzblcat delete mode 100644 examples/data/uzbl/style.css delete mode 100644 examples/data/uzbl/uzbl.png (limited to 'src') diff --git a/.gitignore b/.gitignore index 803a154..078164f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ uzbl-core *.pyc *~ tags -examples/config/enchant diff --git a/Makefile b/Makefile index f48d1f0..626d21e 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,7 @@ test-uzbl-browser: uzbl-browser test-uzbl-core-sandbox: uzbl-core make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core + make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data source ./sandbox/env.sh && uzbl-core --uri http://www.uzbl.org --verbose make DESTDIR=./sandbox uninstall rm -rf ./sandbox/usr @@ -72,6 +73,7 @@ test-uzbl-core-sandbox: uzbl-core test-uzbl-browser-sandbox: uzbl-browser make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-core make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-uzbl-browser + make DESTDIR=./sandbox RUN_PREFIX=`pwd`/sandbox/usr/local install-example-data source ./sandbox/env.sh && uzbl-cookie-daemon restart -nv & source ./sandbox/env.sh && uzbl-event-manager restart -nav & source ./sandbox/env.sh && uzbl-browser --uri http://www.uzbl.org --verbose @@ -102,19 +104,27 @@ install-uzbl-core: all install -m755 uzbl-core $(INSTALLDIR)/bin/uzbl-core install -m644 AUTHORS $(INSTALLDIR)/share/uzbl/docs install -m644 README $(INSTALLDIR)/share/uzbl/docs - sed -i 's#^set prefix.*=.*#set prefix = $(RUN_PREFIX)#' $(INSTALLDIR)/share/uzbl/examples/config/uzbl/config + sed -i 's#^set prefix.*=.*#set prefix = $(RUN_PREFIX)#' $(INSTALLDIR)/share/uzbl/examples/config/config install-uzbl-browser: install -d $(INSTALLDIR)/bin install -m755 src/uzbl-browser $(INSTALLDIR)/bin/uzbl-browser - install -m755 examples/data/uzbl/scripts/uzbl-cookie-daemon $(INSTALLDIR)/bin/uzbl-cookie-daemon - install -m755 examples/data/uzbl/scripts/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager + install -m755 examples/data/scripts/uzbl-cookie-daemon $(INSTALLDIR)/bin/uzbl-cookie-daemon + install -m755 examples/data/scripts/uzbl-event-manager $(INSTALLDIR)/bin/uzbl-event-manager sed -i 's#^PREFIX=.*#PREFIX=$(RUN_PREFIX)#' $(INSTALLDIR)/bin/uzbl-browser sed -i "s#^PREFIX = .*#PREFIX = '$(RUN_PREFIX)'#" $(INSTALLDIR)/bin/uzbl-event-manager install-uzbl-tabbed: install -d $(INSTALLDIR)/bin - install -m755 examples/data/uzbl/scripts/uzbl-tabbed $(INSTALLDIR)/bin/uzbl-tabbed + install -m755 examples/data/scripts/uzbl-tabbed $(INSTALLDIR)/bin/uzbl-tabbed + +# you probably only want to do this manually when testing and/or to the sandbox. not meant for distributors +install-example-data: + install -d $(INSTALLDIR)/home/.config/uzbl + install -d $(INSTALLDIR)/home/.cache/uzbl + install -d $(INSTALLDIR)/home/.local/share/uzbl + cp -rp examples/config/* $(INSTALLDIR)/home/.config/uzbl/ + cp -rp examples/data/* $(INSTALLDIR)/home/.local/share/uzbl/ uninstall: rm -rf $(INSTALLDIR)/bin/uzbl-* diff --git a/examples/config/config b/examples/config/config new file mode 100644 index 0000000..eb17813 --- /dev/null +++ b/examples/config/config @@ -0,0 +1,376 @@ +# example uzbl config. +# all settings are optional. you can use uzbl without any config at all (but it won't do much) + +set prefix = /usr/local + +# === Shortcuts / Aliases =================================================== + +# Config related events (use the request function): +# request BIND = +set bind = request BIND +# request MODE_BIND = +set mode_bind = request MODE_BIND +# request MODE_CONFIG = +set mode_config = request MODE_CONFIG +# request ON_EVENT +set on_event = request ON_EVENT +# request PROGRESS_CONFIG = +set progress = request PROGRESS_CONFIG +# request MODMAP +set modmap = request MODMAP +# request IGNORE_KEY +set ignore_key = request IGNORE_KEY +# request MODKEY_ADDITION +set modkey_addition = request MODKEY_ADDITION + +# Action related events (use the event function): +# event TOGGLE_MODES ... +set toggle_modes = event TOGGLE_MODES + +set set_mode = set mode = +set set_status = set status_message = +set shell_cmd = sh -c + +# Spawn path shortcuts. In spawn the first dir+path match is used in "dir1:dir2:dir3:executable" +set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data:scripts + +# Javascipt helpers. +set jsh = js var run=Uzbl.run; function get(k){return run("print \\\@"+k)}; function set(k, v) {run("set "+k+" = "+v)}; + +# === Handlers =============================================================== + +# --- Hardcoded event handlers ----------------------------------------------- + +# These handlers can't be moved to the new event system yet as we don't +# support events that can wait for a response from a script. +set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket +set scheme_handler = sync_spawn @scripts_dir/scheme.py + +# Open in the same window. +#set new_window = sh 'echo uri "$8" > $4' +# Open a link in a new window. equivalent to default behavior +set new_window = sh 'uzbl-browser -u $8' + +# --- Optional dynamic event handlers ---------------------------------------- + +# Download handler +@on_event DOWNLOAD_REQUEST spawn @scripts_dir/download.sh %s \@proxy_url + +# Load start handler +@on_event LOAD_START @set_status wait + +# Load commit handlers +@on_event LOAD_COMMIT @set_status recv +@on_event LOAD_COMMIT script @scripts_dir/scroll-percentage.js +# Reset the keycmd on navigation +@on_event LOAD_COMMIT @set_mode + +# Load finish handlers +@on_event LOAD_FINISH @set_status done +@on_event LOAD_FINISH spawn @scripts_dir/history.sh + +# Generate a FORM_ACTIVE event if an editable +# element on the loaded site has initial focus +@on_event LOAD_FINISH js if(document.activeElement.type == 'text') {Uzbl.run("event FORM_ACTIVE");} + +# Switch to insert mode if a (editable) html form is clicked +@on_event FORM_ACTIVE @set_mode insert +# Switch to command mode if anything else is clicked +@on_event ROOT_ACTIVE @set_mode command + +# Example CONFIG_CHANGED event handler +#@on_event CONFIG_CHANGED print Config changed: %1 = %2 + +# === Behaviour and appearance =============================================== + +set show_status = 1 +set status_top = 0 +set status_background = #303030 + +set modcmd_style = weight="bold" foreground="red" +set keycmd_style = weight="light" foreground="red" +set prompt_style = foreground="grey" +set cursor_style = underline="single" +set completion_style = foreground="green" +set hint_style = weight="bold" + +set mode_section = [\@[\@mode_indicator]\@] +set keycmd_section = [\@[\@keycmd_prompt]\@\@modcmd\@keycmd\@completion_list] +set progress_section = \@[\@progress_format]\@ +set scroll_section = \@[\@scroll_message]\@ +set uri_section = \@[\@uri]\@ +set name_section = \@[\@NAME]\@ +set status_section = \@status_message +set selected_section = \@[\@SELECTED_URI]\@ + +set status_format = @mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section + +set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI + +# Progress bar config +@progress width = 8 +# %d = done, %p = pending %c = percent done, %i = int done, %s = spinner, +# %t = percent pending, %o = int pending, %r = sprite scroll +@progress format = [%d>%p]%c +@progress done = = +@progress pending = + +# Or ride those spinnas' +#@progress format = [%d%s%p] +#@progress spinner = -\\|/ +#@progress done = - +#@progress pending = + + +# === Core settings ========================================================== + +set useragent = Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO) (@(+uname -o)@ @(+uname -m)@ [@ARCH_UZBL]) (Commit @COMMIT) +set fifo_dir = /tmp +set socket_dir = /tmp + + +# === Key modmapping and ignoring ============================================ + +#modmap +@modmap +@modmap +@modmap + +#modkey_addition +@modkey_addition +@modkey_addition + +#ignore_key +@ignore_key +@ignore_key + + +# === Mode bind aliases ====================================================== + +# Global binding alias (this is done automatically inside the bind plugin). +#set bind = @mode_bind global + +# Insert mode binding alias +set ibind = @mode_bind insert + +# Command mode binding alias +set cbind = @mode_bind command + +# Non-insert mode bindings alias (ebind for edit-bind). +set ebind = @mode_bind global,-insert + + +# === Global & keycmd editing binds ========================================== + +# Resets keycmd and returns to default mode. +@bind = @set_mode + +# Commands for editing and traversing the keycmd. +@ebind = event KEYCMD_EXEC_CURRENT +@ebind = event SET_CURSOR_POS +@ebind = event SET_CURSOR_POS -1 +@ebind = event SET_CURSOR_POS - +@ebind = event SET_CURSOR_POS + +@ebind = event KEYCMD_BACKSPACE +@ebind = event KEYCMD_DELETE +@ebind = event START_COMPLETION +# Readline-ish bindings. +@ebind w = event KEYCMD_STRIP_WORD +@ebind u = event SET_KEYCMD +@ebind a = event SET_CURSOR_POS 0 +@ebind e = event SET_CURSOR_POS -1 + +# Keycmd injection/append examples. +#@ebind su = event INJECT_KEYCMD \@uri +#@ebind st = event INJECT_KEYCMD \@title +#@ebind du = event APPEND_KEYCMD \@uri +#@ebind dt = event APPEND_KEYCMD \@title + + +# === Mouse bindings ========================================================= + +# Middle click +# if clicked on a link open the link in a new uzbl window +# otherwise open the selection in the current window +set load_from_xclip = sh 'echo "uri $(xclip -o)" > $4' +set open_new_window = sh 'uzbl-browser -u \@SELECTED_URI' +@bind = @jsh if(get("SELECTED_URI")) { run("\@open_new_window"); } else { run("\\\@load_from_xclip"); } + + +# === Keyboard bindings ====================================================== + +# With this command you can enter in any command at runtime when prefixed with +# a colon. +@cbind :_ = %s + +# --- Page movement binds --- +@cbind j = scroll vertical 20 +@cbind k = scroll vertical -20 +@cbind h = scroll horizontal -20 +@cbind l = scroll horizontal 20 +@cbind = scroll vertical -100% +@cbind = scroll vertical 100% +@cbind << = scroll vertical begin +@cbind >> = scroll vertical end +@cbind ^ = scroll horizontal begin +@cbind $ = scroll horizontal end +@cbind = scroll vertical end + +# --- Navigation binds --- +@cbind b = back +@cbind m = forward +@cbind S = stop +@cbind r = reload +@cbind R = reload_ign_cache + +# --- Zoom binds --- +@cbind + = zoom_in +@cbind - = zoom_out +@cbind T = toggle_zoom_type +@cbind 1 = set zoom_level 1.0 +@cbind 2 = set zoom_level 2.0 + +# --- Appearance binds --- +@cbind t = toggle_status + +# --- Page searching binds --- +@cbind /* = search %s +@cbind ?* = search_reverse %s +# Jump to next and previous items +@cbind n = search +@cbind N = search_reverse + +# --- Web searching binds --- +@cbind gg_ = uri http://www.google.com/search?q=\@\@ +@cbind \\awiki_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=\@\@&go=Go +@cbind \\wiki_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=\@\@&go=Go + +# --- Handy binds --- +# Set function shortcut +@cbind s__ = set %1 = %2 +# Exit binding +@cbind ZZ = exit +# Dump config to stdout +@cbind !dump = sh "echo dump_config > $4" +# Reload config +@cbind !reload = sh "sed '/^# === Post-load misc commands/,$d' $1 > $4" +# Uzbl Terminal. TODO explain why this is useful +@cbind t = sh 'xterm -e "socat unix-connect:$5 -"' +#@cbind t = sh 'urxvt -e socat unix-connect:$5 -' + +# --- Uri opening prompts --- +@cbind o_ = uri %s +# Or have it load the current uri into the keycmd for editing +@cbind O_ = uri %s + +# --- Mode setting binds --- +# Changing mode via set. +@cbind I = @set_mode insert +# Or toggle between modes by raising the toggle event. +set toggle_cmd_ins = @toggle_modes command insert +@cbind i = @toggle_cmd_ins +# And the global toggle bind. +@bind i = @toggle_cmd_ins + +# --- Hard-bound bookmarks --- +@cbind gh = uri http://www.uzbl.org + +# --- Yanking & pasting binds --- +@cbind y* = @jsh if('%s' == 'u') { run("sh 'echo -n $6 | xclip'"); } else if('%s' == 't') { run("sh 'echo -n $7 | xclip'"); }; run('event SET_KEYCMD'); + +# Go the page from primary selection +@cbind p = sh 'echo "uri `xclip -selection primary -o`" > $4' +# Go to the page in clipboard +@cbind P = sh 'echo "uri `xclip -selection clipboard -o`" > $4' +# Start a new uzbl instance from the page in primary selection +@cbind 'p = sh 'exec uzbl-browser --uri $(xclip -o)' + +# --- Bookmark inserting binds --- +@cbind b_ = sh 'echo -e "$6 %s" >> $XDG_DATA_HOME/uzbl/bookmarks' +# Or use a script to insert a bookmark. +@cbind B = spawn @scripts_dir/insert_bookmark.sh + +# --- Bookmark/history loading --- +@cbind U = spawn @scripts_dir/load_url_from_history.sh +@cbind u = spawn @scripts_dir/load_url_from_bookmarks.sh + +# --- Link following (similar to vimperator and konqueror) --- +@cbind fl* = script @scripts_dir/follow_Numbers.js %s +# Or number with strings instead of numbers: +@cbind fL* = script @scripts_dir/follow_Numbers_Strings.js %s + +# --- Form filler binds --- +# this script allows you to configure (per domain) values to fill in form +# fields (eg login information) and to fill in these values automatically +set formfiller = spawn @scripts_dir/formfiller +@cbind za = @{formfiller}.sh +@cbind ze = @{formfiller}.sh edit +@cbind zn = @{formfiller}.sh new +@cbind zl = @{formfiller}.sh load +# Or the more advanced implementation using perl: (could not get this to run - Dieter) +@cbind LL = @{formfiller}.pl load +@cbind LN = @{formfiller}.pl new +@cbind LE = @{formfiller}.pl edit + +# --- External edit script configuration & binds --- +# Edit form input fields in an external editor (gvim, emacs, urxvt -e vim, ..) +set external_editor = gvim +#set external_editor = xterm -e vim +@cbind E = script @scripts_dir/extedit.js +# And add menu option. +menu_editable_add Open in @external_editor = script @scripts_dir/extedit.js + +# --- Examples --- +# Example showing how to use uzbl's fifo to execute a command. +#@bind X1 = sh 'echo "set zoom_level = 1.0" > "$4"' +#@bind X2 = sh 'echo "js alert (\\"This is sent by the shell via a fifo\\")" > "$4"' + +# Working with the javascript helper variable jsh. +#@bind X3 = @jsh alert(get('zoom_level')); +#@bind X4 = @jsh if(get('mode') == "insert") { alert("You are in insert mode") } else { alert(get('mode')+" is a silly mode.") }; + + +# === Context menu items ===================================================== + +# Default context menu +menu_add Google = set uri = http://google.com +menu_add Go Home = set uri = http://uzbl.org +menu_separator separator_1 +menu_add Quit uzbl = exit + +# Link context menu +menu_link_add Print Link = print \@SELECTED_URI + + +# === Mode configuration ===================================================== + +# Define some mode specific uzbl configurations. +set command = @mode_config command +set insert = @mode_config insert +set stack = @mode_config stack + +# Command mode config. +@command keycmd_style = foreground="red" +@command status_background = #202020 +@command mode_indicator = Cmd + +# Insert mode config. +@insert status_background = #303030 +@insert mode_indicator = Ins + +# Multi-stage-binding mode config. +@stack keycmd_events = 1 +@stack modcmd_updates = 1 +@stack forward_keys = 0 +@stack keycmd_style = foreground="red" +@stack prompt_style = foreground="#888" weight="light" +@stack status_background = #202020 +@stack mode_indicator = Bnd + +set default_mode = command + + +# === Post-load misc commands =============================================== + +# Set the "home" page. +set uri = uzbl.org/doesitwork/@COMMIT diff --git a/examples/config/cookies b/examples/config/cookies new file mode 100644 index 0000000..9b7374a --- /dev/null +++ b/examples/config/cookies @@ -0,0 +1,22 @@ +# This file demonstrates how one *could* manage his cookies. this file is used by the example cookie handler script. +# stick to this format. +# trusted -> always store what we get, send what we have (TODO: by default, or when requested?) +# deny -> deny storing + sending + +# if you don't like to edit this file manually, you could even write a script that adds/removes entries using sed, and call the script from uzbl with a keybind... + + +TRUSTED +bbs.archlinux.org +archlinux.org +linux.com + + + + +DENY +www.icanhascheezburger.com + + + +# rest -> ask \ No newline at end of file diff --git a/examples/config/uzbl/config b/examples/config/uzbl/config deleted file mode 100644 index 3edb36c..0000000 --- a/examples/config/uzbl/config +++ /dev/null @@ -1,376 +0,0 @@ -# example uzbl config. -# all settings are optional. you can use uzbl without any config at all (but it won't do much) - -set prefix = /usr/local - -# === Shortcuts / Aliases =================================================== - -# Config related events (use the request function): -# request BIND = -set bind = request BIND -# request MODE_BIND = -set mode_bind = request MODE_BIND -# request MODE_CONFIG = -set mode_config = request MODE_CONFIG -# request ON_EVENT -set on_event = request ON_EVENT -# request PROGRESS_CONFIG = -set progress = request PROGRESS_CONFIG -# request MODMAP -set modmap = request MODMAP -# request IGNORE_KEY -set ignore_key = request IGNORE_KEY -# request MODKEY_ADDITION -set modkey_addition = request MODKEY_ADDITION - -# Action related events (use the event function): -# event TOGGLE_MODES ... -set toggle_modes = event TOGGLE_MODES - -set set_mode = set mode = -set set_status = set status_message = -set shell_cmd = sh -c - -# Spawn path shortcuts. In spawn the first dir+path match is used in "dir1:dir2:dir3:executable" -set scripts_dir = $XDG_DATA_HOME/uzbl:@prefix/share/uzbl/examples/data/uzbl:scripts - -# Javascipt helpers. -set jsh = js var run=Uzbl.run; function get(k){return run("print \\\@"+k)}; function set(k, v) {run("set "+k+" = "+v)}; - -# === Handlers =============================================================== - -# --- Hardcoded event handlers ----------------------------------------------- - -# These handlers can't be moved to the new event system yet as we don't -# support events that can wait for a response from a script. -set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket -set scheme_handler = sync_spawn @scripts_dir/scheme.py - -# Open in the same window. -#set new_window = sh 'echo uri "$8" > $4' -# Open a link in a new window. equivalent to default behavior -set new_window = sh 'uzbl-browser -u $8' - -# --- Optional dynamic event handlers ---------------------------------------- - -# Download handler -@on_event DOWNLOAD_REQUEST spawn @scripts_dir/download.sh %s \@proxy_url - -# Load start handler -@on_event LOAD_START @set_status wait - -# Load commit handlers -@on_event LOAD_COMMIT @set_status recv -@on_event LOAD_COMMIT script @scripts_dir/scroll-percentage.js -# Reset the keycmd on navigation -@on_event LOAD_COMMIT @set_mode - -# Load finish handlers -@on_event LOAD_FINISH @set_status done -@on_event LOAD_FINISH spawn @scripts_dir/history.sh - -# Generate a FORM_ACTIVE event if an editable -# element on the loaded site has initial focus -@on_event LOAD_FINISH js if(document.activeElement.type == 'text') {Uzbl.run("event FORM_ACTIVE");} - -# Switch to insert mode if a (editable) html form is clicked -@on_event FORM_ACTIVE @set_mode insert -# Switch to command mode if anything else is clicked -@on_event ROOT_ACTIVE @set_mode command - -# Example CONFIG_CHANGED event handler -#@on_event CONFIG_CHANGED print Config changed: %1 = %2 - -# === Behaviour and appearance =============================================== - -set show_status = 1 -set status_top = 0 -set status_background = #303030 - -set modcmd_style = weight="bold" foreground="red" -set keycmd_style = weight="light" foreground="red" -set prompt_style = foreground="grey" -set cursor_style = underline="single" -set completion_style = foreground="green" -set hint_style = weight="bold" - -set mode_section = [\@[\@mode_indicator]\@] -set keycmd_section = [\@[\@keycmd_prompt]\@\@modcmd\@keycmd\@completion_list] -set progress_section = \@[\@progress_format]\@ -set scroll_section = \@[\@scroll_message]\@ -set uri_section = \@[\@uri]\@ -set name_section = \@[\@NAME]\@ -set status_section = \@status_message -set selected_section = \@[\@SELECTED_URI]\@ - -set status_format = @mode_section @keycmd_section @progress_section @uri_section @name_section @status_section @scroll_section @selected_section - -set title_format_long = \@keycmd_prompt \@raw_modcmd \@raw_keycmd \@TITLE - Uzbl browser <\@NAME> \@SELECTED_URI - -# Progress bar config -@progress width = 8 -# %d = done, %p = pending %c = percent done, %i = int done, %s = spinner, -# %t = percent pending, %o = int pending, %r = sprite scroll -@progress format = [%d>%p]%c -@progress done = = -@progress pending = - -# Or ride those spinnas' -#@progress format = [%d%s%p] -#@progress spinner = -\\|/ -#@progress done = - -#@progress pending = - - -# === Core settings ========================================================== - -set useragent = Uzbl (Webkit @WEBKIT_MAJOR.@WEBKIT_MINOR.@WEBKIT_MICRO) (@(+uname -o)@ @(+uname -m)@ [@ARCH_UZBL]) (Commit @COMMIT) -set fifo_dir = /tmp -set socket_dir = /tmp - - -# === Key modmapping and ignoring ============================================ - -#modmap -@modmap -@modmap -@modmap - -#modkey_addition -@modkey_addition -@modkey_addition - -#ignore_key -@ignore_key -@ignore_key - - -# === Mode bind aliases ====================================================== - -# Global binding alias (this is done automatically inside the bind plugin). -#set bind = @mode_bind global - -# Insert mode binding alias -set ibind = @mode_bind insert - -# Command mode binding alias -set cbind = @mode_bind command - -# Non-insert mode bindings alias (ebind for edit-bind). -set ebind = @mode_bind global,-insert - - -# === Global & keycmd editing binds ========================================== - -# Resets keycmd and returns to default mode. -@bind = @set_mode - -# Commands for editing and traversing the keycmd. -@ebind = event KEYCMD_EXEC_CURRENT -@ebind = event SET_CURSOR_POS -@ebind = event SET_CURSOR_POS -1 -@ebind = event SET_CURSOR_POS - -@ebind = event SET_CURSOR_POS + -@ebind = event KEYCMD_BACKSPACE -@ebind = event KEYCMD_DELETE -@ebind = event START_COMPLETION -# Readline-ish bindings. -@ebind w = event KEYCMD_STRIP_WORD -@ebind u = event SET_KEYCMD -@ebind a = event SET_CURSOR_POS 0 -@ebind e = event SET_CURSOR_POS -1 - -# Keycmd injection/append examples. -#@ebind su = event INJECT_KEYCMD \@uri -#@ebind st = event INJECT_KEYCMD \@title -#@ebind du = event APPEND_KEYCMD \@uri -#@ebind dt = event APPEND_KEYCMD \@title - - -# === Mouse bindings ========================================================= - -# Middle click -# if clicked on a link open the link in a new uzbl window -# otherwise open the selection in the current window -set load_from_xclip = sh 'echo "uri $(xclip -o)" > $4' -set open_new_window = sh 'uzbl-browser -u \@SELECTED_URI' -@bind = @jsh if(get("SELECTED_URI")) { run("\@open_new_window"); } else { run("\\\@load_from_xclip"); } - - -# === Keyboard bindings ====================================================== - -# With this command you can enter in any command at runtime when prefixed with -# a colon. -@cbind :_ = %s - -# --- Page movement binds --- -@cbind j = scroll vertical 20 -@cbind k = scroll vertical -20 -@cbind h = scroll horizontal -20 -@cbind l = scroll horizontal 20 -@cbind = scroll vertical -100% -@cbind = scroll vertical 100% -@cbind << = scroll vertical begin -@cbind >> = scroll vertical end -@cbind ^ = scroll horizontal begin -@cbind $ = scroll horizontal end -@cbind = scroll vertical end - -# --- Navigation binds --- -@cbind b = back -@cbind m = forward -@cbind S = stop -@cbind r = reload -@cbind R = reload_ign_cache - -# --- Zoom binds --- -@cbind + = zoom_in -@cbind - = zoom_out -@cbind T = toggle_zoom_type -@cbind 1 = set zoom_level 1.0 -@cbind 2 = set zoom_level 2.0 - -# --- Appearance binds --- -@cbind t = toggle_status - -# --- Page searching binds --- -@cbind /* = search %s -@cbind ?* = search_reverse %s -# Jump to next and previous items -@cbind n = search -@cbind N = search_reverse - -# --- Web searching binds --- -@cbind gg_ = uri http://www.google.com/search?q=\@\@ -@cbind \\awiki_ = uri http://wiki.archlinux.org/index.php/Special:Search?search=\@\@&go=Go -@cbind \\wiki_ = uri http://en.wikipedia.org/w/index.php?title=Special:Search&search=\@\@&go=Go - -# --- Handy binds --- -# Set function shortcut -@cbind s__ = set %1 = %2 -# Exit binding -@cbind ZZ = exit -# Dump config to stdout -@cbind !dump = sh "echo dump_config > $4" -# Reload config -@cbind !reload = sh "sed '/^# === Post-load misc commands/,$d' $1 > $4" -# Uzbl Terminal. TODO explain why this is useful -@cbind t = sh 'xterm -e "socat unix-connect:$5 -"' -#@cbind t = sh 'urxvt -e socat unix-connect:$5 -' - -# --- Uri opening prompts --- -@cbind o_ = uri %s -# Or have it load the current uri into the keycmd for editing -@cbind O_ = uri %s - -# --- Mode setting binds --- -# Changing mode via set. -@cbind I = @set_mode insert -# Or toggle between modes by raising the toggle event. -set toggle_cmd_ins = @toggle_modes command insert -@cbind i = @toggle_cmd_ins -# And the global toggle bind. -@bind i = @toggle_cmd_ins - -# --- Hard-bound bookmarks --- -@cbind gh = uri http://www.uzbl.org - -# --- Yanking & pasting binds --- -@cbind y* = @jsh if('%s' == 'u') { run("sh 'echo -n $6 | xclip'"); } else if('%s' == 't') { run("sh 'echo -n $7 | xclip'"); }; run('event SET_KEYCMD'); - -# Go the page from primary selection -@cbind p = sh 'echo "uri `xclip -selection primary -o`" > $4' -# Go to the page in clipboard -@cbind P = sh 'echo "uri `xclip -selection clipboard -o`" > $4' -# Start a new uzbl instance from the page in primary selection -@cbind 'p = sh 'exec uzbl-browser --uri $(xclip -o)' - -# --- Bookmark inserting binds --- -@cbind b_ = sh 'echo -e "$6 %s" >> $XDG_DATA_HOME/uzbl/bookmarks' -# Or use a script to insert a bookmark. -@cbind B = spawn @scripts_dir/insert_bookmark.sh - -# --- Bookmark/history loading --- -@cbind U = spawn @scripts_dir/load_url_from_history.sh -@cbind u = spawn @scripts_dir/load_url_from_bookmarks.sh - -# --- Link following (similar to vimperator and konqueror) --- -@cbind fl* = script @scripts_dir/follow_Numbers.js %s -# Or number with strings instead of numbers: -@cbind fL* = script @scripts_dir/follow_Numbers_Strings.js %s - -# --- Form filler binds --- -# this script allows you to configure (per domain) values to fill in form -# fields (eg login information) and to fill in these values automatically -set formfiller = spawn @scripts_dir/formfiller -@cbind za = @{formfiller}.sh -@cbind ze = @{formfiller}.sh edit -@cbind zn = @{formfiller}.sh new -@cbind zl = @{formfiller}.sh load -# Or the more advanced implementation using perl: (could not get this to run - Dieter) -@cbind LL = @{formfiller}.pl load -@cbind LN = @{formfiller}.pl new -@cbind LE = @{formfiller}.pl edit - -# --- External edit script configuration & binds --- -# Edit form input fields in an external editor (gvim, emacs, urxvt -e vim, ..) -set external_editor = gvim -#set external_editor = xterm -e vim -@cbind E = script @scripts_dir/extedit.js -# And add menu option. -menu_editable_add Open in @external_editor = script @scripts_dir/extedit.js - -# --- Examples --- -# Example showing how to use uzbl's fifo to execute a command. -#@bind X1 = sh 'echo "set zoom_level = 1.0" > "$4"' -#@bind X2 = sh 'echo "js alert (\\"This is sent by the shell via a fifo\\")" > "$4"' - -# Working with the javascript helper variable jsh. -#@bind X3 = @jsh alert(get('zoom_level')); -#@bind X4 = @jsh if(get('mode') == "insert") { alert("You are in insert mode") } else { alert(get('mode')+" is a silly mode.") }; - - -# === Context menu items ===================================================== - -# Default context menu -menu_add Google = set uri = http://google.com -menu_add Go Home = set uri = http://uzbl.org -menu_separator separator_1 -menu_add Quit uzbl = exit - -# Link context menu -menu_link_add Print Link = print \@SELECTED_URI - - -# === Mode configuration ===================================================== - -# Define some mode specific uzbl configurations. -set command = @mode_config command -set insert = @mode_config insert -set stack = @mode_config stack - -# Command mode config. -@command keycmd_style = foreground="red" -@command status_background = #202020 -@command mode_indicator = Cmd - -# Insert mode config. -@insert status_background = #303030 -@insert mode_indicator = Ins - -# Multi-stage-binding mode config. -@stack keycmd_events = 1 -@stack modcmd_updates = 1 -@stack forward_keys = 0 -@stack keycmd_style = foreground="red" -@stack prompt_style = foreground="#888" weight="light" -@stack status_background = #202020 -@stack mode_indicator = Bnd - -set default_mode = command - - -# === Post-load misc commands =============================================== - -# Set the "home" page. -set uri = uzbl.org/doesitwork/@COMMIT diff --git a/examples/config/uzbl/cookies b/examples/config/uzbl/cookies deleted file mode 100644 index 9b7374a..0000000 --- a/examples/config/uzbl/cookies +++ /dev/null @@ -1,22 +0,0 @@ -# This file demonstrates how one *could* manage his cookies. this file is used by the example cookie handler script. -# stick to this format. -# trusted -> always store what we get, send what we have (TODO: by default, or when requested?) -# deny -> deny storing + sending - -# if you don't like to edit this file manually, you could even write a script that adds/removes entries using sed, and call the script from uzbl with a keybind... - - -TRUSTED -bbs.archlinux.org -archlinux.org -linux.com - - - - -DENY -www.icanhascheezburger.com - - - -# rest -> ask \ No newline at end of file diff --git a/examples/data/bookmarks b/examples/data/bookmarks new file mode 100644 index 0000000..13fcd48 --- /dev/null +++ b/examples/data/bookmarks @@ -0,0 +1,4 @@ +http://www.archlinux.org linux arch +http://www.uzbl.org uzbl browser +http://dieter.plaetinck.be uzbl +http://www.icanhascheezburger.com lolcats fun diff --git a/examples/data/forms/bbs.archlinux.org b/examples/data/forms/bbs.archlinux.org new file mode 100644 index 0000000..73c1539 --- /dev/null +++ b/examples/data/forms/bbs.archlinux.org @@ -0,0 +1,5 @@ +form_sent: +redirect_url: +req_username: +req_password: +login: diff --git a/examples/data/plugins/bind.py b/examples/data/plugins/bind.py new file mode 100644 index 0000000..9e09337 --- /dev/null +++ b/examples/data/plugins/bind.py @@ -0,0 +1,521 @@ +'''Plugin provides support for binds in uzbl. + +For example: + event BIND ZZ = exit -> bind('ZZ', 'exit') + event BIND o _ = uri %s -> bind('o _', 'uri %s') + event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'") + +And it is also possible to execute a function on activation: + bind('DD', myhandler) +''' + +import sys +import re +import pprint + +# Hold the bind dicts for each uzbl instance. +UZBLS = {} + +# Commonly used regular expressions. +MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match +# Matches , <'x':y>, <:'y'>, , <'x'!y>, ... +PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>' +FIND_PROMPTS = re.compile(PROMPTS).split +VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match + +# For accessing a bind glob stack. +ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) + + +# Custom errors. +class ArgumentError(Exception): pass + + +class Bindlet(object): + '''Per-instance bind status/state tracker.''' + + def __init__(self, uzbl): + self.binds = {'global': {}} + self.uzbl = uzbl + self.depth = 0 + self.args = [] + self.last_mode = None + self.after_cmds = None + self.stack_binds = [] + + # A subset of the global mode binds containing non-stack and modkey + # activiated binds for use in the stack mode. + self.globals = [] + + + def __getitem__(self, key): + return self.get_binds(key) + + + def reset(self): + '''Reset the tracker state and return to last mode.''' + + self.depth = 0 + self.args = [] + self.after_cmds = None + self.stack_binds = [] + + if self.last_mode: + mode, self.last_mode = self.last_mode, None + self.uzbl.set_mode(mode) + + self.uzbl.set('keycmd_prompt') + + + def stack(self, bind, args, depth): + '''Enter or add new bind in the next stack level.''' + + if self.depth != depth: + if bind not in self.stack_binds: + self.stack_binds.append(bind) + + return + + current_mode = self.uzbl.get_mode() + if current_mode != 'stack': + self.last_mode = current_mode + self.uzbl.set_mode('stack') + + self.stack_binds = [bind,] + self.args += args + self.depth += 1 + self.after_cmds = bind.prompts[depth] + + + def after(self): + '''If a stack was triggered then set the prompt and default value.''' + + if self.after_cmds is None: + return + + (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None + + self.uzbl.clear_keycmd() + if prompt: + self.uzbl.set('keycmd_prompt', prompt) + + if set and is_cmd: + self.uzbl.send(set) + + elif set and not is_cmd: + self.uzbl.send('event SET_KEYCMD %s' % set) + + + def get_binds(self, mode=None): + '''Return the mode binds + globals. If we are stacked then return + the filtered stack list and modkey & non-stack globals.''' + + if mode is None: + mode = self.uzbl.get_mode() + + if not mode: + mode = 'global' + + if self.depth: + return self.stack_binds + self.globals + + globals = self.binds['global'] + if mode not in self.binds or mode == 'global': + return filter(None, globals.values()) + + binds = dict(globals.items() + self.binds[mode].items()) + return filter(None, binds.values()) + + + def add_bind(self, mode, glob, bind=None): + '''Insert (or override) a bind into the mode bind dict.''' + + if mode not in self.binds: + self.binds[mode] = {glob: bind} + return + + binds = self.binds[mode] + binds[glob] = bind + + if mode == 'global': + # Regen the global-globals list. + self.globals = [] + for bind in binds.values(): + if bind is not None and bind.is_global: + self.globals.append(bind) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = Bindlet(uzbl) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_bindlet(uzbl): + '''Return the bind tracklet for the given uzbl instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def ismodbind(glob): + '''Return True if the glob specifies a modbind.''' + + return bool(MOD_START(glob)) + + +def split_glob(glob): + '''Take a string of the form "cmd _" and return a list of the + modkeys in the glob and the command.''' + + mods = set() + while True: + match = MOD_START(glob) + if not match: + break + + end = match.span()[1] + mods.add(glob[:end]) + glob = glob[end:] + + return (mods, glob) + + +def unquote(str): + '''Remove quotation marks around string.''' + + if str and str[0] == str[-1] and str[0] in ['"', "'"]: + str = str[1:-1] + + return str + + +class Bind(object): + + # Class attribute to hold the number of Bind classes created. + counter = [0,] + + def __init__(self, glob, handler, *args, **kargs): + self.is_callable = callable(handler) + self._repr_cache = None + + if not glob: + raise ArgumentError('glob cannot be blank') + + if self.is_callable: + self.function = handler + self.args = args + self.kargs = kargs + + elif kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + elif hasattr(handler, '__iter__'): + self.commands = handler + + else: + self.commands = [handler,] + list(args) + + self.glob = glob + + # Assign unique id. + self.counter[0] += 1 + self.bid = self.counter[0] + + self.split = split = FIND_PROMPTS(glob) + self.prompts = [] + for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]): + prompt, set = map(unquote, [prompt, set]) + cmd = True if cmd == '!' else False + if prompt and prompt[-1] != ":": + prompt = "%s:" % prompt + + self.prompts.append((prompt, cmd, set)) + + # Check that there is nothing like: fl** + for glob in split[:-1:4]: + if glob.endswith('*'): + msg = "token '*' not at the end of a prompt bind: %r" % split + raise SyntaxError(msg) + + # Check that there is nothing like: fl_ + for glob in split[4::4]: + if not glob: + msg = 'found null segment after first prompt: %r' % split + raise SyntaxError(msg) + + stack = [] + for (index, glob) in enumerate(reversed(split[::4])): + # Is the binding a MODCMD or KEYCMD: + mod_cmd = ismodbind(glob) + + # Do we execute on UPDATES or EXEC events? + on_exec = True if glob[-1] in ['!', '_'] else False + + # Does the command take arguments? + has_args = True if glob[-1] in ['*', '_'] else False + + glob = glob[:-1] if has_args or on_exec else glob + mods, glob = split_glob(glob) + stack.append((on_exec, has_args, mods, glob, index)) + + self.stack = list(reversed(stack)) + self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD]) + + + def __getitem__(self, depth): + '''Get bind info at a depth.''' + + if self.is_global: + return self.stack[0] + + return self.stack[depth] + + + def __repr__(self): + if self._repr_cache: + return self._repr_cache + + args = ['glob=%r' % self.glob, 'bid=%d' % self.bid] + + if self.is_callable: + args.append('function=%r' % self.function) + if self.args: + args.append('args=%r' % self.args) + + if self.kargs: + args.append('kargs=%r' % self.kargs) + + else: + cmdlen = len(self.commands) + cmds = self.commands[0] if cmdlen == 1 else self.commands + args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) + + self._repr_cache = '' % ', '.join(args) + return self._repr_cache + + +def exec_bind(uzbl, bind, *args, **kargs): + '''Execute bind objects.''' + + uzbl.event("EXEC_BIND", bind, args, kargs) + + if bind.is_callable: + args += bind.args + kargs = dict(bind.kargs.items()+kargs.items()) + bind.function(uzbl, *args, **kargs) + return + + if kargs: + raise ArgumentError('cannot supply kargs for uzbl commands') + + commands = [] + cmd_expand = uzbl.cmd_expand + for cmd in bind.commands: + cmd = cmd_expand(cmd, args) + uzbl.send(cmd) + + +def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs): + '''Add a mode bind.''' + + bindlet = get_bindlet(uzbl) + + if not hasattr(modes, '__iter__'): + modes = unicode(modes).split(',') + + # Sort and filter binds. + modes = filter(None, map(unicode.strip, modes)) + + if callable(handler) or (handler is not None and handler.strip()): + bind = Bind(glob, handler, *args, **kargs) + + else: + bind = None + + for mode in modes: + if not VALID_MODE(mode): + raise NameError('invalid mode name: %r' % mode) + + for mode in modes: + if mode[0] == '-': + mode, bind = mode[1:], None + + bindlet.add_bind(mode, glob, bind) + uzbl.event('ADDED_MODE_BIND', mode, glob, bind) + + +def bind(uzbl, glob, handler, *args, **kargs): + '''Legacy bind function.''' + + mode_bind(uzbl, 'global', glob, handler, *args, **kargs) + + +def parse_mode_bind(uzbl, args): + '''Parser for the MODE_BIND event. + + Example events: + MODE_BIND = + MODE_BIND command o_ = uri %s + MODE_BIND insert,command = ... + MODE_BIND global ... = ... + MODE_BIND global,-insert ... = ... + ''' + + if not args: + raise ArgumentError('missing bind arguments') + + split = map(unicode.strip, args.split(' ', 1)) + if len(split) != 2: + raise ArgumentError('missing mode or bind section: %r' % args) + + modes, args = split[0].split(','), split[1] + split = map(unicode.strip, args.split('=', 1)) + if len(split) != 2: + raise ArgumentError('missing delimiter in bind section: %r' % args) + + glob, command = split + mode_bind(uzbl, modes, glob, command) + + +def parse_bind(uzbl, args): + '''Legacy parsing of the BIND event and conversion to the new format. + + Example events: + request BIND = + request BIND o_ = uri %s + request BIND = ... + request BIND ... = ... + ''' + + parse_mode_bind(uzbl, "global %s" % args) + + +def mode_changed(uzbl, mode): + '''Clear the stack on all non-stack mode changes.''' + + if mode != 'stack': + get_bindlet(uzbl).reset() + + +def match_and_exec(uzbl, bind, depth, keylet, bindlet): + + (on_exec, has_args, mod_cmd, glob, more) = bind[depth] + cmd = keylet.modcmd if mod_cmd else keylet.keycmd + + if mod_cmd and keylet.held != mod_cmd: + return False + + if has_args: + if not cmd.startswith(glob): + return False + + args = [cmd[len(glob):],] + + elif cmd != glob: + return False + + else: + args = [] + + if bind.is_global or (not more and depth == 0): + exec_bind(uzbl, bind, *args) + if not has_args: + uzbl.clear_current() + + return True + + elif more: + bindlet.stack(bind, args, depth) + return False + + args = bindlet.args + args + exec_bind(uzbl, bind, *args) + uzbl.set_mode() + if not has_args: + bindlet.reset() + uzbl.clear_current() + + return True + + +def keycmd_update(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if t[MOD_CMD] or t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return + + bindlet.after() + + +def keycmd_exec(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if t[MOD_CMD] or not t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return uzbl.clear_keycmd() + + bindlet.after() + + +def modcmd_update(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if not t[MOD_CMD] or t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return + + bindlet.after() + + +def modcmd_exec(uzbl, keylet): + bindlet = get_bindlet(uzbl) + depth = bindlet.depth + for bind in bindlet.get_binds(): + t = bind[depth] + if not t[MOD_CMD] or not t[ON_EXEC]: + continue + + if match_and_exec(uzbl, bind, depth, keylet, bindlet): + return uzbl.clear_modcmd() + + bindlet.after() + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'BIND': parse_bind, + 'KEYCMD_EXEC': keycmd_exec, + 'KEYCMD_UPDATE': keycmd_update, + 'MODCMD_EXEC': modcmd_exec, + 'MODCMD_UPDATE': modcmd_update, + 'MODE_BIND': parse_mode_bind, + 'MODE_CHANGED': mode_changed, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'bind': bind, + 'mode_bind': mode_bind, + 'get_bindlet': get_bindlet, + }) diff --git a/examples/data/plugins/cmd_expand.py b/examples/data/plugins/cmd_expand.py new file mode 100644 index 0000000..3f6ae2b --- /dev/null +++ b/examples/data/plugins/cmd_expand.py @@ -0,0 +1,42 @@ +def escape(str): + for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]: + str = str.replace(char, (level * '\\') + char) + + return str + + +def cmd_expand(uzbl, cmd, args): + '''Exports a function that provides the following + expansions in any uzbl command string: + + %s = replace('%s', ' '.join(args)) + %r = replace('%r', "'%s'" % escaped(' '.join(args))) + %1 = replace('%1', arg[0]) + %2 = replace('%2', arg[1]) + %n = replace('%n', arg[n-1]) + ''' + + # Ensure (1) all string representable and (2) correct string encoding. + args = map(unicode, args) + + # Direct string replace. + if '%s' in cmd: + cmd = cmd.replace('%s', ' '.join(args)) + + # Escaped and quoted string replace. + if '%r' in cmd: + cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args))) + + # Arg index string replace. + for (index, arg) in enumerate(args): + index += 1 + if '%%%d' % index in cmd: + cmd = cmd.replace('%%%d' % index, unicode(arg)) + + return cmd + + +def init(uzbl): + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export('cmd_expand', cmd_expand) diff --git a/examples/data/plugins/completion.py b/examples/data/plugins/completion.py new file mode 100644 index 0000000..8cea203 --- /dev/null +++ b/examples/data/plugins/completion.py @@ -0,0 +1,206 @@ +'''Keycmd completion.''' + +# A list of functions this plugin exports to be used via uzbl object. +__export__ = ['start_completion', 'get_completion_dict'] + +import re + +# Holds the per-instance completion dicts. +UZBLS = {} + +# Completion level +NONE, ONCE, LIST, COMPLETE = range(4) + +# Default instance dict. +DEFAULTS = {'completions': [], 'level': NONE, 'lock': False} + +# The reverse keyword finding re. +FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall + +# Formats +LIST_FORMAT = " %s " +ITEM_FORMAT = "%s%s" + + +def escape(str): + return str.replace("@", "\@") + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + # Make sure the config keys for all possible completions are known. + uzbl.send('dump_config_as_events') + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_completion_dict(uzbl): + '''Get data stored for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_incomplete_keyword(uzbl): + '''Gets the segment of the keycmd leading up to the cursor position and + uses a regular expression to search backwards finding parially completed + keywords or @variables. Returns a null string if the correct completion + conditions aren't met.''' + + keylet = uzbl.get_keylet() + left_segment = keylet.keycmd[:keylet.cursor] + partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip() + if partial.startswith('set '): + return ('@%s' % partial[4:].lstrip(), True) + + return (partial, False) + + +def stop_completion(uzbl, *args): + '''Stop command completion and return the level to NONE.''' + + d = get_completion_dict(uzbl) + d['level'] = NONE + uzbl.set('completion_list') + + +def complete_completion(uzbl, partial, hint, set_completion=False): + '''Inject the remaining porition of the keyword into the keycmd then stop + the completioning.''' + + if set_completion: + remainder = "%s = " % hint[len(partial):] + + else: + remainder = "%s " % hint[len(partial):] + + uzbl.inject_keycmd(remainder) + stop_completion(uzbl) + + +def partial_completion(uzbl, partial, hint): + '''Inject a common portion of the hints into the keycmd.''' + + remainder = hint[len(partial):] + uzbl.inject_keycmd(remainder) + + +def update_completion_list(uzbl, *args): + '''Checks if the user still has a partially completed keyword under his + cursor then update the completion hints list.''' + + partial = get_incomplete_keyword(uzbl)[0] + if not partial: + return stop_completion(uzbl) + + d = get_completion_dict(uzbl) + if d['level'] < LIST: + return + + hints = [h for h in d['completions'] if h.startswith(partial)] + if not hints: + return uzbl.set('completion_list') + + j = len(partial) + l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)] + uzbl.set('completion_list', LIST_FORMAT % ' '.join(l)) + + +def start_completion(uzbl, *args): + + d = get_completion_dict(uzbl) + if d['lock']: + return + + (partial, set_completion) = get_incomplete_keyword(uzbl) + if not partial: + return stop_completion(uzbl) + + if d['level'] < COMPLETE: + d['level'] += 1 + + hints = [h for h in d['completions'] if h.startswith(partial)] + if not hints: + return + + elif len(hints) == 1: + d['lock'] = True + complete_completion(uzbl, partial, hints[0], set_completion) + d['lock'] = False + return + + elif partial in hints and d['level'] == COMPLETE: + d['lock'] = True + complete_completion(uzbl, partial, partial, set_completion) + d['lock'] = False + return + + smalllen, smallest = sorted([(len(h), h) for h in hints])[0] + common = '' + for i in range(len(partial), smalllen): + char, same = smallest[i], True + for hint in hints: + if hint[i] != char: + same = False + break + + if not same: + break + + common += char + + if common: + d['lock'] = True + partial_completion(uzbl, partial, partial+common) + d['lock'] = False + + update_completion_list(uzbl) + + +def add_builtins(uzbl, args): + '''Pump the space delimited list of builtin commands into the + builtin list.''' + + completions = get_completion_dict(uzbl)['completions'] + builtins = filter(None, map(unicode.strip, args.split(" "))) + for builtin in builtins: + if builtin not in completions: + completions.append(builtin) + + +def add_config_key(uzbl, key, value): + '''Listen on the CONFIG_CHANGED event and add config keys to the variable + list for @var like expansion support.''' + + completions = get_completion_dict(uzbl)['completions'] + key = "@%s" % key + if key not in completions: + completions.append(key) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'BUILTINS': add_builtins, + 'CONFIG_CHANGED': add_config_key, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'KEYCMD_CLEARED': stop_completion, + 'KEYCMD_EXEC': stop_completion, + 'KEYCMD_UPDATE': update_completion_list, + 'START_COMPLETION': start_completion, + 'STOP_COMPLETION': stop_completion, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_completion_dict': get_completion_dict, + 'start_completion': start_completion, + }) diff --git a/examples/data/plugins/config.py b/examples/data/plugins/config.py new file mode 100644 index 0000000..4a848a3 --- /dev/null +++ b/examples/data/plugins/config.py @@ -0,0 +1,97 @@ +import re +import types + +__export__ = ['set', 'get_config'] + +VALIDKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match +TYPECONVERT = {'int': int, 'float': float, 'str': unicode} + +UZBLS = {} + + +def escape(value): + '''A real escaping function may be required.''' + + return unicode(value) + + +def set(uzbl, key, value='', config=None, force=False): + '''Sends a: "set key = value" command to the uzbl instance. If force is + False then only send a set command if the values aren't equal.''' + + if type(value) == types.BooleanType: + value = int(value) + + else: + value = unicode(value) + + if not VALIDKEY(key): + raise KeyError("%r" % key) + + value = escape(value) + if '\n' in value: + value = value.replace("\n", "\\n") + + if not force: + if config is None: + config = get_config(uzbl) + + if key in config and config[key] == value: + return + + uzbl.send('set %s = %s' % (key, value)) + + +class ConfigDict(dict): + def __init__(self, uzbl): + self._uzbl = uzbl + + def __setitem__(self, key, value): + '''Makes "config[key] = value" a wrapper for the set function.''' + + set(self._uzbl, key, value, config=self) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = ConfigDict(uzbl) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del uzbl + + +def get_config(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def variable_set(uzbl, args): + config = get_config(uzbl) + + key, type, value = list(args.split(' ', 2) + ['',])[:3] + old = config[key] if key in config else None + value = TYPECONVERT[type](value) + + dict.__setitem__(config, key, value) + + if old != value: + uzbl.event("CONFIG_CHANGED", key, value) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'VARIABLE_SET': variable_set, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_config': get_config, + 'set': set, + }) diff --git a/examples/data/plugins/keycmd.py b/examples/data/plugins/keycmd.py new file mode 100644 index 0000000..c119077 --- /dev/null +++ b/examples/data/plugins/keycmd.py @@ -0,0 +1,571 @@ +import re + +# Hold the keylets. +UZBLS = {} + +# Keycmd format which includes the markup for the cursor. +KEYCMD_FORMAT = "%s%s%s" +MODCMD_FORMAT = " %s " + + +def escape(str): + for char in ['\\', '@']: + str = str.replace(char, '\\'+char) + + return str + + +def uzbl_escape(str): + return "@[%s]@" % escape(str) if str else '' + + +class Keylet(object): + '''Small per-instance object that tracks all the keys held and characters + typed.''' + + def __init__(self): + # Modcmd tracking + self.held = set() + self.ignored = set() + self.modcmd = '' + self.is_modcmd = False + + # Keycmd tracking + self.keycmd = '' + self.cursor = 0 + + self.modmaps = {} + self.ignores = {} + self.additions = {} + + # Keylet string repr cache. + self._repr_cache = None + + + def get_keycmd(self): + '''Get the keycmd-part of the keylet.''' + + return self.keycmd + + + def get_modcmd(self): + '''Get the modcmd-part of the keylet.''' + + if not self.is_modcmd: + return '' + + return ''.join(self.held) + self.modcmd + + + def modmap_key(self, key): + '''Make some obscure names for some keys friendlier.''' + + if key in self.modmaps: + return self.modmaps[key] + + elif key.endswith('_L') or key.endswith('_R'): + # Remove left-right discrimination and try again. + return self.modmap_key(key[:-2]) + + else: + return key + + + def find_addition(self, modkey): + '''Key has just been pressed, check if this key + the held list + results in a modkey addition. Return that addition and remove all + modkeys that created it.''' + + # Intersection of (held list + modkey) and additions. + added = (self.held | set([modkey])) & set(self.additions.keys()) + for key in added: + if key == modkey or modkey in self.additions[key]: + self.held -= self.additions[key] + return key + + # Held list + ignored list + modkey. + modkeys = self.held | self.ignored | set([modkey]) + for (key, value) in self.additions.items(): + if modkeys.issuperset(value): + self.held -= value + return key + + return modkey + + + def key_ignored(self, key): + '''Check if the given key is ignored by any ignore rules.''' + + for (glob, match) in self.ignores.items(): + if match(key): + return True + + return False + + + def __repr__(self): + '''Return a string representation of the keylet.''' + + if self._repr_cache: + return self._repr_cache + + l = [] + if self.is_modcmd: + l.append('modcmd=%r' % self.get_modcmd()) + + elif self.held: + l.append('held=%r' % ''.join(sorted(self.held))) + + if self.keycmd: + l.append('keycmd=%r' % self.get_keycmd()) + + self._repr_cache = '' % ', '.join(l) + return self._repr_cache + + +def add_modmap(uzbl, key, map): + '''Add modmaps. + + Examples: + set modmap = request MODMAP + @modmap + @modmap + ... + + Then: + @bind = + @bind x = + ... + + ''' + + assert len(key) + modmaps = get_keylet(uzbl).modmaps + + if key[0] == "<" and key[-1] == ">": + key = key[1:-1] + + modmaps[key] = map + uzbl.event("NEW_MODMAP", key, map) + + +def modmap_parse(uzbl, map): + '''Parse a modmap definiton.''' + + split = [s.strip() for s in map.split(' ') if s.split()] + + if not split or len(split) > 2: + raise Exception('Invalid modmap arugments: %r' % map) + + add_modmap(uzbl, *split) + + +def add_key_ignore(uzbl, glob): + '''Add an ignore definition. + + Examples: + set ignore_key = request IGNORE_KEY + @ignore_key + @ignore_key + ... + ''' + + assert len(glob) > 1 + ignores = get_keylet(uzbl).ignores + + glob = "<%s>" % glob.strip("<> ") + restr = glob.replace('*', '[^\s]*') + match = re.compile(restr).match + + ignores[glob] = match + uzbl.event('NEW_KEY_IGNORE', glob) + + +def add_modkey_addition(uzbl, modkeys, result): + '''Add a modkey addition definition. + + Examples: + set mod_addition = request MODKEY_ADDITION + @mod_addition + @mod_addition + @mod_addition + ... + + Then: + @bind = + @bind o = + ... + ''' + + additions = get_keylet(uzbl).additions + modkeys = set(modkeys) + + assert len(modkeys) and result and result not in modkeys + + for (existing_result, existing_modkeys) in additions.items(): + if existing_result != result: + assert modkeys != existing_modkeys + + additions[result] = modkeys + uzbl.event('NEW_MODKEY_ADDITION', modkeys, result) + + +def modkey_addition_parse(uzbl, modkeys): + '''Parse modkey addition definition.''' + + keys = filter(None, map(unicode.strip, modkeys.split(" "))) + keys = ['<%s>' % key.strip("<>") for key in keys if key.strip("<>")] + + assert len(keys) > 1 + add_modkey_addition(uzbl, keys[:-1], keys[-1]) + + +def add_instance(uzbl, *args): + '''Create the Keylet object for this uzbl instance.''' + + UZBLS[uzbl] = Keylet() + + +def del_instance(uzbl, *args): + '''Delete the Keylet object for this uzbl instance.''' + + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_keylet(uzbl): + '''Return the corresponding keylet for this uzbl instance.''' + + # Startup events are not correctly captured and sent over the uzbl socket + # yet so this line is needed because the INSTANCE_START event is lost. + if uzbl not in UZBLS: + add_instance(uzbl) + + keylet = UZBLS[uzbl] + keylet._repr_cache = False + return keylet + + +def clear_keycmd(uzbl): + '''Clear the keycmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.keycmd = '' + k.cursor = 0 + k._repr_cache = False + uzbl.set('keycmd') + uzbl.set('raw_keycmd') + uzbl.event('KEYCMD_CLEARED') + + +def clear_modcmd(uzbl, clear_held=False): + '''Clear the modcmd for this uzbl instance.''' + + k = get_keylet(uzbl) + k.modcmd = '' + k.is_modcmd = False + k._repr_cache = False + if clear_held: + k.ignored = set() + k.held = set() + + uzbl.set('modcmd') + uzbl.set('raw_modcmd') + uzbl.event('MODCMD_CLEARED') + + +def clear_current(uzbl): + '''Clear the modcmd if is_modcmd else clear keycmd.''' + + k = get_keylet(uzbl) + if k.is_modcmd: + clear_modcmd(uzbl) + + else: + clear_keycmd(uzbl) + + +def focus_changed(uzbl, *args): + '''Focus to the uzbl instance has now been lost which means all currently + held keys in the held list will not get a KEY_RELEASE event so clear the + entire held list.''' + + clear_modcmd(uzbl, clear_held=True) + + +def update_event(uzbl, k, execute=True): + '''Raise keycmd & modcmd update events.''' + + config = uzbl.get_config() + keycmd, modcmd = k.get_keycmd(), k.get_modcmd() + + if k.is_modcmd: + uzbl.event('MODCMD_UPDATE', k) + + else: + uzbl.event('KEYCMD_UPDATE', k) + + if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': + new_modcmd = k.get_modcmd() + if not new_modcmd: + uzbl.set('modcmd', config=config) + uzbl.set('raw_modcmd', config=config) + + elif new_modcmd == modcmd: + uzbl.set('raw_modcmd', escape(modcmd), config=config) + uzbl.set('modcmd', MODCMD_FORMAT % uzbl_escape(modcmd), + config=config) + + if 'keycmd_events' in config and config['keycmd_events'] != '1': + return + + new_keycmd = k.get_keycmd() + if not new_keycmd: + uzbl.set('keycmd', config=config) + uzbl.set('raw_keycmd', config=config) + + elif new_keycmd == keycmd: + # Generate the pango markup for the cursor in the keycmd. + curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' ' + chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]] + value = KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks)) + uzbl.set('keycmd', value, config=config) + uzbl.set('raw_keycmd', escape(keycmd), config=config) + + +def inject_str(str, index, inj): + '''Inject a string into string at at given index.''' + + return "%s%s%s" % (str[:index], inj, str[index:]) + + +def get_keylet_and_key(uzbl, key, add=True): + '''Return the keylet and apply any transformations to the key as defined + by the modmapping or modkey addition rules. Return None if the key is + ignored.''' + + keylet = get_keylet(uzbl) + key = keylet.modmap_key(key) + if len(key) == 1: + return (keylet, key) + + modkey = "<%s>" % key.strip("<>") + + if keylet.key_ignored(modkey): + if add: + keylet.ignored.add(modkey) + + elif modkey in keylet.ignored: + keylet.ignored.remove(modkey) + + modkey = keylet.find_addition(modkey) + + if keylet.key_ignored(modkey): + return (keylet, None) + + return (keylet, modkey) + + +def key_press(uzbl, key): + '''Handle KEY_PRESS events. Things done by this function include: + + 1. Ignore all shift key presses (shift can be detected by capital chars) + 3. In non-modcmd mode: + a. append char to keycmd + 4. If not in modcmd mode and a modkey was pressed set modcmd mode. + 5. If in modcmd mode the pressed key is added to the held keys list. + 6. Keycmd is updated and events raised if anything is changed.''' + + (k, key) = get_keylet_and_key(uzbl, key.strip()) + if not key: + return + + if key.lower() == '' and not k.held and k.keycmd: + k.keycmd = inject_str(k.keycmd, k.cursor, ' ') + k.cursor += 1 + + elif not k.held and len(key) == 1: + config = uzbl.get_config() + if 'keycmd_events' in config and config['keycmd_events'] != '1': + k.keycmd = '' + k.cursor = 0 + uzbl.set('keycmd', config=config) + uzbl.set('raw_keycmd', config=config) + return + + k.keycmd = inject_str(k.keycmd, k.cursor, key) + k.cursor += 1 + + elif len(key) > 1: + k.is_modcmd = True + if key not in k.held: + k.held.add(key) + + else: + k.is_modcmd = True + k.modcmd += key + + update_event(uzbl, k) + + +def key_release(uzbl, key): + '''Respond to KEY_RELEASE event. Things done by this function include: + + 1. Remove the key from the keylet held list. + 2. If in a mod-command then raise a MODCMD_EXEC. + 3. Check if any modkey is held, if so set modcmd mode. + 4. Update the keycmd uzbl variable if anything changed.''' + + (k, key) = get_keylet_and_key(uzbl, key.strip(), add=False) + + if key in k.held: + if k.is_modcmd: + uzbl.event('MODCMD_EXEC', k) + + k.held.remove(key) + clear_modcmd(uzbl) + + +def set_keycmd(uzbl, keycmd): + '''Allow setting of the keycmd externally.''' + + k = get_keylet(uzbl) + k.keycmd = keycmd + k._repr_cache = None + k.cursor = len(keycmd) + update_event(uzbl, k, False) + + +def inject_keycmd(uzbl, keycmd): + '''Allow injecting of a string into the keycmd at the cursor position.''' + + k = get_keylet(uzbl) + k.keycmd = inject_str(k.keycmd, k.cursor, keycmd) + k._repr_cache = None + k.cursor += len(keycmd) + update_event(uzbl, k, False) + + +def append_keycmd(uzbl, keycmd): + '''Allow appening of a string to the keycmd.''' + + k = get_keylet(uzbl) + k.keycmd += keycmd + k._repr_cache = None + k.cursor = len(k.keycmd) + update_event(uzbl, k, False) + + +def keycmd_strip_word(uzbl, sep): + ''' Removes the last word from the keycmd, similar to readline ^W ''' + + sep = sep or ' ' + k = get_keylet(uzbl) + if not k.keycmd: + return + + head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] + rfind = head.rfind(sep) + head = head[:rfind] if rfind + 1 else '' + k.keycmd = head + tail + k.cursor = len(head) + update_event(uzbl, k, False) + + +def keycmd_backspace(uzbl, *args): + '''Removes the character at the cursor position in the keycmd.''' + + k = get_keylet(uzbl) + if not k.keycmd: + return + + k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] + k.cursor -= 1 + update_event(uzbl, k, False) + + +def keycmd_delete(uzbl, *args): + '''Removes the character after the cursor position in the keycmd.''' + + k = get_keylet(uzbl) + if not k.keycmd: + return + + k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] + update_event(uzbl, k, False) + + +def keycmd_exec_current(uzbl, *args): + '''Raise a KEYCMD_EXEC with the current keylet and then clear the + keycmd.''' + + k = get_keylet(uzbl) + uzbl.event('KEYCMD_EXEC', k) + clear_keycmd(uzbl) + + +def set_cursor_pos(uzbl, index): + '''Allow setting of the cursor position externally. Supports negative + indexing and relative stepping with '+' and '-'.''' + + k = get_keylet(uzbl) + if index == '-': + cursor = k.cursor - 1 + + elif index == '+': + cursor = k.cursor + 1 + + else: + cursor = int(index.strip()) + if cursor < 0: + cursor = len(k.keycmd) + cursor + 1 + + if cursor < 0: + cursor = 0 + + if cursor > len(k.keycmd): + cursor = len(k.keycmd) + + k.cursor = cursor + update_event(uzbl, k, False) + + +def init(uzbl): + '''Connect handlers to uzbl events.''' + + # Event handling hooks. + uzbl.connect_dict({ + 'APPEND_KEYCMD': append_keycmd, + 'FOCUS_GAINED': focus_changed, + 'FOCUS_LOST': focus_changed, + 'IGNORE_KEY': add_key_ignore, + 'INJECT_KEYCMD': inject_keycmd, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'KEYCMD_BACKSPACE': keycmd_backspace, + 'KEYCMD_DELETE': keycmd_delete, + 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, + 'KEYCMD_STRIP_WORD': keycmd_strip_word, + 'KEY_PRESS': key_press, + 'KEY_RELEASE': key_release, + 'MODKEY_ADDITION': modkey_addition_parse, + 'MODMAP': modmap_parse, + 'SET_CURSOR_POS': set_cursor_pos, + 'SET_KEYCMD': set_keycmd, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'add_key_ignore': add_key_ignore, + 'add_modkey_addition': add_modkey_addition, + 'add_modmap': add_modmap, + 'append_keycmd': append_keycmd, + 'clear_current': clear_current, + 'clear_keycmd': clear_keycmd, + 'clear_modcmd': clear_modcmd, + 'get_keylet': get_keylet, + 'inject_keycmd': inject_keycmd, + 'set_cursor_pos': set_cursor_pos, + 'set_keycmd': set_keycmd, + }) diff --git a/examples/data/plugins/mode.py b/examples/data/plugins/mode.py new file mode 100644 index 0000000..54d865a --- /dev/null +++ b/examples/data/plugins/mode.py @@ -0,0 +1,176 @@ +import sys +import re + +__export__ = ['set_mode', 'get_mode', 'set_mode_config', 'get_mode_config'] + +UZBLS = {} + +DEFAULTS = { + 'mode': '', + 'modes': { + 'insert': { + 'forward_keys': True, + 'keycmd_events': False, + 'modcmd_updates': False, + 'mode_indicator': 'I'}, + 'command': { + 'forward_keys': False, + 'keycmd_events': True, + 'modcmd_updates': True, + 'mode_indicator': 'C'}}} + +FINDSPACES = re.compile("\s+") +VALID_KEY = re.compile("^[\w_]+$").match + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_mode_dict(uzbl): + '''Return the mode dict for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def get_mode_config(uzbl, mode): + '''Return the mode config for a given mode.''' + + modes = get_mode_dict(uzbl)['modes'] + if mode not in modes: + modes[mode] = {} + + return modes[mode] + + +def get_mode(uzbl): + return get_mode_dict(uzbl)['mode'] + + +def mode_changed(uzbl, mode): + '''The mode has just been changed, now set the per-mode config.''' + + if get_mode(uzbl) != mode: + return + + config = uzbl.get_config() + mode_config = get_mode_config(uzbl, mode) + for (key, value) in mode_config.items(): + uzbl.set(key, value, config=config) + + if 'mode_indicator' not in mode_config: + config['mode_indicator'] = mode + + uzbl.clear_keycmd() + uzbl.clear_modcmd() + + +def set_mode(uzbl, mode=None): + '''Set the mode and raise the MODE_CHANGED event if the mode has changed. + Fallback on the default mode if no mode argument was given and the default + mode is not null.''' + + config = uzbl.get_config() + mode_dict = get_mode_dict(uzbl) + if mode is None: + mode_dict['mode'] = '' + if 'default_mode' in config: + mode = config['default_mode'] + + else: + mode = 'command' + + if not VALID_KEY(mode): + raise KeyError("invalid mode name: %r" % mode) + + if 'mode' not in config or config['mode'] != mode: + config['mode'] = mode + + elif mode_dict['mode'] != mode: + mode_dict['mode'] = mode + uzbl.event("MODE_CHANGED", mode) + + +def config_changed(uzbl, key, value): + '''Check for mode related config changes.''' + + value = None if not value else value + if key == 'default_mode': + if not get_mode(uzbl): + set_mode(uzbl, value) + + elif key == 'mode': + set_mode(uzbl, value) + + +def set_mode_config(uzbl, mode, key, value): + '''Set mode specific configs. If the mode being modified is the current + mode then apply the changes on the go.''' + + assert VALID_KEY(mode) and VALID_KEY(key) + + mode_config = get_mode_config(uzbl, mode) + mode_config[key] = value + + if get_mode(uzbl) == mode: + uzbl.set(key, value) + + +def mode_config(uzbl, args): + '''Parse mode config events.''' + + split = map(unicode.strip, FINDSPACES.split(args.lstrip(), 1)) + if len(split) != 2: + raise SyntaxError('invalid mode config syntax: %r' % args) + + mode, set = split + split = map(unicode.strip, set.split('=', 1)) + if len(split) != 2: + raise SyntaxError('invalid set syntax: %r' % args) + + key, value = split + set_mode_config(uzbl, mode, key, value) + + +def toggle_modes(uzbl, modes): + '''Toggle or cycle between or through a list of modes.''' + + assert len(modes.strip()) + + modelist = filter(None, map(unicode.strip, modes.split(' '))) + mode = get_mode(uzbl) + + index = 0 + if mode in modelist: + index = (modelist.index(mode)+1) % len(modelist) + + set_mode(uzbl, modelist[index]) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'CONFIG_CHANGED': config_changed, + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'MODE_CHANGED': mode_changed, + 'MODE_CONFIG': mode_config, + 'TOGGLE_MODES': toggle_modes, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_mode': get_mode, + 'get_mode_config': get_mode_config, + 'set_mode': set_mode, + 'set_mode_config': set_mode_config, + }) diff --git a/examples/data/plugins/on_event.py b/examples/data/plugins/on_event.py new file mode 100644 index 0000000..b9c504a --- /dev/null +++ b/examples/data/plugins/on_event.py @@ -0,0 +1,107 @@ +'''Plugin provides arbitrary binding of uzbl events to uzbl commands. + +Formatting options: + %s = space separated string of the arguments + %r = escaped and quoted version of %s + %1 = argument 1 + %2 = argument 2 + %n = argument n + +Usage: + request ON_EVENT LINK_HOVER set selected_uri = $1 + --> LINK_HOVER http://uzbl.org/ + <-- set selected_uri = http://uzbl.org/ + + request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2 + --> CONFIG_CHANGED selected_uri http://uzbl.org/ + <-- print Config changed: selected_uri = http://uzbl.org/ +''' + +import sys +import re + +__export__ = ['get_on_events', 'on_event'] + +UZBLS = {} + + +def error(msg): + sys.stderr.write('on_event plugin: error: %s\n' % msg) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = {} + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_on_events(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def event_handler(uzbl, *args, **kargs): + '''This function handles all the events being watched by various + on_event definitions and responds accordingly.''' + + events = get_on_events(uzbl) + event = kargs['on_event'] + if event not in events: + return + + commands = events[event] + cmd_expand = uzbl.cmd_expand + for cmd in commands: + cmd = cmd_expand(cmd, args) + uzbl.send(cmd) + + +def on_event(uzbl, event, cmd): + '''Add a new event to watch and respond to.''' + + event = event.upper() + events = get_on_events(uzbl) + if event not in events: + uzbl.connect(event, event_handler, on_event=event) + events[event] = [] + + cmds = events[event] + if cmd not in cmds: + cmds.append(cmd) + + +def parse_on_event(uzbl, args): + '''Parse ON_EVENT events and pass them to the on_event function. + + Syntax: "event ON_EVENT commands".''' + + if not args: + return error("missing on_event arguments") + + split = args.split(' ', 1) + if len(split) != 2: + return error("invalid ON_EVENT syntax: %r" % args) + + event, cmd = split + on_event(uzbl, event, cmd) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'ON_EVENT': parse_on_event, + }) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.export_dict({ + 'get_on_events': get_on_events, + 'on_event': on_event, + }) diff --git a/examples/data/plugins/plugin_template.py b/examples/data/plugins/plugin_template.py new file mode 100644 index 0000000..565a999 --- /dev/null +++ b/examples/data/plugins/plugin_template.py @@ -0,0 +1,76 @@ +'''Plugin template.''' + +# Holds the per-instance data dict. +UZBLS = {} + +# The default instance dict. +DEFAULTS = {} + + +def add_instance(uzbl, *args): + '''Add a new instance with default config options.''' + + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + '''Delete data stored for an instance.''' + + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_myplugin_dict(uzbl): + '''Get data stored for an instance.''' + + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def myplugin_function(uzbl, *args, **kargs): + '''Custom plugin function which is exported by the __export__ list at the + top of the file for use by other functions/callbacks.''' + + print "My plugin function arguments:", args, kargs + + # Get the per-instance data object. + data = get_myplugin_dict(uzbl) + + # Function logic goes here. + + +def myplugin_event_parser(uzbl, args): + '''Parses MYPLUGIN_EVENT raised by uzbl or another plugin.''' + + print "Got MYPLUGIN_EVENT with arguments: %r" % args + + # Parsing logic goes here. + + +def init(uzbl): + '''The main function of the plugin which is used to attach all the event + hooks that are going to be used throughout the plugins life. This function + is called each time a UzblInstance() object is created in the event + manager.''' + + # Make a dictionary comprising of {"EVENT_NAME": handler, ..} to the event + # handler stack: + uzbl.connect_dict({ + # event name function + 'INSTANCE_START': add_instance, + 'INSTANCE_EXIT': del_instance, + 'MYPLUGIN_EVENT': myplugin_event_parser, + }) + + # Or connect a handler to an event manually and supply additional optional + # arguments: + #uzbl.connect("MYOTHER_EVENT", myother_event_parser, True, limit=20) + + # Function exports to the uzbl object, `function(uzbl, *args, ..)` + # becomes `uzbl.function(*args, ..)`. + uzbl.connect_dict({ + # external name function + 'myplugin_function': myplugin_function, + }) diff --git a/examples/data/plugins/progress_bar.py b/examples/data/plugins/progress_bar.py new file mode 100644 index 0000000..89ba175 --- /dev/null +++ b/examples/data/plugins/progress_bar.py @@ -0,0 +1,159 @@ +import sys + +UZBLS = {} + +DEFAULTS = {'width': 8, + 'done': '=', + 'pending': '.', + 'format': '[%d%a%p]%c', + 'spinner': '-\\|/', + 'sprites': 'loading', + 'updates': 0, + 'progress': 100} + + +def error(msg): + sys.stderr.write("progress_bar plugin: error: %s\n" % msg) + + +def add_instance(uzbl, *args): + UZBLS[uzbl] = dict(DEFAULTS) + + +def del_instance(uzbl, *args): + if uzbl in UZBLS: + del UZBLS[uzbl] + + +def get_progress_config(uzbl): + if uzbl not in UZBLS: + add_instance(uzbl) + + return UZBLS[uzbl] + + +def update_progress(uzbl, prog=None): + '''Updates the progress_format variable on LOAD_PROGRESS update. + + The current substitution options are: + %d = done char * done + %p = pending char * remaining + %c = percent done + %i = int done + %s = -\|/ spinner + %t = percent pending + %o = int pending + %r = sprites + ''' + + prog_config = get_progress_config(uzbl) + config = uzbl.get_config() + + if prog is None: + prog = prog_config['progress'] + + else: + prog = int(prog) + prog_config['progress'] = prog + + prog_config['updates'] += 1 + format = prog_config['format'] + width = prog_config['width'] + + # Inflate the done and pending bars to stop the progress bar + # jumping around. + if '%c' in format or '%i' in format: + count = format.count('%c') + format.count('%i') + width += (3-len(str(prog))) * count + + if '%t' in format or '%o' in format: + count = format.count('%t') + format.count('%o') + width += (3-len(str(100-prog))) * count + + done = int(((prog/100.0)*width)+0.5) + pending = width - done + + if '%d' in format: + format = format.replace('%d', prog_config['done']*done) + + if '%p' in format: + format = format.replace('%p', prog_config['pending']*pending) + + if '%c' in format: + format = format.replace('%c', '%d%%' % prog) + + if '%i' in format: + format = format.replace('%i', '%d' % prog) + + if '%t' in format: + format = format.replace('%t', '%d%%' % (100-prog)) + + if '%o' in format: + format = format.replace('%o', '%d' % (100-prog)) + + if '%s' in format: + spinner = prog_config['spinner'] + spin = '-' if not spinner else spinner + index = 0 if prog == 100 else prog_config['updates'] % len(spin) + char = '\\\\' if spin[index] == '\\' else spin[index] + format = format.replace('%s', char) + + if '%r' in format: + sprites = prog_config['sprites'] + sprites = '-' if not sprites else sprites + index = int(((prog/100.0)*len(sprites))+0.5)-1 + sprite = '\\\\' if sprites[index] == '\\' else sprites[index] + format = format.replace('%r', sprite) + + if 'progress_format' not in config or config['progress_format'] != format: + config['progress_format'] = format + + +def progress_config(uzbl, args): + '''Parse PROGRESS_CONFIG events from the uzbl instance. + + Syntax: event PROGRESS_CONFIG = + ''' + + split = args.split('=', 1) + if len(split) != 2: + return error("invalid syntax: %r" % args) + + key, value = map(unicode.strip, split) + prog_config = get_progress_config(uzbl) + + if key not in prog_config: + return error("key error: %r" % args) + + if type(prog_config[key]) == type(1): + try: + value = int(value) + + except: + return error("invalid type: %r" % args) + + elif not value: + value = ' ' + + prog_config[key] = value + update_progress(uzbl) + + +def reset_progress(uzbl, args): + '''Reset the spinner counter, reset the progress int and re-draw the + progress bar on LOAD_COMMIT.''' + + prog_dict = get_progress_config(uzbl) + prog_dict['updates'] = prog_dict['progress'] = 0 + update_progress(uzbl) + + +def init(uzbl): + # Event handling hooks. + uzbl.connect_dict({ + 'INSTANCE_EXIT': del_instance, + 'INSTANCE_START': add_instance, + 'LOAD_COMMIT': reset_progress, + 'LOAD_PROGRESS': update_progress, + 'PROGRESS_CONFIG': progress_config, + }) diff --git a/examples/data/scripts/cookies.sh b/examples/data/scripts/cookies.sh new file mode 100755 index 0000000..ee2ce51 --- /dev/null +++ b/examples/data/scripts/cookies.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +set -n; + +# THIS IS EXPERIMENTAL AND COULD BE INSECURE !!!!!! + +# this is an example bash script of how you could manage your cookies. it is very raw and basic and not as good as uzbl-cookie-daemon +# we use the cookies.txt format (See http://kb.mozillazine.org/Cookies.txt) +# This is one textfile with entries like this: +# kb.mozillazine.org FALSE / FALSE 1146030396 wikiUserID 16993 +# domain alow-read-other-subdomains path http-required expiration name value +# you probably want your cookies config file in your $XDG_CONFIG_HOME ( eg $HOME/.config/uzbl/cookies) +# Note. in uzbl there is no strict definition on what a session is. it's YOUR job to clear cookies marked as end_session if you want to keep cookies only valid during a "session" +# MAYBE TODO: allow user to edit cookie before saving. this cannot be done with zenity :( +# TODO: different cookie paths per config (eg per group of uzbl instances) + +# TODO: correct implementation. +# see http://curl.haxx.se/rfc/cookie_spec.html +# http://en.wikipedia.org/wiki/HTTP_cookie + +# TODO : check expires= before sending. +# write sample script that cleans up cookies dir based on expires attribute. +# TODO: check uri against domain attribute. and path also. +# implement secure attribute. +# support blocking or not for 3rd parties +# http://kb.mozillazine.org/Cookies.txt +# don't always append cookies, sometimes we need to overwrite + +cookie_config=${XDG_CONFIG_HOME:-${HOME}/.config}/uzbl/cookies +[ "x$cookie_config" = x ] && exit 1 +[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/" ] &&\ +cookie_data=${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/cookies.txt || exit 1 + +notifier= +#notifier=notify-send +#notify_wrapper () { +# echo "$@" >> $HOME/cookielog +#} +#notifier=notifier_wrapper + +# if this variable is set, we will use it to inform you when and which cookies we store, and when/which we send. +# it's primarily used for debugging +notifier= +which zenity &>/dev/null || exit 2 + +# Example cookie: +# test_cookie=CheckForPermission; expires=Thu, 07-May-2009 19:17:55 GMT; path=/; domain=.doubleclick.net + +# uri=$6 +# uri=${uri/http:\/\/} # strip 'http://' part +# host=${uri/\/*/} +action=$8 # GET/PUT +shift +host=$9 +shift +path=$9 +shift +cookie=$9 + +field_domain=$host +field_path=$path +field_name= +field_value= +field_exp='end_session' + +notify() { + [ -n "$notifier" ] && $notifier "$@" +} + + +# FOR NOW LETS KEEP IT SIMPLE AND JUST ALWAYS PUT AND ALWAYS GET +parse_cookie() { + IFS=$';' + first_pair=1 + for pair in $cookie + do + if [ "x$first_pair" = x1 ] + then + field_name=${pair%%=*} + field_value=${pair#*=} + first_pair=0 + else + echo "$pair" | read -r pair #strip leading/trailing wite space + key=${pair%%=*} + val=${pair#*=} + [ "$key" == expires ] && field_exp=`date -u -d "$val" +'%s'` + # TODO: domain + [ "$key" == path ] && field_path=$val + fi + done + unset IFS +} + +# match cookies in cookies.txt against hostname and path +get_cookie() { + path_esc=${path//\//\\/} + search="^[^\t]*$host\t[^\t]*\t$path_esc" + cookie=`awk "/$search/" $cookie_data 2>/dev/null | tail -n 1` + if [ -z "$cookie" ] + then + notify "Get_cookie: search: $search in $cookie_data -> no result" + false + else + notify "Get_cookie: search: $search in $cookie_data -> result: $cookie" + echo "$cookie" | \ + read domain alow_read_other_subdomains path http_required expiration name \ + value; + cookie="$name=$value" + true + fi +} + +save_cookie() { + if parse_cookie + then + data="$field_domain\tFALSE\t$field_path\tFALSE\t$field_exp\t$field_name\t$field_value" + notify "save_cookie: adding $data to $cookie_data" + echo -e "$data" >> $cookie_data + else + notify "not saving a cookie. since we don't have policies yet, parse_cookie must have returned false. this is a bug" + fi +} + +[ "x$action" = xPUT ] && save_cookie +[ "x$action" = xGET ] && get_cookie && echo "$cookie" + +exit + + +# TODO: implement this later. +# $1 = section (TRUSTED or DENY) +# $2 =url +match() { + sed -n "/$1/,/^\$/p" $cookie_config 2>/dev/null | grep -q "^$host" +} + +fetch_cookie() { + cookie=`cat $cookie_data` +} + +store_cookie() { + echo $cookie > $cookie_data +} + +if match TRUSTED $host +then + [ "x$action" = xPUT ] && store_cookie $host + [ "x$action" = xGET ] && fetch_cookie && echo "$cookie" +elif ! match DENY $host +then + [ "x$action" = xPUT ] && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Accept this cookie from $host ?" --entry-text="$cookie"` && store_cookie $host + [ "x$action" = xGET ] && fetch_cookie && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Submit this cookie to $host ?" --entry-text="$cookie"` && echo $cookie +fi +exit 0 diff --git a/examples/data/scripts/download.sh b/examples/data/scripts/download.sh new file mode 100755 index 0000000..1c7d039 --- /dev/null +++ b/examples/data/scripts/download.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# just an example of how you could handle your downloads +# try some pattern matching on the uri to determine what we should do + +# Some sites block the default wget --user-agent.. +GET="wget --user-agent=Firefox" + +dest="$HOME" +url="$8" + +http_proxy="$9" +export http_proxy + +test "x$url" = "x" && { echo "you must supply a url! ($url)"; exit 1; } + +# only changes the dir for the $get sub process +if echo "$url" | grep -E '.*\.torrent' >/dev/null; +then + ( cd "$dest"; $GET "$url") +else + ( cd "$dest"; $GET "$url") +fi diff --git a/examples/data/scripts/extedit.js b/examples/data/scripts/extedit.js new file mode 100644 index 0000000..8ed346d --- /dev/null +++ b/examples/data/scripts/extedit.js @@ -0,0 +1,102 @@ +/* + * Edit forms in external editor + * + * (c) 2009, Robert Manea + * utf8 functions are (c) by Webtoolkit.info (http://www.webtoolkit.info/) + * + * + * Installation: + * - Copy this script to $HOME/.local/share/uzbl/scripts + * - Add the following to $HOME/.config/uzbl/config: + * @bind E = script @scripts_dir/extedit.js + * - Set your preferred editor + * set editor = gvim + * - non-GUI editors + * set editor = xterm -e vim + * + * Usage: + * Select (click) an editable form, go to command mode and hit E + * +*/ + + +function utf8_decode ( str_data ) { + var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0; + + str_data += ''; + + while ( i < str_data.length ) { + c1 = str_data.charCodeAt(i); + if (c1 < 128) { + tmp_arr[ac++] = String.fromCharCode(c1); + i++; + } else if ((c1 > 191) && (c1 < 224)) { + c2 = str_data.charCodeAt(i+1); + tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = str_data.charCodeAt(i+1); + c3 = str_data.charCodeAt(i+2); + tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + + return tmp_arr.join(''); +} + + +function utf8_encode ( argString ) { + var string = (argString+''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + + var utftext = ""; + var start, end; + var stringl = 0; + + start = end = 0; + stringl = string.length; + for (var n = 0; n < stringl; n++) { + var c1 = string.charCodeAt(n); + var enc = null; + + if (c1 < 128) { + end++; + } else if (c1 > 127 && c1 < 2048) { + enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128); + } else { + enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128); + } + if (enc !== null) { + if (end > start) { + utftext += string.substring(start, end); + } + utftext += enc; + start = end = n+1; + } + } + + if (end > start) { + utftext += string.substring(start, string.length); + } + + return utftext; +} + + +(function() { + var actelem = document.activeElement; + + if(actelem.type == 'text' || actelem.type == 'textarea') { + var editor = Uzbl.run("print @external_editor") || "gvim"; + var filename = Uzbl.run("print @(mktemp /tmp/uzbl_edit.XXXXXX)@"); + + if(actelem.value) + Uzbl.run("sh 'echo " + window.btoa(utf8_encode(actelem.value)) + " | base64 -d > " + filename + "'"); + + Uzbl.run("sync_sh '" + editor + " " + filename + "'"); + actelem.value = utf8_decode(window.atob(Uzbl.run("print @(base64 -w 0 " + filename + ")@"))); + + Uzbl.run("sh 'rm -f " + filename + "'"); + } + + })(); diff --git a/examples/data/scripts/follow_Numbers.js b/examples/data/scripts/follow_Numbers.js new file mode 100644 index 0000000..00b279e --- /dev/null +++ b/examples/data/scripts/follow_Numbers.js @@ -0,0 +1,228 @@ +/* This is the basic linkfollowing script. + * Its pretty stable, only using numbers to navigate. + * + * TODO: Some pages mess around a lot with the zIndex which + * lets some hints in the background. + * TODO: Some positions are not calculated correctly (mostly + * because of uber-fancy-designed-webpages. Basic HTML and CSS + * works good + * TODO: Still some links can't be followed/unexpected things + * happen. Blame some freaky webdesigners ;) + */ + +//Just some shortcuts and globals +var uzblid = 'uzbl_link_hint'; +var uzbldivid = uzblid + '_div_container'; +var doc = document; +var win = window; +var links = document.links; +var forms = document.forms; + +//Reset keycmd, modcmd and return to default mode. +function clearKeycmd() { Uzbl.run('set mode ='); } + +//Make onlick-links "clickable" +try { + HTMLElement.prototype.click = function() { + if (typeof this.onclick == 'function') { + this.onclick({ + type: 'click' + }); + } + }; +} catch(e) {} +//Catch the ESC keypress to stop linkfollowing +function keyPressHandler(e) { + var kC = window.event ? event.keyCode: e.keyCode; + var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; + if (kC == Esc) { + removeAllHints(); + } +} +//Calculate element position to draw the hint +//Pretty accurate but on fails in some very fancy cases +function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; + var height = el.offsetHeight; + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return [up, left, width, height]; +} +//Calculate if an element is visible +function isVisible(el) { + if (el == doc) { + return true; + } + if (!el) { + return false; + } + if (!el.parentNode) { + return false; + } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); +} +//Calculate if an element is on the viewport. +function elementInViewport(el) { + offset = elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; + var height = offset[3]; + return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; +} +//Removes all hints/leftovers that might be generated +//by this script. +function removeAllHints() { + var elements = doc.getElementById(uzbldivid); + if (elements) { + elements.parentNode.removeChild(elements); + } +} +//Generate a hint for an element with the given label +//Here you can play around with the style of the hints! +function generateHint(el, label) { + var pos = elementPosition(el); + var hint = doc.createElement('div'); + hint.setAttribute('name', uzblid); + hint.innerText = label; + hint.style.display = 'inline'; + hint.style.backgroundColor = '#B9FF00'; + hint.style.border = '2px solid #4A6600'; + hint.style.color = 'black'; + hint.style.fontSize = '9px'; + hint.style.fontWeight = 'bold'; + hint.style.lineHeight = '9px'; + hint.style.margin = '0px'; + hint.style.padding = '1px'; + hint.style.position = 'absolute'; + hint.style.zIndex = '1000'; + hint.style.left = pos[1] + 'px'; + hint.style.top = pos[0] + 'px'; + var img = el.getElementsByTagName('img'); + if (img.length > 0) { + hint.style.left = pos[1] + img[0].width / 2 + 'px'; + } + hint.style.textDecoration = 'none'; + hint.style.webkitBorderRadius = '6px'; + // Play around with this, pretty funny things to do :) + hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; + return hint; +} +//Here we choose what to do with an element if we +//want to "follow" it. On form elements we "select" +//or pass the focus, on links we try to perform a click, +//but at least set the href of the link. (needs some improvements) +function clickElem(item) { + removeAllHints(); + clearKeycmd(); + if (item) { + var name = item.tagName; + if (name == 'A') { + item.click(); + window.location = item.href; + } else if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { + item.focus(); + item.select(); + } else { + item.click(); + } + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + } else { + item.click(); + window.location = item.href; + } + } +} +//Returns a list of all links (in this version +//just the elements itself, but in other versions, we +//add the label here. +function addLinks() { + res = [[], []]; + for (var l = 0; l < links.length; l++) { + var li = links[l]; + if (isVisible(li) && elementInViewport(li)) { + res[0].push(li); + } + } + return res; +} +//Same as above, just for the form elements +function addFormElems() { + res = [[], []]; + for (var f = 0; f < forms.length; f++) { + for (var e = 0; e < forms[f].elements.length; e++) { + var el = forms[f].elements[e]; + if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { + res[0].push(el); + } + } + } + return res; +} +//Draw all hints for all elements passed. "len" is for +//the number of chars we should use to avoid collisions +function reDrawHints(elems, chars) { + removeAllHints(); + var hintdiv = doc.createElement('div'); + hintdiv.setAttribute('id', uzbldivid); + for (var i = 0; i < elems[0].length; i++) { + if (elems[0][i]) { + var label = elems[1][i].substring(chars); + var h = generateHint(elems[0][i], label); + hintdiv.appendChild(h); + } + } + if (document.body) { + document.body.appendChild(hintdiv); + } +} +//Put it all together +function followLinks(follow) { + var s = follow.split(''); + var linknr = parseInt(follow, 10); + if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); + var linkelems = addLinks(); + var formelems = addFormElems(); + var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; + var len = (elems[0].length + '').length; + var oldDiv = doc.getElementById(uzbldivid); + var leftover = [[], []]; + if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { + clickElem(elems[0][linknr]); + } else { + for (var j = 0; j < elems[0].length; j++) { + var b = true; + var label = j + ''; + var n = label.length; + for (n; n < len; n++) { + label = '0' + label; + } + for (var k = 0; k < s.length; k++) { + b = b && label.charAt(k) == s[k]; + } + if (b) { + leftover[0].push(elems[0][j]); + leftover[1].push(label); + } + } + reDrawHints(leftover, s.length); + } +} +followLinks('%s'); diff --git a/examples/data/scripts/follow_Numbers_Strings.js b/examples/data/scripts/follow_Numbers_Strings.js new file mode 100644 index 0000000..e50da5d --- /dev/null +++ b/examples/data/scripts/follow_Numbers_Strings.js @@ -0,0 +1,212 @@ +var uzblid = 'uzbl_link_hint'; +var uzbldivid = uzblid + '_div_container'; +var doc = document; +var win = window; +var links = document.links; +var forms = document.forms; + +//Reset keycmd, modcmd and return to default mode. +function clearKeycmd() { Uzbl.run('set mode ='); } + +try { + HTMLElement.prototype.click = function() { + if (typeof this.onclick == 'function') { + this.onclick({ + type: 'click' + }); + } + }; +} catch(e) {} +function keyPressHandler(e) { + var kC = window.event ? event.keyCode: e.keyCode; + var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; + if (kC == Esc) { + removeAllHints(); + } +} +function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; + var width = el.offsetWidth; + var height = el.offsetHeight; + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return [up, left, width, height]; +} +function isVisible(el) { + if (el == doc) { + return true; + } + if (!el) { + return false; + } + if (!el.parentNode) { + return false; + } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); +} +function elementInViewport(el) { + offset = elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; + var height = offset[3]; + return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; +} +function removeAllHints() { + var elements = doc.getElementById(uzbldivid); + if (elements) { + elements.parentNode.removeChild(elements); + } +} +function generateHint(el, label) { + var pos = elementPosition(el); + var hint = doc.createElement('div'); + hint.setAttribute('name', uzblid); + hint.innerText = label; + hint.style.display = 'inline'; + hint.style.backgroundColor = '#B9FF00'; + hint.style.border = '2px solid #4A6600'; + hint.style.color = 'black'; + hint.style.zIndex = '1000'; + hint.style.fontSize = '9px'; + hint.style.fontWeight = 'bold'; + hint.style.lineHeight = '9px'; + hint.style.margin = '0px'; + hint.style.padding = '1px'; + hint.style.position = 'absolute'; + hint.style.left = pos[1] + 'px'; + hint.style.top = pos[0] + 'px'; + var img = el.getElementsByTagName('img'); + if (img.length > 0) { + hint.style.left = pos[1] + img[0].width / 2 + 'px'; + } + hint.style.textDecoration = 'none'; + hint.style.webkitBorderRadius = '6px'; + hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; + return hint; +} + +function clickElem(item) { + removeAllHints(); + clearKeycmd(); + if (item) { + var name = item.tagName; + if (name == 'A') { + item.click(); + window.location = item.href; + } else if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { + item.focus(); + item.select(); + } else { + item.click(); + } + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + } else { + item.click(); + window.location = item.href; + } + } +} + +function addLinks() { + res = [[], []]; + for (var l = 0; l < links.length; l++) { + var li = links[l]; + if (isVisible(li) && elementInViewport(li)) { + res[0].push(li); + res[1].push(li.innerText.toLowerCase()); + } + } + return res; +} +function addFormElems() { + res = [[], []]; + for (var f = 0; f < forms.length; f++) { + for (var e = 0; e < forms[f].elements.length; e++) { + var el = forms[f].elements[e]; + if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { + res[0].push(el); + if (el.getAttribute('value')) { + res[1].push(el.getAttribute('value').toLowerCase()); + } else { + res[1].push(el.getAttribute('name').toLowerCase()); + } + } + } + } + return res; +} +function reDrawHints(elems, len) { + var hintdiv = doc.createElement('div'); + hintdiv.setAttribute('id', uzbldivid); + hintdiv.style.opacity = '0.0'; + for (var i = 0; i < elems[0].length; i++) { + var label = i + ''; + var n = label.length; + for (n; n < len; n++) { + label = '0' + label; + } + if (elems[0][i]) { + var h = generateHint(elems[0][i], label); + hintdiv.appendChild(h); + } + } + if (document.body) { + document.body.appendChild(hintdiv); + hintdiv.style.opacity = '0.7' + } +} +function followLinks(follow) { + var s = follow.split(''); + var linknr = parseInt(follow, 10); + if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); + var linkelems = addLinks(); + var formelems = addFormElems(); + var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; + var len = (elems[0].length + '').length; + var oldDiv = doc.getElementById(uzbldivid); + var leftover = [[], []]; + if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { + clickElem(elems[0][linknr]); + } else { + for (var j = 0; j < elems[0].length; j++) { + var b = true; + for (var k = 0; k < s.length; k++) { + b = b && elems[1][j].charAt(k) == s[k]; + } + if (!b) { + elems[0][j] = null; + elems[1][j] = null; + } else { + leftover[0].push(elems[0][j]); + leftover[1].push(elems[1][j]); + } + } + if (leftover[0].length == 1) { + clickElem(leftover[0][0]); + } else if (!oldDiv) { + if (linknr + 1 || s.length == 0) { + reDrawHints(elems, len); + } else { + reDrawHints(leftover, len); + } + } + } +} +followLinks('%s'); diff --git a/examples/data/scripts/formfiller.pl b/examples/data/scripts/formfiller.pl new file mode 100755 index 0000000..74dcc80 --- /dev/null +++ b/examples/data/scripts/formfiller.pl @@ -0,0 +1,99 @@ +#!/usr/bin/perl + +# a slightly more advanced form filler +# +# uses settings file like: $keydir/ +#TODO: fallback to $HOME/.local/share +# user arg 1: +# edit: force editing of the file (fetches if file is missing) +# load: fill forms from file (fetches if file is missing) +# new: fetch new file + +# usage example: +# bind LL = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl load +# bind LN = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl new +# bind LE = spawn /usr/share/uzbl/examples/data/scripts/formfiller.pl edit + +use strict; +use warnings; + +my $keydir = $ENV{XDG_CONFIG_HOME} . "/uzbl/forms"; +my ($config,$pid,$xid,$fifoname,$socket,$url,$title,$cmd) = @ARGV; +if (!defined $fifoname || $fifoname eq "") { die "No fifo"; } + +sub domain { + my ($url) = @_; + $url =~ s#http(s)?://([A-Za-z0-9\.-]+)(/.*)?#$2#; + return $url; +}; + +my $editor = "xterm -e vim"; +#my $editor = "gvim"; + +# ideally, there would be some way to ask uzbl for the html content instead of having to redownload it with +# Also, you may need to fake the user-agent on some sites (like facebook) + my $downloader = "curl -A 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.10) Gecko/2009042810 GranParadiso/3.0.10' "; +#my $downloader = "curl -s"; + +my @fields = ("type","name","value"); + +my %command; + +$command{load} = sub { + my ($domain) = @_; + my $filename = "$keydir/$domain"; + if (-e $filename){ + open(my $file, $filename) or die "Failed to open $filename: $!"; + my (@lines) = <$file>; + close($file); + $|++; + open(my $fifo, ">>", $fifoname) or die "Failed to open $fifoname: $!"; + foreach my $line (@lines) { + next if ($line =~ m/^#/); + my ($type,$name,$value) = ($line =~ /^\s*(\w+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*$/); + if ($type eq "checkbox") + { + printf $fifo 'js document.getElementsByName("%s")[0].checked = %s;', $name, $value; + } elsif ($type eq "submit") + { + printf $fifo 'js function fs (n) {try{n.submit()} catch (e){fs(n.parentNode)}}; fs(document.getElementsByName("%s")[0]);', $name; + } elsif ($type ne "") + { + printf $fifo 'js document.getElementsByName("%s")[0].value = "%s";', $name, $value; + } + print $fifo "\n"; + } + $|--; + } else { + $command{new}->($domain); + $command{edit}->($domain); + } +}; +$command{edit} = sub { + my ($domain) = @_; + my $file = "$keydir/$domain"; + if(-e $file){ + system ($editor, $file); + } else { + $command{new}->($domain); + } +}; +$command{new} = sub { + my ($domain) = @_; + my $filename = "$keydir/$domain"; + open (my $file,">>", $filename) or die "Failed to open $filename: $!"; + $|++; + print $file "# Make sure that there are no extra submits, since it may trigger the wrong one.\n"; + printf $file "#%-10s | %-10s | %s\n", @fields; + print $file "#------------------------------\n"; + my @data = `$downloader $url`; + foreach my $line (@data){ + if($line =~ m/].*?)>/i){ + $line =~ s/.*(].*?)>).*/$1/; + printf $file " %-10s | %-10s | %s\n", map { my ($r) = $line =~ /.*$_=["'](.*?)["']/;$r } @fields; + }; + }; + $|--; +}; + +$command{$cmd}->(domain($url)); diff --git a/examples/data/scripts/formfiller.sh b/examples/data/scripts/formfiller.sh new file mode 100755 index 0000000..10afaba --- /dev/null +++ b/examples/data/scripts/formfiller.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# simple html form (eg for logins) filler (and manager) for uzbl. +# uses settings files like: $keydir/ +# files contain lines like: : + + +# user arg 1: +# edit: force editing the file (falls back to new if not found) +# new: start with a new file. +# load: try to load from file into form + +# something else (or empty): if file not available: new, otherwise load. + +keydir=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/forms +[ -d "`dirname $keydir`" ] || exit 1 +[ -d "$keydir" ] || mkdir "$keydir" + +editor=${VISUAL} +if [[ -z ${editor} ]]; then + #editor='gvim' + editor='urxvt -e vim' +fi + +config=$1; shift +pid=$1; shift +xid=$1; shift +fifo=$1; shift +socket=$1; shift +url=$1; shift +title=$1; shift +action=$1 + +[ -d $keydir ] || mkdir $keydir || exit 1 + +if [ "$action" != 'edit' -a "$action" != 'new' -a "$action" != 'load' ] +then + action=new + [[ -e $keydir/$domain ]] && action=load +elif [ "$action" == 'edit' ] && [[ ! -e $keydir/$domain ]] +then + action=new +fi +domain=$(echo $url | sed -re 's|(http\|https)+://([A-Za-z0-9\.]+)/.*|\2|') + + +#regex='s|.*.*|\1: |p' # sscj's first version, does not work on http://wiki.archlinux.org/index.php?title=Special:UserLogin&returnto=Main_Page + regex='s|.*> $fifo +else + if [ "$action" == 'new' ] + then + curl "$url" | grep ' $keydir/$domain + fi + [[ -e $keydir/$domain ]] || exit 3 #this should never happen, but you never know. + $editor $keydir/$domain #TODO: if user aborts save in editor, the file is already overwritten +fi diff --git a/examples/data/scripts/hint.js b/examples/data/scripts/hint.js new file mode 100644 index 0000000..ec7f1e2 --- /dev/null +++ b/examples/data/scripts/hint.js @@ -0,0 +1,26 @@ +for (var i=0; i < document.links.length; i++) { + var uzblid = 'uzbl_link_hint_'; + var li = document.links[i]; + var pre = document.getElementById(uzblid+i); + + if (pre) { + li.removeChild(pre); + } else { + var hint = document.createElement('div'); + hint.setAttribute('id',uzblid+i); + hint.innerHTML = i; + hint.style.display='inline'; + hint.style.lineHeight='90%'; + hint.style.backgroundColor='red'; + hint.style.color='white'; + hint.style.fontSize='small-xx'; + hint.style.fontWeight='light'; + hint.style.margin='0px'; + hint.style.padding='2px'; + hint.style.position='absolute'; + hint.style.textDecoration='none'; + hint.style.left=li.style.left; + hint.style.top=li.style.top; + li.insertAdjacentElement('afterBegin',hint); + } +} diff --git a/examples/data/scripts/history.sh b/examples/data/scripts/history.sh new file mode 100755 index 0000000..7c83aa6 --- /dev/null +++ b/examples/data/scripts/history.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history +[ -d `dirname $file` ] || exit 1 +echo `date +'%Y-%m-%d %H:%M:%S'`" $6 $7" >> $file diff --git a/examples/data/scripts/insert_bookmark.sh b/examples/data/scripts/insert_bookmark.sh new file mode 100755 index 0000000..c34e7db --- /dev/null +++ b/examples/data/scripts/insert_bookmark.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +[ -d "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl" ] || exit 1 +file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/bookmarks + +which zenity &>/dev/null || exit 2 + +entry=`zenity --entry --text="Add bookmark. add tags after the '\t', separated by spaces" --entry-text="$6 $7\t"` +exitstatus=$? +if [ $exitstatus -ne 0 ]; then exit $exitstatus; fi +url=`echo $entry | awk '{print $1}'` + +# TODO: check if already exists, if so, and tags are different: ask if you want to replace tags +echo "$entry" >/dev/null #for some reason we need this.. don't ask me why +echo -e "$entry" >> $file +true diff --git a/examples/data/scripts/instance-select-wmii.sh b/examples/data/scripts/instance-select-wmii.sh new file mode 100755 index 0000000..2bf13ba --- /dev/null +++ b/examples/data/scripts/instance-select-wmii.sh @@ -0,0 +1,54 @@ +#!/bin/sh + + +# This script allows you to focus another uzbl window +# It considers all uzbl windows in the current tag +# you can select one from a list, or go to the next/previous one +# It does not change the layout (stacked/tiled/floating) nor does it +# changes the size or viewing mode of a uzbl window +# When your current uzbl window is maximized, the one you change to +# will be maximized as well. +# See http://www.uzbl.org/wiki/wmii for more info +# $1 must be one of 'list', 'next', 'prev' + +COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" + +if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch +else + DMENU="dmenu -i" +fi + +if [ "$1" == 'list' ] +then + list= + # get window id's of uzbl clients. we could also get the label in one shot but it's pretty tricky + for i in $(wmiir read /tag/sel/index | grep uzbl |cut -d ' ' -f2) + do + label=$(wmiir read /client/$i/label) + list="$list$i : $label\n" + done + window=$(echo -e "$list" | $DMENU $COLORS | cut -d ' ' -f1) + wmiir xwrite /tag/sel/ctl "select client $window" +elif [ "$1" == 'next' ] +then + current=$(wmiir read /client/sel/ctl | head -n 1) + # find the next uzbl window and focus it + next=$(wmiir read /tag/sel/index | grep -A 10000 " $current " | grep -m 1 uzbl | cut -d ' ' -f2) + if [ x"$next" != "x" ] + then + wmiir xwrite /tag/sel/ctl "select client $next" + fi +elif [ "$1" == 'prev' ] +then + current=$(wmiir read /client/sel/ctl | head -n 1) + prev=$(wmiir read /tag/sel/index | grep -B 10000 " $current " | tac | grep -m 1 uzbl | cut -d ' ' -f2) + if [ x"$prev" != "x" ] + then + wmiir xwrite /tag/sel/ctl "select client $prev" + fi +else + echo "\$1 not valid" >&2 + exit 2 +fi diff --git a/examples/data/scripts/linkfollow.js b/examples/data/scripts/linkfollow.js new file mode 100644 index 0000000..3109cda --- /dev/null +++ b/examples/data/scripts/linkfollow.js @@ -0,0 +1,269 @@ +// link follower for uzbl +// requires http://github.com/DuClare/uzbl/commit/6c11777067bdb8aac09bba78d54caea04f85e059 +// +// first, it needs to be loaded before every time it is used. +// One way would be to use the load_commit_handler: +// set load_commit_handler = sh 'echo "script /usr/share/uzbl/examples/data/scripts/linkfollow.js" > "$4"' +// +// when script is loaded, it can be invoked with +// bind f* = js hints.set("%s", hints.open) +// bind f_ = js hints.follow("%s",hints.open) +// +// At the moment, it may be useful to have way of forcing uzbl to load the script +// bind :lf = script /usr/share/uzbl/examples/data/scripts/linkfollow.js +// +// The default style for the hints are pretty ugly, so it is recommended to add the following +// to config file +// set stylesheet_uri = /usr/share/uzbl/examples/data/style.css +// +// based on follow_Numbers.js +// +// TODO: fix styling for the first element +// TODO: emulate mouseover events when visiting some elements +// TODO: rewrite the element->action handling + + +function Hints(){ + + // Settings + //////////////////////////////////////////////////////////////////////////// + + // if set to true, you must explicitly call hints.follow(), otherwise it will + // follow the link if there is only one matching result + var requireReturn = true; + + // Case sensitivity flag + var matchCase = "i"; + + // For case sensitive matching, uncomment: + // var matchCase = ""; + + + var uzblid = 'uzbl_hint'; + var uzblclass = 'uzbl_highlight'; + var uzblclassfirst = 'uzbl_h_first'; + var doc = document; + var visible = []; + var hintdiv; + + this.set = hint; + this.follow = follow; + this.keyPressHandler = keyPressHandler; + + function elementPosition(el) { + var up = el.offsetTop; + var left = el.offsetLeft; var width = el.offsetWidth; + var height = el.offsetHeight; + + while (el.offsetParent) { + el = el.offsetParent; + up += el.offsetTop; + left += el.offsetLeft; + } + return {up: up, left: left, width: width, height: height}; + } + + function elementInViewport(p) { + return (p.up < window.pageYOffset + window.innerHeight && + p.left < window.pageXOffset + window.innerWidth && + (p.up + p.height) > window.pageYOffset && + (p.left + p.width) > window.pageXOffset); + } + + function isVisible(el) { + if (el == doc) { return true; } + if (!el) { return false; } + if (!el.parentNode) { return false; } + if (el.style) { + if (el.style.display == 'none') { + return false; + } + if (el.style.visibility == 'hidden') { + return false; + } + } + return isVisible(el.parentNode); + } + + // the vimperator defaults minus the xhtml elements, since it gave DOM errors + var hintable = " //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select"; + + function Matcher(str){ + var numbers = str.replace(/[^\d]/g,""); + var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,matchCase)}); + this.test = test; + this.toString = toString; + this.numbers = numbers; + function matchAgainst(element){ + if(element.node.nodeName == "INPUT"){ + return element.node.value; + } else { + return element.node.textContent; + } + } + function test(element) { + // test all the regexp + var item = matchAgainst(element); + return words.every(function (regex) { return item.match(regex)}); + } + } + + function HintElement(node,pos){ + + this.node = node; + this.isHinted = false; + this.position = pos; + this.num = 0; + + this.addHint = function (labelNum) { + // TODO: fix uzblclassfirst + if(!this.isHinted){ + this.node.className += " " + uzblclass; + } + this.isHinted = true; + + // create hint + var hintNode = doc.createElement('div'); + hintNode.name = uzblid; + hintNode.innerText = labelNum; + hintNode.style.left = this.position.left + 'px'; + hintNode.style.top = this.position.up + 'px'; + hintNode.style.position = "absolute"; + doc.body.firstChild.appendChild(hintNode); + + } + this.removeHint = function(){ + if(this.isHinted){ + var s = (this.num)?uzblclassfirst:uzblclass; + this.node.className = this.node.className.replace(new RegExp(" "+s,"g"),""); + this.isHinted = false; + } + } + } + + function createHintDiv(){ + var hintdiv = doc.getElementById(uzblid); + if(hintdiv){ + hintdiv.parentNode.removeChild(hintdiv); + } + hintdiv = doc.createElement("div"); + hintdiv.setAttribute('id',uzblid); + doc.body.insertBefore(hintdiv,doc.body.firstChild); + return hintdiv; + } + + function init(){ + // WHAT? + doc.body.setAttribute("onkeyup","hints.keyPressHandler(event)"); + hintdiv = createHintDiv(); + visible = []; + + var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null); + for (var i = 0;i&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch + # show tags as well + goto=`$DMENU $COLORS < $file | awk '{print $1}'` +else + DMENU="dmenu -i" + # because they are all after each other, just show the url, not their tags. + goto=`awk '{print $1}' $file | $DMENU $COLORS` +fi + +#[ -n "$goto" ] && echo "uri $goto" > $4 +[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/scripts/load_url_from_history.sh b/examples/data/scripts/load_url_from_history.sh new file mode 100755 index 0000000..62e02ac --- /dev/null +++ b/examples/data/scripts/load_url_from_history.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +history_file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history +[ -r "$history_file" ] || exit 1 + +# choose from all entries, sorted and uniqued +# goto=`awk '{print $3}' $history_file | sort -u | dmenu -i` +COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" +if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]'; +then + DMENU="dmenu -i -xs -rs -l 10" # vertical patch + # choose an item in reverse order, showing also the date and page titles + # pick the last field from the first 3 fields. this way you can pick a url (prefixed with date & time) or type just a new url. + goto=`tac $history_file | $DMENU $COLORS | cut -d ' ' -f -3 | awk '{print $NF}'` +else + DMENU="dmenu -i" + # choose from all entries (no date or title), the first one being current url, and after that all others, sorted and uniqued, in ascending order + current=`tail -n 1 $history_file | awk '{print $3}'`; + goto=`(echo $current; awk '{print $3}' $history_file | grep -v "^$current\$" \ + | sort -u) | $DMENU $COLORS` +fi + +[ -n "$goto" ] && echo "uri $goto" > $4 +#[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/scripts/scheme.py b/examples/data/scripts/scheme.py new file mode 100755 index 0000000..0916466 --- /dev/null +++ b/examples/data/scripts/scheme.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import os, subprocess, sys, urlparse + +def detach_open(cmd): + # Thanks to the vast knowledge of Laurence Withers (lwithers) and this message: + # http://mail.python.org/pipermail/python-list/2006-November/587523.html + if not os.fork(): + null = os.open(os.devnull,os.O_WRONLY) + for i in range(3): os.dup2(null,i) + os.close(null) + subprocess.Popen(cmd) + print 'USED' + +if __name__ == '__main__': + uri = sys.argv[8] + u = urlparse.urlparse(uri) + if u.scheme == 'mailto': + detach_open(['xterm', '-e', 'mail', u.path]) + elif u.scheme == 'xmpp': + # Someone check for safe arguments to gajim-remote + detach_open(['gajim-remote', 'open_chat', uri]) + elif u.scheme == 'git': + detach_open(['git', 'clone', '--', uri], cwd=os.path.expanduser('~/src')) diff --git a/examples/data/scripts/scroll-percentage.js b/examples/data/scripts/scroll-percentage.js new file mode 100644 index 0000000..c9a51aa --- /dev/null +++ b/examples/data/scripts/scroll-percentage.js @@ -0,0 +1,68 @@ +// VIM ruler style scroll message +(function() { + var run = Uzbl.run; + var update_message = function() { + var innerHeight = window.innerHeight; + var scrollY = window.scrollY; + var height = document.height; + var message; + + if (UzblZoom.type === "full") { + var zoom_level = UzblZoom.level; + innerHeight = Math.ceil(innerHeight * zoom_level); + scrollY = Math.ceil(scrollY * zoom_level); + height -= 1; + } + + if (! height) { + message = ""; + } + else if (height <= innerHeight) { + message = run("print @scroll_all_indicator") || "All"; + } + else if (scrollY === 0) { + message = run("print @scroll_top_indicator") || "Top"; + } + else if (scrollY + innerHeight >= height) { + message = run("print @scroll_bottom_indicator") || "Bot"; + } + else { + var percentage = Math.round(scrollY / (height - innerHeight) * 100); + message = percentage + "%"; + } + run("set scroll_message=" + message); + }; + + self.UzblZoom = { + get level() { + return Number(run("print @zoom_level")) || 1; + }, + set level(level) { + if (typeof level === "number" && level > 0) { + run("set zoom_level = " + level); + update_message(); + } + }, + get type() { + return run("print @zoom_type") || "text"; + }, + set type(type) { + if ((type === "text" || type === "full") && this.type != type) { + run("toggle_zoom_type"); + run("set zoom_type = " + type); + update_message(); + } + }, + toggle_type: function() { + this.type = (this.type === "text" ? "full" : "text"); + } + }; + + window.addEventListener("DOMContentLoaded", update_message, false); + window.addEventListener("load", update_message, false); + window.addEventListener("resize", update_message, false); + window.addEventListener("scroll", update_message, false); + update_message(); +})(); + +// vim: set noet ff=unix diff --git a/examples/data/scripts/session.sh b/examples/data/scripts/session.sh new file mode 100755 index 0000000..1059b5e --- /dev/null +++ b/examples/data/scripts/session.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Very simple session manager for uzbl-browser. When called with "endsession" as the +# argument, it'll backup $sessionfile, look for fifos in $fifodir and +# instruct each of them to store their current url in $sessionfile and +# terminate themselves. Run with "launch" as the argument and an instance of +# uzbl-browser will be launched for each stored url. "endinstance" is used internally +# and doesn't need to be called manually at any point. +# Add a line like 'bind quit = /path/to/session.sh endsession' to your config + +[ -d ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl ] || exit 1 +scriptfile=$0 # this script +sessionfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/browser-session # the file in which the "session" (i.e. urls) are stored +configfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/config # uzbl configuration file +UZBL="uzbl-browser -c $configfile" # add custom flags and whatever here. + +fifodir=/tmp # remember to change this if you instructed uzbl to put its fifos elsewhere +thisfifo="$4" +act="$8" +url="$6" + +if [ "$act." = "." ]; then + act="$1" +fi + + +case $act in + "launch" ) + urls=`cat $sessionfile` + if [ "$urls." = "." ]; then + $UZBL + else + for url in $urls; do + $UZBL --uri "$url" & + done + fi + exit 0 + ;; + + "endinstance" ) + if [ "$url" != "(null)" ]; then + echo "$url" >> $sessionfile; + fi + echo "exit" > "$thisfifo" + ;; + + "endsession" ) + mv "$sessionfile" "$sessionfile~" + for fifo in $fifodir/uzbl_fifo_*; do + if [ "$fifo" != "$thisfifo" ]; then + echo "spawn $scriptfile endinstance" > "$fifo" + fi + done + echo "spawn $scriptfile endinstance" > "$thisfifo" + ;; + + * ) echo "session manager: bad action" + echo "Usage: $scriptfile [COMMAND] where commands are:" + echo " launch - Restore a saved session or start a new one" + echo " endsession - Quit the running session. Must be called from uzbl" + ;; +esac diff --git a/examples/data/scripts/uzbl-cookie-daemon b/examples/data/scripts/uzbl-cookie-daemon new file mode 100755 index 0000000..fde8b8e --- /dev/null +++ b/examples/data/scripts/uzbl-cookie-daemon @@ -0,0 +1,664 @@ +#!/usr/bin/env python + +# The Python Cookie Daemon for Uzbl. +# Copyright (c) 2009, Tom Adams +# Copyright (c) 2009, Dieter Plaetinck +# Copyright (c) 2009, Mason Larobina +# Copyright (c) 2009, Michael Fiano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +''' +The Python Cookie Daemon +======================== + +This daemon is a re-write of the original cookies.py script found in uzbl's +master branch. This script provides more functionality than the original +cookies.py by adding numerous command line options to specify different cookie +jar locations, socket locations, verbose output, etc. This functionality is +very useful as it allows you to run multiple daemons at once serving cookies +to different groups of uzbl instances as required. + +Keeping up to date +================== + +Check the cookie daemon uzbl-wiki page for more information on where to +find the latest version of the cookie_daemon.py + + http://www.uzbl.org/wiki/cookie_daemon.py + +Command line options +==================== + +Use the following command to get a full list of the cookie_daemon.py command +line options: + + ./cookie_daemon.py --help + +Talking with uzbl +================= + +In order to get uzbl to talk to a running cookie daemon you add the following +to your uzbl config: + + set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket + +Or if you prefer using the $HOME variable: + + set cookie_handler = talk_to_socket $HOME/.cache/uzbl/cookie_daemon_socket + +Todo list +========= + + - Use a pid file to make force killing a running daemon possible. + +Reporting bugs / getting help +============================= + +The best way to report bugs and or get help with the cookie daemon is to +contact the maintainers it the #uzbl irc channel found on the Freenode IRC +network (irc.freenode.org). +''' + +import cookielib +import os +import sys +import urllib2 +import select +import socket +import time +import atexit +from traceback import print_exc +from signal import signal, SIGTERM +from optparse import OptionParser +from os.path import join + +try: + import cStringIO as StringIO + +except ImportError: + import StringIO + + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return join(os.environ['HOME'], default) + +# Setup xdg paths. +CACHE_DIR = join(xdghome('CACHE', '.cache/'), 'uzbl/') +DATA_DIR = join(xdghome('DATA', '.local/share/'), 'uzbl/') +CONFIG_DIR = join(xdghome('CONFIG', '.config/'), 'uzbl/') + +# Ensure data paths exist. +for path in [CACHE_DIR, DATA_DIR, CONFIG_DIR]: + if not os.path.exists(path): + os.makedirs(path) + +# Default config +config = { + + # Default cookie jar, whitelist, and daemon socket locations. + 'cookie_jar': join(DATA_DIR, 'cookies.txt'), + 'cookie_whitelist': join(CONFIG_DIR, 'cookie_whitelist'), + 'cookie_socket': join(CACHE_DIR, 'cookie_daemon_socket'), + + # Don't use a cookie whitelist policy by default. + 'use_whitelist': False, + + # Time out after x seconds of inactivity (set to 0 for never time out). + # WARNING: Do not use this option if you are manually launching the daemon. + 'daemon_timeout': 0, + + # Daemonise by default. + 'daemon_mode': True, + + # Optionally print helpful debugging messages to the terminal. + 'verbose': False, + +} # End of config dictionary. + + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + + +_SCRIPTNAME = os.path.basename(sys.argv[0]) +def echo(msg): + '''Prints only if the verbose flag has been set.''' + + if config['verbose']: + sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) + + +def error(msg): + '''Prints error message and exits.''' + + sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) + sys.exit(1) + + +def mkbasedir(filepath): + '''Create the base directories of the file in the file-path if the dirs + don't exist.''' + + dirname = os.path.dirname(filepath) + if not os.path.exists(dirname): + echo("creating dirs: %r" % dirname) + os.makedirs(dirname) + + +def daemon_running(cookie_socket): + '''Check if another process (hopefully a cookie_daemon.py) is listening + on the cookie daemon socket. If another process is found to be + listening on the socket exit the daemon immediately and leave the + socket alone. If the connect fails assume the socket has been abandoned + and delete it (to be re-created in the create socket function).''' + + if not os.path.exists(cookie_socket): + return False + + if os.path.isfile(cookie_socket): + raise Exception("regular file at %r is not a socket" % cookie_socket) + + + if os.path.isdir(cookie_socket): + raise Exception("directory at %r is not a socket" % cookie_socket) + + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + sock.connect(cookie_socket) + sock.close() + echo("detected daemon listening on %r" % cookie_socket) + return True + + except socket.error: + # Failed to connect to cookie_socket so assume it has been + # abandoned by another cookie daemon process. + if os.path.exists(cookie_socket): + echo("deleting abandoned socket at %r" % cookie_socket) + os.remove(cookie_socket) + + return False + + +def send_command(cookie_socket, cmd): + '''Send a command to a running cookie daemon.''' + + if not daemon_running(cookie_socket): + return False + + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + sock.connect(cookie_socket) + sock.send(cmd) + sock.close() + echo("sent command %r to %r" % (cmd, cookie_socket)) + return True + + except socket.error: + print_exc() + error("failed to send message %r to %r" % (cmd, cookie_socket)) + return False + + +def kill_daemon(cookie_socket): + '''Send the "EXIT" command to running cookie_daemon.''' + + if send_command(cookie_socket, "EXIT"): + # Now ensure the cookie_socket is cleaned up. + start = time.time() + while os.path.exists(cookie_socket): + time.sleep(0.1) + if (time.time() - start) > 5: + error("force deleting socket %r" % cookie_socket) + os.remove(cookie_socket) + return + + echo("stopped daemon listening on %r"% cookie_socket) + + else: + if os.path.exists(cookie_socket): + os.remove(cookie_socket) + echo("removed abandoned/broken socket %r" % cookie_socket) + + +def daemonize(): + '''Daemonize the process using the Stevens' double-fork magic.''' + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #1 failed") + sys.exit(1) + + os.chdir('/') + os.setsid() + os.umask(0) + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #2 failed") + sys.exit(1) + + sys.stdout.flush() + sys.stderr.flush() + + devnull = '/dev/null' + stdin = file(devnull, 'r') + stdout = file(devnull, 'a+') + stderr = file(devnull, 'a+', 0) + + os.dup2(stdin.fileno(), sys.stdin.fileno()) + os.dup2(stdout.fileno(), sys.stdout.fileno()) + os.dup2(stderr.fileno(), sys.stderr.fileno()) + + +class CookieMonster: + '''The uzbl cookie daemon class.''' + + def __init__(self): + '''Initialise class variables.''' + + self.server_socket = None + self.jar = None + self.last_request = time.time() + self._running = False + + + def run(self): + '''Start the daemon.''' + + # The check healthy function will exit if another daemon is detected + # listening on the cookie socket and remove the abandoned socket if + # there isnt. + if os.path.exists(config['cookie_socket']): + if daemon_running(config['cookie_socket']): + sys.exit(1) + + # Create cookie daemon socket. + self.create_socket() + + # Daemonize process. + if config['daemon_mode']: + echo("entering daemon mode") + daemonize() + + # Register a function to cleanup on exit. + atexit.register(self.quit) + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) + + # Create cookie jar object from file. + self.open_cookie_jar() + + # Create a way to exit nested loops by setting a running flag. + self._running = True + + while self._running: + try: + # Enter main listen loop. + self.listen() + + except KeyboardInterrupt: + self._running = False + print + + except socket.error: + print_exc() + + except: + # Clean up + self.del_socket() + + # Raise exception + raise + + # Always delete the socket before calling create again. + self.del_socket() + # Create cookie daemon socket. + self.create_socket() + + + def load_whitelist(self): + '''Load the cookie jar whitelist policy.''' + + cookie_whitelist = config['cookie_whitelist'] + + if cookie_whitelist: + mkbasedir(cookie_whitelist) + + # Create cookie whitelist file if it does not exist. + if not os.path.exists(cookie_whitelist): + open(cookie_whitelist, 'w').close() + + # Read cookie whitelist file into list. + file = open(cookie_whitelist,'r') + domain_list = [line.rstrip('\n') for line in file] + file.close() + + # Define policy of allowed domains + policy = cookielib.DefaultCookiePolicy(allowed_domains=domain_list) + self.jar.set_policy(policy) + + # Save the last modified time of the whitelist. + self._whitelistmtime = os.stat(cookie_whitelist).st_mtime + + + def open_cookie_jar(self): + '''Open the cookie jar.''' + + cookie_jar = config['cookie_jar'] + cookie_whitelist = config['cookie_whitelist'] + + if cookie_jar: + mkbasedir(cookie_jar) + + # Create cookie jar object from file. + self.jar = cookielib.MozillaCookieJar(cookie_jar) + + # Load cookie whitelist policy. + if config['use_whitelist']: + self.load_whitelist() + + if cookie_jar: + try: + # Attempt to load cookies from the cookie jar. + self.jar.load(ignore_discard=True) + + # Ensure restrictive permissions are set on the cookie jar + # to prevent other users on the system from hi-jacking your + # authenticated sessions simply by copying your cookie jar. + os.chmod(cookie_jar, 0600) + + except: + pass + + + def reload_whitelist(self): + '''Reload the cookie whitelist.''' + + cookie_whitelist = config['cookie_whitelist'] + if os.path.exists(cookie_whitelist): + echo("reloading whitelist %r" % cookie_whitelist) + self.open_cookie_jar() + + + def create_socket(self): + '''Create AF_UNIX socket for communication with uzbl instances.''' + + cookie_socket = config['cookie_socket'] + mkbasedir(cookie_socket) + + self.server_socket = socket.socket(socket.AF_UNIX, + socket.SOCK_SEQPACKET) + + self.server_socket.bind(cookie_socket) + + # Set restrictive permissions on the cookie socket to prevent other + # users on the system from data-mining your cookies. + os.chmod(cookie_socket, 0600) + + + def listen(self): + '''Listen for incoming cookie PUT and GET requests.''' + + daemon_timeout = config['daemon_timeout'] + echo("listening on %r" % config['cookie_socket']) + + while self._running: + # This line tells the socket how many pending incoming connections + # to enqueue at once. Raising this number may or may not increase + # performance. + self.server_socket.listen(1) + + if bool(select.select([self.server_socket], [], [], 1)[0]): + client_socket, _ = self.server_socket.accept() + self.handle_request(client_socket) + self.last_request = time.time() + client_socket.close() + continue + + if daemon_timeout: + # Checks if the daemon has been idling for too long. + idle = time.time() - self.last_request + if idle > daemon_timeout: + self._running = False + + + def handle_request(self, client_socket): + '''Connection made, now to serve a cookie PUT or GET request.''' + + # Receive cookie request from client. + data = client_socket.recv(8192) + if not data: + return + + # Cookie argument list in packet is null separated. + argv = data.split("\0") + action = argv[0].upper().strip() + + # Catch the EXIT command sent to kill running daemons. + if action == "EXIT": + self._running = False + return + + # Catch whitelist RELOAD command. + elif action == "RELOAD": + self.reload_whitelist() + return + + # Return if command unknown. + elif action not in ['GET', 'PUT']: + error("unknown command %r." % argv) + return + + # Determine whether or not to print cookie data to terminal. + print_cookie = (config['verbose'] and not config['daemon_mode']) + if print_cookie: + print ' '.join(argv[:4]) + + uri = urllib2.urlparse.ParseResult( + scheme=argv[1], + netloc=argv[2], + path=argv[3], + params='', + query='', + fragment='').geturl() + + req = urllib2.Request(uri) + + if action == "GET": + self.jar.add_cookie_header(req) + if req.has_header('Cookie'): + cookie = req.get_header('Cookie') + client_socket.send(cookie) + if print_cookie: + print cookie + + else: + client_socket.send("\0") + + elif action == "PUT": + cookie = argv[4] if len(argv) > 3 else None + if print_cookie: + print cookie + + self.put_cookie(req, cookie) + + if print_cookie: + print + + + def put_cookie(self, req, cookie=None): + '''Put a cookie in the cookie jar.''' + + hdr = urllib2.httplib.HTTPMessage(\ + StringIO.StringIO('Set-Cookie: %s' % cookie)) + res = urllib2.addinfourl(StringIO.StringIO(), hdr, + req.get_full_url()) + self.jar.extract_cookies(res, req) + if config['cookie_jar']: + self.jar.save(ignore_discard=True) + + + def del_socket(self): + '''Remove the cookie_socket file on exit. In a way the cookie_socket + is the daemons pid file equivalent.''' + + if self.server_socket: + try: + self.server_socket.close() + + except: + pass + + self.server_socket = None + + cookie_socket = config['cookie_socket'] + if os.path.exists(cookie_socket): + echo("deleting socket %r" % cookie_socket) + os.remove(cookie_socket) + + + def quit(self): + '''Called on exit to make sure all loose ends are tied up.''' + + self.del_socket() + sys.exit(0) + + +def main(): + '''Main function.''' + + # Define command line parameters. + usage = "usage: %prog [options] {start|stop|restart|reload}" + parser = OptionParser(usage=usage) + parser.add_option('-n', '--no-daemon', dest='no_daemon', + action='store_true', help="don't daemonise the process.") + + parser.add_option('-v', '--verbose', dest="verbose", + action='store_true', help="print verbose output.") + + parser.add_option('-t', '--daemon-timeout', dest='daemon_timeout', + action="store", metavar="SECONDS", help="shutdown the daemon after x "\ + "seconds inactivity. WARNING: Do not use this when launching the "\ + "cookie daemon manually.") + + parser.add_option('-s', '--cookie-socket', dest="cookie_socket", + metavar="SOCKET", help="manually specify the socket location.") + + parser.add_option('-j', '--cookie-jar', dest='cookie_jar', + metavar="FILE", help="manually specify the cookie jar location.") + + parser.add_option('-m', '--memory', dest='memory', action='store_true', + help="store cookies in memory only - do not write to disk") + + parser.add_option('-u', '--use-whitelist', dest='usewhitelist', + action='store_true', help="use cookie whitelist policy") + + parser.add_option('-w', '--cookie-whitelist', dest='whitelist', + action='store', help="manually specify whitelist location", + metavar='FILE') + + # Parse the command line arguments. + (options, args) = parser.parse_args() + + expand = lambda p: os.path.realpath(os.path.expandvars(p)) + + initcommands = ['start', 'stop', 'restart', 'reload'] + for arg in args: + if arg not in initcommands: + error("unknown argument %r" % args[0]) + sys.exit(1) + + if len(args) > 1: + error("the daemon only accepts one {%s} action at a time." + % '|'.join(initcommands)) + sys.exit(1) + + if len(args): + action = args[0] + + else: + action = "start" + + if options.no_daemon: + config['daemon_mode'] = False + + if options.cookie_socket: + config['cookie_socket'] = expand(options.cookie_socket) + + if options.cookie_jar: + config['cookie_jar'] = expand(options.cookie_jar) + + if options.memory: + config['cookie_jar'] = None + + if options.whitelist: + config['cookie_whitelist'] = expand(options.whitelist) + + if options.whitelist or options.usewhitelist: + config['use_whitelist'] = True + + if options.daemon_timeout: + try: + config['daemon_timeout'] = int(options.daemon_timeout) + + except ValueError: + error("expected int argument for -t, --daemon-timeout") + + # Expand $VAR's in config keys that relate to paths. + for key in ['cookie_socket', 'cookie_jar', 'cookie_whitelist']: + if config[key]: + config[key] = os.path.expandvars(config[key]) + + if options.verbose: + config['verbose'] = True + import pprint + sys.stderr.write("%s\n" % pprint.pformat(config)) + + # It would be better if we didn't need to start this python process just + # to send a command to the socket, but unfortunately socat doesn't seem + # to support SEQPACKET. + if action == "reload": + send_command(config['cookie_socket'], "RELOAD") + + if action in ['stop', 'restart']: + kill_daemon(config['cookie_socket']) + + if action in ['start', 'restart']: + CookieMonster().run() + + +if __name__ == "__main__": + main() diff --git a/examples/data/scripts/uzbl-event-manager b/examples/data/scripts/uzbl-event-manager new file mode 100755 index 0000000..99b215a --- /dev/null +++ b/examples/data/scripts/uzbl-event-manager @@ -0,0 +1,833 @@ +#!/usr/bin/env python + +# Event Manager for Uzbl +# Copyright (c) 2009, Mason Larobina +# Copyright (c) 2009, Dieter Plaetinck +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +''' + +E V E N T _ M A N A G E R . P Y +=============================== + +Event manager for uzbl written in python. + +''' + +import imp +import os +import sys +import re +import socket +import pprint +import time +import atexit +from select import select +from signal import signal, SIGTERM +from optparse import OptionParser +from traceback import print_exc +from functools import partial + + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return os.path.join(os.environ['HOME'], default) + + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +# `make install` will put the correct value here for your system +PREFIX = '/usr/local/' + +# Setup xdg paths. +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') +CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') + +# Event manager config dictionary. This is not to be confused with the config +# dict that tracks variables in the uzbl instance. +CONFIG = { + 'verbose': False, + 'daemon_mode': True, + 'auto_close': False, + + 'plugins_load': [], + 'plugins_ignore': [], + + 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'), + os.path.join(PREFIX, 'share/uzbl/examples/data/plugins/')], + + 'server_socket': os.path.join(CACHE_DIR, 'event_daemon'), + 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'), +} + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + + +# Define some globals. +SCRIPTNAME = os.path.basename(sys.argv[0]) +FINDSPACES = re.compile("\s+") + + +class ArgumentError(Exception): + pass + + +def echo(msg): + '''Prints only if the verbose flag has been set.''' + + if CONFIG['verbose']: + sys.stdout.write("%s: %s\n" % (SCRIPTNAME, msg)) + + +def error(msg): + '''Prints error messages to stderr.''' + + sys.stderr.write("%s: error: %s\n" % (SCRIPTNAME, msg)) + + +def counter(): + '''Generate unique object id's.''' + + i = 0 + while True: + i += 1 + yield i + + +def find_plugins(plugin_dirs): + '''Find all event manager plugins in the plugin dirs and return a + dictionary of {'plugin-name.py': '/full/path/to/plugin-name.py', ...}''' + + plugins = {} + + for plugin_dir in plugin_dirs: + plugin_dir = os.path.realpath(os.path.expandvars(plugin_dir)) + if not os.path.isdir(plugin_dir): + continue + + for filename in os.listdir(plugin_dir): + if not filename.lower().endswith('.py'): + continue + + path = os.path.join(plugin_dir, filename) + if not os.path.isfile(path): + continue + + if filename not in plugins: + plugins[filename] = plugin_dir + + return plugins + + +def load_plugins(plugin_dirs, load=None, ignore=None): + '''Load event manager plugins found in the plugin_dirs.''' + + load = [] if load is None else load + ignore = [] if ignore is None else ignore + + # Find the plugins in the plugin_dirs. + found = find_plugins(plugin_dirs) + + if load: + # Ignore anything not in the load list. + for plugin in found.keys(): + if plugin not in load: + del found[plugin] + + if ignore: + # Ignore anything in the ignore list. + for plugin in found.keys(): + if plugin in ignore: + del found[plugin] + + # Print plugin list to be loaded. + pprint.pprint(found) + + loaded = {} + # Load all found plugins into the loaded dict. + for (filename, plugin_dir) in found.items(): + name = filename[:-3] + info = imp.find_module(name, [plugin_dir]) + plugin = imp.load_module(name, *info) + loaded[(plugin_dir, filename)] = plugin + + return loaded + + +def daemonize(): + '''Daemonize the process using the Stevens' double-fork magic.''' + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #1 failed") + sys.exit(1) + + os.chdir('/') + os.setsid() + os.umask(0) + + try: + if os.fork(): + os._exit(0) + + except OSError: + print_exc() + sys.stderr.write("fork #2 failed") + sys.exit(1) + + sys.stdout.flush() + sys.stderr.flush() + + devnull = '/dev/null' + stdin = file(devnull, 'r') + stdout = file(devnull, 'a+') + stderr = file(devnull, 'a+', 0) + + os.dup2(stdin.fileno(), sys.stdin.fileno()) + os.dup2(stdout.fileno(), sys.stdout.fileno()) + os.dup2(stderr.fileno(), sys.stderr.fileno()) + + +def make_dirs(path): + '''Make all basedirs recursively as required.''' + + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + +def make_pid_file(pid_file): + '''Make pid file at given pid_file location.''' + + make_dirs(pid_file) + fileobj = open(pid_file, 'w') + fileobj.write('%d' % os.getpid()) + fileobj.close() + + +def del_pid_file(pid_file): + '''Delete pid file at given pid_file location.''' + + if os.path.isfile(pid_file): + os.remove(pid_file) + + +def get_pid(pid_file): + '''Read pid from pid_file.''' + + try: + fileobj = open(pid_file, 'r') + pid = int(fileobj.read()) + fileobj.close() + return pid + + except IOError, ValueError: + print_exc() + return None + + +def pid_running(pid): + '''Returns True if a process with the given pid is running.''' + + try: + os.kill(pid, 0) + + except OSError: + return False + + else: + return True + + +def term_process(pid): + '''Send a SIGTERM signal to the process with the given pid.''' + + if not pid_running(pid): + return False + + os.kill(pid, SIGTERM) + + start = time.time() + while True: + if not pid_running(pid): + return True + + if time.time() - start > 5: + raise OSError('failed to stop process with pid: %d' % pid) + + time.sleep(0.25) + + +def parse_msg(uzbl, msg): + '''Parse an incoming msg from a uzbl instance. All non-event messages + will be printed here and not be passed to the uzbl instance event + handler function.''' + + if not msg: + return + + cmd = FINDSPACES.split(msg, 3) + if not cmd or cmd[0] != 'EVENT': + # Not an event message. + print '---', msg + return + + while len(cmd) < 4: + cmd.append('') + + event, args = cmd[2], cmd[3] + if not event: + return + + try: + uzbl.event(event, args) + + except: + print_exc() + + +class EventHandler(object): + + nexthid = counter().next + + def __init__(self, event, handler, *args, **kargs): + if not callable(handler): + raise ArgumentError("EventHandler object requires a callable " + "object function for the handler argument not: %r" % handler) + + self.function = handler + self.args = args + self.kargs = kargs + self.event = event + self.hid = self.nexthid() + + + def __repr__(self): + args = ["event=%s" % self.event, "hid=%d" % self.hid, + "function=%r" % self.function] + + if self.args: + args.append("args=%r" % self.args) + + if self.kargs: + args.append("kargs=%r" % self.kargs) + + return "" % ', '.join(args) + + +class UzblInstance(object): + + # Give all plugins access to the main config dict. + config = CONFIG + + def __init__(self, parent, client_socket): + + # Internal variables. + self.exports = {} + self.handlers = {} + self.parent = parent + self.client_socket = client_socket + + self.depth = 0 + self.buffer = '' + self.pid = None + + # Call the init function in every plugin. The init function in each + # plugin is where that plugin connects functions to events and exports + # functions to the uzbl object. + for plugin in self.parent['plugins'].values(): + try: + plugin.init(self) + + except: + raise + + + def send(self, msg): + '''Send a command to the uzbl instance via the socket file.''' + + msg = msg.strip() + if self.client_socket: + print '%s<-- %s' % (' ' * self.depth, msg) + self.client_socket.send(("%s\n" % msg).encode('utf-8')) + + else: + print '%s!-- %s' % (' ' * self.depth, msg) + + + def export(self, name, function): + '''Export `function(uzbl, *args, ..)` inside a plugin to the uzbl + object like so `uzbl.function(*args, ..)`. This will allow other + plugins to call functions inside the current plugin (which is currently + calling this function) via the uzbl object.''' + + self.__dict__.__setitem__(name, partial(function, self)) + + + def export_dict(self, export_dict): + '''Export multiple (name, function)'s at once inside a dict of the + form `{name1: function1, name2: function2, ...}`.''' + + for (name, function) in export_dict.items(): + self.export(name, function) + + + def connect(self, event, handler, *args, **kargs): + '''Connect a uzbl event with a handler. Handlers can either be a + function or a uzbl command string.''' + + event = event.upper().strip() + assert event and ' ' not in event + + if event not in self.handlers.keys(): + self.handlers[event] = [] + + handlerobj = EventHandler(event, handler, *args, **kargs) + self.handlers[event].append(handlerobj) + print handlerobj + + + def connect_dict(self, connect_dict): + '''Connect a dictionary comprising of {"EVENT_NAME": handler, ..} to + the event handler stack. + + If you need to supply args or kargs to an event use the normal connect + function.''' + + for (event, handler) in connect_dict.items(): + self.connect(event, handler) + + + def remove_by_id(self, hid): + '''Remove connected event handler by unique handler id.''' + + for (event, handlers) in self.handlers.items(): + for handler in list(handlers): + if hid != handler.hid: + continue + + echo("removed %r" % handler) + handlers.remove(handler) + return + + echo('unable to find & remove handler with id: %d' % hid) + + + def remove(self, handler): + '''Remove connected event handler.''' + + for (event, handlers) in self.handlers.items(): + if handler in handlers: + echo("removed %r" % handler) + handlers.remove(handler) + return + + echo('unable to find & remove handler: %r' % handler) + + + def exec_handler(self, handler, *args, **kargs): + '''Execute event handler function.''' + + args += handler.args + kargs = dict(handler.kargs.items()+kargs.items()) + handler.function(self, *args, **kargs) + + + def event(self, event, *args, **kargs): + '''Raise an event.''' + + event = event.upper() + elems = [event,] + if args: elems.append(unicode(args)) + if kargs: elems.append(unicode(kargs)) + print "%s--> %s" % (' ' * self.depth, ' '.join(elems)) + + if event == "INSTANCE_START" and args: + self.pid = int(args[0]) + + if event not in self.handlers: + return + + for handler in self.handlers[event]: + self.depth += 1 + try: + self.exec_handler(handler, *args, **kargs) + + except: + print_exc() + + self.depth -= 1 + + + def close(self): + '''Close the client socket and clean up.''' + + try: + self.client_socket.close() + + except: + pass + + for (name, plugin) in self.parent['plugins'].items(): + if hasattr(plugin, 'cleanup'): + plugin.cleanup(self) + + +class UzblEventDaemon(dict): + def __init__(self): + + # Init variables and dict keys. + dict.__init__(self, {'uzbls': {}}) + self.running = None + self.server_socket = None + self.socket_location = None + + # Register that the event daemon server has started by creating the + # pid file. + make_pid_file(CONFIG['pid_file']) + + # Register a function to clean up the socket and pid file on exit. + atexit.register(self.quit) + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) + + # Load plugins, first-build of the plugins may be a costly operation. + self['plugins'] = load_plugins(CONFIG['plugin_dirs'], + CONFIG['plugins_load'], CONFIG['plugins_ignore']) + + + def _create_server_socket(self): + '''Create the event manager daemon socket for uzbl instance duplex + communication.''' + + server_socket = CONFIG['server_socket'] + server_socket = os.path.realpath(os.path.expandvars(server_socket)) + self.socket_location = server_socket + + # Delete socket if it exists. + if os.path.exists(server_socket): + os.remove(server_socket) + + self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.server_socket.bind(server_socket) + self.server_socket.listen(5) + + + def _close_server_socket(self): + '''Close and delete the server socket.''' + + try: + self.server_socket.close() + self.server_socket = None + + if os.path.exists(self.socket_location): + os.remove(self.socket_location) + + except: + pass + + + def run(self): + '''Main event daemon loop.''' + + # Create event daemon socket. + self._create_server_socket() + echo('listening on: %s' % self.socket_location) + + if CONFIG['daemon_mode']: + echo('entering daemon mode.') + daemonize() + # The pid has changed so update the pid file. + make_pid_file(CONFIG['pid_file']) + + # Now listen for incoming connections and or data. + self.listen() + + # Clean up. + self.quit() + + + def listen(self): + '''Accept incoming connections and constantly poll instance sockets + for incoming data.''' + + self.running = True + while self.running: + + sockets = [self.server_socket] + self['uzbls'].keys() + + reads, _, errors = select(sockets, [], sockets, 1) + + if self.server_socket in reads: + self.accept_connection() + reads.remove(self.server_socket) + + for client in reads: + self.read_socket(client) + + for client in errors: + error('Unknown error on socket: %r' % client) + self.close_connection(client) + + + def read_socket(self, client): + '''Read data from an instance socket and pass to the uzbl objects + event handler function.''' + + uzbl = self['uzbls'][client] + try: + raw = unicode(client.recv(8192), 'utf-8', 'ignore') + + except: + print_exc() + raw = None + + if not raw: + # Read null byte, close socket. + return self.close_connection(client) + + uzbl.buffer += raw + msgs = uzbl.buffer.split('\n') + uzbl.buffer = msgs.pop() + + for msg in msgs: + try: + parse_msg(uzbl, msg.strip()) + + except: + print_exc() + + + def accept_connection(self): + '''Accept incoming connection to the server socket.''' + + client_socket = self.server_socket.accept()[0] + + uzbl = UzblInstance(self, client_socket) + self['uzbls'][client_socket] = uzbl + + + def close_connection(self, client): + '''Clean up after instance close.''' + + try: + if client in self['uzbls']: + uzbl = self['uzbls'][client] + uzbl.close() + del self['uzbls'][client] + + except: + print_exc() + + if not len(self['uzbls']) and CONFIG['auto_close']: + echo('auto closing event manager.') + self.running = False + + + def quit(self): + '''Close all instance socket objects, server socket and delete the + pid file.''' + + echo('shutting down event manager.') + + for client in self['uzbls'].keys(): + self.close_connection(client) + + echo('unlinking: %r' % self.socket_location) + self._close_server_socket() + + echo('deleting pid file: %r' % CONFIG['pid_file']) + del_pid_file(CONFIG['pid_file']) + + +def stop_action(): + '''Stop the event manager daemon.''' + + pid_file = CONFIG['pid_file'] + if not os.path.isfile(pid_file): + return echo('no running daemon found.') + + echo('found pid file: %r' % pid_file) + pid = get_pid(pid_file) + if not pid_running(pid): + echo('no process with pid: %d' % pid) + return os.remove(pid_file) + + echo("terminating process with pid: %d" % pid) + term_process(pid) + if os.path.isfile(pid_file): + os.remove(pid_file) + + echo('stopped event daemon.') + + +def start_action(): + '''Start the event manager daemon.''' + + pid_file = CONFIG['pid_file'] + if os.path.isfile(pid_file): + echo('found pid file: %r' % pid_file) + pid = get_pid(pid_file) + if pid_running(pid): + return echo('event daemon already started with pid: %d' % pid) + + echo('no process with pid: %d' % pid) + os.remove(pid_file) + + echo('starting event manager.') + UzblEventDaemon().run() + + +def restart_action(): + '''Restart the event manager daemon.''' + + echo('restarting event manager daemon.') + stop_action() + start_action() + + +def list_action(): + '''List all the plugins being loaded by the event daemon.''' + + plugins = find_plugins(CONFIG['plugin_dirs']) + dirs = {} + + for (plugin, plugin_dir) in plugins.items(): + if plugin_dir not in dirs: + dirs[plugin_dir] = [] + + dirs[plugin_dir].append(plugin) + + for (index, (plugin_dir, plugin_list)) in enumerate(sorted(dirs.items())): + if index: + print + + print "%s:" % plugin_dir + for plugin in sorted(plugin_list): + print " %s" % plugin + + +if __name__ == "__main__": + USAGE = "usage: %prog [options] {start|stop|restart|list}" + PARSER = OptionParser(usage=USAGE) + PARSER.add_option('-v', '--verbose', dest='verbose', action="store_true", + help="print verbose output.") + + PARSER.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store", + metavar="DIRS", help="Specify plugin directories in the form of "\ + "'dir1:dir2:dir3'.") + + PARSER.add_option('-l', '--load-plugins', dest="load", action="store", + metavar="PLUGINS", help="comma separated list of plugins to load") + + PARSER.add_option('-i', '--ignore-plugins', dest="ignore", action="store", + metavar="PLUGINS", help="comma separated list of plugins to ignore") + + PARSER.add_option('-p', '--pid-file', dest='pid', action='store', + metavar='FILE', help="specify pid file location") + + PARSER.add_option('-s', '--server-socket', dest='socket', action='store', + metavar='SOCKET', help="specify the daemon socket location") + + PARSER.add_option('-n', '--no-daemon', dest="daemon", + action="store_true", help="don't enter daemon mode.") + + PARSER.add_option('-a', '--auto-close', dest='autoclose', + action='store_true', help='auto close after all instances disconnect.') + + (OPTIONS, ARGS) = PARSER.parse_args() + + # init like {start|stop|..} daemon actions dict. + DAEMON_ACTIONS = {'start': start_action, 'stop': stop_action, + 'restart': restart_action, 'list': list_action} + + if not ARGS: + ACTION = 'start' + + elif len(ARGS) == 1: + ACTION = ARGS[0] + if ACTION not in DAEMON_ACTIONS: + raise ArgumentError("unknown argument: %r" % ACTION) + + else: + raise ArgumentError("too many arguments: %r" % ARGS) + + # parse other flags & options. + if OPTIONS.verbose: + CONFIG['verbose'] = True + + if OPTIONS.plugin_dirs: + PLUGIN_DIRS = [] + for DIR in OPTIONS.plugin_dirs.split(':'): + if not DIR: + continue + + PLUGIN_DIRS.append(os.path.realpath(DIR)) + + CONFIG['plugin_dirs'] = PLUGIN_DIRS + echo("plugin search dirs: %r" % PLUGIN_DIRS) + + if OPTIONS.load and OPTIONS.ignore: + error("you can't load and ignore at the same time.") + sys.exit(1) + + elif OPTIONS.load: + LOAD = CONFIG['plugins_load'] + for PLUGIN in OPTIONS.load.split(','): + if PLUGIN.strip(): + LOAD.append(PLUGIN.strip()) + + echo('only loading plugin(s): %s' % ', '.join(LOAD)) + + elif OPTIONS.ignore: + IGNORE = CONFIG['plugins_ignore'] + for PLUGIN in OPTIONS.ignore.split(','): + if PLUGIN.strip(): + IGNORE.append(PLUGIN.strip()) + + echo('ignoring plugin(s): %s' % ', '.join(IGNORE)) + + if OPTIONS.autoclose: + CONFIG['auto_close'] = True + echo('will auto close.') + + if OPTIONS.pid: + CONFIG['pid_file'] = os.path.realpath(OPTIONS.pid) + echo("pid file location: %r" % CONFIG['pid_file']) + + if OPTIONS.socket: + CONFIG['server_socket'] = os.path.realpath(OPTIONS.socket) + echo("daemon socket location: %s" % CONFIG['server_socket']) + + if OPTIONS.daemon: + CONFIG['daemon_mode'] = False + + # Now {start|stop|...} + DAEMON_ACTIONS[ACTION]() diff --git a/examples/data/scripts/uzbl-tabbed b/examples/data/scripts/uzbl-tabbed new file mode 100755 index 0000000..7bd90d5 --- /dev/null +++ b/examples/data/scripts/uzbl-tabbed @@ -0,0 +1,1417 @@ +#!/usr/bin/env python + +# Uzbl tabbing wrapper using a fifo socket interface +# Copyright (c) 2009, Tom Adams +# Copyright (c) 2009, Chris van Dijk +# Copyright (c) 2009, Mason Larobina +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# Author(s): +# Tom Adams +# Wrote the original uzbl_tabbed.py as a proof of concept. +# +# Chris van Dijk (quigybo) +# Made signifigant headway on the old uzbl_tabbing.py script on the +# uzbl wiki +# +# Mason Larobina +# Rewrite of the uzbl_tabbing.py script to use a fifo socket interface +# and inherit configuration options from the user's uzbl config. +# +# Contributor(s): +# mxey +# uzbl_config path now honors XDG_CONFIG_HOME if it exists. +# +# Romain Bignon +# Fix for session restoration code. +# +# Jake Probst +# Wrote a patch that overflows tabs in the tablist on to new lines when +# running of room. +# +# Devon Jones +# Fifo command bring_to_front which brings the gtk window to focus. +# +# Simon Lipp (sloonz) +# Various + + +# Dependencies: +# pygtk - python bindings for gtk. +# pango - python bindings needed for text rendering & layout in gtk widgets. +# pygobject - GLib's GObject bindings for python. +# +# Optional dependencies: +# simplejson - save uzbl_tabbed.py sessions & presets in json. +# +# Note: I haven't included version numbers with this dependency list because +# I've only ever tested uzbl_tabbed.py on the latest stable versions of these +# packages in Gentoo's portage. Package names may vary on different systems. + + +# Configuration: +# Because this version of uzbl_tabbed is able to inherit options from your main +# uzbl configuration file you may wish to configure uzbl tabbed from there. +# Here is a list of configuration options that can be customised and some +# example values for each: +# +# General tabbing options: +# show_tablist = 1 +# show_gtk_tabs = 0 +# tablist_top = 1 +# gtk_tab_pos = (top|left|bottom|right) +# gtk_refresh = 1000 +# switch_to_new_tabs = 1 +# capture_new_windows = 1 +# multiline_tabs = 1 +# +# Tab title options: +# tab_titles = 1 +# tab_indexes = 1 +# new_tab_title = Loading +# max_title_len = 50 +# show_ellipsis = 1 +# +# Session options: +# save_session = 1 +# json_session = 0 +# session_file = $HOME/.local/share/uzbl/session +# +# Inherited uzbl options: +# fifo_dir = /tmp +# socket_dir = /tmp +# icon_path = $HOME/.local/share/uzbl/uzbl.png +# status_background = #303030 +# +# Misc options: +# window_size = 800,800 +# verbose = 0 +# +# And uzbl_tabbed.py takes care of the actual binding of the commands via each +# instances fifo socket. +# +# Custom tab styling: +# tab_colours = foreground = "#888" background = "#303030" +# tab_text_colours = foreground = "#bbb" +# selected_tab = foreground = "#fff" +# selected_tab_text = foreground = "green" +# tab_indicate_https = 1 +# https_colours = foreground = "#888" +# https_text_colours = foreground = "#9c8e2d" +# selected_https = foreground = "#fff" +# selected_https_text = foreground = "gold" +# +# How these styling values are used are soley defined by the syling policy +# handler below (the function in the config section). So you can for example +# turn the tab text colour Firetruck-Red in the event "error" appears in the +# tab title or some other arbitrary event. You may wish to make a trusted +# hosts file and turn tab titles of tabs visiting trusted hosts purple. + + +# Issues: +# - new windows are not caught and opened in a new tab. +# - when uzbl_tabbed.py crashes it takes all the children with it. +# - when a new tab is opened when using gtk tabs the tab button itself +# grabs focus from its child for a few seconds. +# - when switch_to_new_tabs is not selected the notebook page is +# maintained but the new window grabs focus (try as I might to stop it). + + +# Todo: +# - add command line options to use a different session file, not use a +# session file and or open a uri on starup. +# - ellipsize individual tab titles when the tab-list becomes over-crowded +# - add "<" & ">" arrows to tablist to indicate that only a subset of the +# currently open tabs are being displayed on the tablist. +# - add the small tab-list display when both gtk tabs and text vim-like +# tablist are hidden (I.e. [ 1 2 3 4 5 ]) +# - check spelling. +# - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into +# the collective. Resistance is futile! + + +import pygtk +import gtk +import subprocess +import os +import re +import time +import getopt +import pango +import select +import sys +import gobject +import socket +import random +import hashlib +import atexit +import types + +from gobject import io_add_watch, source_remove, timeout_add, IO_IN, IO_HUP +from signal import signal, SIGTERM, SIGINT +from optparse import OptionParser, OptionGroup + + +pygtk.require('2.0') + +_SCRIPTNAME = os.path.basename(sys.argv[0]) +def error(msg): + sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) + +# ============================================================================ +# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def xdghome(key, default): + '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise + use $HOME and the default path.''' + + xdgkey = "XDG_%s_HOME" % key + if xdgkey in os.environ.keys() and os.environ[xdgkey]: + return os.environ[xdgkey] + + return os.path.join(os.environ['HOME'], default) + +# Setup xdg paths. +DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') + +# Ensure uzbl xdg paths exist +if not os.path.exists(DATA_DIR): + os.makedirs(DATA_DIR) + +# All of these settings can be inherited from your uzbl config file. +config = { + # Tab options + 'show_tablist': True, # Show text uzbl like statusbar tab-list + 'show_gtk_tabs': False, # Show gtk notebook tabs + 'tablist_top': True, # Display tab-list at top of window + 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) + 'gtk_refresh': 1000, # Tablist refresh millisecond interval + 'switch_to_new_tabs': True, # Upon opening a new tab switch to it + 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows + 'multiline_tabs': True, # Tabs overflow onto new tablist lines. + + # Tab title options + 'tab_titles': True, # Display tab titles (else only tab-nums) + 'tab_indexes': True, # Display tab nums (else only tab titles) + 'new_tab_title': 'Loading', # New tab title + 'max_title_len': 50, # Truncate title at n characters + 'show_ellipsis': True, # Show ellipsis when truncating titles + + # Session options + 'save_session': True, # Save session in file when quit + 'json_session': False, # Use json to save session. + 'saved_sessions_dir': os.path.join(DATA_DIR, 'sessions/'), + 'session_file': os.path.join(DATA_DIR, 'session'), + + # Inherited uzbl options + 'fifo_dir': '/tmp', # Path to look for uzbl fifo. + 'socket_dir': '/tmp', # Path to look for uzbl socket. + 'icon_path': os.path.join(DATA_DIR, 'uzbl.png'), + 'status_background': "#303030", # Default background for all panels. + + # Misc options + 'window_size': "800,800", # width,height in pixels. + 'verbose': False, # Print verbose output. + + # Add custom tab style definitions to be used by the tab colour policy + # handler here. Because these are added to the config dictionary like + # any other uzbl_tabbed configuration option remember that they can + # be superseeded from your main uzbl config file. + 'tab_colours': 'foreground = "#888" background = "#303030"', + 'tab_text_colours': 'foreground = "#bbb"', + 'selected_tab': 'foreground = "#fff"', + 'selected_tab_text': 'foreground = "green"', + 'tab_indicate_https': True, + 'https_colours': 'foreground = "#888"', + 'https_text_colours': 'foreground = "#9c8e2d"', + 'selected_https': 'foreground = "#fff"', + 'selected_https_text': 'foreground = "gold"', + +} # End of config dict. + +UZBL_TABBED_VARS = config.keys() + +# This is the tab style policy handler. Every time the tablist is updated +# this function is called to determine how to colourise that specific tab +# according the simple/complex rules as defined here. You may even wish to +# move this function into another python script and import it using: +# from mycustomtabbingconfig import colour_selector +# Remember to rename, delete or comment out this function if you do that. + +def colour_selector(tabindex, currentpage, uzbl): + '''Tablist styling policy handler. This function must return a tuple of + the form (tab style, text style).''' + + # Just as an example: + # if 'error' in uzbl.title: + # if tabindex == currentpage: + # return ('foreground="#fff"', 'foreground="red"') + # return ('foreground="#888"', 'foreground="red"') + + # Style tabs to indicate connected via https. + if config['tab_indicate_https'] and uzbl.uri.startswith("https://"): + if tabindex == currentpage: + return (config['selected_https'], config['selected_https_text']) + return (config['https_colours'], config['https_text_colours']) + + # Style to indicate selected. + if tabindex == currentpage: + return (config['selected_tab'], config['selected_tab_text']) + + # Default tab style. + return (config['tab_colours'], config['tab_text_colours']) + +# ============================================================================ +# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: +# ============================================================================ + +def echo(msg): + if config['verbose']: + sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) + + +def counter(): + '''To infinity and beyond!''' + + i = 0 + while True: + i += 1 + yield i + + +def escape(s): + '''Replaces html markup in tab titles that screw around with pango.''' + + for (split, glue) in [('&','&'), ('<', '<'), ('>', '>')]: + s = s.replace(split, glue) + return s + + +class SocketClient: + '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance''' + + # List of UzblInstance objects not already linked with a SocketClient + instances_queue = {} + + def __init__(self, socket): + self._buffer = "" + self._socket = socket + self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\ + io_add_watch(socket, IO_HUP, self._socket_closed)] + self.uzbl = None + + + def _socket_recv(self, fd, condition): + '''Data available on socket, process it''' + + self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ? + return True + + + def _socket_closed(self, fd, condition): + '''Remote client exited''' + self.uzbl.close() + return False + + + def _feed(self, data): + '''An Uzbl instance sent some data, parse it''' + + self._buffer += data + if self.uzbl: + if "\n" in self._buffer: + cmds = self._buffer.split("\n") + + if cmds[-1]: # Last command has been received incomplete, don't process it + self._buffer, cmds = cmds[-1], cmds[:-1] + else: + self._buffer = "" + + for cmd in cmds: + if cmd: + self.uzbl.parse_command(cmd) + else: + name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M) + uzbl = self.instances_queue.get(name[0]) + if uzbl: + del self.instances_queue[name[0]] + self.uzbl = uzbl + self.uzbl.got_socket(self) + self._feed("") + + def send(self, data): + '''Child socket send function.''' + + self._socket.send(data + "\n") + + def close(self): + '''Close the connection''' + + if self._socket: + self._socket.close() + self._socket = None + map(source_remove, self._watchers) + self._watchers = [] + + +class UzblInstance: + '''Uzbl instance meta-data/meta-action object.''' + + def __init__(self, parent, tab, name, uri, title, switch): + + self.parent = parent + self.tab = tab + self.name = name + self.title = title + self.tabtitle = "" + self.uri = uri + self._client = None + self._switch = switch # Switch to tab after loading ? + self.title_changed() + + + def got_socket(self, client): + '''Uzbl instance is now connected''' + + self._client = client + self.parent.config_uzbl(self) + if self._switch: + tabid = self.parent.notebook.page_num(self.tab) + self.parent.goto_tab(tabid) + + + def title_changed(self, gtk_only = True): # GTK-only is for indexes + '''self.title has changed, update the tabs list''' + + tab_titles = config['tab_titles'] + tab_indexes = config['tab_indexes'] + show_ellipsis = config['show_ellipsis'] + max_title_len = config['max_title_len'] + + # Unicode heavy strings do not like being truncated/sliced so by + # re-encoding the string sliced of limbs are removed. + self.tabtitle = self.title[:max_title_len + int(show_ellipsis)] + if type(self.tabtitle) != types.UnicodeType: + self.tabtitle = unicode(self.tabtitle, 'utf-8', 'ignore') + + self.tabtitle = self.tabtitle.encode('utf-8', 'ignore').strip() + + if show_ellipsis and len(self.tabtitle) != len(self.title): + self.tabtitle += "\xe2\x80\xa6" + + gtk_tab_format = "%d %s" + index = self.parent.notebook.page_num(self.tab) + if tab_titles and tab_indexes: + self.parent.notebook.set_tab_label_text(self.tab, + gtk_tab_format % (index, self.tabtitle)) + elif tab_titles: + self.parent.notebook.set_tab_label_text(self.tab, self.tabtitle) + else: + self.parent.notebook.set_tab_label_text(self.tab, str(index)) + + # If instance is current tab, update window title + if index == self.parent.notebook.get_current_page(): + title_format = "%s - Uzbl Browser" + self.parent.window.set_title(title_format % self.title) + + # Non-GTK tabs + if not gtk_only: + self.parent.update_tablist() + + + def set(self, key, val): + ''' Send the SET command to Uzbl ''' + + if self._client: + self._client.send('set %s = %s') #TODO: escape chars ? + + + def exit(self): + ''' Ask the Uzbl instance to close ''' + + if self._client: + self._client.send('exit') + + + def parse_command(self, cmd): + ''' Parse event givent by the Uzbl instance ''' + + type, _, args = cmd.split(" ", 2) + if type == "EVENT": + type, args = args.split(" ", 1) + if type == "TITLE_CHANGED": + self.title = args + self.title_changed() + elif type == "VARIABLE_SET": + var, _, val = args.split(" ", 2) + try: + val = int(val) + except: + pass + + if var in UZBL_TABBED_VARS: + if config[var] != val: + config[var] = val + if var == "show_gtk_tabs": + self.parent.notebook.set_show_tabs(bool(val)) + elif var == "show_tablist" or var == "tablist_top": + self.parent.update_tablist_display() + elif var == "gtk_tab_pos": + self.parent.update_gtk_tab_pos() + elif var == "status_background": + col = gtk.gdk.color_parse(config['status_background']) + self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) + elif var == "tab_titles" or var == "tab_indexes": + for tab in self.parent.notebook: + self.parent.tabs[tab].title_changed(True) + + self.parent.update_tablist() + else: + config[var] = val + + if var == "uri": + self.uri = var + self.parent.update_tablist() + elif type == "NEW_TAB": + self.parent.new_tab(args) + elif type == "NEXT_TAB": + if args: + self.parent.next_tab(int(args)) + else: + self.parent.next_tab() + elif type == "PREV_TAB": + if args: + self.parent.prev_tab(int(args)) + else: + self.parent.prev_tab() + elif type == "GOTO_TAB": + self.parent.goto_tab(int(args)) + elif type == "FIRST_TAB": + self.parent.goto_tab(0) + elif type == "LAST_TAB": + self.parent.goto_tab(-1) + elif type == "PRESET_TABS": + self.parent.parse_command(["preset"] + args.split()) + elif type == "BRING_TO_FRONT": + self.parent.window.present() + elif type == "CLEAN_TABS": + self.parent.clean_slate() + elif type == "EXIT_ALL_TABS": + self.parent.quitrequest() + + + def close(self): + '''The remote instance exited''' + + if self._client: + self._client.close() + self._client = None + + +class UzblTabbed: + '''A tabbed version of uzbl using gtk.Notebook''' + + def __init__(self): + '''Create tablist, window and notebook.''' + + self._timers = {} + self._buffer = "" + self._killed = False + + # A list of the recently closed tabs + self._closed = [] + + # Holds metadata on the uzbl childen open. + self.tabs = {} + + # Uzbl sockets (socket => SocketClient) + self.clients = {} + + # Generates a unique id for uzbl socket filenames. + self.next_pid = counter().next + + # Create main window + self.window = gtk.Window() + try: + window_size = map(int, config['window_size'].split(',')) + self.window.set_default_size(*window_size) + + except: + error("Invalid value for default_size in config file.") + + self.window.set_title("Uzbl Browser") + self.window.set_border_width(0) + + # Set main window icon + icon_path = config['icon_path'] + if os.path.exists(icon_path): + self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) + + else: + icon_path = '/usr/share/uzbl/examples/data/uzbl.png' + if os.path.exists(icon_path): + self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) + + # Attach main window event handlers + self.window.connect("delete-event", self.quitrequest) + + # Create tab list + vbox = gtk.VBox() + self.vbox = vbox + self.window.add(vbox) + ebox = gtk.EventBox() + self.ebox = ebox + self.tablist = gtk.Label() + + self.tablist.set_use_markup(True) + self.tablist.set_justify(gtk.JUSTIFY_LEFT) + self.tablist.set_line_wrap(False) + self.tablist.set_selectable(False) + self.tablist.set_padding(2,2) + self.tablist.set_alignment(0,0) + self.tablist.set_ellipsize(pango.ELLIPSIZE_END) + self.tablist.set_text(" ") + self.tablist.show() + ebox.add(self.tablist) + ebox.show() + bgcolor = gtk.gdk.color_parse(config['status_background']) + ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) + + # Create notebook + self.notebook = gtk.Notebook() + self.notebook.set_show_tabs(config['show_gtk_tabs']) + + # Set tab position + self.update_gtk_tab_pos() + + self.notebook.set_show_border(False) + self.notebook.set_scrollable(True) + self.notebook.set_border_width(0) + + self.notebook.connect("page-removed", self.tab_closed) + self.notebook.connect("switch-page", self.tab_changed) + self.notebook.connect("page-added", self.tab_opened) + + self.notebook.show() + vbox.pack_start(self.notebook, True, True, 0) + vbox.reorder_child(self.notebook, 1) + self.update_tablist_display() + + self.vbox.show() + self.window.show() + self.wid = self.notebook.window.xid + + # Store information about the applications fifo and socket. + fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid() + socket_filename = 'uzbltabbed_%d.socket' % os.getpid() + self._fifo = None + self._socket = None + self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename) + self.socket_path = os.path.join(config['socket_dir'], socket_filename) + + # Now initialise the fifo and the socket + self.init_fifo() + self.init_socket() + + # If we are using sessions then load the last one if it exists. + if config['save_session']: + self.load_session() + + + def run(self): + '''UzblTabbed main function that calls the gtk loop.''' + + if not self.clients and not SocketClient.instances_queue and not self.tabs: + self.new_tab() + + gtk_refresh = int(config['gtk_refresh']) + if gtk_refresh < 100: + gtk_refresh = 100 + + # Make SIGTERM act orderly. + signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) + + # Catch keyboard interrupts + signal(SIGINT, lambda signum, stack_frame: self.terminate(SIGINT)) + + try: + gtk.main() + + except: + error("encounted error %r" % sys.exc_info()[1]) + + # Unlink fifo socket + self.unlink_fifo() + self.close_socket() + + # Attempt to close all uzbl instances nicely. + self.quitrequest() + + # Allow time for all the uzbl instances to quit. + time.sleep(1) + + raise + + + def terminate(self, termsig=None): + '''Handle termination signals and exit safely and cleanly.''' + + # Not required but at least it lets the user know what killed his + # browsing session. + if termsig == SIGTERM: + error("caught SIGTERM signal") + + elif termsig == SIGINT: + error("caught keyboard interrupt") + + else: + error("caught unknown signal") + + error("commencing infanticide!") + + # Sends the exit signal to all uzbl instances. + self.quitrequest() + + + def init_socket(self): + '''Create interprocess communication socket.''' + + def accept(sock, condition): + '''A new uzbl instance was created''' + + client, _ = sock.accept() + self.clients[client] = SocketClient(client) + + return True + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(self.socket_path) + sock.listen(1) + + # Add event handler for IO_IN event. + self._socket = (sock, io_add_watch(sock, IO_IN, accept)) + + echo("[socket] listening at %r" % self.socket_path) + + # Add atexit register to destroy the socket on program termination. + atexit.register(self.close_socket) + + + def close_socket(self): + '''Close the socket when closing the application''' + + if self._socket: + (fd, watcher) = self._socket + source_remove(watcher) + fd.close() + os.unlink(self.socket_path) + self._socket = None + + + def init_fifo(self): + '''Create interprocess communication fifo.''' + + if os.path.exists(self.fifo_path): + if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK): + os.mkfifo(self.fifo_path) + + else: + basedir = os.path.dirname(self.fifo_path) + if not os.path.exists(basedir): + os.makedirs(basedir) + + os.mkfifo(self.fifo_path) + + # Add event handlers for IO_IN & IO_HUP events. + self.setup_fifo_watchers() + + echo("[fifo] listening at %r" % self.fifo_path) + + # Add atexit register to destroy the fifo on program termination. + atexit.register(self.unlink_fifo) + + + def unlink_fifo(self): + '''Unlink the fifo socket. Note: This function is called automatically + on exit by an atexit register.''' + + # Make sure the fifo fd is closed. + self.close_fifo() + + # And unlink if the real fifo exists. + if os.path.exists(self.fifo_path): + os.unlink(self.fifo_path) + echo("unlinked %r" % self.fifo_path) + + + def close_fifo(self): + '''Remove all event handlers watching the fifo and close the fd.''' + + # Already closed + if self._fifo is None: return + + (fd, watchers) = self._fifo + os.close(fd) + + # Stop all gobject io watchers watching the fifo. + for gid in watchers: + source_remove(gid) + + self._fifo = None + + + def setup_fifo_watchers(self): + '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event + handlers.''' + + # Close currently open fifo fd and kill all watchers + self.close_fifo() + + fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK) + + # Add gobject io event handlers to the fifo socket. + watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\ + io_add_watch(fd, IO_HUP, self.main_fifo_hangup)] + + self._fifo = (fd, watchers) + + + def main_fifo_hangup(self, fd, cb_condition): + '''Handle main fifo socket hangups.''' + + # Close old fd, open new fifo socket and add io event handlers. + self.setup_fifo_watchers() + + # Kill the gobject event handler calling this handler function. + return False + + + def main_fifo_read(self, fd, cb_condition): + '''Read from main fifo socket.''' + + self._buffer = os.read(fd, 1024) + temp = self._buffer.split("\n") + self._buffer = temp.pop() + cmds = [s.strip().split() for s in temp if len(s.strip())] + + for cmd in cmds: + try: + #print cmd + self.parse_command(cmd) + + except: + error("parse_command: invalid command %s" % ' '.join(cmd)) + raise + + return True + + + def parse_command(self, cmd): + '''Parse instructions from uzbl child processes.''' + + # Commands ( [] = optional, {} = required ) + # new [uri] + # open new tab and head to optional uri. + # close [tab-num] + # close current tab or close via tab id. + # next [n-tabs] + # open next tab or n tabs down. Supports negative indexing. + # prev [n-tabs] + # open prev tab or n tabs down. Supports negative indexing. + # goto {tab-n} + # goto tab n. + # first + # goto first tab. + # last + # goto last tab. + # title {pid} {document-title} + # updates tablist title. + # uri {pid} {document-location} + # updates tablist uri + # bring_to_front + # brings the gtk window to focus. + # exit + # exits uzbl_tabbed.py + + if cmd[0] == "new": + if len(cmd) == 2: + self.new_tab(cmd[1]) + + else: + self.new_tab() + + elif cmd[0] == "newfromclip": + uri = subprocess.Popen(['xclip','-selection','clipboard','-o'],\ + stdout=subprocess.PIPE).communicate()[0] + if uri: + self.new_tab(uri) + + elif cmd[0] == "close": + if len(cmd) == 2: + self.close_tab(int(cmd[1])) + + else: + self.close_tab() + + elif cmd[0] == "next": + if len(cmd) == 2: + self.next_tab(int(cmd[1])) + + else: + self.next_tab() + + elif cmd[0] == "prev": + if len(cmd) == 2: + self.prev_tab(int(cmd[1])) + + else: + self.prev_tab() + + elif cmd[0] == "goto": + self.goto_tab(int(cmd[1])) + + elif cmd[0] == "first": + self.goto_tab(0) + + elif cmd[0] == "last": + self.goto_tab(-1) + + elif cmd[0] in ["title", "uri"]: + if len(cmd) > 2: + uzbl = self.get_tab_by_name(int(cmd[1])) + if uzbl: + old = getattr(uzbl, cmd[0]) + new = ' '.join(cmd[2:]) + setattr(uzbl, cmd[0], new) + if old != new: + self.update_tablist() + + else: + error("parse_command: no uzbl with name %r" % int(cmd[1])) + + elif cmd[0] == "preset": + if len(cmd) < 3: + error("parse_command: invalid preset command") + + elif cmd[1] == "save": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + self.save_session(path) + + elif cmd[1] == "load": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + self.load_session(path) + + elif cmd[1] == "del": + path = os.path.join(config['saved_sessions_dir'], cmd[2]) + if os.path.isfile(path): + os.remove(path) + + else: + error("parse_command: preset %r does not exist." % path) + + elif cmd[1] == "list": + uzbl = self.get_tab_by_name(int(cmd[2])) + if uzbl: + if not os.path.isdir(config['saved_sessions_dir']): + js = "js alert('No saved presets.');" + uzbl._client.send(js) + + else: + listdir = os.listdir(config['saved_sessions_dir']) + listdir = "\\n".join(listdir) + js = "js alert('Session presets:\\n\\n%s');" % listdir + uzbl._client.send(js) + + else: + error("parse_command: unknown tab name.") + + else: + error("parse_command: unknown parse command %r"\ + % ' '.join(cmd)) + + elif cmd[0] == "bring_to_front": + self.window.present() + + elif cmd[0] == "clean": + self.clean_slate() + + elif cmd[0] == "exit": + self.quitrequest() + + else: + error("parse_command: unknown command %r" % ' '.join(cmd)) + + + def get_tab_by_name(self, name): + '''Return uzbl instance by name.''' + + for (tab, uzbl) in self.tabs.items(): + if uzbl.name == name: + return uzbl + + return False + + + def new_tab(self, uri='', title='', switch=None): + '''Add a new tab to the notebook and start a new instance of uzbl. + Use the switch option to negate config['switch_to_new_tabs'] option + when you need to load multiple tabs at a time (I.e. like when + restoring a session from a file).''' + + tab = gtk.Socket() + tab.show() + self.notebook.append_page(tab) + sid = tab.get_id() + uri = uri.strip() + name = "%d-%d" % (os.getpid(), self.next_pid()) + + if switch is None: + switch = config['switch_to_new_tabs'] + + if not title: + title = config['new_tab_title'] + + cmd = ['uzbl-browser', '-n', name, '-s', str(sid), + '--connect-socket', self.socket_path, '--uri', uri] + subprocess.Popen(cmd) # TODO: do i need close_fds=True ? + + uzbl = UzblInstance(self, tab, name, uri, title, switch) + SocketClient.instances_queue[name] = uzbl + self.tabs[tab] = uzbl + + + def clean_slate(self): + '''Close all open tabs and open a fresh brand new one.''' + + self.new_tab() + tabs = self.tabs.keys() + for tab in list(self.notebook)[:-1]: + if tab not in tabs: continue + uzbl = self.tabs[tab] + uzbl.exit() + + + def config_uzbl(self, uzbl): + '''Send bind commands for tab new/close/next/prev to a uzbl + instance.''' + + # Set definitions here + # set(key, command back to fifo) + if config['capture_new_windows']: + uzbl.set("new_window", r'new $8') + + + def goto_tab(self, index): + '''Goto tab n (supports negative indexing).''' + + title_format = "%s - Uzbl Browser" + + tabs = list(self.notebook) + if 0 <= index < len(tabs): + self.notebook.set_current_page(index) + uzbl = self.tabs[self.notebook.get_nth_page(index)] + self.window.set_title(title_format % uzbl.title) + self.update_tablist() + return None + + try: + tab = tabs[index] + # Update index because index might have previously been a + # negative index. + index = tabs.index(tab) + self.notebook.set_current_page(index) + uzbl = self.tabs[self.notebook.get_nth_page(index)] + self.window.set_title(title_format % uzbl.title) + self.update_tablist() + + except IndexError: + pass + + + def next_tab(self, step=1): + '''Switch to next tab or n tabs right.''' + + if step < 1: + error("next_tab: invalid step %r" % step) + return None + + ntabs = self.notebook.get_n_pages() + tabn = (self.notebook.get_current_page() + step) % ntabs + self.goto_tab(tabn) + + + def prev_tab(self, step=1): + '''Switch to prev tab or n tabs left.''' + + if step < 1: + error("prev_tab: invalid step %r" % step) + return None + + ntabs = self.notebook.get_n_pages() + tabn = self.notebook.get_current_page() - step + while tabn < 0: tabn += ntabs + self.goto_tab(tabn) + + + def close_tab(self, tabn=None): + '''Closes current tab. Supports negative indexing.''' + + if tabn is None: + tabn = self.notebook.get_current_page() + + else: + try: + tab = list(self.notebook)[tabn] + + except IndexError: + error("close_tab: invalid index %r" % tabn) + return None + + self.notebook.remove_page(tabn) + + + def tab_opened(self, notebook, tab, index): + '''Called upon tab creation. Called by page-added signal.''' + + if config['switch_to_new_tabs']: + self.notebook.set_focus_child(tab) + + else: + oldindex = self.notebook.get_current_page() + oldtab = self.notebook.get_nth_page(oldindex) + self.notebook.set_focus_child(oldtab) + + + def tab_closed(self, notebook, tab, index): + '''Close the window if no tabs are left. Called by page-removed + signal.''' + + if tab in self.tabs.keys(): + uzbl = self.tabs[tab] + uzbl.close() + + self._closed.append((uzbl.uri, uzbl.title)) + self._closed = self._closed[-10:] + del self.tabs[tab] + + if self.notebook.get_n_pages() == 0: + if not self._killed and config['save_session']: + if os.path.exists(config['session_file']): + os.remove(config['session_file']) + + self.quit() + + for tab in self.notebook: + self.tabs[tab].title_changed(True) + self.update_tablist() + + return True + + + def tab_changed(self, notebook, page, index): + '''Refresh tab list. Called by switch-page signal.''' + + tab = self.notebook.get_nth_page(index) + self.notebook.set_focus_child(tab) + self.update_tablist(index) + return True + + + def update_tablist_display(self): + '''Called when show_tablist or tablist_top has changed''' + + if self.ebox in self.vbox.get_children(): + self.vbox.remove(self.ebox) + + if config['show_tablist']: + self.vbox.pack_start(self.ebox, False, False, 0) + if config['tablist_top']: + self.vbox.reorder_child(self.ebox, 0) + else: + self.vbox.reorder_child(self.ebox, 2) + + def update_gtk_tab_pos(self): + ''' Called when gtk_tab_pos has changed ''' + + allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, + 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} + if config['gtk_tab_pos'] in allposes.keys(): + self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) + + + def update_tablist(self, curpage=None): + '''Upate tablist status bar.''' + + if not config['show_tablist']: + return True + + tab_titles = config['tab_titles'] + tab_indexes = config['tab_indexes'] + multiline_tabs = config['multiline_tabs'] + + if multiline_tabs: + multiline = [] + + tabs = self.tabs.keys() + if curpage is None: + curpage = self.notebook.get_current_page() + + pango = "" + normal = (config['tab_colours'], config['tab_text_colours']) + selected = (config['selected_tab'], config['selected_tab_text']) + + if tab_titles and tab_indexes: + tab_format = " [ %(index)d %(title)s ] " + elif tab_titles: + tab_format = " [ %(title)s ] " + else: + tab_format = " [ %(index)d ] " + + for index, tab in enumerate(self.notebook): + if tab not in tabs: continue + uzbl = self.tabs[tab] + title = escape(uzbl.tabtitle) + + style = colour_selector(index, curpage, uzbl) + (tabc, textc) = style + + if multiline_tabs: + opango = pango + + pango += tab_format % locals() + + self.tablist.set_markup(pango) + listwidth = self.tablist.get_layout().get_pixel_size()[0] + winwidth = self.window.get_size()[0] + + if listwidth > (winwidth - 20): + multiline.append(opango) + pango = tab_format % locals() + else: + pango += tab_format % locals() + + if multiline_tabs: + multiline.append(pango) + self.tablist.set_markup(' '.join(multiline)) + + else: + self.tablist.set_markup(pango) + + return True + + + def save_session(self, session_file=None): + '''Save the current session to file for restoration on next load.''' + + strip = str.strip + + if session_file is None: + session_file = config['session_file'] + + tabs = self.tabs.keys() + state = [] + for tab in list(self.notebook): + if tab not in tabs: continue + uzbl = self.tabs[tab] + if not uzbl.uri: continue + state += [(uzbl.uri, uzbl.title),] + + session = {'curtab': self.notebook.get_current_page(), + 'tabs': state} + + if config['json_session']: + raw = json.dumps(session) + + else: + lines = ["curtab = %d" % session['curtab'],] + for (uri, title) in session['tabs']: + lines += ["%s\t%s" % (strip(uri), strip(title)),] + + raw = "\n".join(lines) + + if not os.path.isfile(session_file): + dirname = os.path.dirname(session_file) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + h = open(session_file, 'w') + h.write(raw) + h.close() + + + def load_session(self, session_file=None): + '''Load a saved session from file.''' + + default_path = False + strip = str.strip + json_session = config['json_session'] + delete_loaded = False + + if session_file is None: + default_path = True + delete_loaded = True + session_file = config['session_file'] + + if not os.path.isfile(session_file): + return False + + h = open(session_file, 'r') + raw = h.read() + h.close() + if json_session: + if sum([1 for s in raw.split("\n") if strip(s)]) != 1: + error("Warning: The session file %r does not look json. "\ + "Trying to load it as a non-json session file."\ + % session_file) + json_session = False + + if json_session: + try: + session = json.loads(raw) + curtab, tabs = session['curtab'], session['tabs'] + + except: + error("Failed to load jsonifed session from %r"\ + % session_file) + return None + + else: + tabs = [] + strip = str.strip + curtab, tabs = 0, [] + lines = [s for s in raw.split("\n") if strip(s)] + if len(lines) < 2: + error("Warning: The non-json session file %r looks invalid."\ + % session_file) + return None + + try: + for line in lines: + if line.startswith("curtab"): + curtab = int(line.split()[-1]) + + else: + uri, title = line.split("\t",1) + tabs += [(strip(uri), strip(title)),] + + except: + error("Warning: failed to load session file %r" % session_file) + return None + + session = {'curtab': curtab, 'tabs': tabs} + + # Now populate notebook with the loaded session. + for (index, (uri, title)) in enumerate(tabs): + self.new_tab(uri=uri, title=title, switch=(curtab==index)) + + # A saved session has been loaded now delete it. + if delete_loaded and os.path.exists(session_file): + os.remove(session_file) + + # There may be other state information in the session dict of use to + # other functions. Of course however the non-json session object is + # just a dummy object of no use to no one. + return session + + + def quitrequest(self, *args): + '''Attempt to close all uzbl instances nicely and exit.''' + + self._killed = True + + if config['save_session']: + if len(list(self.notebook)) > 1: + self.save_session() + + else: + # Notebook has one page open so delete the session file. + if os.path.isfile(config['session_file']): + os.remove(config['session_file']) + + for (tab, uzbl) in self.tabs.items(): + uzbl.exit() + + # Add a gobject timer to make sure the application force-quits after a + # reasonable period. Calling quit when all the tabs haven't had time to + # close should be a last resort. + timer = "force-quit" + timerid = timeout_add(5000, self.quit, timer) + self._timers[timer] = timerid + + + def quit(self, *args): + '''Cleanup and quit. Called by delete-event signal.''' + + # Close the fifo socket, remove any gobject io event handlers and + # delete socket. + self.unlink_fifo() + self.close_socket() + + # Remove all gobject timers that are still ticking. + for (timerid, gid) in self._timers.items(): + source_remove(gid) + del self._timers[timerid] + + try: + gtk.main_quit() + + except: + pass + + +if __name__ == "__main__": + + # Build command line parser + usage = "usage: %prog [OPTIONS] {URIS}..." + parser = OptionParser(usage=usage) + parser.add_option('-n', '--no-session', dest='nosession', + action='store_true', help="ignore session saving a loading.") + parser.add_option('-v', '--verbose', dest='verbose', + action='store_true', help='print verbose output.') + + # Parse command line options + (options, uris) = parser.parse_args() + + if options.nosession: + config['save_session'] = False + + if options.verbose: + config['verbose'] = True + + if config['json_session']: + try: + import simplejson as json + + except: + error("Warning: json_session set but cannot import the python "\ + "module simplejson. Fix: \"set json_session = 0\" or "\ + "install the simplejson python module to remove this warning.") + config['json_session'] = False + + if config['verbose']: + import pprint + sys.stderr.write("%s\n" % pprint.pformat(config)) + + uzbl = UzblTabbed() + + # All extra arguments given to uzbl_tabbed.py are interpreted as + # web-locations to opened in new tabs. + lasturi = len(uris)-1 + for (index,uri) in enumerate(uris): + uzbl.new_tab(uri, switch=(index==lasturi)) + + uzbl.run() diff --git a/examples/data/scripts/uzblcat b/examples/data/scripts/uzblcat new file mode 100755 index 0000000..e955608 --- /dev/null +++ b/examples/data/scripts/uzblcat @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# uzblcat - safely push html to uzbl +# See http://www.uzbl.org/wiki/html-mode + +from sys import stdin, stdout + +stdout.write("uri data:text/html,") +for line in stdin: + stdout.write(line[0:-1]) + +# vim: set noet ff=unix + diff --git a/examples/data/style.css b/examples/data/style.css new file mode 100644 index 0000000..f9b111e --- /dev/null +++ b/examples/data/style.css @@ -0,0 +1,25 @@ +.uzbl_highlight { background-color: yellow;} +.uzbl_h_first { background-color: lightgreen;} + +.uzbl_follow { border-style: dotted; + border-width: thin; +} + +#uzbl_hint > div { + display: inline; + border: 2px solid #4a6600; + background-color: #b9ff00; + color: black; + font-size: 9px; + font-weight: bold; + line-height: 9px; + margin: 0px; + padding: 0px; + position: absolute; + z-index: 1000; + -webkit-border-radius: 6px; + text-decoration: none; + -wekit-transform: scale(1) rotate(0deg) translate(-6px,-5px); +} + +/* vim:set et ts=4: */ diff --git a/examples/data/uzbl.png b/examples/data/uzbl.png new file mode 100644 index 0000000..773ea84 Binary files /dev/null and b/examples/data/uzbl.png differ diff --git a/examples/data/uzbl/bookmarks b/examples/data/uzbl/bookmarks deleted file mode 100644 index 13fcd48..0000000 --- a/examples/data/uzbl/bookmarks +++ /dev/null @@ -1,4 +0,0 @@ -http://www.archlinux.org linux arch -http://www.uzbl.org uzbl browser -http://dieter.plaetinck.be uzbl -http://www.icanhascheezburger.com lolcats fun diff --git a/examples/data/uzbl/forms/bbs.archlinux.org b/examples/data/uzbl/forms/bbs.archlinux.org deleted file mode 100644 index 73c1539..0000000 --- a/examples/data/uzbl/forms/bbs.archlinux.org +++ /dev/null @@ -1,5 +0,0 @@ -form_sent: -redirect_url: -req_username: -req_password: -login: diff --git a/examples/data/uzbl/plugins/bind.py b/examples/data/uzbl/plugins/bind.py deleted file mode 100644 index 9e09337..0000000 --- a/examples/data/uzbl/plugins/bind.py +++ /dev/null @@ -1,521 +0,0 @@ -'''Plugin provides support for binds in uzbl. - -For example: - event BIND ZZ = exit -> bind('ZZ', 'exit') - event BIND o _ = uri %s -> bind('o _', 'uri %s') - event BIND fl* = sh 'echo %s' -> bind('fl*', "sh 'echo %s'") - -And it is also possible to execute a function on activation: - bind('DD', myhandler) -''' - -import sys -import re -import pprint - -# Hold the bind dicts for each uzbl instance. -UZBLS = {} - -# Commonly used regular expressions. -MOD_START = re.compile('^<([A-Z][A-Za-z0-9-_]*)>').match -# Matches , <'x':y>, <:'y'>, , <'x'!y>, ... -PROMPTS = '<(\"[^\"]*\"|\'[^\']*\'|[^:!>]*)(:|!)(\"[^\"]*\"|\'[^\']*\'|[^>]*)>' -FIND_PROMPTS = re.compile(PROMPTS).split -VALID_MODE = re.compile('^(-|)[A-Za-z0-9][A-Za-z0-9_]*$').match - -# For accessing a bind glob stack. -ON_EXEC, HAS_ARGS, MOD_CMD, GLOB, MORE = range(5) - - -# Custom errors. -class ArgumentError(Exception): pass - - -class Bindlet(object): - '''Per-instance bind status/state tracker.''' - - def __init__(self, uzbl): - self.binds = {'global': {}} - self.uzbl = uzbl - self.depth = 0 - self.args = [] - self.last_mode = None - self.after_cmds = None - self.stack_binds = [] - - # A subset of the global mode binds containing non-stack and modkey - # activiated binds for use in the stack mode. - self.globals = [] - - - def __getitem__(self, key): - return self.get_binds(key) - - - def reset(self): - '''Reset the tracker state and return to last mode.''' - - self.depth = 0 - self.args = [] - self.after_cmds = None - self.stack_binds = [] - - if self.last_mode: - mode, self.last_mode = self.last_mode, None - self.uzbl.set_mode(mode) - - self.uzbl.set('keycmd_prompt') - - - def stack(self, bind, args, depth): - '''Enter or add new bind in the next stack level.''' - - if self.depth != depth: - if bind not in self.stack_binds: - self.stack_binds.append(bind) - - return - - current_mode = self.uzbl.get_mode() - if current_mode != 'stack': - self.last_mode = current_mode - self.uzbl.set_mode('stack') - - self.stack_binds = [bind,] - self.args += args - self.depth += 1 - self.after_cmds = bind.prompts[depth] - - - def after(self): - '''If a stack was triggered then set the prompt and default value.''' - - if self.after_cmds is None: - return - - (prompt, is_cmd, set), self.after_cmds = self.after_cmds, None - - self.uzbl.clear_keycmd() - if prompt: - self.uzbl.set('keycmd_prompt', prompt) - - if set and is_cmd: - self.uzbl.send(set) - - elif set and not is_cmd: - self.uzbl.send('event SET_KEYCMD %s' % set) - - - def get_binds(self, mode=None): - '''Return the mode binds + globals. If we are stacked then return - the filtered stack list and modkey & non-stack globals.''' - - if mode is None: - mode = self.uzbl.get_mode() - - if not mode: - mode = 'global' - - if self.depth: - return self.stack_binds + self.globals - - globals = self.binds['global'] - if mode not in self.binds or mode == 'global': - return filter(None, globals.values()) - - binds = dict(globals.items() + self.binds[mode].items()) - return filter(None, binds.values()) - - - def add_bind(self, mode, glob, bind=None): - '''Insert (or override) a bind into the mode bind dict.''' - - if mode not in self.binds: - self.binds[mode] = {glob: bind} - return - - binds = self.binds[mode] - binds[glob] = bind - - if mode == 'global': - # Regen the global-globals list. - self.globals = [] - for bind in binds.values(): - if bind is not None and bind.is_global: - self.globals.append(bind) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = Bindlet(uzbl) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_bindlet(uzbl): - '''Return the bind tracklet for the given uzbl instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def ismodbind(glob): - '''Return True if the glob specifies a modbind.''' - - return bool(MOD_START(glob)) - - -def split_glob(glob): - '''Take a string of the form "cmd _" and return a list of the - modkeys in the glob and the command.''' - - mods = set() - while True: - match = MOD_START(glob) - if not match: - break - - end = match.span()[1] - mods.add(glob[:end]) - glob = glob[end:] - - return (mods, glob) - - -def unquote(str): - '''Remove quotation marks around string.''' - - if str and str[0] == str[-1] and str[0] in ['"', "'"]: - str = str[1:-1] - - return str - - -class Bind(object): - - # Class attribute to hold the number of Bind classes created. - counter = [0,] - - def __init__(self, glob, handler, *args, **kargs): - self.is_callable = callable(handler) - self._repr_cache = None - - if not glob: - raise ArgumentError('glob cannot be blank') - - if self.is_callable: - self.function = handler - self.args = args - self.kargs = kargs - - elif kargs: - raise ArgumentError('cannot supply kargs for uzbl commands') - - elif hasattr(handler, '__iter__'): - self.commands = handler - - else: - self.commands = [handler,] + list(args) - - self.glob = glob - - # Assign unique id. - self.counter[0] += 1 - self.bid = self.counter[0] - - self.split = split = FIND_PROMPTS(glob) - self.prompts = [] - for (prompt, cmd, set) in zip(split[1::4], split[2::4], split[3::4]): - prompt, set = map(unquote, [prompt, set]) - cmd = True if cmd == '!' else False - if prompt and prompt[-1] != ":": - prompt = "%s:" % prompt - - self.prompts.append((prompt, cmd, set)) - - # Check that there is nothing like: fl** - for glob in split[:-1:4]: - if glob.endswith('*'): - msg = "token '*' not at the end of a prompt bind: %r" % split - raise SyntaxError(msg) - - # Check that there is nothing like: fl_ - for glob in split[4::4]: - if not glob: - msg = 'found null segment after first prompt: %r' % split - raise SyntaxError(msg) - - stack = [] - for (index, glob) in enumerate(reversed(split[::4])): - # Is the binding a MODCMD or KEYCMD: - mod_cmd = ismodbind(glob) - - # Do we execute on UPDATES or EXEC events? - on_exec = True if glob[-1] in ['!', '_'] else False - - # Does the command take arguments? - has_args = True if glob[-1] in ['*', '_'] else False - - glob = glob[:-1] if has_args or on_exec else glob - mods, glob = split_glob(glob) - stack.append((on_exec, has_args, mods, glob, index)) - - self.stack = list(reversed(stack)) - self.is_global = (len(self.stack) == 1 and self.stack[0][MOD_CMD]) - - - def __getitem__(self, depth): - '''Get bind info at a depth.''' - - if self.is_global: - return self.stack[0] - - return self.stack[depth] - - - def __repr__(self): - if self._repr_cache: - return self._repr_cache - - args = ['glob=%r' % self.glob, 'bid=%d' % self.bid] - - if self.is_callable: - args.append('function=%r' % self.function) - if self.args: - args.append('args=%r' % self.args) - - if self.kargs: - args.append('kargs=%r' % self.kargs) - - else: - cmdlen = len(self.commands) - cmds = self.commands[0] if cmdlen == 1 else self.commands - args.append('command%s=%r' % ('s' if cmdlen-1 else '', cmds)) - - self._repr_cache = '' % ', '.join(args) - return self._repr_cache - - -def exec_bind(uzbl, bind, *args, **kargs): - '''Execute bind objects.''' - - uzbl.event("EXEC_BIND", bind, args, kargs) - - if bind.is_callable: - args += bind.args - kargs = dict(bind.kargs.items()+kargs.items()) - bind.function(uzbl, *args, **kargs) - return - - if kargs: - raise ArgumentError('cannot supply kargs for uzbl commands') - - commands = [] - cmd_expand = uzbl.cmd_expand - for cmd in bind.commands: - cmd = cmd_expand(cmd, args) - uzbl.send(cmd) - - -def mode_bind(uzbl, modes, glob, handler=None, *args, **kargs): - '''Add a mode bind.''' - - bindlet = get_bindlet(uzbl) - - if not hasattr(modes, '__iter__'): - modes = unicode(modes).split(',') - - # Sort and filter binds. - modes = filter(None, map(unicode.strip, modes)) - - if callable(handler) or (handler is not None and handler.strip()): - bind = Bind(glob, handler, *args, **kargs) - - else: - bind = None - - for mode in modes: - if not VALID_MODE(mode): - raise NameError('invalid mode name: %r' % mode) - - for mode in modes: - if mode[0] == '-': - mode, bind = mode[1:], None - - bindlet.add_bind(mode, glob, bind) - uzbl.event('ADDED_MODE_BIND', mode, glob, bind) - - -def bind(uzbl, glob, handler, *args, **kargs): - '''Legacy bind function.''' - - mode_bind(uzbl, 'global', glob, handler, *args, **kargs) - - -def parse_mode_bind(uzbl, args): - '''Parser for the MODE_BIND event. - - Example events: - MODE_BIND = - MODE_BIND command o_ = uri %s - MODE_BIND insert,command = ... - MODE_BIND global ... = ... - MODE_BIND global,-insert ... = ... - ''' - - if not args: - raise ArgumentError('missing bind arguments') - - split = map(unicode.strip, args.split(' ', 1)) - if len(split) != 2: - raise ArgumentError('missing mode or bind section: %r' % args) - - modes, args = split[0].split(','), split[1] - split = map(unicode.strip, args.split('=', 1)) - if len(split) != 2: - raise ArgumentError('missing delimiter in bind section: %r' % args) - - glob, command = split - mode_bind(uzbl, modes, glob, command) - - -def parse_bind(uzbl, args): - '''Legacy parsing of the BIND event and conversion to the new format. - - Example events: - request BIND = - request BIND o_ = uri %s - request BIND = ... - request BIND ... = ... - ''' - - parse_mode_bind(uzbl, "global %s" % args) - - -def mode_changed(uzbl, mode): - '''Clear the stack on all non-stack mode changes.''' - - if mode != 'stack': - get_bindlet(uzbl).reset() - - -def match_and_exec(uzbl, bind, depth, keylet, bindlet): - - (on_exec, has_args, mod_cmd, glob, more) = bind[depth] - cmd = keylet.modcmd if mod_cmd else keylet.keycmd - - if mod_cmd and keylet.held != mod_cmd: - return False - - if has_args: - if not cmd.startswith(glob): - return False - - args = [cmd[len(glob):],] - - elif cmd != glob: - return False - - else: - args = [] - - if bind.is_global or (not more and depth == 0): - exec_bind(uzbl, bind, *args) - if not has_args: - uzbl.clear_current() - - return True - - elif more: - bindlet.stack(bind, args, depth) - return False - - args = bindlet.args + args - exec_bind(uzbl, bind, *args) - uzbl.set_mode() - if not has_args: - bindlet.reset() - uzbl.clear_current() - - return True - - -def keycmd_update(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if t[MOD_CMD] or t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return - - bindlet.after() - - -def keycmd_exec(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if t[MOD_CMD] or not t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return uzbl.clear_keycmd() - - bindlet.after() - - -def modcmd_update(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if not t[MOD_CMD] or t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return - - bindlet.after() - - -def modcmd_exec(uzbl, keylet): - bindlet = get_bindlet(uzbl) - depth = bindlet.depth - for bind in bindlet.get_binds(): - t = bind[depth] - if not t[MOD_CMD] or not t[ON_EXEC]: - continue - - if match_and_exec(uzbl, bind, depth, keylet, bindlet): - return uzbl.clear_modcmd() - - bindlet.after() - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'BIND': parse_bind, - 'KEYCMD_EXEC': keycmd_exec, - 'KEYCMD_UPDATE': keycmd_update, - 'MODCMD_EXEC': modcmd_exec, - 'MODCMD_UPDATE': modcmd_update, - 'MODE_BIND': parse_mode_bind, - 'MODE_CHANGED': mode_changed, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'bind': bind, - 'mode_bind': mode_bind, - 'get_bindlet': get_bindlet, - }) diff --git a/examples/data/uzbl/plugins/cmd_expand.py b/examples/data/uzbl/plugins/cmd_expand.py deleted file mode 100644 index 3f6ae2b..0000000 --- a/examples/data/uzbl/plugins/cmd_expand.py +++ /dev/null @@ -1,42 +0,0 @@ -def escape(str): - for (level, char) in [(3, '\\'), (2, "'"), (2, '"'), (1, '@')]: - str = str.replace(char, (level * '\\') + char) - - return str - - -def cmd_expand(uzbl, cmd, args): - '''Exports a function that provides the following - expansions in any uzbl command string: - - %s = replace('%s', ' '.join(args)) - %r = replace('%r', "'%s'" % escaped(' '.join(args))) - %1 = replace('%1', arg[0]) - %2 = replace('%2', arg[1]) - %n = replace('%n', arg[n-1]) - ''' - - # Ensure (1) all string representable and (2) correct string encoding. - args = map(unicode, args) - - # Direct string replace. - if '%s' in cmd: - cmd = cmd.replace('%s', ' '.join(args)) - - # Escaped and quoted string replace. - if '%r' in cmd: - cmd = cmd.replace('%r', "'%s'" % escape(' '.join(args))) - - # Arg index string replace. - for (index, arg) in enumerate(args): - index += 1 - if '%%%d' % index in cmd: - cmd = cmd.replace('%%%d' % index, unicode(arg)) - - return cmd - - -def init(uzbl): - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export('cmd_expand', cmd_expand) diff --git a/examples/data/uzbl/plugins/completion.py b/examples/data/uzbl/plugins/completion.py deleted file mode 100644 index 8cea203..0000000 --- a/examples/data/uzbl/plugins/completion.py +++ /dev/null @@ -1,206 +0,0 @@ -'''Keycmd completion.''' - -# A list of functions this plugin exports to be used via uzbl object. -__export__ = ['start_completion', 'get_completion_dict'] - -import re - -# Holds the per-instance completion dicts. -UZBLS = {} - -# Completion level -NONE, ONCE, LIST, COMPLETE = range(4) - -# Default instance dict. -DEFAULTS = {'completions': [], 'level': NONE, 'lock': False} - -# The reverse keyword finding re. -FIND_SEGMENT = re.compile("(\@[\w_]+|set[\s]+[\w_]+|[\w_]+)$").findall - -# Formats -LIST_FORMAT = " %s " -ITEM_FORMAT = "%s%s" - - -def escape(str): - return str.replace("@", "\@") - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - # Make sure the config keys for all possible completions are known. - uzbl.send('dump_config_as_events') - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_completion_dict(uzbl): - '''Get data stored for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def get_incomplete_keyword(uzbl): - '''Gets the segment of the keycmd leading up to the cursor position and - uses a regular expression to search backwards finding parially completed - keywords or @variables. Returns a null string if the correct completion - conditions aren't met.''' - - keylet = uzbl.get_keylet() - left_segment = keylet.keycmd[:keylet.cursor] - partial = (FIND_SEGMENT(left_segment) + ['',])[0].lstrip() - if partial.startswith('set '): - return ('@%s' % partial[4:].lstrip(), True) - - return (partial, False) - - -def stop_completion(uzbl, *args): - '''Stop command completion and return the level to NONE.''' - - d = get_completion_dict(uzbl) - d['level'] = NONE - uzbl.set('completion_list') - - -def complete_completion(uzbl, partial, hint, set_completion=False): - '''Inject the remaining porition of the keyword into the keycmd then stop - the completioning.''' - - if set_completion: - remainder = "%s = " % hint[len(partial):] - - else: - remainder = "%s " % hint[len(partial):] - - uzbl.inject_keycmd(remainder) - stop_completion(uzbl) - - -def partial_completion(uzbl, partial, hint): - '''Inject a common portion of the hints into the keycmd.''' - - remainder = hint[len(partial):] - uzbl.inject_keycmd(remainder) - - -def update_completion_list(uzbl, *args): - '''Checks if the user still has a partially completed keyword under his - cursor then update the completion hints list.''' - - partial = get_incomplete_keyword(uzbl)[0] - if not partial: - return stop_completion(uzbl) - - d = get_completion_dict(uzbl) - if d['level'] < LIST: - return - - hints = [h for h in d['completions'] if h.startswith(partial)] - if not hints: - return uzbl.set('completion_list') - - j = len(partial) - l = [ITEM_FORMAT % (escape(h[:j]), h[j:]) for h in sorted(hints)] - uzbl.set('completion_list', LIST_FORMAT % ' '.join(l)) - - -def start_completion(uzbl, *args): - - d = get_completion_dict(uzbl) - if d['lock']: - return - - (partial, set_completion) = get_incomplete_keyword(uzbl) - if not partial: - return stop_completion(uzbl) - - if d['level'] < COMPLETE: - d['level'] += 1 - - hints = [h for h in d['completions'] if h.startswith(partial)] - if not hints: - return - - elif len(hints) == 1: - d['lock'] = True - complete_completion(uzbl, partial, hints[0], set_completion) - d['lock'] = False - return - - elif partial in hints and d['level'] == COMPLETE: - d['lock'] = True - complete_completion(uzbl, partial, partial, set_completion) - d['lock'] = False - return - - smalllen, smallest = sorted([(len(h), h) for h in hints])[0] - common = '' - for i in range(len(partial), smalllen): - char, same = smallest[i], True - for hint in hints: - if hint[i] != char: - same = False - break - - if not same: - break - - common += char - - if common: - d['lock'] = True - partial_completion(uzbl, partial, partial+common) - d['lock'] = False - - update_completion_list(uzbl) - - -def add_builtins(uzbl, args): - '''Pump the space delimited list of builtin commands into the - builtin list.''' - - completions = get_completion_dict(uzbl)['completions'] - builtins = filter(None, map(unicode.strip, args.split(" "))) - for builtin in builtins: - if builtin not in completions: - completions.append(builtin) - - -def add_config_key(uzbl, key, value): - '''Listen on the CONFIG_CHANGED event and add config keys to the variable - list for @var like expansion support.''' - - completions = get_completion_dict(uzbl)['completions'] - key = "@%s" % key - if key not in completions: - completions.append(key) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'BUILTINS': add_builtins, - 'CONFIG_CHANGED': add_config_key, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'KEYCMD_CLEARED': stop_completion, - 'KEYCMD_EXEC': stop_completion, - 'KEYCMD_UPDATE': update_completion_list, - 'START_COMPLETION': start_completion, - 'STOP_COMPLETION': stop_completion, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_completion_dict': get_completion_dict, - 'start_completion': start_completion, - }) diff --git a/examples/data/uzbl/plugins/config.py b/examples/data/uzbl/plugins/config.py deleted file mode 100644 index 4a848a3..0000000 --- a/examples/data/uzbl/plugins/config.py +++ /dev/null @@ -1,97 +0,0 @@ -import re -import types - -__export__ = ['set', 'get_config'] - -VALIDKEY = re.compile("^[a-zA-Z][a-zA-Z0-9_]*$").match -TYPECONVERT = {'int': int, 'float': float, 'str': unicode} - -UZBLS = {} - - -def escape(value): - '''A real escaping function may be required.''' - - return unicode(value) - - -def set(uzbl, key, value='', config=None, force=False): - '''Sends a: "set key = value" command to the uzbl instance. If force is - False then only send a set command if the values aren't equal.''' - - if type(value) == types.BooleanType: - value = int(value) - - else: - value = unicode(value) - - if not VALIDKEY(key): - raise KeyError("%r" % key) - - value = escape(value) - if '\n' in value: - value = value.replace("\n", "\\n") - - if not force: - if config is None: - config = get_config(uzbl) - - if key in config and config[key] == value: - return - - uzbl.send('set %s = %s' % (key, value)) - - -class ConfigDict(dict): - def __init__(self, uzbl): - self._uzbl = uzbl - - def __setitem__(self, key, value): - '''Makes "config[key] = value" a wrapper for the set function.''' - - set(self._uzbl, key, value, config=self) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = ConfigDict(uzbl) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del uzbl - - -def get_config(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def variable_set(uzbl, args): - config = get_config(uzbl) - - key, type, value = list(args.split(' ', 2) + ['',])[:3] - old = config[key] if key in config else None - value = TYPECONVERT[type](value) - - dict.__setitem__(config, key, value) - - if old != value: - uzbl.event("CONFIG_CHANGED", key, value) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'VARIABLE_SET': variable_set, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_config': get_config, - 'set': set, - }) diff --git a/examples/data/uzbl/plugins/keycmd.py b/examples/data/uzbl/plugins/keycmd.py deleted file mode 100644 index c119077..0000000 --- a/examples/data/uzbl/plugins/keycmd.py +++ /dev/null @@ -1,571 +0,0 @@ -import re - -# Hold the keylets. -UZBLS = {} - -# Keycmd format which includes the markup for the cursor. -KEYCMD_FORMAT = "%s%s%s" -MODCMD_FORMAT = " %s " - - -def escape(str): - for char in ['\\', '@']: - str = str.replace(char, '\\'+char) - - return str - - -def uzbl_escape(str): - return "@[%s]@" % escape(str) if str else '' - - -class Keylet(object): - '''Small per-instance object that tracks all the keys held and characters - typed.''' - - def __init__(self): - # Modcmd tracking - self.held = set() - self.ignored = set() - self.modcmd = '' - self.is_modcmd = False - - # Keycmd tracking - self.keycmd = '' - self.cursor = 0 - - self.modmaps = {} - self.ignores = {} - self.additions = {} - - # Keylet string repr cache. - self._repr_cache = None - - - def get_keycmd(self): - '''Get the keycmd-part of the keylet.''' - - return self.keycmd - - - def get_modcmd(self): - '''Get the modcmd-part of the keylet.''' - - if not self.is_modcmd: - return '' - - return ''.join(self.held) + self.modcmd - - - def modmap_key(self, key): - '''Make some obscure names for some keys friendlier.''' - - if key in self.modmaps: - return self.modmaps[key] - - elif key.endswith('_L') or key.endswith('_R'): - # Remove left-right discrimination and try again. - return self.modmap_key(key[:-2]) - - else: - return key - - - def find_addition(self, modkey): - '''Key has just been pressed, check if this key + the held list - results in a modkey addition. Return that addition and remove all - modkeys that created it.''' - - # Intersection of (held list + modkey) and additions. - added = (self.held | set([modkey])) & set(self.additions.keys()) - for key in added: - if key == modkey or modkey in self.additions[key]: - self.held -= self.additions[key] - return key - - # Held list + ignored list + modkey. - modkeys = self.held | self.ignored | set([modkey]) - for (key, value) in self.additions.items(): - if modkeys.issuperset(value): - self.held -= value - return key - - return modkey - - - def key_ignored(self, key): - '''Check if the given key is ignored by any ignore rules.''' - - for (glob, match) in self.ignores.items(): - if match(key): - return True - - return False - - - def __repr__(self): - '''Return a string representation of the keylet.''' - - if self._repr_cache: - return self._repr_cache - - l = [] - if self.is_modcmd: - l.append('modcmd=%r' % self.get_modcmd()) - - elif self.held: - l.append('held=%r' % ''.join(sorted(self.held))) - - if self.keycmd: - l.append('keycmd=%r' % self.get_keycmd()) - - self._repr_cache = '' % ', '.join(l) - return self._repr_cache - - -def add_modmap(uzbl, key, map): - '''Add modmaps. - - Examples: - set modmap = request MODMAP - @modmap - @modmap - ... - - Then: - @bind = - @bind x = - ... - - ''' - - assert len(key) - modmaps = get_keylet(uzbl).modmaps - - if key[0] == "<" and key[-1] == ">": - key = key[1:-1] - - modmaps[key] = map - uzbl.event("NEW_MODMAP", key, map) - - -def modmap_parse(uzbl, map): - '''Parse a modmap definiton.''' - - split = [s.strip() for s in map.split(' ') if s.split()] - - if not split or len(split) > 2: - raise Exception('Invalid modmap arugments: %r' % map) - - add_modmap(uzbl, *split) - - -def add_key_ignore(uzbl, glob): - '''Add an ignore definition. - - Examples: - set ignore_key = request IGNORE_KEY - @ignore_key - @ignore_key - ... - ''' - - assert len(glob) > 1 - ignores = get_keylet(uzbl).ignores - - glob = "<%s>" % glob.strip("<> ") - restr = glob.replace('*', '[^\s]*') - match = re.compile(restr).match - - ignores[glob] = match - uzbl.event('NEW_KEY_IGNORE', glob) - - -def add_modkey_addition(uzbl, modkeys, result): - '''Add a modkey addition definition. - - Examples: - set mod_addition = request MODKEY_ADDITION - @mod_addition - @mod_addition - @mod_addition - ... - - Then: - @bind = - @bind o = - ... - ''' - - additions = get_keylet(uzbl).additions - modkeys = set(modkeys) - - assert len(modkeys) and result and result not in modkeys - - for (existing_result, existing_modkeys) in additions.items(): - if existing_result != result: - assert modkeys != existing_modkeys - - additions[result] = modkeys - uzbl.event('NEW_MODKEY_ADDITION', modkeys, result) - - -def modkey_addition_parse(uzbl, modkeys): - '''Parse modkey addition definition.''' - - keys = filter(None, map(unicode.strip, modkeys.split(" "))) - keys = ['<%s>' % key.strip("<>") for key in keys if key.strip("<>")] - - assert len(keys) > 1 - add_modkey_addition(uzbl, keys[:-1], keys[-1]) - - -def add_instance(uzbl, *args): - '''Create the Keylet object for this uzbl instance.''' - - UZBLS[uzbl] = Keylet() - - -def del_instance(uzbl, *args): - '''Delete the Keylet object for this uzbl instance.''' - - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_keylet(uzbl): - '''Return the corresponding keylet for this uzbl instance.''' - - # Startup events are not correctly captured and sent over the uzbl socket - # yet so this line is needed because the INSTANCE_START event is lost. - if uzbl not in UZBLS: - add_instance(uzbl) - - keylet = UZBLS[uzbl] - keylet._repr_cache = False - return keylet - - -def clear_keycmd(uzbl): - '''Clear the keycmd for this uzbl instance.''' - - k = get_keylet(uzbl) - k.keycmd = '' - k.cursor = 0 - k._repr_cache = False - uzbl.set('keycmd') - uzbl.set('raw_keycmd') - uzbl.event('KEYCMD_CLEARED') - - -def clear_modcmd(uzbl, clear_held=False): - '''Clear the modcmd for this uzbl instance.''' - - k = get_keylet(uzbl) - k.modcmd = '' - k.is_modcmd = False - k._repr_cache = False - if clear_held: - k.ignored = set() - k.held = set() - - uzbl.set('modcmd') - uzbl.set('raw_modcmd') - uzbl.event('MODCMD_CLEARED') - - -def clear_current(uzbl): - '''Clear the modcmd if is_modcmd else clear keycmd.''' - - k = get_keylet(uzbl) - if k.is_modcmd: - clear_modcmd(uzbl) - - else: - clear_keycmd(uzbl) - - -def focus_changed(uzbl, *args): - '''Focus to the uzbl instance has now been lost which means all currently - held keys in the held list will not get a KEY_RELEASE event so clear the - entire held list.''' - - clear_modcmd(uzbl, clear_held=True) - - -def update_event(uzbl, k, execute=True): - '''Raise keycmd & modcmd update events.''' - - config = uzbl.get_config() - keycmd, modcmd = k.get_keycmd(), k.get_modcmd() - - if k.is_modcmd: - uzbl.event('MODCMD_UPDATE', k) - - else: - uzbl.event('KEYCMD_UPDATE', k) - - if 'modcmd_updates' not in config or config['modcmd_updates'] == '1': - new_modcmd = k.get_modcmd() - if not new_modcmd: - uzbl.set('modcmd', config=config) - uzbl.set('raw_modcmd', config=config) - - elif new_modcmd == modcmd: - uzbl.set('raw_modcmd', escape(modcmd), config=config) - uzbl.set('modcmd', MODCMD_FORMAT % uzbl_escape(modcmd), - config=config) - - if 'keycmd_events' in config and config['keycmd_events'] != '1': - return - - new_keycmd = k.get_keycmd() - if not new_keycmd: - uzbl.set('keycmd', config=config) - uzbl.set('raw_keycmd', config=config) - - elif new_keycmd == keycmd: - # Generate the pango markup for the cursor in the keycmd. - curchar = keycmd[k.cursor] if k.cursor < len(keycmd) else ' ' - chunks = [keycmd[:k.cursor], curchar, keycmd[k.cursor+1:]] - value = KEYCMD_FORMAT % tuple(map(uzbl_escape, chunks)) - uzbl.set('keycmd', value, config=config) - uzbl.set('raw_keycmd', escape(keycmd), config=config) - - -def inject_str(str, index, inj): - '''Inject a string into string at at given index.''' - - return "%s%s%s" % (str[:index], inj, str[index:]) - - -def get_keylet_and_key(uzbl, key, add=True): - '''Return the keylet and apply any transformations to the key as defined - by the modmapping or modkey addition rules. Return None if the key is - ignored.''' - - keylet = get_keylet(uzbl) - key = keylet.modmap_key(key) - if len(key) == 1: - return (keylet, key) - - modkey = "<%s>" % key.strip("<>") - - if keylet.key_ignored(modkey): - if add: - keylet.ignored.add(modkey) - - elif modkey in keylet.ignored: - keylet.ignored.remove(modkey) - - modkey = keylet.find_addition(modkey) - - if keylet.key_ignored(modkey): - return (keylet, None) - - return (keylet, modkey) - - -def key_press(uzbl, key): - '''Handle KEY_PRESS events. Things done by this function include: - - 1. Ignore all shift key presses (shift can be detected by capital chars) - 3. In non-modcmd mode: - a. append char to keycmd - 4. If not in modcmd mode and a modkey was pressed set modcmd mode. - 5. If in modcmd mode the pressed key is added to the held keys list. - 6. Keycmd is updated and events raised if anything is changed.''' - - (k, key) = get_keylet_and_key(uzbl, key.strip()) - if not key: - return - - if key.lower() == '' and not k.held and k.keycmd: - k.keycmd = inject_str(k.keycmd, k.cursor, ' ') - k.cursor += 1 - - elif not k.held and len(key) == 1: - config = uzbl.get_config() - if 'keycmd_events' in config and config['keycmd_events'] != '1': - k.keycmd = '' - k.cursor = 0 - uzbl.set('keycmd', config=config) - uzbl.set('raw_keycmd', config=config) - return - - k.keycmd = inject_str(k.keycmd, k.cursor, key) - k.cursor += 1 - - elif len(key) > 1: - k.is_modcmd = True - if key not in k.held: - k.held.add(key) - - else: - k.is_modcmd = True - k.modcmd += key - - update_event(uzbl, k) - - -def key_release(uzbl, key): - '''Respond to KEY_RELEASE event. Things done by this function include: - - 1. Remove the key from the keylet held list. - 2. If in a mod-command then raise a MODCMD_EXEC. - 3. Check if any modkey is held, if so set modcmd mode. - 4. Update the keycmd uzbl variable if anything changed.''' - - (k, key) = get_keylet_and_key(uzbl, key.strip(), add=False) - - if key in k.held: - if k.is_modcmd: - uzbl.event('MODCMD_EXEC', k) - - k.held.remove(key) - clear_modcmd(uzbl) - - -def set_keycmd(uzbl, keycmd): - '''Allow setting of the keycmd externally.''' - - k = get_keylet(uzbl) - k.keycmd = keycmd - k._repr_cache = None - k.cursor = len(keycmd) - update_event(uzbl, k, False) - - -def inject_keycmd(uzbl, keycmd): - '''Allow injecting of a string into the keycmd at the cursor position.''' - - k = get_keylet(uzbl) - k.keycmd = inject_str(k.keycmd, k.cursor, keycmd) - k._repr_cache = None - k.cursor += len(keycmd) - update_event(uzbl, k, False) - - -def append_keycmd(uzbl, keycmd): - '''Allow appening of a string to the keycmd.''' - - k = get_keylet(uzbl) - k.keycmd += keycmd - k._repr_cache = None - k.cursor = len(k.keycmd) - update_event(uzbl, k, False) - - -def keycmd_strip_word(uzbl, sep): - ''' Removes the last word from the keycmd, similar to readline ^W ''' - - sep = sep or ' ' - k = get_keylet(uzbl) - if not k.keycmd: - return - - head, tail = k.keycmd[:k.cursor].rstrip(sep), k.keycmd[k.cursor:] - rfind = head.rfind(sep) - head = head[:rfind] if rfind + 1 else '' - k.keycmd = head + tail - k.cursor = len(head) - update_event(uzbl, k, False) - - -def keycmd_backspace(uzbl, *args): - '''Removes the character at the cursor position in the keycmd.''' - - k = get_keylet(uzbl) - if not k.keycmd: - return - - k.keycmd = k.keycmd[:k.cursor-1] + k.keycmd[k.cursor:] - k.cursor -= 1 - update_event(uzbl, k, False) - - -def keycmd_delete(uzbl, *args): - '''Removes the character after the cursor position in the keycmd.''' - - k = get_keylet(uzbl) - if not k.keycmd: - return - - k.keycmd = k.keycmd[:k.cursor] + k.keycmd[k.cursor+1:] - update_event(uzbl, k, False) - - -def keycmd_exec_current(uzbl, *args): - '''Raise a KEYCMD_EXEC with the current keylet and then clear the - keycmd.''' - - k = get_keylet(uzbl) - uzbl.event('KEYCMD_EXEC', k) - clear_keycmd(uzbl) - - -def set_cursor_pos(uzbl, index): - '''Allow setting of the cursor position externally. Supports negative - indexing and relative stepping with '+' and '-'.''' - - k = get_keylet(uzbl) - if index == '-': - cursor = k.cursor - 1 - - elif index == '+': - cursor = k.cursor + 1 - - else: - cursor = int(index.strip()) - if cursor < 0: - cursor = len(k.keycmd) + cursor + 1 - - if cursor < 0: - cursor = 0 - - if cursor > len(k.keycmd): - cursor = len(k.keycmd) - - k.cursor = cursor - update_event(uzbl, k, False) - - -def init(uzbl): - '''Connect handlers to uzbl events.''' - - # Event handling hooks. - uzbl.connect_dict({ - 'APPEND_KEYCMD': append_keycmd, - 'FOCUS_GAINED': focus_changed, - 'FOCUS_LOST': focus_changed, - 'IGNORE_KEY': add_key_ignore, - 'INJECT_KEYCMD': inject_keycmd, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'KEYCMD_BACKSPACE': keycmd_backspace, - 'KEYCMD_DELETE': keycmd_delete, - 'KEYCMD_EXEC_CURRENT': keycmd_exec_current, - 'KEYCMD_STRIP_WORD': keycmd_strip_word, - 'KEY_PRESS': key_press, - 'KEY_RELEASE': key_release, - 'MODKEY_ADDITION': modkey_addition_parse, - 'MODMAP': modmap_parse, - 'SET_CURSOR_POS': set_cursor_pos, - 'SET_KEYCMD': set_keycmd, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'add_key_ignore': add_key_ignore, - 'add_modkey_addition': add_modkey_addition, - 'add_modmap': add_modmap, - 'append_keycmd': append_keycmd, - 'clear_current': clear_current, - 'clear_keycmd': clear_keycmd, - 'clear_modcmd': clear_modcmd, - 'get_keylet': get_keylet, - 'inject_keycmd': inject_keycmd, - 'set_cursor_pos': set_cursor_pos, - 'set_keycmd': set_keycmd, - }) diff --git a/examples/data/uzbl/plugins/mode.py b/examples/data/uzbl/plugins/mode.py deleted file mode 100644 index 54d865a..0000000 --- a/examples/data/uzbl/plugins/mode.py +++ /dev/null @@ -1,176 +0,0 @@ -import sys -import re - -__export__ = ['set_mode', 'get_mode', 'set_mode_config', 'get_mode_config'] - -UZBLS = {} - -DEFAULTS = { - 'mode': '', - 'modes': { - 'insert': { - 'forward_keys': True, - 'keycmd_events': False, - 'modcmd_updates': False, - 'mode_indicator': 'I'}, - 'command': { - 'forward_keys': False, - 'keycmd_events': True, - 'modcmd_updates': True, - 'mode_indicator': 'C'}}} - -FINDSPACES = re.compile("\s+") -VALID_KEY = re.compile("^[\w_]+$").match - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_mode_dict(uzbl): - '''Return the mode dict for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def get_mode_config(uzbl, mode): - '''Return the mode config for a given mode.''' - - modes = get_mode_dict(uzbl)['modes'] - if mode not in modes: - modes[mode] = {} - - return modes[mode] - - -def get_mode(uzbl): - return get_mode_dict(uzbl)['mode'] - - -def mode_changed(uzbl, mode): - '''The mode has just been changed, now set the per-mode config.''' - - if get_mode(uzbl) != mode: - return - - config = uzbl.get_config() - mode_config = get_mode_config(uzbl, mode) - for (key, value) in mode_config.items(): - uzbl.set(key, value, config=config) - - if 'mode_indicator' not in mode_config: - config['mode_indicator'] = mode - - uzbl.clear_keycmd() - uzbl.clear_modcmd() - - -def set_mode(uzbl, mode=None): - '''Set the mode and raise the MODE_CHANGED event if the mode has changed. - Fallback on the default mode if no mode argument was given and the default - mode is not null.''' - - config = uzbl.get_config() - mode_dict = get_mode_dict(uzbl) - if mode is None: - mode_dict['mode'] = '' - if 'default_mode' in config: - mode = config['default_mode'] - - else: - mode = 'command' - - if not VALID_KEY(mode): - raise KeyError("invalid mode name: %r" % mode) - - if 'mode' not in config or config['mode'] != mode: - config['mode'] = mode - - elif mode_dict['mode'] != mode: - mode_dict['mode'] = mode - uzbl.event("MODE_CHANGED", mode) - - -def config_changed(uzbl, key, value): - '''Check for mode related config changes.''' - - value = None if not value else value - if key == 'default_mode': - if not get_mode(uzbl): - set_mode(uzbl, value) - - elif key == 'mode': - set_mode(uzbl, value) - - -def set_mode_config(uzbl, mode, key, value): - '''Set mode specific configs. If the mode being modified is the current - mode then apply the changes on the go.''' - - assert VALID_KEY(mode) and VALID_KEY(key) - - mode_config = get_mode_config(uzbl, mode) - mode_config[key] = value - - if get_mode(uzbl) == mode: - uzbl.set(key, value) - - -def mode_config(uzbl, args): - '''Parse mode config events.''' - - split = map(unicode.strip, FINDSPACES.split(args.lstrip(), 1)) - if len(split) != 2: - raise SyntaxError('invalid mode config syntax: %r' % args) - - mode, set = split - split = map(unicode.strip, set.split('=', 1)) - if len(split) != 2: - raise SyntaxError('invalid set syntax: %r' % args) - - key, value = split - set_mode_config(uzbl, mode, key, value) - - -def toggle_modes(uzbl, modes): - '''Toggle or cycle between or through a list of modes.''' - - assert len(modes.strip()) - - modelist = filter(None, map(unicode.strip, modes.split(' '))) - mode = get_mode(uzbl) - - index = 0 - if mode in modelist: - index = (modelist.index(mode)+1) % len(modelist) - - set_mode(uzbl, modelist[index]) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'CONFIG_CHANGED': config_changed, - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'MODE_CHANGED': mode_changed, - 'MODE_CONFIG': mode_config, - 'TOGGLE_MODES': toggle_modes, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_mode': get_mode, - 'get_mode_config': get_mode_config, - 'set_mode': set_mode, - 'set_mode_config': set_mode_config, - }) diff --git a/examples/data/uzbl/plugins/on_event.py b/examples/data/uzbl/plugins/on_event.py deleted file mode 100644 index b9c504a..0000000 --- a/examples/data/uzbl/plugins/on_event.py +++ /dev/null @@ -1,107 +0,0 @@ -'''Plugin provides arbitrary binding of uzbl events to uzbl commands. - -Formatting options: - %s = space separated string of the arguments - %r = escaped and quoted version of %s - %1 = argument 1 - %2 = argument 2 - %n = argument n - -Usage: - request ON_EVENT LINK_HOVER set selected_uri = $1 - --> LINK_HOVER http://uzbl.org/ - <-- set selected_uri = http://uzbl.org/ - - request ON_EVENT CONFIG_CHANGED print Config changed: %1 = %2 - --> CONFIG_CHANGED selected_uri http://uzbl.org/ - <-- print Config changed: selected_uri = http://uzbl.org/ -''' - -import sys -import re - -__export__ = ['get_on_events', 'on_event'] - -UZBLS = {} - - -def error(msg): - sys.stderr.write('on_event plugin: error: %s\n' % msg) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = {} - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_on_events(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def event_handler(uzbl, *args, **kargs): - '''This function handles all the events being watched by various - on_event definitions and responds accordingly.''' - - events = get_on_events(uzbl) - event = kargs['on_event'] - if event not in events: - return - - commands = events[event] - cmd_expand = uzbl.cmd_expand - for cmd in commands: - cmd = cmd_expand(cmd, args) - uzbl.send(cmd) - - -def on_event(uzbl, event, cmd): - '''Add a new event to watch and respond to.''' - - event = event.upper() - events = get_on_events(uzbl) - if event not in events: - uzbl.connect(event, event_handler, on_event=event) - events[event] = [] - - cmds = events[event] - if cmd not in cmds: - cmds.append(cmd) - - -def parse_on_event(uzbl, args): - '''Parse ON_EVENT events and pass them to the on_event function. - - Syntax: "event ON_EVENT commands".''' - - if not args: - return error("missing on_event arguments") - - split = args.split(' ', 1) - if len(split) != 2: - return error("invalid ON_EVENT syntax: %r" % args) - - event, cmd = split - on_event(uzbl, event, cmd) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'ON_EVENT': parse_on_event, - }) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.export_dict({ - 'get_on_events': get_on_events, - 'on_event': on_event, - }) diff --git a/examples/data/uzbl/plugins/plugin_template.py b/examples/data/uzbl/plugins/plugin_template.py deleted file mode 100644 index 565a999..0000000 --- a/examples/data/uzbl/plugins/plugin_template.py +++ /dev/null @@ -1,76 +0,0 @@ -'''Plugin template.''' - -# Holds the per-instance data dict. -UZBLS = {} - -# The default instance dict. -DEFAULTS = {} - - -def add_instance(uzbl, *args): - '''Add a new instance with default config options.''' - - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - '''Delete data stored for an instance.''' - - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_myplugin_dict(uzbl): - '''Get data stored for an instance.''' - - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def myplugin_function(uzbl, *args, **kargs): - '''Custom plugin function which is exported by the __export__ list at the - top of the file for use by other functions/callbacks.''' - - print "My plugin function arguments:", args, kargs - - # Get the per-instance data object. - data = get_myplugin_dict(uzbl) - - # Function logic goes here. - - -def myplugin_event_parser(uzbl, args): - '''Parses MYPLUGIN_EVENT raised by uzbl or another plugin.''' - - print "Got MYPLUGIN_EVENT with arguments: %r" % args - - # Parsing logic goes here. - - -def init(uzbl): - '''The main function of the plugin which is used to attach all the event - hooks that are going to be used throughout the plugins life. This function - is called each time a UzblInstance() object is created in the event - manager.''' - - # Make a dictionary comprising of {"EVENT_NAME": handler, ..} to the event - # handler stack: - uzbl.connect_dict({ - # event name function - 'INSTANCE_START': add_instance, - 'INSTANCE_EXIT': del_instance, - 'MYPLUGIN_EVENT': myplugin_event_parser, - }) - - # Or connect a handler to an event manually and supply additional optional - # arguments: - #uzbl.connect("MYOTHER_EVENT", myother_event_parser, True, limit=20) - - # Function exports to the uzbl object, `function(uzbl, *args, ..)` - # becomes `uzbl.function(*args, ..)`. - uzbl.connect_dict({ - # external name function - 'myplugin_function': myplugin_function, - }) diff --git a/examples/data/uzbl/plugins/progress_bar.py b/examples/data/uzbl/plugins/progress_bar.py deleted file mode 100644 index 89ba175..0000000 --- a/examples/data/uzbl/plugins/progress_bar.py +++ /dev/null @@ -1,159 +0,0 @@ -import sys - -UZBLS = {} - -DEFAULTS = {'width': 8, - 'done': '=', - 'pending': '.', - 'format': '[%d%a%p]%c', - 'spinner': '-\\|/', - 'sprites': 'loading', - 'updates': 0, - 'progress': 100} - - -def error(msg): - sys.stderr.write("progress_bar plugin: error: %s\n" % msg) - - -def add_instance(uzbl, *args): - UZBLS[uzbl] = dict(DEFAULTS) - - -def del_instance(uzbl, *args): - if uzbl in UZBLS: - del UZBLS[uzbl] - - -def get_progress_config(uzbl): - if uzbl not in UZBLS: - add_instance(uzbl) - - return UZBLS[uzbl] - - -def update_progress(uzbl, prog=None): - '''Updates the progress_format variable on LOAD_PROGRESS update. - - The current substitution options are: - %d = done char * done - %p = pending char * remaining - %c = percent done - %i = int done - %s = -\|/ spinner - %t = percent pending - %o = int pending - %r = sprites - ''' - - prog_config = get_progress_config(uzbl) - config = uzbl.get_config() - - if prog is None: - prog = prog_config['progress'] - - else: - prog = int(prog) - prog_config['progress'] = prog - - prog_config['updates'] += 1 - format = prog_config['format'] - width = prog_config['width'] - - # Inflate the done and pending bars to stop the progress bar - # jumping around. - if '%c' in format or '%i' in format: - count = format.count('%c') + format.count('%i') - width += (3-len(str(prog))) * count - - if '%t' in format or '%o' in format: - count = format.count('%t') + format.count('%o') - width += (3-len(str(100-prog))) * count - - done = int(((prog/100.0)*width)+0.5) - pending = width - done - - if '%d' in format: - format = format.replace('%d', prog_config['done']*done) - - if '%p' in format: - format = format.replace('%p', prog_config['pending']*pending) - - if '%c' in format: - format = format.replace('%c', '%d%%' % prog) - - if '%i' in format: - format = format.replace('%i', '%d' % prog) - - if '%t' in format: - format = format.replace('%t', '%d%%' % (100-prog)) - - if '%o' in format: - format = format.replace('%o', '%d' % (100-prog)) - - if '%s' in format: - spinner = prog_config['spinner'] - spin = '-' if not spinner else spinner - index = 0 if prog == 100 else prog_config['updates'] % len(spin) - char = '\\\\' if spin[index] == '\\' else spin[index] - format = format.replace('%s', char) - - if '%r' in format: - sprites = prog_config['sprites'] - sprites = '-' if not sprites else sprites - index = int(((prog/100.0)*len(sprites))+0.5)-1 - sprite = '\\\\' if sprites[index] == '\\' else sprites[index] - format = format.replace('%r', sprite) - - if 'progress_format' not in config or config['progress_format'] != format: - config['progress_format'] = format - - -def progress_config(uzbl, args): - '''Parse PROGRESS_CONFIG events from the uzbl instance. - - Syntax: event PROGRESS_CONFIG = - ''' - - split = args.split('=', 1) - if len(split) != 2: - return error("invalid syntax: %r" % args) - - key, value = map(unicode.strip, split) - prog_config = get_progress_config(uzbl) - - if key not in prog_config: - return error("key error: %r" % args) - - if type(prog_config[key]) == type(1): - try: - value = int(value) - - except: - return error("invalid type: %r" % args) - - elif not value: - value = ' ' - - prog_config[key] = value - update_progress(uzbl) - - -def reset_progress(uzbl, args): - '''Reset the spinner counter, reset the progress int and re-draw the - progress bar on LOAD_COMMIT.''' - - prog_dict = get_progress_config(uzbl) - prog_dict['updates'] = prog_dict['progress'] = 0 - update_progress(uzbl) - - -def init(uzbl): - # Event handling hooks. - uzbl.connect_dict({ - 'INSTANCE_EXIT': del_instance, - 'INSTANCE_START': add_instance, - 'LOAD_COMMIT': reset_progress, - 'LOAD_PROGRESS': update_progress, - 'PROGRESS_CONFIG': progress_config, - }) diff --git a/examples/data/uzbl/scripts/cookies.sh b/examples/data/uzbl/scripts/cookies.sh deleted file mode 100755 index ee2ce51..0000000 --- a/examples/data/uzbl/scripts/cookies.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/sh - -set -n; - -# THIS IS EXPERIMENTAL AND COULD BE INSECURE !!!!!! - -# this is an example bash script of how you could manage your cookies. it is very raw and basic and not as good as uzbl-cookie-daemon -# we use the cookies.txt format (See http://kb.mozillazine.org/Cookies.txt) -# This is one textfile with entries like this: -# kb.mozillazine.org FALSE / FALSE 1146030396 wikiUserID 16993 -# domain alow-read-other-subdomains path http-required expiration name value -# you probably want your cookies config file in your $XDG_CONFIG_HOME ( eg $HOME/.config/uzbl/cookies) -# Note. in uzbl there is no strict definition on what a session is. it's YOUR job to clear cookies marked as end_session if you want to keep cookies only valid during a "session" -# MAYBE TODO: allow user to edit cookie before saving. this cannot be done with zenity :( -# TODO: different cookie paths per config (eg per group of uzbl instances) - -# TODO: correct implementation. -# see http://curl.haxx.se/rfc/cookie_spec.html -# http://en.wikipedia.org/wiki/HTTP_cookie - -# TODO : check expires= before sending. -# write sample script that cleans up cookies dir based on expires attribute. -# TODO: check uri against domain attribute. and path also. -# implement secure attribute. -# support blocking or not for 3rd parties -# http://kb.mozillazine.org/Cookies.txt -# don't always append cookies, sometimes we need to overwrite - -cookie_config=${XDG_CONFIG_HOME:-${HOME}/.config}/uzbl/cookies -[ "x$cookie_config" = x ] && exit 1 -[ -d "${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/" ] &&\ -cookie_data=${XDG_DATA_HOME:-${HOME}/.local/share}/uzbl/cookies.txt || exit 1 - -notifier= -#notifier=notify-send -#notify_wrapper () { -# echo "$@" >> $HOME/cookielog -#} -#notifier=notifier_wrapper - -# if this variable is set, we will use it to inform you when and which cookies we store, and when/which we send. -# it's primarily used for debugging -notifier= -which zenity &>/dev/null || exit 2 - -# Example cookie: -# test_cookie=CheckForPermission; expires=Thu, 07-May-2009 19:17:55 GMT; path=/; domain=.doubleclick.net - -# uri=$6 -# uri=${uri/http:\/\/} # strip 'http://' part -# host=${uri/\/*/} -action=$8 # GET/PUT -shift -host=$9 -shift -path=$9 -shift -cookie=$9 - -field_domain=$host -field_path=$path -field_name= -field_value= -field_exp='end_session' - -notify() { - [ -n "$notifier" ] && $notifier "$@" -} - - -# FOR NOW LETS KEEP IT SIMPLE AND JUST ALWAYS PUT AND ALWAYS GET -parse_cookie() { - IFS=$';' - first_pair=1 - for pair in $cookie - do - if [ "x$first_pair" = x1 ] - then - field_name=${pair%%=*} - field_value=${pair#*=} - first_pair=0 - else - echo "$pair" | read -r pair #strip leading/trailing wite space - key=${pair%%=*} - val=${pair#*=} - [ "$key" == expires ] && field_exp=`date -u -d "$val" +'%s'` - # TODO: domain - [ "$key" == path ] && field_path=$val - fi - done - unset IFS -} - -# match cookies in cookies.txt against hostname and path -get_cookie() { - path_esc=${path//\//\\/} - search="^[^\t]*$host\t[^\t]*\t$path_esc" - cookie=`awk "/$search/" $cookie_data 2>/dev/null | tail -n 1` - if [ -z "$cookie" ] - then - notify "Get_cookie: search: $search in $cookie_data -> no result" - false - else - notify "Get_cookie: search: $search in $cookie_data -> result: $cookie" - echo "$cookie" | \ - read domain alow_read_other_subdomains path http_required expiration name \ - value; - cookie="$name=$value" - true - fi -} - -save_cookie() { - if parse_cookie - then - data="$field_domain\tFALSE\t$field_path\tFALSE\t$field_exp\t$field_name\t$field_value" - notify "save_cookie: adding $data to $cookie_data" - echo -e "$data" >> $cookie_data - else - notify "not saving a cookie. since we don't have policies yet, parse_cookie must have returned false. this is a bug" - fi -} - -[ "x$action" = xPUT ] && save_cookie -[ "x$action" = xGET ] && get_cookie && echo "$cookie" - -exit - - -# TODO: implement this later. -# $1 = section (TRUSTED or DENY) -# $2 =url -match() { - sed -n "/$1/,/^\$/p" $cookie_config 2>/dev/null | grep -q "^$host" -} - -fetch_cookie() { - cookie=`cat $cookie_data` -} - -store_cookie() { - echo $cookie > $cookie_data -} - -if match TRUSTED $host -then - [ "x$action" = xPUT ] && store_cookie $host - [ "x$action" = xGET ] && fetch_cookie && echo "$cookie" -elif ! match DENY $host -then - [ "x$action" = xPUT ] && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Accept this cookie from $host ?" --entry-text="$cookie"` && store_cookie $host - [ "x$action" = xGET ] && fetch_cookie && cookie=`zenity --entry --title 'Uzbl Cookie handler' --text "Submit this cookie to $host ?" --entry-text="$cookie"` && echo $cookie -fi -exit 0 diff --git a/examples/data/uzbl/scripts/download.sh b/examples/data/uzbl/scripts/download.sh deleted file mode 100755 index 1c7d039..0000000 --- a/examples/data/uzbl/scripts/download.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh -# just an example of how you could handle your downloads -# try some pattern matching on the uri to determine what we should do - -# Some sites block the default wget --user-agent.. -GET="wget --user-agent=Firefox" - -dest="$HOME" -url="$8" - -http_proxy="$9" -export http_proxy - -test "x$url" = "x" && { echo "you must supply a url! ($url)"; exit 1; } - -# only changes the dir for the $get sub process -if echo "$url" | grep -E '.*\.torrent' >/dev/null; -then - ( cd "$dest"; $GET "$url") -else - ( cd "$dest"; $GET "$url") -fi diff --git a/examples/data/uzbl/scripts/extedit.js b/examples/data/uzbl/scripts/extedit.js deleted file mode 100644 index 8ed346d..0000000 --- a/examples/data/uzbl/scripts/extedit.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Edit forms in external editor - * - * (c) 2009, Robert Manea - * utf8 functions are (c) by Webtoolkit.info (http://www.webtoolkit.info/) - * - * - * Installation: - * - Copy this script to $HOME/.local/share/uzbl/scripts - * - Add the following to $HOME/.config/uzbl/config: - * @bind E = script @scripts_dir/extedit.js - * - Set your preferred editor - * set editor = gvim - * - non-GUI editors - * set editor = xterm -e vim - * - * Usage: - * Select (click) an editable form, go to command mode and hit E - * -*/ - - -function utf8_decode ( str_data ) { - var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0; - - str_data += ''; - - while ( i < str_data.length ) { - c1 = str_data.charCodeAt(i); - if (c1 < 128) { - tmp_arr[ac++] = String.fromCharCode(c1); - i++; - } else if ((c1 > 191) && (c1 < 224)) { - c2 = str_data.charCodeAt(i+1); - tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); - i += 2; - } else { - c2 = str_data.charCodeAt(i+1); - c3 = str_data.charCodeAt(i+2); - tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); - i += 3; - } - } - - return tmp_arr.join(''); -} - - -function utf8_encode ( argString ) { - var string = (argString+''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n"); - - var utftext = ""; - var start, end; - var stringl = 0; - - start = end = 0; - stringl = string.length; - for (var n = 0; n < stringl; n++) { - var c1 = string.charCodeAt(n); - var enc = null; - - if (c1 < 128) { - end++; - } else if (c1 > 127 && c1 < 2048) { - enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128); - } else { - enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128); - } - if (enc !== null) { - if (end > start) { - utftext += string.substring(start, end); - } - utftext += enc; - start = end = n+1; - } - } - - if (end > start) { - utftext += string.substring(start, string.length); - } - - return utftext; -} - - -(function() { - var actelem = document.activeElement; - - if(actelem.type == 'text' || actelem.type == 'textarea') { - var editor = Uzbl.run("print @external_editor") || "gvim"; - var filename = Uzbl.run("print @(mktemp /tmp/uzbl_edit.XXXXXX)@"); - - if(actelem.value) - Uzbl.run("sh 'echo " + window.btoa(utf8_encode(actelem.value)) + " | base64 -d > " + filename + "'"); - - Uzbl.run("sync_sh '" + editor + " " + filename + "'"); - actelem.value = utf8_decode(window.atob(Uzbl.run("print @(base64 -w 0 " + filename + ")@"))); - - Uzbl.run("sh 'rm -f " + filename + "'"); - } - - })(); diff --git a/examples/data/uzbl/scripts/follow_Numbers.js b/examples/data/uzbl/scripts/follow_Numbers.js deleted file mode 100644 index 00b279e..0000000 --- a/examples/data/uzbl/scripts/follow_Numbers.js +++ /dev/null @@ -1,228 +0,0 @@ -/* This is the basic linkfollowing script. - * Its pretty stable, only using numbers to navigate. - * - * TODO: Some pages mess around a lot with the zIndex which - * lets some hints in the background. - * TODO: Some positions are not calculated correctly (mostly - * because of uber-fancy-designed-webpages. Basic HTML and CSS - * works good - * TODO: Still some links can't be followed/unexpected things - * happen. Blame some freaky webdesigners ;) - */ - -//Just some shortcuts and globals -var uzblid = 'uzbl_link_hint'; -var uzbldivid = uzblid + '_div_container'; -var doc = document; -var win = window; -var links = document.links; -var forms = document.forms; - -//Reset keycmd, modcmd and return to default mode. -function clearKeycmd() { Uzbl.run('set mode ='); } - -//Make onlick-links "clickable" -try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ - type: 'click' - }); - } - }; -} catch(e) {} -//Catch the ESC keypress to stop linkfollowing -function keyPressHandler(e) { - var kC = window.event ? event.keyCode: e.keyCode; - var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; - if (kC == Esc) { - removeAllHints(); - } -} -//Calculate element position to draw the hint -//Pretty accurate but on fails in some very fancy cases -function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; - var height = el.offsetHeight; - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return [up, left, width, height]; -} -//Calculate if an element is visible -function isVisible(el) { - if (el == doc) { - return true; - } - if (!el) { - return false; - } - if (!el.parentNode) { - return false; - } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); -} -//Calculate if an element is on the viewport. -function elementInViewport(el) { - offset = elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; - var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; -} -//Removes all hints/leftovers that might be generated -//by this script. -function removeAllHints() { - var elements = doc.getElementById(uzbldivid); - if (elements) { - elements.parentNode.removeChild(elements); - } -} -//Generate a hint for an element with the given label -//Here you can play around with the style of the hints! -function generateHint(el, label) { - var pos = elementPosition(el); - var hint = doc.createElement('div'); - hint.setAttribute('name', uzblid); - hint.innerText = label; - hint.style.display = 'inline'; - hint.style.backgroundColor = '#B9FF00'; - hint.style.border = '2px solid #4A6600'; - hint.style.color = 'black'; - hint.style.fontSize = '9px'; - hint.style.fontWeight = 'bold'; - hint.style.lineHeight = '9px'; - hint.style.margin = '0px'; - hint.style.padding = '1px'; - hint.style.position = 'absolute'; - hint.style.zIndex = '1000'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; - var img = el.getElementsByTagName('img'); - if (img.length > 0) { - hint.style.left = pos[1] + img[0].width / 2 + 'px'; - } - hint.style.textDecoration = 'none'; - hint.style.webkitBorderRadius = '6px'; - // Play around with this, pretty funny things to do :) - hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; - return hint; -} -//Here we choose what to do with an element if we -//want to "follow" it. On form elements we "select" -//or pass the focus, on links we try to perform a click, -//but at least set the href of the link. (needs some improvements) -function clickElem(item) { - removeAllHints(); - clearKeycmd(); - if (item) { - var name = item.tagName; - if (name == 'A') { - item.click(); - window.location = item.href; - } else if (name == 'INPUT') { - var type = item.getAttribute('type').toUpperCase(); - if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { - item.focus(); - item.select(); - } else { - item.click(); - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { - item.focus(); - item.select(); - } else { - item.click(); - window.location = item.href; - } - } -} -//Returns a list of all links (in this version -//just the elements itself, but in other versions, we -//add the label here. -function addLinks() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (isVisible(li) && elementInViewport(li)) { - res[0].push(li); - } - } - return res; -} -//Same as above, just for the form elements -function addFormElems() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { - res[0].push(el); - } - } - } - return res; -} -//Draw all hints for all elements passed. "len" is for -//the number of chars we should use to avoid collisions -function reDrawHints(elems, chars) { - removeAllHints(); - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - for (var i = 0; i < elems[0].length; i++) { - if (elems[0][i]) { - var label = elems[1][i].substring(chars); - var h = generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) { - document.body.appendChild(hintdiv); - } -} -//Put it all together -function followLinks(follow) { - var s = follow.split(''); - var linknr = parseInt(follow, 10); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = addLinks(); - var formelems = addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = (elems[0].length + '').length; - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { - clickElem(elems[0][linknr]); - } else { - for (var j = 0; j < elems[0].length; j++) { - var b = true; - var label = j + ''; - var n = label.length; - for (n; n < len; n++) { - label = '0' + label; - } - for (var k = 0; k < s.length; k++) { - b = b && label.charAt(k) == s[k]; - } - if (b) { - leftover[0].push(elems[0][j]); - leftover[1].push(label); - } - } - reDrawHints(leftover, s.length); - } -} -followLinks('%s'); diff --git a/examples/data/uzbl/scripts/follow_Numbers_Strings.js b/examples/data/uzbl/scripts/follow_Numbers_Strings.js deleted file mode 100644 index e50da5d..0000000 --- a/examples/data/uzbl/scripts/follow_Numbers_Strings.js +++ /dev/null @@ -1,212 +0,0 @@ -var uzblid = 'uzbl_link_hint'; -var uzbldivid = uzblid + '_div_container'; -var doc = document; -var win = window; -var links = document.links; -var forms = document.forms; - -//Reset keycmd, modcmd and return to default mode. -function clearKeycmd() { Uzbl.run('set mode ='); } - -try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ - type: 'click' - }); - } - }; -} catch(e) {} -function keyPressHandler(e) { - var kC = window.event ? event.keyCode: e.keyCode; - var Esc = window.event ? 27 : e.DOM_VK_ESCAPE; - if (kC == Esc) { - removeAllHints(); - } -} -function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; - var height = el.offsetHeight; - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return [up, left, width, height]; -} -function isVisible(el) { - if (el == doc) { - return true; - } - if (!el) { - return false; - } - if (!el.parentNode) { - return false; - } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); -} -function elementInViewport(el) { - offset = elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; - var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; -} -function removeAllHints() { - var elements = doc.getElementById(uzbldivid); - if (elements) { - elements.parentNode.removeChild(elements); - } -} -function generateHint(el, label) { - var pos = elementPosition(el); - var hint = doc.createElement('div'); - hint.setAttribute('name', uzblid); - hint.innerText = label; - hint.style.display = 'inline'; - hint.style.backgroundColor = '#B9FF00'; - hint.style.border = '2px solid #4A6600'; - hint.style.color = 'black'; - hint.style.zIndex = '1000'; - hint.style.fontSize = '9px'; - hint.style.fontWeight = 'bold'; - hint.style.lineHeight = '9px'; - hint.style.margin = '0px'; - hint.style.padding = '1px'; - hint.style.position = 'absolute'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; - var img = el.getElementsByTagName('img'); - if (img.length > 0) { - hint.style.left = pos[1] + img[0].width / 2 + 'px'; - } - hint.style.textDecoration = 'none'; - hint.style.webkitBorderRadius = '6px'; - hint.style.webkitTransform = 'scale(1) rotate(0deg) translate(-6px,-5px)'; - return hint; -} - -function clickElem(item) { - removeAllHints(); - clearKeycmd(); - if (item) { - var name = item.tagName; - if (name == 'A') { - item.click(); - window.location = item.href; - } else if (name == 'INPUT') { - var type = item.getAttribute('type').toUpperCase(); - if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { - item.focus(); - item.select(); - } else { - item.click(); - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { - item.focus(); - item.select(); - } else { - item.click(); - window.location = item.href; - } - } -} - -function addLinks() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (isVisible(li) && elementInViewport(li)) { - res[0].push(li); - res[1].push(li.innerText.toLowerCase()); - } - } - return res; -} -function addFormElems() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) { - res[0].push(el); - if (el.getAttribute('value')) { - res[1].push(el.getAttribute('value').toLowerCase()); - } else { - res[1].push(el.getAttribute('name').toLowerCase()); - } - } - } - } - return res; -} -function reDrawHints(elems, len) { - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - hintdiv.style.opacity = '0.0'; - for (var i = 0; i < elems[0].length; i++) { - var label = i + ''; - var n = label.length; - for (n; n < len; n++) { - label = '0' + label; - } - if (elems[0][i]) { - var h = generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) { - document.body.appendChild(hintdiv); - hintdiv.style.opacity = '0.7' - } -} -function followLinks(follow) { - var s = follow.split(''); - var linknr = parseInt(follow, 10); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = addLinks(); - var formelems = addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = (elems[0].length + '').length; - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (linknr + 1 && s.length == len && linknr < elems[0].length && linknr >= 0) { - clickElem(elems[0][linknr]); - } else { - for (var j = 0; j < elems[0].length; j++) { - var b = true; - for (var k = 0; k < s.length; k++) { - b = b && elems[1][j].charAt(k) == s[k]; - } - if (!b) { - elems[0][j] = null; - elems[1][j] = null; - } else { - leftover[0].push(elems[0][j]); - leftover[1].push(elems[1][j]); - } - } - if (leftover[0].length == 1) { - clickElem(leftover[0][0]); - } else if (!oldDiv) { - if (linknr + 1 || s.length == 0) { - reDrawHints(elems, len); - } else { - reDrawHints(leftover, len); - } - } - } -} -followLinks('%s'); diff --git a/examples/data/uzbl/scripts/formfiller.pl b/examples/data/uzbl/scripts/formfiller.pl deleted file mode 100755 index 23da347..0000000 --- a/examples/data/uzbl/scripts/formfiller.pl +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/perl - -# a slightly more advanced form filler -# -# uses settings file like: $keydir/ -#TODO: fallback to $HOME/.local/share -# user arg 1: -# edit: force editing of the file (fetches if file is missing) -# load: fill forms from file (fetches if file is missing) -# new: fetch new file - -# usage example: -# bind LL = spawn /usr/share/uzbl/examples/scripts/formfiller.pl load -# bind LN = spawn /usr/share/uzbl/examples/scripts/formfiller.pl new -# bind LE = spawn /usr/share/uzbl/examples/scripts/formfiller.pl edit - -use strict; -use warnings; - -my $keydir = $ENV{XDG_CONFIG_HOME} . "/uzbl/forms"; -my ($config,$pid,$xid,$fifoname,$socket,$url,$title,$cmd) = @ARGV; -if (!defined $fifoname || $fifoname eq "") { die "No fifo"; } - -sub domain { - my ($url) = @_; - $url =~ s#http(s)?://([A-Za-z0-9\.-]+)(/.*)?#$2#; - return $url; -}; - -my $editor = "xterm -e vim"; -#my $editor = "gvim"; - -# ideally, there would be some way to ask uzbl for the html content instead of having to redownload it with -# Also, you may need to fake the user-agent on some sites (like facebook) - my $downloader = "curl -A 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.10) Gecko/2009042810 GranParadiso/3.0.10' "; -#my $downloader = "curl -s"; - -my @fields = ("type","name","value"); - -my %command; - -$command{load} = sub { - my ($domain) = @_; - my $filename = "$keydir/$domain"; - if (-e $filename){ - open(my $file, $filename) or die "Failed to open $filename: $!"; - my (@lines) = <$file>; - close($file); - $|++; - open(my $fifo, ">>", $fifoname) or die "Failed to open $fifoname: $!"; - foreach my $line (@lines) { - next if ($line =~ m/^#/); - my ($type,$name,$value) = ($line =~ /^\s*(\w+)\s*\|\s*(.*?)\s*\|\s*(.*?)\s*$/); - if ($type eq "checkbox") - { - printf $fifo 'js document.getElementsByName("%s")[0].checked = %s;', $name, $value; - } elsif ($type eq "submit") - { - printf $fifo 'js function fs (n) {try{n.submit()} catch (e){fs(n.parentNode)}}; fs(document.getElementsByName("%s")[0]);', $name; - } elsif ($type ne "") - { - printf $fifo 'js document.getElementsByName("%s")[0].value = "%s";', $name, $value; - } - print $fifo "\n"; - } - $|--; - } else { - $command{new}->($domain); - $command{edit}->($domain); - } -}; -$command{edit} = sub { - my ($domain) = @_; - my $file = "$keydir/$domain"; - if(-e $file){ - system ($editor, $file); - } else { - $command{new}->($domain); - } -}; -$command{new} = sub { - my ($domain) = @_; - my $filename = "$keydir/$domain"; - open (my $file,">>", $filename) or die "Failed to open $filename: $!"; - $|++; - print $file "# Make sure that there are no extra submits, since it may trigger the wrong one.\n"; - printf $file "#%-10s | %-10s | %s\n", @fields; - print $file "#------------------------------\n"; - my @data = `$downloader $url`; - foreach my $line (@data){ - if($line =~ m/].*?)>/i){ - $line =~ s/.*(].*?)>).*/$1/; - printf $file " %-10s | %-10s | %s\n", map { my ($r) = $line =~ /.*$_=["'](.*?)["']/;$r } @fields; - }; - }; - $|--; -}; - -$command{$cmd}->(domain($url)); diff --git a/examples/data/uzbl/scripts/formfiller.sh b/examples/data/uzbl/scripts/formfiller.sh deleted file mode 100755 index 10afaba..0000000 --- a/examples/data/uzbl/scripts/formfiller.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -# simple html form (eg for logins) filler (and manager) for uzbl. -# uses settings files like: $keydir/ -# files contain lines like: : - - -# user arg 1: -# edit: force editing the file (falls back to new if not found) -# new: start with a new file. -# load: try to load from file into form - -# something else (or empty): if file not available: new, otherwise load. - -keydir=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/forms -[ -d "`dirname $keydir`" ] || exit 1 -[ -d "$keydir" ] || mkdir "$keydir" - -editor=${VISUAL} -if [[ -z ${editor} ]]; then - #editor='gvim' - editor='urxvt -e vim' -fi - -config=$1; shift -pid=$1; shift -xid=$1; shift -fifo=$1; shift -socket=$1; shift -url=$1; shift -title=$1; shift -action=$1 - -[ -d $keydir ] || mkdir $keydir || exit 1 - -if [ "$action" != 'edit' -a "$action" != 'new' -a "$action" != 'load' ] -then - action=new - [[ -e $keydir/$domain ]] && action=load -elif [ "$action" == 'edit' ] && [[ ! -e $keydir/$domain ]] -then - action=new -fi -domain=$(echo $url | sed -re 's|(http\|https)+://([A-Za-z0-9\.]+)/.*|\2|') - - -#regex='s|.*.*|\1: |p' # sscj's first version, does not work on http://wiki.archlinux.org/index.php?title=Special:UserLogin&returnto=Main_Page - regex='s|.*> $fifo -else - if [ "$action" == 'new' ] - then - curl "$url" | grep ' $keydir/$domain - fi - [[ -e $keydir/$domain ]] || exit 3 #this should never happen, but you never know. - $editor $keydir/$domain #TODO: if user aborts save in editor, the file is already overwritten -fi diff --git a/examples/data/uzbl/scripts/hint.js b/examples/data/uzbl/scripts/hint.js deleted file mode 100644 index ec7f1e2..0000000 --- a/examples/data/uzbl/scripts/hint.js +++ /dev/null @@ -1,26 +0,0 @@ -for (var i=0; i < document.links.length; i++) { - var uzblid = 'uzbl_link_hint_'; - var li = document.links[i]; - var pre = document.getElementById(uzblid+i); - - if (pre) { - li.removeChild(pre); - } else { - var hint = document.createElement('div'); - hint.setAttribute('id',uzblid+i); - hint.innerHTML = i; - hint.style.display='inline'; - hint.style.lineHeight='90%'; - hint.style.backgroundColor='red'; - hint.style.color='white'; - hint.style.fontSize='small-xx'; - hint.style.fontWeight='light'; - hint.style.margin='0px'; - hint.style.padding='2px'; - hint.style.position='absolute'; - hint.style.textDecoration='none'; - hint.style.left=li.style.left; - hint.style.top=li.style.top; - li.insertAdjacentElement('afterBegin',hint); - } -} diff --git a/examples/data/uzbl/scripts/history.sh b/examples/data/uzbl/scripts/history.sh deleted file mode 100755 index 7c83aa6..0000000 --- a/examples/data/uzbl/scripts/history.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history -[ -d `dirname $file` ] || exit 1 -echo `date +'%Y-%m-%d %H:%M:%S'`" $6 $7" >> $file diff --git a/examples/data/uzbl/scripts/insert_bookmark.sh b/examples/data/uzbl/scripts/insert_bookmark.sh deleted file mode 100755 index c34e7db..0000000 --- a/examples/data/uzbl/scripts/insert_bookmark.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -[ -d "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl" ] || exit 1 -file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/bookmarks - -which zenity &>/dev/null || exit 2 - -entry=`zenity --entry --text="Add bookmark. add tags after the '\t', separated by spaces" --entry-text="$6 $7\t"` -exitstatus=$? -if [ $exitstatus -ne 0 ]; then exit $exitstatus; fi -url=`echo $entry | awk '{print $1}'` - -# TODO: check if already exists, if so, and tags are different: ask if you want to replace tags -echo "$entry" >/dev/null #for some reason we need this.. don't ask me why -echo -e "$entry" >> $file -true diff --git a/examples/data/uzbl/scripts/instance-select-wmii.sh b/examples/data/uzbl/scripts/instance-select-wmii.sh deleted file mode 100755 index 2bf13ba..0000000 --- a/examples/data/uzbl/scripts/instance-select-wmii.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh - - -# This script allows you to focus another uzbl window -# It considers all uzbl windows in the current tag -# you can select one from a list, or go to the next/previous one -# It does not change the layout (stacked/tiled/floating) nor does it -# changes the size or viewing mode of a uzbl window -# When your current uzbl window is maximized, the one you change to -# will be maximized as well. -# See http://www.uzbl.org/wiki/wmii for more info -# $1 must be one of 'list', 'next', 'prev' - -COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" - -if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch -else - DMENU="dmenu -i" -fi - -if [ "$1" == 'list' ] -then - list= - # get window id's of uzbl clients. we could also get the label in one shot but it's pretty tricky - for i in $(wmiir read /tag/sel/index | grep uzbl |cut -d ' ' -f2) - do - label=$(wmiir read /client/$i/label) - list="$list$i : $label\n" - done - window=$(echo -e "$list" | $DMENU $COLORS | cut -d ' ' -f1) - wmiir xwrite /tag/sel/ctl "select client $window" -elif [ "$1" == 'next' ] -then - current=$(wmiir read /client/sel/ctl | head -n 1) - # find the next uzbl window and focus it - next=$(wmiir read /tag/sel/index | grep -A 10000 " $current " | grep -m 1 uzbl | cut -d ' ' -f2) - if [ x"$next" != "x" ] - then - wmiir xwrite /tag/sel/ctl "select client $next" - fi -elif [ "$1" == 'prev' ] -then - current=$(wmiir read /client/sel/ctl | head -n 1) - prev=$(wmiir read /tag/sel/index | grep -B 10000 " $current " | tac | grep -m 1 uzbl | cut -d ' ' -f2) - if [ x"$prev" != "x" ] - then - wmiir xwrite /tag/sel/ctl "select client $prev" - fi -else - echo "\$1 not valid" >&2 - exit 2 -fi diff --git a/examples/data/uzbl/scripts/linkfollow.js b/examples/data/uzbl/scripts/linkfollow.js deleted file mode 100644 index 0eb629b..0000000 --- a/examples/data/uzbl/scripts/linkfollow.js +++ /dev/null @@ -1,269 +0,0 @@ -// link follower for uzbl -// requires http://github.com/DuClare/uzbl/commit/6c11777067bdb8aac09bba78d54caea04f85e059 -// -// first, it needs to be loaded before every time it is used. -// One way would be to use the load_commit_handler: -// set load_commit_handler = sh 'echo "script /usr/share/uzbl/examples/scripts/linkfollow.js" > "$4"' -// -// when script is loaded, it can be invoked with -// bind f* = js hints.set("%s", hints.open) -// bind f_ = js hints.follow("%s",hints.open) -// -// At the moment, it may be useful to have way of forcing uzbl to load the script -// bind :lf = script /usr/share/uzbl/examples/scripts/linkfollow.js -// -// The default style for the hints are pretty ugly, so it is recommended to add the following -// to config file -// set stylesheet_uri = /usr/share/uzbl/examples/data/style.css -// -// based on follow_Numbers.js -// -// TODO: fix styling for the first element -// TODO: emulate mouseover events when visiting some elements -// TODO: rewrite the element->action handling - - -function Hints(){ - - // Settings - //////////////////////////////////////////////////////////////////////////// - - // if set to true, you must explicitly call hints.follow(), otherwise it will - // follow the link if there is only one matching result - var requireReturn = true; - - // Case sensitivity flag - var matchCase = "i"; - - // For case sensitive matching, uncomment: - // var matchCase = ""; - - - var uzblid = 'uzbl_hint'; - var uzblclass = 'uzbl_highlight'; - var uzblclassfirst = 'uzbl_h_first'; - var doc = document; - var visible = []; - var hintdiv; - - this.set = hint; - this.follow = follow; - this.keyPressHandler = keyPressHandler; - - function elementPosition(el) { - var up = el.offsetTop; - var left = el.offsetLeft; var width = el.offsetWidth; - var height = el.offsetHeight; - - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return {up: up, left: left, width: width, height: height}; - } - - function elementInViewport(p) { - return (p.up < window.pageYOffset + window.innerHeight && - p.left < window.pageXOffset + window.innerWidth && - (p.up + p.height) > window.pageYOffset && - (p.left + p.width) > window.pageXOffset); - } - - function isVisible(el) { - if (el == doc) { return true; } - if (!el) { return false; } - if (!el.parentNode) { return false; } - if (el.style) { - if (el.style.display == 'none') { - return false; - } - if (el.style.visibility == 'hidden') { - return false; - } - } - return isVisible(el.parentNode); - } - - // the vimperator defaults minus the xhtml elements, since it gave DOM errors - var hintable = " //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select"; - - function Matcher(str){ - var numbers = str.replace(/[^\d]/g,""); - var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,matchCase)}); - this.test = test; - this.toString = toString; - this.numbers = numbers; - function matchAgainst(element){ - if(element.node.nodeName == "INPUT"){ - return element.node.value; - } else { - return element.node.textContent; - } - } - function test(element) { - // test all the regexp - var item = matchAgainst(element); - return words.every(function (regex) { return item.match(regex)}); - } - } - - function HintElement(node,pos){ - - this.node = node; - this.isHinted = false; - this.position = pos; - this.num = 0; - - this.addHint = function (labelNum) { - // TODO: fix uzblclassfirst - if(!this.isHinted){ - this.node.className += " " + uzblclass; - } - this.isHinted = true; - - // create hint - var hintNode = doc.createElement('div'); - hintNode.name = uzblid; - hintNode.innerText = labelNum; - hintNode.style.left = this.position.left + 'px'; - hintNode.style.top = this.position.up + 'px'; - hintNode.style.position = "absolute"; - doc.body.firstChild.appendChild(hintNode); - - } - this.removeHint = function(){ - if(this.isHinted){ - var s = (this.num)?uzblclassfirst:uzblclass; - this.node.className = this.node.className.replace(new RegExp(" "+s,"g"),""); - this.isHinted = false; - } - } - } - - function createHintDiv(){ - var hintdiv = doc.getElementById(uzblid); - if(hintdiv){ - hintdiv.parentNode.removeChild(hintdiv); - } - hintdiv = doc.createElement("div"); - hintdiv.setAttribute('id',uzblid); - doc.body.insertBefore(hintdiv,doc.body.firstChild); - return hintdiv; - } - - function init(){ - // WHAT? - doc.body.setAttribute("onkeyup","hints.keyPressHandler(event)"); - hintdiv = createHintDiv(); - visible = []; - - var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null); - for (var i = 0;i&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]' -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch - # show tags as well - goto=`$DMENU $COLORS < $file | awk '{print $1}'` -else - DMENU="dmenu -i" - # because they are all after each other, just show the url, not their tags. - goto=`awk '{print $1}' $file | $DMENU $COLORS` -fi - -#[ -n "$goto" ] && echo "uri $goto" > $4 -[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/uzbl/scripts/load_url_from_history.sh b/examples/data/uzbl/scripts/load_url_from_history.sh deleted file mode 100755 index 62e02ac..0000000 --- a/examples/data/uzbl/scripts/load_url_from_history.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -history_file=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/history -[ -r "$history_file" ] || exit 1 - -# choose from all entries, sorted and uniqued -# goto=`awk '{print $3}' $history_file | sort -u | dmenu -i` -COLORS=" -nb #303030 -nf khaki -sb #CCFFAA -sf #303030" -if dmenu --help 2>&1 | grep -q '\[-rs\] \[-ni\] \[-nl\] \[-xs\]'; -then - DMENU="dmenu -i -xs -rs -l 10" # vertical patch - # choose an item in reverse order, showing also the date and page titles - # pick the last field from the first 3 fields. this way you can pick a url (prefixed with date & time) or type just a new url. - goto=`tac $history_file | $DMENU $COLORS | cut -d ' ' -f -3 | awk '{print $NF}'` -else - DMENU="dmenu -i" - # choose from all entries (no date or title), the first one being current url, and after that all others, sorted and uniqued, in ascending order - current=`tail -n 1 $history_file | awk '{print $3}'`; - goto=`(echo $current; awk '{print $3}' $history_file | grep -v "^$current\$" \ - | sort -u) | $DMENU $COLORS` -fi - -[ -n "$goto" ] && echo "uri $goto" > $4 -#[ -n "$goto" ] && echo "uri $goto" | socat - unix-connect:$5 diff --git a/examples/data/uzbl/scripts/scheme.py b/examples/data/uzbl/scripts/scheme.py deleted file mode 100755 index 0916466..0000000 --- a/examples/data/uzbl/scripts/scheme.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -import os, subprocess, sys, urlparse - -def detach_open(cmd): - # Thanks to the vast knowledge of Laurence Withers (lwithers) and this message: - # http://mail.python.org/pipermail/python-list/2006-November/587523.html - if not os.fork(): - null = os.open(os.devnull,os.O_WRONLY) - for i in range(3): os.dup2(null,i) - os.close(null) - subprocess.Popen(cmd) - print 'USED' - -if __name__ == '__main__': - uri = sys.argv[8] - u = urlparse.urlparse(uri) - if u.scheme == 'mailto': - detach_open(['xterm', '-e', 'mail', u.path]) - elif u.scheme == 'xmpp': - # Someone check for safe arguments to gajim-remote - detach_open(['gajim-remote', 'open_chat', uri]) - elif u.scheme == 'git': - detach_open(['git', 'clone', '--', uri], cwd=os.path.expanduser('~/src')) diff --git a/examples/data/uzbl/scripts/scroll-percentage.js b/examples/data/uzbl/scripts/scroll-percentage.js deleted file mode 100644 index c9a51aa..0000000 --- a/examples/data/uzbl/scripts/scroll-percentage.js +++ /dev/null @@ -1,68 +0,0 @@ -// VIM ruler style scroll message -(function() { - var run = Uzbl.run; - var update_message = function() { - var innerHeight = window.innerHeight; - var scrollY = window.scrollY; - var height = document.height; - var message; - - if (UzblZoom.type === "full") { - var zoom_level = UzblZoom.level; - innerHeight = Math.ceil(innerHeight * zoom_level); - scrollY = Math.ceil(scrollY * zoom_level); - height -= 1; - } - - if (! height) { - message = ""; - } - else if (height <= innerHeight) { - message = run("print @scroll_all_indicator") || "All"; - } - else if (scrollY === 0) { - message = run("print @scroll_top_indicator") || "Top"; - } - else if (scrollY + innerHeight >= height) { - message = run("print @scroll_bottom_indicator") || "Bot"; - } - else { - var percentage = Math.round(scrollY / (height - innerHeight) * 100); - message = percentage + "%"; - } - run("set scroll_message=" + message); - }; - - self.UzblZoom = { - get level() { - return Number(run("print @zoom_level")) || 1; - }, - set level(level) { - if (typeof level === "number" && level > 0) { - run("set zoom_level = " + level); - update_message(); - } - }, - get type() { - return run("print @zoom_type") || "text"; - }, - set type(type) { - if ((type === "text" || type === "full") && this.type != type) { - run("toggle_zoom_type"); - run("set zoom_type = " + type); - update_message(); - } - }, - toggle_type: function() { - this.type = (this.type === "text" ? "full" : "text"); - } - }; - - window.addEventListener("DOMContentLoaded", update_message, false); - window.addEventListener("load", update_message, false); - window.addEventListener("resize", update_message, false); - window.addEventListener("scroll", update_message, false); - update_message(); -})(); - -// vim: set noet ff=unix diff --git a/examples/data/uzbl/scripts/session.sh b/examples/data/uzbl/scripts/session.sh deleted file mode 100755 index 1059b5e..0000000 --- a/examples/data/uzbl/scripts/session.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh - -# Very simple session manager for uzbl-browser. When called with "endsession" as the -# argument, it'll backup $sessionfile, look for fifos in $fifodir and -# instruct each of them to store their current url in $sessionfile and -# terminate themselves. Run with "launch" as the argument and an instance of -# uzbl-browser will be launched for each stored url. "endinstance" is used internally -# and doesn't need to be called manually at any point. -# Add a line like 'bind quit = /path/to/session.sh endsession' to your config - -[ -d ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl ] || exit 1 -scriptfile=$0 # this script -sessionfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/browser-session # the file in which the "session" (i.e. urls) are stored -configfile=${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/config # uzbl configuration file -UZBL="uzbl-browser -c $configfile" # add custom flags and whatever here. - -fifodir=/tmp # remember to change this if you instructed uzbl to put its fifos elsewhere -thisfifo="$4" -act="$8" -url="$6" - -if [ "$act." = "." ]; then - act="$1" -fi - - -case $act in - "launch" ) - urls=`cat $sessionfile` - if [ "$urls." = "." ]; then - $UZBL - else - for url in $urls; do - $UZBL --uri "$url" & - done - fi - exit 0 - ;; - - "endinstance" ) - if [ "$url" != "(null)" ]; then - echo "$url" >> $sessionfile; - fi - echo "exit" > "$thisfifo" - ;; - - "endsession" ) - mv "$sessionfile" "$sessionfile~" - for fifo in $fifodir/uzbl_fifo_*; do - if [ "$fifo" != "$thisfifo" ]; then - echo "spawn $scriptfile endinstance" > "$fifo" - fi - done - echo "spawn $scriptfile endinstance" > "$thisfifo" - ;; - - * ) echo "session manager: bad action" - echo "Usage: $scriptfile [COMMAND] where commands are:" - echo " launch - Restore a saved session or start a new one" - echo " endsession - Quit the running session. Must be called from uzbl" - ;; -esac diff --git a/examples/data/uzbl/scripts/uzbl-cookie-daemon b/examples/data/uzbl/scripts/uzbl-cookie-daemon deleted file mode 100755 index fde8b8e..0000000 --- a/examples/data/uzbl/scripts/uzbl-cookie-daemon +++ /dev/null @@ -1,664 +0,0 @@ -#!/usr/bin/env python - -# The Python Cookie Daemon for Uzbl. -# Copyright (c) 2009, Tom Adams -# Copyright (c) 2009, Dieter Plaetinck -# Copyright (c) 2009, Mason Larobina -# Copyright (c) 2009, Michael Fiano -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -''' -The Python Cookie Daemon -======================== - -This daemon is a re-write of the original cookies.py script found in uzbl's -master branch. This script provides more functionality than the original -cookies.py by adding numerous command line options to specify different cookie -jar locations, socket locations, verbose output, etc. This functionality is -very useful as it allows you to run multiple daemons at once serving cookies -to different groups of uzbl instances as required. - -Keeping up to date -================== - -Check the cookie daemon uzbl-wiki page for more information on where to -find the latest version of the cookie_daemon.py - - http://www.uzbl.org/wiki/cookie_daemon.py - -Command line options -==================== - -Use the following command to get a full list of the cookie_daemon.py command -line options: - - ./cookie_daemon.py --help - -Talking with uzbl -================= - -In order to get uzbl to talk to a running cookie daemon you add the following -to your uzbl config: - - set cookie_handler = talk_to_socket $XDG_CACHE_HOME/uzbl/cookie_daemon_socket - -Or if you prefer using the $HOME variable: - - set cookie_handler = talk_to_socket $HOME/.cache/uzbl/cookie_daemon_socket - -Todo list -========= - - - Use a pid file to make force killing a running daemon possible. - -Reporting bugs / getting help -============================= - -The best way to report bugs and or get help with the cookie daemon is to -contact the maintainers it the #uzbl irc channel found on the Freenode IRC -network (irc.freenode.org). -''' - -import cookielib -import os -import sys -import urllib2 -import select -import socket -import time -import atexit -from traceback import print_exc -from signal import signal, SIGTERM -from optparse import OptionParser -from os.path import join - -try: - import cStringIO as StringIO - -except ImportError: - import StringIO - - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return join(os.environ['HOME'], default) - -# Setup xdg paths. -CACHE_DIR = join(xdghome('CACHE', '.cache/'), 'uzbl/') -DATA_DIR = join(xdghome('DATA', '.local/share/'), 'uzbl/') -CONFIG_DIR = join(xdghome('CONFIG', '.config/'), 'uzbl/') - -# Ensure data paths exist. -for path in [CACHE_DIR, DATA_DIR, CONFIG_DIR]: - if not os.path.exists(path): - os.makedirs(path) - -# Default config -config = { - - # Default cookie jar, whitelist, and daemon socket locations. - 'cookie_jar': join(DATA_DIR, 'cookies.txt'), - 'cookie_whitelist': join(CONFIG_DIR, 'cookie_whitelist'), - 'cookie_socket': join(CACHE_DIR, 'cookie_daemon_socket'), - - # Don't use a cookie whitelist policy by default. - 'use_whitelist': False, - - # Time out after x seconds of inactivity (set to 0 for never time out). - # WARNING: Do not use this option if you are manually launching the daemon. - 'daemon_timeout': 0, - - # Daemonise by default. - 'daemon_mode': True, - - # Optionally print helpful debugging messages to the terminal. - 'verbose': False, - -} # End of config dictionary. - - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - - -_SCRIPTNAME = os.path.basename(sys.argv[0]) -def echo(msg): - '''Prints only if the verbose flag has been set.''' - - if config['verbose']: - sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) - - -def error(msg): - '''Prints error message and exits.''' - - sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) - sys.exit(1) - - -def mkbasedir(filepath): - '''Create the base directories of the file in the file-path if the dirs - don't exist.''' - - dirname = os.path.dirname(filepath) - if not os.path.exists(dirname): - echo("creating dirs: %r" % dirname) - os.makedirs(dirname) - - -def daemon_running(cookie_socket): - '''Check if another process (hopefully a cookie_daemon.py) is listening - on the cookie daemon socket. If another process is found to be - listening on the socket exit the daemon immediately and leave the - socket alone. If the connect fails assume the socket has been abandoned - and delete it (to be re-created in the create socket function).''' - - if not os.path.exists(cookie_socket): - return False - - if os.path.isfile(cookie_socket): - raise Exception("regular file at %r is not a socket" % cookie_socket) - - - if os.path.isdir(cookie_socket): - raise Exception("directory at %r is not a socket" % cookie_socket) - - try: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - sock.connect(cookie_socket) - sock.close() - echo("detected daemon listening on %r" % cookie_socket) - return True - - except socket.error: - # Failed to connect to cookie_socket so assume it has been - # abandoned by another cookie daemon process. - if os.path.exists(cookie_socket): - echo("deleting abandoned socket at %r" % cookie_socket) - os.remove(cookie_socket) - - return False - - -def send_command(cookie_socket, cmd): - '''Send a command to a running cookie daemon.''' - - if not daemon_running(cookie_socket): - return False - - try: - sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) - sock.connect(cookie_socket) - sock.send(cmd) - sock.close() - echo("sent command %r to %r" % (cmd, cookie_socket)) - return True - - except socket.error: - print_exc() - error("failed to send message %r to %r" % (cmd, cookie_socket)) - return False - - -def kill_daemon(cookie_socket): - '''Send the "EXIT" command to running cookie_daemon.''' - - if send_command(cookie_socket, "EXIT"): - # Now ensure the cookie_socket is cleaned up. - start = time.time() - while os.path.exists(cookie_socket): - time.sleep(0.1) - if (time.time() - start) > 5: - error("force deleting socket %r" % cookie_socket) - os.remove(cookie_socket) - return - - echo("stopped daemon listening on %r"% cookie_socket) - - else: - if os.path.exists(cookie_socket): - os.remove(cookie_socket) - echo("removed abandoned/broken socket %r" % cookie_socket) - - -def daemonize(): - '''Daemonize the process using the Stevens' double-fork magic.''' - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #1 failed") - sys.exit(1) - - os.chdir('/') - os.setsid() - os.umask(0) - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #2 failed") - sys.exit(1) - - sys.stdout.flush() - sys.stderr.flush() - - devnull = '/dev/null' - stdin = file(devnull, 'r') - stdout = file(devnull, 'a+') - stderr = file(devnull, 'a+', 0) - - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - -class CookieMonster: - '''The uzbl cookie daemon class.''' - - def __init__(self): - '''Initialise class variables.''' - - self.server_socket = None - self.jar = None - self.last_request = time.time() - self._running = False - - - def run(self): - '''Start the daemon.''' - - # The check healthy function will exit if another daemon is detected - # listening on the cookie socket and remove the abandoned socket if - # there isnt. - if os.path.exists(config['cookie_socket']): - if daemon_running(config['cookie_socket']): - sys.exit(1) - - # Create cookie daemon socket. - self.create_socket() - - # Daemonize process. - if config['daemon_mode']: - echo("entering daemon mode") - daemonize() - - # Register a function to cleanup on exit. - atexit.register(self.quit) - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) - - # Create cookie jar object from file. - self.open_cookie_jar() - - # Create a way to exit nested loops by setting a running flag. - self._running = True - - while self._running: - try: - # Enter main listen loop. - self.listen() - - except KeyboardInterrupt: - self._running = False - print - - except socket.error: - print_exc() - - except: - # Clean up - self.del_socket() - - # Raise exception - raise - - # Always delete the socket before calling create again. - self.del_socket() - # Create cookie daemon socket. - self.create_socket() - - - def load_whitelist(self): - '''Load the cookie jar whitelist policy.''' - - cookie_whitelist = config['cookie_whitelist'] - - if cookie_whitelist: - mkbasedir(cookie_whitelist) - - # Create cookie whitelist file if it does not exist. - if not os.path.exists(cookie_whitelist): - open(cookie_whitelist, 'w').close() - - # Read cookie whitelist file into list. - file = open(cookie_whitelist,'r') - domain_list = [line.rstrip('\n') for line in file] - file.close() - - # Define policy of allowed domains - policy = cookielib.DefaultCookiePolicy(allowed_domains=domain_list) - self.jar.set_policy(policy) - - # Save the last modified time of the whitelist. - self._whitelistmtime = os.stat(cookie_whitelist).st_mtime - - - def open_cookie_jar(self): - '''Open the cookie jar.''' - - cookie_jar = config['cookie_jar'] - cookie_whitelist = config['cookie_whitelist'] - - if cookie_jar: - mkbasedir(cookie_jar) - - # Create cookie jar object from file. - self.jar = cookielib.MozillaCookieJar(cookie_jar) - - # Load cookie whitelist policy. - if config['use_whitelist']: - self.load_whitelist() - - if cookie_jar: - try: - # Attempt to load cookies from the cookie jar. - self.jar.load(ignore_discard=True) - - # Ensure restrictive permissions are set on the cookie jar - # to prevent other users on the system from hi-jacking your - # authenticated sessions simply by copying your cookie jar. - os.chmod(cookie_jar, 0600) - - except: - pass - - - def reload_whitelist(self): - '''Reload the cookie whitelist.''' - - cookie_whitelist = config['cookie_whitelist'] - if os.path.exists(cookie_whitelist): - echo("reloading whitelist %r" % cookie_whitelist) - self.open_cookie_jar() - - - def create_socket(self): - '''Create AF_UNIX socket for communication with uzbl instances.''' - - cookie_socket = config['cookie_socket'] - mkbasedir(cookie_socket) - - self.server_socket = socket.socket(socket.AF_UNIX, - socket.SOCK_SEQPACKET) - - self.server_socket.bind(cookie_socket) - - # Set restrictive permissions on the cookie socket to prevent other - # users on the system from data-mining your cookies. - os.chmod(cookie_socket, 0600) - - - def listen(self): - '''Listen for incoming cookie PUT and GET requests.''' - - daemon_timeout = config['daemon_timeout'] - echo("listening on %r" % config['cookie_socket']) - - while self._running: - # This line tells the socket how many pending incoming connections - # to enqueue at once. Raising this number may or may not increase - # performance. - self.server_socket.listen(1) - - if bool(select.select([self.server_socket], [], [], 1)[0]): - client_socket, _ = self.server_socket.accept() - self.handle_request(client_socket) - self.last_request = time.time() - client_socket.close() - continue - - if daemon_timeout: - # Checks if the daemon has been idling for too long. - idle = time.time() - self.last_request - if idle > daemon_timeout: - self._running = False - - - def handle_request(self, client_socket): - '''Connection made, now to serve a cookie PUT or GET request.''' - - # Receive cookie request from client. - data = client_socket.recv(8192) - if not data: - return - - # Cookie argument list in packet is null separated. - argv = data.split("\0") - action = argv[0].upper().strip() - - # Catch the EXIT command sent to kill running daemons. - if action == "EXIT": - self._running = False - return - - # Catch whitelist RELOAD command. - elif action == "RELOAD": - self.reload_whitelist() - return - - # Return if command unknown. - elif action not in ['GET', 'PUT']: - error("unknown command %r." % argv) - return - - # Determine whether or not to print cookie data to terminal. - print_cookie = (config['verbose'] and not config['daemon_mode']) - if print_cookie: - print ' '.join(argv[:4]) - - uri = urllib2.urlparse.ParseResult( - scheme=argv[1], - netloc=argv[2], - path=argv[3], - params='', - query='', - fragment='').geturl() - - req = urllib2.Request(uri) - - if action == "GET": - self.jar.add_cookie_header(req) - if req.has_header('Cookie'): - cookie = req.get_header('Cookie') - client_socket.send(cookie) - if print_cookie: - print cookie - - else: - client_socket.send("\0") - - elif action == "PUT": - cookie = argv[4] if len(argv) > 3 else None - if print_cookie: - print cookie - - self.put_cookie(req, cookie) - - if print_cookie: - print - - - def put_cookie(self, req, cookie=None): - '''Put a cookie in the cookie jar.''' - - hdr = urllib2.httplib.HTTPMessage(\ - StringIO.StringIO('Set-Cookie: %s' % cookie)) - res = urllib2.addinfourl(StringIO.StringIO(), hdr, - req.get_full_url()) - self.jar.extract_cookies(res, req) - if config['cookie_jar']: - self.jar.save(ignore_discard=True) - - - def del_socket(self): - '''Remove the cookie_socket file on exit. In a way the cookie_socket - is the daemons pid file equivalent.''' - - if self.server_socket: - try: - self.server_socket.close() - - except: - pass - - self.server_socket = None - - cookie_socket = config['cookie_socket'] - if os.path.exists(cookie_socket): - echo("deleting socket %r" % cookie_socket) - os.remove(cookie_socket) - - - def quit(self): - '''Called on exit to make sure all loose ends are tied up.''' - - self.del_socket() - sys.exit(0) - - -def main(): - '''Main function.''' - - # Define command line parameters. - usage = "usage: %prog [options] {start|stop|restart|reload}" - parser = OptionParser(usage=usage) - parser.add_option('-n', '--no-daemon', dest='no_daemon', - action='store_true', help="don't daemonise the process.") - - parser.add_option('-v', '--verbose', dest="verbose", - action='store_true', help="print verbose output.") - - parser.add_option('-t', '--daemon-timeout', dest='daemon_timeout', - action="store", metavar="SECONDS", help="shutdown the daemon after x "\ - "seconds inactivity. WARNING: Do not use this when launching the "\ - "cookie daemon manually.") - - parser.add_option('-s', '--cookie-socket', dest="cookie_socket", - metavar="SOCKET", help="manually specify the socket location.") - - parser.add_option('-j', '--cookie-jar', dest='cookie_jar', - metavar="FILE", help="manually specify the cookie jar location.") - - parser.add_option('-m', '--memory', dest='memory', action='store_true', - help="store cookies in memory only - do not write to disk") - - parser.add_option('-u', '--use-whitelist', dest='usewhitelist', - action='store_true', help="use cookie whitelist policy") - - parser.add_option('-w', '--cookie-whitelist', dest='whitelist', - action='store', help="manually specify whitelist location", - metavar='FILE') - - # Parse the command line arguments. - (options, args) = parser.parse_args() - - expand = lambda p: os.path.realpath(os.path.expandvars(p)) - - initcommands = ['start', 'stop', 'restart', 'reload'] - for arg in args: - if arg not in initcommands: - error("unknown argument %r" % args[0]) - sys.exit(1) - - if len(args) > 1: - error("the daemon only accepts one {%s} action at a time." - % '|'.join(initcommands)) - sys.exit(1) - - if len(args): - action = args[0] - - else: - action = "start" - - if options.no_daemon: - config['daemon_mode'] = False - - if options.cookie_socket: - config['cookie_socket'] = expand(options.cookie_socket) - - if options.cookie_jar: - config['cookie_jar'] = expand(options.cookie_jar) - - if options.memory: - config['cookie_jar'] = None - - if options.whitelist: - config['cookie_whitelist'] = expand(options.whitelist) - - if options.whitelist or options.usewhitelist: - config['use_whitelist'] = True - - if options.daemon_timeout: - try: - config['daemon_timeout'] = int(options.daemon_timeout) - - except ValueError: - error("expected int argument for -t, --daemon-timeout") - - # Expand $VAR's in config keys that relate to paths. - for key in ['cookie_socket', 'cookie_jar', 'cookie_whitelist']: - if config[key]: - config[key] = os.path.expandvars(config[key]) - - if options.verbose: - config['verbose'] = True - import pprint - sys.stderr.write("%s\n" % pprint.pformat(config)) - - # It would be better if we didn't need to start this python process just - # to send a command to the socket, but unfortunately socat doesn't seem - # to support SEQPACKET. - if action == "reload": - send_command(config['cookie_socket'], "RELOAD") - - if action in ['stop', 'restart']: - kill_daemon(config['cookie_socket']) - - if action in ['start', 'restart']: - CookieMonster().run() - - -if __name__ == "__main__": - main() diff --git a/examples/data/uzbl/scripts/uzbl-event-manager b/examples/data/uzbl/scripts/uzbl-event-manager deleted file mode 100755 index b3fdb3c..0000000 --- a/examples/data/uzbl/scripts/uzbl-event-manager +++ /dev/null @@ -1,833 +0,0 @@ -#!/usr/bin/env python - -# Event Manager for Uzbl -# Copyright (c) 2009, Mason Larobina -# Copyright (c) 2009, Dieter Plaetinck -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -''' - -E V E N T _ M A N A G E R . P Y -=============================== - -Event manager for uzbl written in python. - -''' - -import imp -import os -import sys -import re -import socket -import pprint -import time -import atexit -from select import select -from signal import signal, SIGTERM -from optparse import OptionParser -from traceback import print_exc -from functools import partial - - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return os.path.join(os.environ['HOME'], default) - - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -# `make install` will put the correct value here for your system -PREFIX = '/usr/local/' - -# Setup xdg paths. -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') -CACHE_DIR = os.path.join(xdghome('CACHE', '.cache/'), 'uzbl/') - -# Event manager config dictionary. This is not to be confused with the config -# dict that tracks variables in the uzbl instance. -CONFIG = { - 'verbose': False, - 'daemon_mode': True, - 'auto_close': False, - - 'plugins_load': [], - 'plugins_ignore': [], - - 'plugin_dirs': [os.path.join(DATA_DIR, 'plugins/'), - os.path.join(PREFIX, 'share/uzbl/examples/data/uzbl/plugins/')], - - 'server_socket': os.path.join(CACHE_DIR, 'event_daemon'), - 'pid_file': os.path.join(CACHE_DIR, 'event_daemon.pid'), -} - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - - -# Define some globals. -SCRIPTNAME = os.path.basename(sys.argv[0]) -FINDSPACES = re.compile("\s+") - - -class ArgumentError(Exception): - pass - - -def echo(msg): - '''Prints only if the verbose flag has been set.''' - - if CONFIG['verbose']: - sys.stdout.write("%s: %s\n" % (SCRIPTNAME, msg)) - - -def error(msg): - '''Prints error messages to stderr.''' - - sys.stderr.write("%s: error: %s\n" % (SCRIPTNAME, msg)) - - -def counter(): - '''Generate unique object id's.''' - - i = 0 - while True: - i += 1 - yield i - - -def find_plugins(plugin_dirs): - '''Find all event manager plugins in the plugin dirs and return a - dictionary of {'plugin-name.py': '/full/path/to/plugin-name.py', ...}''' - - plugins = {} - - for plugin_dir in plugin_dirs: - plugin_dir = os.path.realpath(os.path.expandvars(plugin_dir)) - if not os.path.isdir(plugin_dir): - continue - - for filename in os.listdir(plugin_dir): - if not filename.lower().endswith('.py'): - continue - - path = os.path.join(plugin_dir, filename) - if not os.path.isfile(path): - continue - - if filename not in plugins: - plugins[filename] = plugin_dir - - return plugins - - -def load_plugins(plugin_dirs, load=None, ignore=None): - '''Load event manager plugins found in the plugin_dirs.''' - - load = [] if load is None else load - ignore = [] if ignore is None else ignore - - # Find the plugins in the plugin_dirs. - found = find_plugins(plugin_dirs) - - if load: - # Ignore anything not in the load list. - for plugin in found.keys(): - if plugin not in load: - del found[plugin] - - if ignore: - # Ignore anything in the ignore list. - for plugin in found.keys(): - if plugin in ignore: - del found[plugin] - - # Print plugin list to be loaded. - pprint.pprint(found) - - loaded = {} - # Load all found plugins into the loaded dict. - for (filename, plugin_dir) in found.items(): - name = filename[:-3] - info = imp.find_module(name, [plugin_dir]) - plugin = imp.load_module(name, *info) - loaded[(plugin_dir, filename)] = plugin - - return loaded - - -def daemonize(): - '''Daemonize the process using the Stevens' double-fork magic.''' - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #1 failed") - sys.exit(1) - - os.chdir('/') - os.setsid() - os.umask(0) - - try: - if os.fork(): - os._exit(0) - - except OSError: - print_exc() - sys.stderr.write("fork #2 failed") - sys.exit(1) - - sys.stdout.flush() - sys.stderr.flush() - - devnull = '/dev/null' - stdin = file(devnull, 'r') - stdout = file(devnull, 'a+') - stderr = file(devnull, 'a+', 0) - - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - -def make_dirs(path): - '''Make all basedirs recursively as required.''' - - dirname = os.path.dirname(path) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - -def make_pid_file(pid_file): - '''Make pid file at given pid_file location.''' - - make_dirs(pid_file) - fileobj = open(pid_file, 'w') - fileobj.write('%d' % os.getpid()) - fileobj.close() - - -def del_pid_file(pid_file): - '''Delete pid file at given pid_file location.''' - - if os.path.isfile(pid_file): - os.remove(pid_file) - - -def get_pid(pid_file): - '''Read pid from pid_file.''' - - try: - fileobj = open(pid_file, 'r') - pid = int(fileobj.read()) - fileobj.close() - return pid - - except IOError, ValueError: - print_exc() - return None - - -def pid_running(pid): - '''Returns True if a process with the given pid is running.''' - - try: - os.kill(pid, 0) - - except OSError: - return False - - else: - return True - - -def term_process(pid): - '''Send a SIGTERM signal to the process with the given pid.''' - - if not pid_running(pid): - return False - - os.kill(pid, SIGTERM) - - start = time.time() - while True: - if not pid_running(pid): - return True - - if time.time() - start > 5: - raise OSError('failed to stop process with pid: %d' % pid) - - time.sleep(0.25) - - -def parse_msg(uzbl, msg): - '''Parse an incoming msg from a uzbl instance. All non-event messages - will be printed here and not be passed to the uzbl instance event - handler function.''' - - if not msg: - return - - cmd = FINDSPACES.split(msg, 3) - if not cmd or cmd[0] != 'EVENT': - # Not an event message. - print '---', msg - return - - while len(cmd) < 4: - cmd.append('') - - event, args = cmd[2], cmd[3] - if not event: - return - - try: - uzbl.event(event, args) - - except: - print_exc() - - -class EventHandler(object): - - nexthid = counter().next - - def __init__(self, event, handler, *args, **kargs): - if not callable(handler): - raise ArgumentError("EventHandler object requires a callable " - "object function for the handler argument not: %r" % handler) - - self.function = handler - self.args = args - self.kargs = kargs - self.event = event - self.hid = self.nexthid() - - - def __repr__(self): - args = ["event=%s" % self.event, "hid=%d" % self.hid, - "function=%r" % self.function] - - if self.args: - args.append("args=%r" % self.args) - - if self.kargs: - args.append("kargs=%r" % self.kargs) - - return "" % ', '.join(args) - - -class UzblInstance(object): - - # Give all plugins access to the main config dict. - config = CONFIG - - def __init__(self, parent, client_socket): - - # Internal variables. - self.exports = {} - self.handlers = {} - self.parent = parent - self.client_socket = client_socket - - self.depth = 0 - self.buffer = '' - self.pid = None - - # Call the init function in every plugin. The init function in each - # plugin is where that plugin connects functions to events and exports - # functions to the uzbl object. - for plugin in self.parent['plugins'].values(): - try: - plugin.init(self) - - except: - raise - - - def send(self, msg): - '''Send a command to the uzbl instance via the socket file.''' - - msg = msg.strip() - if self.client_socket: - print '%s<-- %s' % (' ' * self.depth, msg) - self.client_socket.send(("%s\n" % msg).encode('utf-8')) - - else: - print '%s!-- %s' % (' ' * self.depth, msg) - - - def export(self, name, function): - '''Export `function(uzbl, *args, ..)` inside a plugin to the uzbl - object like so `uzbl.function(*args, ..)`. This will allow other - plugins to call functions inside the current plugin (which is currently - calling this function) via the uzbl object.''' - - self.__dict__.__setitem__(name, partial(function, self)) - - - def export_dict(self, export_dict): - '''Export multiple (name, function)'s at once inside a dict of the - form `{name1: function1, name2: function2, ...}`.''' - - for (name, function) in export_dict.items(): - self.export(name, function) - - - def connect(self, event, handler, *args, **kargs): - '''Connect a uzbl event with a handler. Handlers can either be a - function or a uzbl command string.''' - - event = event.upper().strip() - assert event and ' ' not in event - - if event not in self.handlers.keys(): - self.handlers[event] = [] - - handlerobj = EventHandler(event, handler, *args, **kargs) - self.handlers[event].append(handlerobj) - print handlerobj - - - def connect_dict(self, connect_dict): - '''Connect a dictionary comprising of {"EVENT_NAME": handler, ..} to - the event handler stack. - - If you need to supply args or kargs to an event use the normal connect - function.''' - - for (event, handler) in connect_dict.items(): - self.connect(event, handler) - - - def remove_by_id(self, hid): - '''Remove connected event handler by unique handler id.''' - - for (event, handlers) in self.handlers.items(): - for handler in list(handlers): - if hid != handler.hid: - continue - - echo("removed %r" % handler) - handlers.remove(handler) - return - - echo('unable to find & remove handler with id: %d' % hid) - - - def remove(self, handler): - '''Remove connected event handler.''' - - for (event, handlers) in self.handlers.items(): - if handler in handlers: - echo("removed %r" % handler) - handlers.remove(handler) - return - - echo('unable to find & remove handler: %r' % handler) - - - def exec_handler(self, handler, *args, **kargs): - '''Execute event handler function.''' - - args += handler.args - kargs = dict(handler.kargs.items()+kargs.items()) - handler.function(self, *args, **kargs) - - - def event(self, event, *args, **kargs): - '''Raise an event.''' - - event = event.upper() - elems = [event,] - if args: elems.append(unicode(args)) - if kargs: elems.append(unicode(kargs)) - print "%s--> %s" % (' ' * self.depth, ' '.join(elems)) - - if event == "INSTANCE_START" and args: - self.pid = int(args[0]) - - if event not in self.handlers: - return - - for handler in self.handlers[event]: - self.depth += 1 - try: - self.exec_handler(handler, *args, **kargs) - - except: - print_exc() - - self.depth -= 1 - - - def close(self): - '''Close the client socket and clean up.''' - - try: - self.client_socket.close() - - except: - pass - - for (name, plugin) in self.parent['plugins'].items(): - if hasattr(plugin, 'cleanup'): - plugin.cleanup(self) - - -class UzblEventDaemon(dict): - def __init__(self): - - # Init variables and dict keys. - dict.__init__(self, {'uzbls': {}}) - self.running = None - self.server_socket = None - self.socket_location = None - - # Register that the event daemon server has started by creating the - # pid file. - make_pid_file(CONFIG['pid_file']) - - # Register a function to clean up the socket and pid file on exit. - atexit.register(self.quit) - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: sys.exit(1)) - - # Load plugins, first-build of the plugins may be a costly operation. - self['plugins'] = load_plugins(CONFIG['plugin_dirs'], - CONFIG['plugins_load'], CONFIG['plugins_ignore']) - - - def _create_server_socket(self): - '''Create the event manager daemon socket for uzbl instance duplex - communication.''' - - server_socket = CONFIG['server_socket'] - server_socket = os.path.realpath(os.path.expandvars(server_socket)) - self.socket_location = server_socket - - # Delete socket if it exists. - if os.path.exists(server_socket): - os.remove(server_socket) - - self.server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.server_socket.bind(server_socket) - self.server_socket.listen(5) - - - def _close_server_socket(self): - '''Close and delete the server socket.''' - - try: - self.server_socket.close() - self.server_socket = None - - if os.path.exists(self.socket_location): - os.remove(self.socket_location) - - except: - pass - - - def run(self): - '''Main event daemon loop.''' - - # Create event daemon socket. - self._create_server_socket() - echo('listening on: %s' % self.socket_location) - - if CONFIG['daemon_mode']: - echo('entering daemon mode.') - daemonize() - # The pid has changed so update the pid file. - make_pid_file(CONFIG['pid_file']) - - # Now listen for incoming connections and or data. - self.listen() - - # Clean up. - self.quit() - - - def listen(self): - '''Accept incoming connections and constantly poll instance sockets - for incoming data.''' - - self.running = True - while self.running: - - sockets = [self.server_socket] + self['uzbls'].keys() - - reads, _, errors = select(sockets, [], sockets, 1) - - if self.server_socket in reads: - self.accept_connection() - reads.remove(self.server_socket) - - for client in reads: - self.read_socket(client) - - for client in errors: - error('Unknown error on socket: %r' % client) - self.close_connection(client) - - - def read_socket(self, client): - '''Read data from an instance socket and pass to the uzbl objects - event handler function.''' - - uzbl = self['uzbls'][client] - try: - raw = unicode(client.recv(8192), 'utf-8', 'ignore') - - except: - print_exc() - raw = None - - if not raw: - # Read null byte, close socket. - return self.close_connection(client) - - uzbl.buffer += raw - msgs = uzbl.buffer.split('\n') - uzbl.buffer = msgs.pop() - - for msg in msgs: - try: - parse_msg(uzbl, msg.strip()) - - except: - print_exc() - - - def accept_connection(self): - '''Accept incoming connection to the server socket.''' - - client_socket = self.server_socket.accept()[0] - - uzbl = UzblInstance(self, client_socket) - self['uzbls'][client_socket] = uzbl - - - def close_connection(self, client): - '''Clean up after instance close.''' - - try: - if client in self['uzbls']: - uzbl = self['uzbls'][client] - uzbl.close() - del self['uzbls'][client] - - except: - print_exc() - - if not len(self['uzbls']) and CONFIG['auto_close']: - echo('auto closing event manager.') - self.running = False - - - def quit(self): - '''Close all instance socket objects, server socket and delete the - pid file.''' - - echo('shutting down event manager.') - - for client in self['uzbls'].keys(): - self.close_connection(client) - - echo('unlinking: %r' % self.socket_location) - self._close_server_socket() - - echo('deleting pid file: %r' % CONFIG['pid_file']) - del_pid_file(CONFIG['pid_file']) - - -def stop_action(): - '''Stop the event manager daemon.''' - - pid_file = CONFIG['pid_file'] - if not os.path.isfile(pid_file): - return echo('no running daemon found.') - - echo('found pid file: %r' % pid_file) - pid = get_pid(pid_file) - if not pid_running(pid): - echo('no process with pid: %d' % pid) - return os.remove(pid_file) - - echo("terminating process with pid: %d" % pid) - term_process(pid) - if os.path.isfile(pid_file): - os.remove(pid_file) - - echo('stopped event daemon.') - - -def start_action(): - '''Start the event manager daemon.''' - - pid_file = CONFIG['pid_file'] - if os.path.isfile(pid_file): - echo('found pid file: %r' % pid_file) - pid = get_pid(pid_file) - if pid_running(pid): - return echo('event daemon already started with pid: %d' % pid) - - echo('no process with pid: %d' % pid) - os.remove(pid_file) - - echo('starting event manager.') - UzblEventDaemon().run() - - -def restart_action(): - '''Restart the event manager daemon.''' - - echo('restarting event manager daemon.') - stop_action() - start_action() - - -def list_action(): - '''List all the plugins being loaded by the event daemon.''' - - plugins = find_plugins(CONFIG['plugin_dirs']) - dirs = {} - - for (plugin, plugin_dir) in plugins.items(): - if plugin_dir not in dirs: - dirs[plugin_dir] = [] - - dirs[plugin_dir].append(plugin) - - for (index, (plugin_dir, plugin_list)) in enumerate(sorted(dirs.items())): - if index: - print - - print "%s:" % plugin_dir - for plugin in sorted(plugin_list): - print " %s" % plugin - - -if __name__ == "__main__": - USAGE = "usage: %prog [options] {start|stop|restart|list}" - PARSER = OptionParser(usage=USAGE) - PARSER.add_option('-v', '--verbose', dest='verbose', action="store_true", - help="print verbose output.") - - PARSER.add_option('-d', '--plugin-dirs', dest='plugin_dirs', action="store", - metavar="DIRS", help="Specify plugin directories in the form of "\ - "'dir1:dir2:dir3'.") - - PARSER.add_option('-l', '--load-plugins', dest="load", action="store", - metavar="PLUGINS", help="comma separated list of plugins to load") - - PARSER.add_option('-i', '--ignore-plugins', dest="ignore", action="store", - metavar="PLUGINS", help="comma separated list of plugins to ignore") - - PARSER.add_option('-p', '--pid-file', dest='pid', action='store', - metavar='FILE', help="specify pid file location") - - PARSER.add_option('-s', '--server-socket', dest='socket', action='store', - metavar='SOCKET', help="specify the daemon socket location") - - PARSER.add_option('-n', '--no-daemon', dest="daemon", - action="store_true", help="don't enter daemon mode.") - - PARSER.add_option('-a', '--auto-close', dest='autoclose', - action='store_true', help='auto close after all instances disconnect.') - - (OPTIONS, ARGS) = PARSER.parse_args() - - # init like {start|stop|..} daemon actions dict. - DAEMON_ACTIONS = {'start': start_action, 'stop': stop_action, - 'restart': restart_action, 'list': list_action} - - if not ARGS: - ACTION = 'start' - - elif len(ARGS) == 1: - ACTION = ARGS[0] - if ACTION not in DAEMON_ACTIONS: - raise ArgumentError("unknown argument: %r" % ACTION) - - else: - raise ArgumentError("too many arguments: %r" % ARGS) - - # parse other flags & options. - if OPTIONS.verbose: - CONFIG['verbose'] = True - - if OPTIONS.plugin_dirs: - PLUGIN_DIRS = [] - for DIR in OPTIONS.plugin_dirs.split(':'): - if not DIR: - continue - - PLUGIN_DIRS.append(os.path.realpath(DIR)) - - CONFIG['plugin_dirs'] = PLUGIN_DIRS - echo("plugin search dirs: %r" % PLUGIN_DIRS) - - if OPTIONS.load and OPTIONS.ignore: - error("you can't load and ignore at the same time.") - sys.exit(1) - - elif OPTIONS.load: - LOAD = CONFIG['plugins_load'] - for PLUGIN in OPTIONS.load.split(','): - if PLUGIN.strip(): - LOAD.append(PLUGIN.strip()) - - echo('only loading plugin(s): %s' % ', '.join(LOAD)) - - elif OPTIONS.ignore: - IGNORE = CONFIG['plugins_ignore'] - for PLUGIN in OPTIONS.ignore.split(','): - if PLUGIN.strip(): - IGNORE.append(PLUGIN.strip()) - - echo('ignoring plugin(s): %s' % ', '.join(IGNORE)) - - if OPTIONS.autoclose: - CONFIG['auto_close'] = True - echo('will auto close.') - - if OPTIONS.pid: - CONFIG['pid_file'] = os.path.realpath(OPTIONS.pid) - echo("pid file location: %r" % CONFIG['pid_file']) - - if OPTIONS.socket: - CONFIG['server_socket'] = os.path.realpath(OPTIONS.socket) - echo("daemon socket location: %s" % CONFIG['server_socket']) - - if OPTIONS.daemon: - CONFIG['daemon_mode'] = False - - # Now {start|stop|...} - DAEMON_ACTIONS[ACTION]() diff --git a/examples/data/uzbl/scripts/uzbl-tabbed b/examples/data/uzbl/scripts/uzbl-tabbed deleted file mode 100755 index 5d1a9f8..0000000 --- a/examples/data/uzbl/scripts/uzbl-tabbed +++ /dev/null @@ -1,1417 +0,0 @@ -#!/usr/bin/env python - -# Uzbl tabbing wrapper using a fifo socket interface -# Copyright (c) 2009, Tom Adams -# Copyright (c) 2009, Chris van Dijk -# Copyright (c) 2009, Mason Larobina -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# Author(s): -# Tom Adams -# Wrote the original uzbl_tabbed.py as a proof of concept. -# -# Chris van Dijk (quigybo) -# Made signifigant headway on the old uzbl_tabbing.py script on the -# uzbl wiki -# -# Mason Larobina -# Rewrite of the uzbl_tabbing.py script to use a fifo socket interface -# and inherit configuration options from the user's uzbl config. -# -# Contributor(s): -# mxey -# uzbl_config path now honors XDG_CONFIG_HOME if it exists. -# -# Romain Bignon -# Fix for session restoration code. -# -# Jake Probst -# Wrote a patch that overflows tabs in the tablist on to new lines when -# running of room. -# -# Devon Jones -# Fifo command bring_to_front which brings the gtk window to focus. -# -# Simon Lipp (sloonz) -# Various - - -# Dependencies: -# pygtk - python bindings for gtk. -# pango - python bindings needed for text rendering & layout in gtk widgets. -# pygobject - GLib's GObject bindings for python. -# -# Optional dependencies: -# simplejson - save uzbl_tabbed.py sessions & presets in json. -# -# Note: I haven't included version numbers with this dependency list because -# I've only ever tested uzbl_tabbed.py on the latest stable versions of these -# packages in Gentoo's portage. Package names may vary on different systems. - - -# Configuration: -# Because this version of uzbl_tabbed is able to inherit options from your main -# uzbl configuration file you may wish to configure uzbl tabbed from there. -# Here is a list of configuration options that can be customised and some -# example values for each: -# -# General tabbing options: -# show_tablist = 1 -# show_gtk_tabs = 0 -# tablist_top = 1 -# gtk_tab_pos = (top|left|bottom|right) -# gtk_refresh = 1000 -# switch_to_new_tabs = 1 -# capture_new_windows = 1 -# multiline_tabs = 1 -# -# Tab title options: -# tab_titles = 1 -# tab_indexes = 1 -# new_tab_title = Loading -# max_title_len = 50 -# show_ellipsis = 1 -# -# Session options: -# save_session = 1 -# json_session = 0 -# session_file = $HOME/.local/share/uzbl/session -# -# Inherited uzbl options: -# fifo_dir = /tmp -# socket_dir = /tmp -# icon_path = $HOME/.local/share/uzbl/uzbl.png -# status_background = #303030 -# -# Misc options: -# window_size = 800,800 -# verbose = 0 -# -# And uzbl_tabbed.py takes care of the actual binding of the commands via each -# instances fifo socket. -# -# Custom tab styling: -# tab_colours = foreground = "#888" background = "#303030" -# tab_text_colours = foreground = "#bbb" -# selected_tab = foreground = "#fff" -# selected_tab_text = foreground = "green" -# tab_indicate_https = 1 -# https_colours = foreground = "#888" -# https_text_colours = foreground = "#9c8e2d" -# selected_https = foreground = "#fff" -# selected_https_text = foreground = "gold" -# -# How these styling values are used are soley defined by the syling policy -# handler below (the function in the config section). So you can for example -# turn the tab text colour Firetruck-Red in the event "error" appears in the -# tab title or some other arbitrary event. You may wish to make a trusted -# hosts file and turn tab titles of tabs visiting trusted hosts purple. - - -# Issues: -# - new windows are not caught and opened in a new tab. -# - when uzbl_tabbed.py crashes it takes all the children with it. -# - when a new tab is opened when using gtk tabs the tab button itself -# grabs focus from its child for a few seconds. -# - when switch_to_new_tabs is not selected the notebook page is -# maintained but the new window grabs focus (try as I might to stop it). - - -# Todo: -# - add command line options to use a different session file, not use a -# session file and or open a uri on starup. -# - ellipsize individual tab titles when the tab-list becomes over-crowded -# - add "<" & ">" arrows to tablist to indicate that only a subset of the -# currently open tabs are being displayed on the tablist. -# - add the small tab-list display when both gtk tabs and text vim-like -# tablist are hidden (I.e. [ 1 2 3 4 5 ]) -# - check spelling. -# - pass a uzbl socketid to uzbl_tabbed.py and have it assimilated into -# the collective. Resistance is futile! - - -import pygtk -import gtk -import subprocess -import os -import re -import time -import getopt -import pango -import select -import sys -import gobject -import socket -import random -import hashlib -import atexit -import types - -from gobject import io_add_watch, source_remove, timeout_add, IO_IN, IO_HUP -from signal import signal, SIGTERM, SIGINT -from optparse import OptionParser, OptionGroup - - -pygtk.require('2.0') - -_SCRIPTNAME = os.path.basename(sys.argv[0]) -def error(msg): - sys.stderr.write("%s: error: %s\n" % (_SCRIPTNAME, msg)) - -# ============================================================================ -# ::: Default configuration section :::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def xdghome(key, default): - '''Attempts to use the environ XDG_*_HOME paths if they exist otherwise - use $HOME and the default path.''' - - xdgkey = "XDG_%s_HOME" % key - if xdgkey in os.environ.keys() and os.environ[xdgkey]: - return os.environ[xdgkey] - - return os.path.join(os.environ['HOME'], default) - -# Setup xdg paths. -DATA_DIR = os.path.join(xdghome('DATA', '.local/share/'), 'uzbl/') - -# Ensure uzbl xdg paths exist -if not os.path.exists(DATA_DIR): - os.makedirs(DATA_DIR) - -# All of these settings can be inherited from your uzbl config file. -config = { - # Tab options - 'show_tablist': True, # Show text uzbl like statusbar tab-list - 'show_gtk_tabs': False, # Show gtk notebook tabs - 'tablist_top': True, # Display tab-list at top of window - 'gtk_tab_pos': 'top', # Gtk tab position (top|left|bottom|right) - 'gtk_refresh': 1000, # Tablist refresh millisecond interval - 'switch_to_new_tabs': True, # Upon opening a new tab switch to it - 'capture_new_windows': True, # Use uzbl_tabbed to catch new windows - 'multiline_tabs': True, # Tabs overflow onto new tablist lines. - - # Tab title options - 'tab_titles': True, # Display tab titles (else only tab-nums) - 'tab_indexes': True, # Display tab nums (else only tab titles) - 'new_tab_title': 'Loading', # New tab title - 'max_title_len': 50, # Truncate title at n characters - 'show_ellipsis': True, # Show ellipsis when truncating titles - - # Session options - 'save_session': True, # Save session in file when quit - 'json_session': False, # Use json to save session. - 'saved_sessions_dir': os.path.join(DATA_DIR, 'sessions/'), - 'session_file': os.path.join(DATA_DIR, 'session'), - - # Inherited uzbl options - 'fifo_dir': '/tmp', # Path to look for uzbl fifo. - 'socket_dir': '/tmp', # Path to look for uzbl socket. - 'icon_path': os.path.join(DATA_DIR, 'uzbl.png'), - 'status_background': "#303030", # Default background for all panels. - - # Misc options - 'window_size': "800,800", # width,height in pixels. - 'verbose': False, # Print verbose output. - - # Add custom tab style definitions to be used by the tab colour policy - # handler here. Because these are added to the config dictionary like - # any other uzbl_tabbed configuration option remember that they can - # be superseeded from your main uzbl config file. - 'tab_colours': 'foreground = "#888" background = "#303030"', - 'tab_text_colours': 'foreground = "#bbb"', - 'selected_tab': 'foreground = "#fff"', - 'selected_tab_text': 'foreground = "green"', - 'tab_indicate_https': True, - 'https_colours': 'foreground = "#888"', - 'https_text_colours': 'foreground = "#9c8e2d"', - 'selected_https': 'foreground = "#fff"', - 'selected_https_text': 'foreground = "gold"', - -} # End of config dict. - -UZBL_TABBED_VARS = config.keys() - -# This is the tab style policy handler. Every time the tablist is updated -# this function is called to determine how to colourise that specific tab -# according the simple/complex rules as defined here. You may even wish to -# move this function into another python script and import it using: -# from mycustomtabbingconfig import colour_selector -# Remember to rename, delete or comment out this function if you do that. - -def colour_selector(tabindex, currentpage, uzbl): - '''Tablist styling policy handler. This function must return a tuple of - the form (tab style, text style).''' - - # Just as an example: - # if 'error' in uzbl.title: - # if tabindex == currentpage: - # return ('foreground="#fff"', 'foreground="red"') - # return ('foreground="#888"', 'foreground="red"') - - # Style tabs to indicate connected via https. - if config['tab_indicate_https'] and uzbl.uri.startswith("https://"): - if tabindex == currentpage: - return (config['selected_https'], config['selected_https_text']) - return (config['https_colours'], config['https_text_colours']) - - # Style to indicate selected. - if tabindex == currentpage: - return (config['selected_tab'], config['selected_tab_text']) - - # Default tab style. - return (config['tab_colours'], config['tab_text_colours']) - -# ============================================================================ -# ::: End of configuration section ::::::::::::::::::::::::::::::::::::::::::: -# ============================================================================ - -def echo(msg): - if config['verbose']: - sys.stderr.write("%s: %s\n" % (_SCRIPTNAME, msg)) - - -def counter(): - '''To infinity and beyond!''' - - i = 0 - while True: - i += 1 - yield i - - -def escape(s): - '''Replaces html markup in tab titles that screw around with pango.''' - - for (split, glue) in [('&','&'), ('<', '<'), ('>', '>')]: - s = s.replace(split, glue) - return s - - -class SocketClient: - '''Represents a Uzbl instance, which is not necessarly linked with a UzblInstance''' - - # List of UzblInstance objects not already linked with a SocketClient - instances_queue = {} - - def __init__(self, socket): - self._buffer = "" - self._socket = socket - self._watchers = [io_add_watch(socket, IO_IN, self._socket_recv),\ - io_add_watch(socket, IO_HUP, self._socket_closed)] - self.uzbl = None - - - def _socket_recv(self, fd, condition): - '''Data available on socket, process it''' - - self._feed(self._socket.recv(1024)) #TODO: is io_add_watch edge or level-triggered ? - return True - - - def _socket_closed(self, fd, condition): - '''Remote client exited''' - self.uzbl.close() - return False - - - def _feed(self, data): - '''An Uzbl instance sent some data, parse it''' - - self._buffer += data - if self.uzbl: - if "\n" in self._buffer: - cmds = self._buffer.split("\n") - - if cmds[-1]: # Last command has been received incomplete, don't process it - self._buffer, cmds = cmds[-1], cmds[:-1] - else: - self._buffer = "" - - for cmd in cmds: - if cmd: - self.uzbl.parse_command(cmd) - else: - name = re.findall('^EVENT \[(\d+-\d+)\] INSTANCE_START \d+$', self._buffer, re.M) - uzbl = self.instances_queue.get(name[0]) - if uzbl: - del self.instances_queue[name[0]] - self.uzbl = uzbl - self.uzbl.got_socket(self) - self._feed("") - - def send(self, data): - '''Child socket send function.''' - - self._socket.send(data + "\n") - - def close(self): - '''Close the connection''' - - if self._socket: - self._socket.close() - self._socket = None - map(source_remove, self._watchers) - self._watchers = [] - - -class UzblInstance: - '''Uzbl instance meta-data/meta-action object.''' - - def __init__(self, parent, tab, name, uri, title, switch): - - self.parent = parent - self.tab = tab - self.name = name - self.title = title - self.tabtitle = "" - self.uri = uri - self._client = None - self._switch = switch # Switch to tab after loading ? - self.title_changed() - - - def got_socket(self, client): - '''Uzbl instance is now connected''' - - self._client = client - self.parent.config_uzbl(self) - if self._switch: - tabid = self.parent.notebook.page_num(self.tab) - self.parent.goto_tab(tabid) - - - def title_changed(self, gtk_only = True): # GTK-only is for indexes - '''self.title has changed, update the tabs list''' - - tab_titles = config['tab_titles'] - tab_indexes = config['tab_indexes'] - show_ellipsis = config['show_ellipsis'] - max_title_len = config['max_title_len'] - - # Unicode heavy strings do not like being truncated/sliced so by - # re-encoding the string sliced of limbs are removed. - self.tabtitle = self.title[:max_title_len + int(show_ellipsis)] - if type(self.tabtitle) != types.UnicodeType: - self.tabtitle = unicode(self.tabtitle, 'utf-8', 'ignore') - - self.tabtitle = self.tabtitle.encode('utf-8', 'ignore').strip() - - if show_ellipsis and len(self.tabtitle) != len(self.title): - self.tabtitle += "\xe2\x80\xa6" - - gtk_tab_format = "%d %s" - index = self.parent.notebook.page_num(self.tab) - if tab_titles and tab_indexes: - self.parent.notebook.set_tab_label_text(self.tab, - gtk_tab_format % (index, self.tabtitle)) - elif tab_titles: - self.parent.notebook.set_tab_label_text(self.tab, self.tabtitle) - else: - self.parent.notebook.set_tab_label_text(self.tab, str(index)) - - # If instance is current tab, update window title - if index == self.parent.notebook.get_current_page(): - title_format = "%s - Uzbl Browser" - self.parent.window.set_title(title_format % self.title) - - # Non-GTK tabs - if not gtk_only: - self.parent.update_tablist() - - - def set(self, key, val): - ''' Send the SET command to Uzbl ''' - - if self._client: - self._client.send('set %s = %s') #TODO: escape chars ? - - - def exit(self): - ''' Ask the Uzbl instance to close ''' - - if self._client: - self._client.send('exit') - - - def parse_command(self, cmd): - ''' Parse event givent by the Uzbl instance ''' - - type, _, args = cmd.split(" ", 2) - if type == "EVENT": - type, args = args.split(" ", 1) - if type == "TITLE_CHANGED": - self.title = args - self.title_changed() - elif type == "VARIABLE_SET": - var, _, val = args.split(" ", 2) - try: - val = int(val) - except: - pass - - if var in UZBL_TABBED_VARS: - if config[var] != val: - config[var] = val - if var == "show_gtk_tabs": - self.parent.notebook.set_show_tabs(bool(val)) - elif var == "show_tablist" or var == "tablist_top": - self.parent.update_tablist_display() - elif var == "gtk_tab_pos": - self.parent.update_gtk_tab_pos() - elif var == "status_background": - col = gtk.gdk.color_parse(config['status_background']) - self.parent.ebox.modify_bg(gtk.STATE_NORMAL, col) - elif var == "tab_titles" or var == "tab_indexes": - for tab in self.parent.notebook: - self.parent.tabs[tab].title_changed(True) - - self.parent.update_tablist() - else: - config[var] = val - - if var == "uri": - self.uri = var - self.parent.update_tablist() - elif type == "NEW_TAB": - self.parent.new_tab(args) - elif type == "NEXT_TAB": - if args: - self.parent.next_tab(int(args)) - else: - self.parent.next_tab() - elif type == "PREV_TAB": - if args: - self.parent.prev_tab(int(args)) - else: - self.parent.prev_tab() - elif type == "GOTO_TAB": - self.parent.goto_tab(int(args)) - elif type == "FIRST_TAB": - self.parent.goto_tab(0) - elif type == "LAST_TAB": - self.parent.goto_tab(-1) - elif type == "PRESET_TABS": - self.parent.parse_command(["preset"] + args.split()) - elif type == "BRING_TO_FRONT": - self.parent.window.present() - elif type == "CLEAN_TABS": - self.parent.clean_slate() - elif type == "EXIT_ALL_TABS": - self.parent.quitrequest() - - - def close(self): - '''The remote instance exited''' - - if self._client: - self._client.close() - self._client = None - - -class UzblTabbed: - '''A tabbed version of uzbl using gtk.Notebook''' - - def __init__(self): - '''Create tablist, window and notebook.''' - - self._timers = {} - self._buffer = "" - self._killed = False - - # A list of the recently closed tabs - self._closed = [] - - # Holds metadata on the uzbl childen open. - self.tabs = {} - - # Uzbl sockets (socket => SocketClient) - self.clients = {} - - # Generates a unique id for uzbl socket filenames. - self.next_pid = counter().next - - # Create main window - self.window = gtk.Window() - try: - window_size = map(int, config['window_size'].split(',')) - self.window.set_default_size(*window_size) - - except: - error("Invalid value for default_size in config file.") - - self.window.set_title("Uzbl Browser") - self.window.set_border_width(0) - - # Set main window icon - icon_path = config['icon_path'] - if os.path.exists(icon_path): - self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) - - else: - icon_path = '/usr/share/uzbl/examples/data/uzbl/uzbl.png' - if os.path.exists(icon_path): - self.window.set_icon(gtk.gdk.pixbuf_new_from_file(icon_path)) - - # Attach main window event handlers - self.window.connect("delete-event", self.quitrequest) - - # Create tab list - vbox = gtk.VBox() - self.vbox = vbox - self.window.add(vbox) - ebox = gtk.EventBox() - self.ebox = ebox - self.tablist = gtk.Label() - - self.tablist.set_use_markup(True) - self.tablist.set_justify(gtk.JUSTIFY_LEFT) - self.tablist.set_line_wrap(False) - self.tablist.set_selectable(False) - self.tablist.set_padding(2,2) - self.tablist.set_alignment(0,0) - self.tablist.set_ellipsize(pango.ELLIPSIZE_END) - self.tablist.set_text(" ") - self.tablist.show() - ebox.add(self.tablist) - ebox.show() - bgcolor = gtk.gdk.color_parse(config['status_background']) - ebox.modify_bg(gtk.STATE_NORMAL, bgcolor) - - # Create notebook - self.notebook = gtk.Notebook() - self.notebook.set_show_tabs(config['show_gtk_tabs']) - - # Set tab position - self.update_gtk_tab_pos() - - self.notebook.set_show_border(False) - self.notebook.set_scrollable(True) - self.notebook.set_border_width(0) - - self.notebook.connect("page-removed", self.tab_closed) - self.notebook.connect("switch-page", self.tab_changed) - self.notebook.connect("page-added", self.tab_opened) - - self.notebook.show() - vbox.pack_start(self.notebook, True, True, 0) - vbox.reorder_child(self.notebook, 1) - self.update_tablist_display() - - self.vbox.show() - self.window.show() - self.wid = self.notebook.window.xid - - # Store information about the applications fifo and socket. - fifo_filename = 'uzbltabbed_%d.fifo' % os.getpid() - socket_filename = 'uzbltabbed_%d.socket' % os.getpid() - self._fifo = None - self._socket = None - self.fifo_path = os.path.join(config['fifo_dir'], fifo_filename) - self.socket_path = os.path.join(config['socket_dir'], socket_filename) - - # Now initialise the fifo and the socket - self.init_fifo() - self.init_socket() - - # If we are using sessions then load the last one if it exists. - if config['save_session']: - self.load_session() - - - def run(self): - '''UzblTabbed main function that calls the gtk loop.''' - - if not self.clients and not SocketClient.instances_queue and not self.tabs: - self.new_tab() - - gtk_refresh = int(config['gtk_refresh']) - if gtk_refresh < 100: - gtk_refresh = 100 - - # Make SIGTERM act orderly. - signal(SIGTERM, lambda signum, stack_frame: self.terminate(SIGTERM)) - - # Catch keyboard interrupts - signal(SIGINT, lambda signum, stack_frame: self.terminate(SIGINT)) - - try: - gtk.main() - - except: - error("encounted error %r" % sys.exc_info()[1]) - - # Unlink fifo socket - self.unlink_fifo() - self.close_socket() - - # Attempt to close all uzbl instances nicely. - self.quitrequest() - - # Allow time for all the uzbl instances to quit. - time.sleep(1) - - raise - - - def terminate(self, termsig=None): - '''Handle termination signals and exit safely and cleanly.''' - - # Not required but at least it lets the user know what killed his - # browsing session. - if termsig == SIGTERM: - error("caught SIGTERM signal") - - elif termsig == SIGINT: - error("caught keyboard interrupt") - - else: - error("caught unknown signal") - - error("commencing infanticide!") - - # Sends the exit signal to all uzbl instances. - self.quitrequest() - - - def init_socket(self): - '''Create interprocess communication socket.''' - - def accept(sock, condition): - '''A new uzbl instance was created''' - - client, _ = sock.accept() - self.clients[client] = SocketClient(client) - - return True - - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.bind(self.socket_path) - sock.listen(1) - - # Add event handler for IO_IN event. - self._socket = (sock, io_add_watch(sock, IO_IN, accept)) - - echo("[socket] listening at %r" % self.socket_path) - - # Add atexit register to destroy the socket on program termination. - atexit.register(self.close_socket) - - - def close_socket(self): - '''Close the socket when closing the application''' - - if self._socket: - (fd, watcher) = self._socket - source_remove(watcher) - fd.close() - os.unlink(self.socket_path) - self._socket = None - - - def init_fifo(self): - '''Create interprocess communication fifo.''' - - if os.path.exists(self.fifo_path): - if not os.access(self.fifo_path, os.F_OK | os.R_OK | os.W_OK): - os.mkfifo(self.fifo_path) - - else: - basedir = os.path.dirname(self.fifo_path) - if not os.path.exists(basedir): - os.makedirs(basedir) - - os.mkfifo(self.fifo_path) - - # Add event handlers for IO_IN & IO_HUP events. - self.setup_fifo_watchers() - - echo("[fifo] listening at %r" % self.fifo_path) - - # Add atexit register to destroy the fifo on program termination. - atexit.register(self.unlink_fifo) - - - def unlink_fifo(self): - '''Unlink the fifo socket. Note: This function is called automatically - on exit by an atexit register.''' - - # Make sure the fifo fd is closed. - self.close_fifo() - - # And unlink if the real fifo exists. - if os.path.exists(self.fifo_path): - os.unlink(self.fifo_path) - echo("unlinked %r" % self.fifo_path) - - - def close_fifo(self): - '''Remove all event handlers watching the fifo and close the fd.''' - - # Already closed - if self._fifo is None: return - - (fd, watchers) = self._fifo - os.close(fd) - - # Stop all gobject io watchers watching the fifo. - for gid in watchers: - source_remove(gid) - - self._fifo = None - - - def setup_fifo_watchers(self): - '''Open fifo socket fd and setup gobject IO_IN & IO_HUP event - handlers.''' - - # Close currently open fifo fd and kill all watchers - self.close_fifo() - - fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK) - - # Add gobject io event handlers to the fifo socket. - watchers = [io_add_watch(fd, IO_IN, self.main_fifo_read),\ - io_add_watch(fd, IO_HUP, self.main_fifo_hangup)] - - self._fifo = (fd, watchers) - - - def main_fifo_hangup(self, fd, cb_condition): - '''Handle main fifo socket hangups.''' - - # Close old fd, open new fifo socket and add io event handlers. - self.setup_fifo_watchers() - - # Kill the gobject event handler calling this handler function. - return False - - - def main_fifo_read(self, fd, cb_condition): - '''Read from main fifo socket.''' - - self._buffer = os.read(fd, 1024) - temp = self._buffer.split("\n") - self._buffer = temp.pop() - cmds = [s.strip().split() for s in temp if len(s.strip())] - - for cmd in cmds: - try: - #print cmd - self.parse_command(cmd) - - except: - error("parse_command: invalid command %s" % ' '.join(cmd)) - raise - - return True - - - def parse_command(self, cmd): - '''Parse instructions from uzbl child processes.''' - - # Commands ( [] = optional, {} = required ) - # new [uri] - # open new tab and head to optional uri. - # close [tab-num] - # close current tab or close via tab id. - # next [n-tabs] - # open next tab or n tabs down. Supports negative indexing. - # prev [n-tabs] - # open prev tab or n tabs down. Supports negative indexing. - # goto {tab-n} - # goto tab n. - # first - # goto first tab. - # last - # goto last tab. - # title {pid} {document-title} - # updates tablist title. - # uri {pid} {document-location} - # updates tablist uri - # bring_to_front - # brings the gtk window to focus. - # exit - # exits uzbl_tabbed.py - - if cmd[0] == "new": - if len(cmd) == 2: - self.new_tab(cmd[1]) - - else: - self.new_tab() - - elif cmd[0] == "newfromclip": - uri = subprocess.Popen(['xclip','-selection','clipboard','-o'],\ - stdout=subprocess.PIPE).communicate()[0] - if uri: - self.new_tab(uri) - - elif cmd[0] == "close": - if len(cmd) == 2: - self.close_tab(int(cmd[1])) - - else: - self.close_tab() - - elif cmd[0] == "next": - if len(cmd) == 2: - self.next_tab(int(cmd[1])) - - else: - self.next_tab() - - elif cmd[0] == "prev": - if len(cmd) == 2: - self.prev_tab(int(cmd[1])) - - else: - self.prev_tab() - - elif cmd[0] == "goto": - self.goto_tab(int(cmd[1])) - - elif cmd[0] == "first": - self.goto_tab(0) - - elif cmd[0] == "last": - self.goto_tab(-1) - - elif cmd[0] in ["title", "uri"]: - if len(cmd) > 2: - uzbl = self.get_tab_by_name(int(cmd[1])) - if uzbl: - old = getattr(uzbl, cmd[0]) - new = ' '.join(cmd[2:]) - setattr(uzbl, cmd[0], new) - if old != new: - self.update_tablist() - - else: - error("parse_command: no uzbl with name %r" % int(cmd[1])) - - elif cmd[0] == "preset": - if len(cmd) < 3: - error("parse_command: invalid preset command") - - elif cmd[1] == "save": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - self.save_session(path) - - elif cmd[1] == "load": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - self.load_session(path) - - elif cmd[1] == "del": - path = os.path.join(config['saved_sessions_dir'], cmd[2]) - if os.path.isfile(path): - os.remove(path) - - else: - error("parse_command: preset %r does not exist." % path) - - elif cmd[1] == "list": - uzbl = self.get_tab_by_name(int(cmd[2])) - if uzbl: - if not os.path.isdir(config['saved_sessions_dir']): - js = "js alert('No saved presets.');" - uzbl._client.send(js) - - else: - listdir = os.listdir(config['saved_sessions_dir']) - listdir = "\\n".join(listdir) - js = "js alert('Session presets:\\n\\n%s');" % listdir - uzbl._client.send(js) - - else: - error("parse_command: unknown tab name.") - - else: - error("parse_command: unknown parse command %r"\ - % ' '.join(cmd)) - - elif cmd[0] == "bring_to_front": - self.window.present() - - elif cmd[0] == "clean": - self.clean_slate() - - elif cmd[0] == "exit": - self.quitrequest() - - else: - error("parse_command: unknown command %r" % ' '.join(cmd)) - - - def get_tab_by_name(self, name): - '''Return uzbl instance by name.''' - - for (tab, uzbl) in self.tabs.items(): - if uzbl.name == name: - return uzbl - - return False - - - def new_tab(self, uri='', title='', switch=None): - '''Add a new tab to the notebook and start a new instance of uzbl. - Use the switch option to negate config['switch_to_new_tabs'] option - when you need to load multiple tabs at a time (I.e. like when - restoring a session from a file).''' - - tab = gtk.Socket() - tab.show() - self.notebook.append_page(tab) - sid = tab.get_id() - uri = uri.strip() - name = "%d-%d" % (os.getpid(), self.next_pid()) - - if switch is None: - switch = config['switch_to_new_tabs'] - - if not title: - title = config['new_tab_title'] - - cmd = ['uzbl-browser', '-n', name, '-s', str(sid), - '--connect-socket', self.socket_path, '--uri', uri] - subprocess.Popen(cmd) # TODO: do i need close_fds=True ? - - uzbl = UzblInstance(self, tab, name, uri, title, switch) - SocketClient.instances_queue[name] = uzbl - self.tabs[tab] = uzbl - - - def clean_slate(self): - '''Close all open tabs and open a fresh brand new one.''' - - self.new_tab() - tabs = self.tabs.keys() - for tab in list(self.notebook)[:-1]: - if tab not in tabs: continue - uzbl = self.tabs[tab] - uzbl.exit() - - - def config_uzbl(self, uzbl): - '''Send bind commands for tab new/close/next/prev to a uzbl - instance.''' - - # Set definitions here - # set(key, command back to fifo) - if config['capture_new_windows']: - uzbl.set("new_window", r'new $8') - - - def goto_tab(self, index): - '''Goto tab n (supports negative indexing).''' - - title_format = "%s - Uzbl Browser" - - tabs = list(self.notebook) - if 0 <= index < len(tabs): - self.notebook.set_current_page(index) - uzbl = self.tabs[self.notebook.get_nth_page(index)] - self.window.set_title(title_format % uzbl.title) - self.update_tablist() - return None - - try: - tab = tabs[index] - # Update index because index might have previously been a - # negative index. - index = tabs.index(tab) - self.notebook.set_current_page(index) - uzbl = self.tabs[self.notebook.get_nth_page(index)] - self.window.set_title(title_format % uzbl.title) - self.update_tablist() - - except IndexError: - pass - - - def next_tab(self, step=1): - '''Switch to next tab or n tabs right.''' - - if step < 1: - error("next_tab: invalid step %r" % step) - return None - - ntabs = self.notebook.get_n_pages() - tabn = (self.notebook.get_current_page() + step) % ntabs - self.goto_tab(tabn) - - - def prev_tab(self, step=1): - '''Switch to prev tab or n tabs left.''' - - if step < 1: - error("prev_tab: invalid step %r" % step) - return None - - ntabs = self.notebook.get_n_pages() - tabn = self.notebook.get_current_page() - step - while tabn < 0: tabn += ntabs - self.goto_tab(tabn) - - - def close_tab(self, tabn=None): - '''Closes current tab. Supports negative indexing.''' - - if tabn is None: - tabn = self.notebook.get_current_page() - - else: - try: - tab = list(self.notebook)[tabn] - - except IndexError: - error("close_tab: invalid index %r" % tabn) - return None - - self.notebook.remove_page(tabn) - - - def tab_opened(self, notebook, tab, index): - '''Called upon tab creation. Called by page-added signal.''' - - if config['switch_to_new_tabs']: - self.notebook.set_focus_child(tab) - - else: - oldindex = self.notebook.get_current_page() - oldtab = self.notebook.get_nth_page(oldindex) - self.notebook.set_focus_child(oldtab) - - - def tab_closed(self, notebook, tab, index): - '''Close the window if no tabs are left. Called by page-removed - signal.''' - - if tab in self.tabs.keys(): - uzbl = self.tabs[tab] - uzbl.close() - - self._closed.append((uzbl.uri, uzbl.title)) - self._closed = self._closed[-10:] - del self.tabs[tab] - - if self.notebook.get_n_pages() == 0: - if not self._killed and config['save_session']: - if os.path.exists(config['session_file']): - os.remove(config['session_file']) - - self.quit() - - for tab in self.notebook: - self.tabs[tab].title_changed(True) - self.update_tablist() - - return True - - - def tab_changed(self, notebook, page, index): - '''Refresh tab list. Called by switch-page signal.''' - - tab = self.notebook.get_nth_page(index) - self.notebook.set_focus_child(tab) - self.update_tablist(index) - return True - - - def update_tablist_display(self): - '''Called when show_tablist or tablist_top has changed''' - - if self.ebox in self.vbox.get_children(): - self.vbox.remove(self.ebox) - - if config['show_tablist']: - self.vbox.pack_start(self.ebox, False, False, 0) - if config['tablist_top']: - self.vbox.reorder_child(self.ebox, 0) - else: - self.vbox.reorder_child(self.ebox, 2) - - def update_gtk_tab_pos(self): - ''' Called when gtk_tab_pos has changed ''' - - allposes = {'left': gtk.POS_LEFT, 'right':gtk.POS_RIGHT, - 'top':gtk.POS_TOP, 'bottom':gtk.POS_BOTTOM} - if config['gtk_tab_pos'] in allposes.keys(): - self.notebook.set_tab_pos(allposes[config['gtk_tab_pos']]) - - - def update_tablist(self, curpage=None): - '''Upate tablist status bar.''' - - if not config['show_tablist']: - return True - - tab_titles = config['tab_titles'] - tab_indexes = config['tab_indexes'] - multiline_tabs = config['multiline_tabs'] - - if multiline_tabs: - multiline = [] - - tabs = self.tabs.keys() - if curpage is None: - curpage = self.notebook.get_current_page() - - pango = "" - normal = (config['tab_colours'], config['tab_text_colours']) - selected = (config['selected_tab'], config['selected_tab_text']) - - if tab_titles and tab_indexes: - tab_format = " [ %(index)d %(title)s ] " - elif tab_titles: - tab_format = " [ %(title)s ] " - else: - tab_format = " [ %(index)d ] " - - for index, tab in enumerate(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - title = escape(uzbl.tabtitle) - - style = colour_selector(index, curpage, uzbl) - (tabc, textc) = style - - if multiline_tabs: - opango = pango - - pango += tab_format % locals() - - self.tablist.set_markup(pango) - listwidth = self.tablist.get_layout().get_pixel_size()[0] - winwidth = self.window.get_size()[0] - - if listwidth > (winwidth - 20): - multiline.append(opango) - pango = tab_format % locals() - else: - pango += tab_format % locals() - - if multiline_tabs: - multiline.append(pango) - self.tablist.set_markup(' '.join(multiline)) - - else: - self.tablist.set_markup(pango) - - return True - - - def save_session(self, session_file=None): - '''Save the current session to file for restoration on next load.''' - - strip = str.strip - - if session_file is None: - session_file = config['session_file'] - - tabs = self.tabs.keys() - state = [] - for tab in list(self.notebook): - if tab not in tabs: continue - uzbl = self.tabs[tab] - if not uzbl.uri: continue - state += [(uzbl.uri, uzbl.title),] - - session = {'curtab': self.notebook.get_current_page(), - 'tabs': state} - - if config['json_session']: - raw = json.dumps(session) - - else: - lines = ["curtab = %d" % session['curtab'],] - for (uri, title) in session['tabs']: - lines += ["%s\t%s" % (strip(uri), strip(title)),] - - raw = "\n".join(lines) - - if not os.path.isfile(session_file): - dirname = os.path.dirname(session_file) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - h = open(session_file, 'w') - h.write(raw) - h.close() - - - def load_session(self, session_file=None): - '''Load a saved session from file.''' - - default_path = False - strip = str.strip - json_session = config['json_session'] - delete_loaded = False - - if session_file is None: - default_path = True - delete_loaded = True - session_file = config['session_file'] - - if not os.path.isfile(session_file): - return False - - h = open(session_file, 'r') - raw = h.read() - h.close() - if json_session: - if sum([1 for s in raw.split("\n") if strip(s)]) != 1: - error("Warning: The session file %r does not look json. "\ - "Trying to load it as a non-json session file."\ - % session_file) - json_session = False - - if json_session: - try: - session = json.loads(raw) - curtab, tabs = session['curtab'], session['tabs'] - - except: - error("Failed to load jsonifed session from %r"\ - % session_file) - return None - - else: - tabs = [] - strip = str.strip - curtab, tabs = 0, [] - lines = [s for s in raw.split("\n") if strip(s)] - if len(lines) < 2: - error("Warning: The non-json session file %r looks invalid."\ - % session_file) - return None - - try: - for line in lines: - if line.startswith("curtab"): - curtab = int(line.split()[-1]) - - else: - uri, title = line.split("\t",1) - tabs += [(strip(uri), strip(title)),] - - except: - error("Warning: failed to load session file %r" % session_file) - return None - - session = {'curtab': curtab, 'tabs': tabs} - - # Now populate notebook with the loaded session. - for (index, (uri, title)) in enumerate(tabs): - self.new_tab(uri=uri, title=title, switch=(curtab==index)) - - # A saved session has been loaded now delete it. - if delete_loaded and os.path.exists(session_file): - os.remove(session_file) - - # There may be other state information in the session dict of use to - # other functions. Of course however the non-json session object is - # just a dummy object of no use to no one. - return session - - - def quitrequest(self, *args): - '''Attempt to close all uzbl instances nicely and exit.''' - - self._killed = True - - if config['save_session']: - if len(list(self.notebook)) > 1: - self.save_session() - - else: - # Notebook has one page open so delete the session file. - if os.path.isfile(config['session_file']): - os.remove(config['session_file']) - - for (tab, uzbl) in self.tabs.items(): - uzbl.exit() - - # Add a gobject timer to make sure the application force-quits after a - # reasonable period. Calling quit when all the tabs haven't had time to - # close should be a last resort. - timer = "force-quit" - timerid = timeout_add(5000, self.quit, timer) - self._timers[timer] = timerid - - - def quit(self, *args): - '''Cleanup and quit. Called by delete-event signal.''' - - # Close the fifo socket, remove any gobject io event handlers and - # delete socket. - self.unlink_fifo() - self.close_socket() - - # Remove all gobject timers that are still ticking. - for (timerid, gid) in self._timers.items(): - source_remove(gid) - del self._timers[timerid] - - try: - gtk.main_quit() - - except: - pass - - -if __name__ == "__main__": - - # Build command line parser - usage = "usage: %prog [OPTIONS] {URIS}..." - parser = OptionParser(usage=usage) - parser.add_option('-n', '--no-session', dest='nosession', - action='store_true', help="ignore session saving a loading.") - parser.add_option('-v', '--verbose', dest='verbose', - action='store_true', help='print verbose output.') - - # Parse command line options - (options, uris) = parser.parse_args() - - if options.nosession: - config['save_session'] = False - - if options.verbose: - config['verbose'] = True - - if config['json_session']: - try: - import simplejson as json - - except: - error("Warning: json_session set but cannot import the python "\ - "module simplejson. Fix: \"set json_session = 0\" or "\ - "install the simplejson python module to remove this warning.") - config['json_session'] = False - - if config['verbose']: - import pprint - sys.stderr.write("%s\n" % pprint.pformat(config)) - - uzbl = UzblTabbed() - - # All extra arguments given to uzbl_tabbed.py are interpreted as - # web-locations to opened in new tabs. - lasturi = len(uris)-1 - for (index,uri) in enumerate(uris): - uzbl.new_tab(uri, switch=(index==lasturi)) - - uzbl.run() diff --git a/examples/data/uzbl/scripts/uzblcat b/examples/data/uzbl/scripts/uzblcat deleted file mode 100755 index e955608..0000000 --- a/examples/data/uzbl/scripts/uzblcat +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python -# uzblcat - safely push html to uzbl -# See http://www.uzbl.org/wiki/html-mode - -from sys import stdin, stdout - -stdout.write("uri data:text/html,") -for line in stdin: - stdout.write(line[0:-1]) - -# vim: set noet ff=unix - diff --git a/examples/data/uzbl/style.css b/examples/data/uzbl/style.css deleted file mode 100644 index f9b111e..0000000 --- a/examples/data/uzbl/style.css +++ /dev/null @@ -1,25 +0,0 @@ -.uzbl_highlight { background-color: yellow;} -.uzbl_h_first { background-color: lightgreen;} - -.uzbl_follow { border-style: dotted; - border-width: thin; -} - -#uzbl_hint > div { - display: inline; - border: 2px solid #4a6600; - background-color: #b9ff00; - color: black; - font-size: 9px; - font-weight: bold; - line-height: 9px; - margin: 0px; - padding: 0px; - position: absolute; - z-index: 1000; - -webkit-border-radius: 6px; - text-decoration: none; - -wekit-transform: scale(1) rotate(0deg) translate(-6px,-5px); -} - -/* vim:set et ts=4: */ diff --git a/examples/data/uzbl/uzbl.png b/examples/data/uzbl/uzbl.png deleted file mode 100644 index 773ea84..0000000 Binary files a/examples/data/uzbl/uzbl.png and /dev/null differ diff --git a/sandbox/env.sh b/sandbox/env.sh index 0bf812a..122a7f2 100755 --- a/sandbox/env.sh +++ b/sandbox/env.sh @@ -5,8 +5,9 @@ # - executing limits scope of variables too much (even with exporting) # maybe we should spawn processes from here with an 'exec' at the end? -export XDG_DATA_HOME=./sandbox/examples/data -export XDG_CACHE_HOME=./sandbox/examples/cache -export XDG_CONFIG_HOME=./sandbox/examples/config -#export PATH="./sandbox/usr/local/share/uzbl/examples/data/uzbl/scripts/:$PATH" # needed when running uzbl-browser from here? don't think so.. +export HOME=./sandbox/home +export XDG_DATA_HOME=$HOME/.local/share +export XDG_CACHE_HOME=$HOME/.cache +export XDG_CONFIG_HOME=$HOME/.config +#export PATH="./sandbox/usr/local/share/uzbl/examples/data/scripts/:$PATH" # needed when running uzbl-browser from here? don't think so.. export PATH="./sandbox/usr/local/bin:$PATH" # needed to run uzbl-browser etc from here diff --git a/src/uzbl-browser b/src/uzbl-browser index d9b9752..8a7ab36 100755 --- a/src/uzbl-browser +++ b/src/uzbl-browser @@ -40,7 +40,7 @@ done # if no config exists yet in the recommended location, put the default (recommended) config there if [ ! -f $XDG_CONFIG_HOME/uzbl/config ] then - if ! cp $PREFIX/share/uzbl/examples/config/uzbl/config $XDG_CONFIG_HOME/uzbl/config + if ! cp $PREFIX/share/uzbl/examples/config/config $XDG_CONFIG_HOME/uzbl/config then echo "Could not copy default config to $XDG_CONFIG_HOME/uzbl/config" >&2 exit 3 -- cgit v1.2.3 From d84b07fd743b1460c0acede3889020165d193dfb Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Sat, 2 Jan 2010 01:18:10 +0100 Subject: eval_js: catch non-object (int, string,...) exceptions --- src/uzbl-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/uzbl-core.c b/src/uzbl-core.c index 8a053f6..0acd0c5 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -1077,7 +1077,7 @@ eval_js(WebKitWebView * web_view, gchar *script, GString *result) { JSStringRelease(js_result_string); } - else if (js_exc && JSValueIsObject(context, js_exc)) { + else if (js_exc) { size_t size; JSStringRef prop, val; JSObjectRef exc = JSValueToObject(context, js_exc, NULL); -- cgit v1.2.3 From c8fb24d1069aa776bccdf3141adb9d3c3f6e8101 Mon Sep 17 00:00:00 2001 From: Simon Lipp Date: Sat, 2 Jan 2010 00:32:19 +0100 Subject: add a scrollbars_visible option --- README | 1 + src/callbacks.c | 13 +++++++++++++ src/callbacks.h | 3 +++ src/uzbl-core.c | 1 + src/uzbl-core.h | 1 + 5 files changed, 19 insertions(+) (limited to 'src') diff --git a/README b/README index 217e6bf..45a3465 100644 --- a/README +++ b/README @@ -235,6 +235,7 @@ Besides the builtin variables you can also define your own ones and use them in - `fantasy_font_family` = "Pterra" - `serif_font_family` = serif (example "DejaVu Serif") - `sans_serif_font_family` = sans (example "DejaVu Sans") + - `scrollbars_visible`: set to 1 to have GTK scrollbars if the document doesn't fit into the window (defaults to 0) * Constants (not dumpable or writeable): diff --git a/src/callbacks.c b/src/callbacks.c index dab92c1..9130f5f 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -270,6 +270,19 @@ cmd_useragent() { } } +void +cmd_scrollbars_visibility() { + if(uzbl.gui.scrollbars_visible) { + uzbl.gui.bar_h = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (uzbl.gui.scrolled_win)); + uzbl.gui.bar_v = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (uzbl.gui.scrolled_win)); + } + else { + uzbl.gui.bar_v = gtk_range_get_adjustment (GTK_RANGE (uzbl.gui.scbar_v)); + uzbl.gui.bar_h = gtk_range_get_adjustment (GTK_RANGE (uzbl.gui.scbar_h)); + } + gtk_widget_set_scroll_adjustments (GTK_WIDGET (uzbl.gui.web_view), uzbl.gui.bar_h, uzbl.gui.bar_v); +} + /* requires webkit >=1.1.14 */ void cmd_view_source() { diff --git a/src/callbacks.h b/src/callbacks.h index 3f318f2..882ffa4 100644 --- a/src/callbacks.h +++ b/src/callbacks.h @@ -112,6 +112,9 @@ cmd_set_geometry(); void cmd_view_source(); +void +cmd_scrollbars_visibility(); + void cmd_load_start(); diff --git a/src/uzbl-core.c b/src/uzbl-core.c index 0acd0c5..ce1c77d 100644 --- a/src/uzbl-core.c +++ b/src/uzbl-core.c @@ -138,6 +138,7 @@ const struct var_name_to_ptr_t { { "default_encoding", PTR_V_STR(uzbl.behave.default_encoding, 1, cmd_default_encoding)}, { "enforce_96_dpi", PTR_V_INT(uzbl.behave.enforce_96dpi, 1, cmd_enforce_96dpi)}, { "caret_browsing", PTR_V_INT(uzbl.behave.caret_browsing, 1, cmd_caret_browsing)}, + { "scrollbars_visible", PTR_V_INT(uzbl.gui.scrollbars_visible, 1, cmd_scrollbars_visibility)}, /* constants (not dumpable or writeable) */ { "WEBKIT_MAJOR", PTR_C_INT(uzbl.info.webkit_major, NULL)}, diff --git a/src/uzbl-core.h b/src/uzbl-core.h index df9eb1a..70a383c 100644 --- a/src/uzbl-core.h +++ b/src/uzbl-core.h @@ -54,6 +54,7 @@ typedef struct { GtkScrollbar* scbar_h; // (These are still hidden) GtkAdjustment* bar_v; // Information about document length GtkAdjustment* bar_h; // and scrolling position + int scrollbars_visible; WebKitWebView* web_view; gchar* main_title; gchar* icon; -- cgit v1.2.3