viking

webkit based web browser for Enlightenment
Log | Files | Refs | LICENSE

commit 35ed20e3d46752766ec8f286884d525ed753830d
Author: Kyle Milz <kmilz@ucalgary.ca>
Date:   Wed, 19 Sep 2012 17:15:55 -0600

viking initial commit.

Diffstat:
AMakefile | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommands.c | 1509+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acommands.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.h | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adata/desktop/viking.desktop | 10++++++++++
Adata/desktop/viking.jpg | 0
Ahinting.js | 455+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajsmn/Makefile | 26++++++++++++++++++++++++++
Ajsmn/jsmn.c | 226+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajsmn/jsmn.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Akeymap.h | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.c | 1533+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.h | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autilities.c | 1070+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autilities.h | 43+++++++++++++++++++++++++++++++++++++++++++
Aviking.h | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
16 files changed, 5679 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,81 @@ +TARGET = viking + +# Objectfiles, needed for $(TARGET) +OBJ = main.o utilities.o commands.o +# Manpages +MAN1 = evi2.1 +MAN5 = evirc.5 +# Used libraries to get needed CFLAGS and LDFLAGS form pkg-config +LIBS = elementary ewebkit +# Files to removo by clean target +CLEAN = $(TARGET) $(OBJ) $(DEPS) javascript.h +# Files to install by install target or remove by uninstall target +MANINSTALL = $(addprefix $(MANDIR)/man1/,$(MAN1)) \ + $(addprefix $(MANDIR)/man5/,$(MAN5)) +INSTALL = $(BINDIR)/$(TARGET) $(MANINSTALL) + +# DEBUG build? Off by default +V_DEBUG = 1 + +CFLAGS += `pkg-config --cflags $(LIBS)` +LDFLAGS += `pkg-config --libs $(LIBS)` -Ljsmn -ljsmn + +# TA: This is a pretty stringent list of warnings to bail on! +ifeq ($(V_DEBUG),1) +CFLAGS += -g -ggdb -Wstrict-prototypes +CFLAGS += -Wno-long-long -Wall -Wmissing-declarations +endif + +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +MANDIR ?= $(PREFIX)/share/man +# Mode bits for normal not executable files +FMOD ?= 0644 +# Mode bits for directories +DMOD ?= 0755 +# Mode bits for executables +EXECMOD ?= 0755 +# Destination directory to install files +DESTDIR ?= / + +# auto garerated dependancies for object files +DEPS = $(OBJ:%.o=%.d) + +all: $(TARGET) + +-include $(DEPS) + +main.o: javascript.h +javascript.h: hinting.js + perl ./js-merge-helper.pl + +$(TARGET): $(OBJ) + $(CC) $^ $(LDFLAGS) -o $@ + +.PHONY: clean install uninstall +clean: + -rm -f $(CLEAN) +install: $(addprefix $(DESTDIR)/,$(INSTALL)) +uninstall: + rm -f $(addprefix $(DESTDIR)/,$(INSTALL)) + +# pattern rule to inslall executabels +$(DESTDIR)/$(BINDIR)/%: ./% + -[ -e '$(@D)' ] || mkdir -p '$(@D)' && chmod $(DMOD) '$(@D)' + cp -f '$<' '$@' + -strip -s '$@' + chmod $(EXECMOD) '$@' + +# pattern rules to install manpages +$(DESTDIR)/$(MANDIR)/man1/%: ./% + -[ -e '$(@D)' ] || mkdir -p '$(@D)' && chmod $(DMOD) '$(@D)' + cp -f '$<' '$@' + chmod $(FMOD) '$@' + +$(DESTDIR)/$(MANDIR)/man5/%: ./% + -[ -e '$(@D)' ] || mkdir -p '$(@D)' && chmod $(DMOD) '$(@D)' + cp -f '$<' '$@' + chmod $(FMOD) '$@' + +%.o: %.c + $(CC) -MMD -c $(CFLAGS) $< -o $@ diff --git a/commands.c b/commands.c @@ -0,0 +1,1509 @@ + +#define _GNU_SOURCE +#include <Elementary.h> +#include <EWebKit.h> +#include <libsoup/soup.h> + +#include "viking.h" +#include "commands.h" +#include "utilities.h" +#include "main.h" + +extern unsigned int scrollstep; +extern unsigned int pagingkeep; +extern char defaultsearch[MAX_SETTING_SIZE]; + + +void fill_suggline(char * suggline, const char * command, const char *fill_with); +Evas_Object * fill_eventbox(const char * completion_line); + + + +void fill_suggline(char * suggline, const char * command, const char *fill_with) { + memset(suggline, 0, 512); + strncpy(suggline, command, 512); + strncat(suggline, " ", 1); + strncat(suggline, fill_with, 512 - strlen(suggline) - 1); +} + +Evas_Object *fill_eventbox(const char * completion_line) +{ + /* + GtkBox * row; + Evas_Object *row_eventbox, *el; + GdkColor color; + char *markup, *markup_tmp; + + row = GTK_BOX(gtk_hbox_new(FALSE, 0)); + row_eventbox = gtk_event_box_new(); + gdk_color_parse(completionbgcolor[0], &color); + gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color); + el = gtk_label_new(NULL); + markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line)); + markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", + markup_tmp, "</span>", NULL); + gtk_label_set_markup(GTK_LABEL(el), markup); + g_free(markup_tmp); + g_free(markup); + gtk_misc_set_alignment(GTK_MISC(el), 0, 0); + gtk_box_pack_start(row, el, TRUE, TRUE, 2); + gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row)); + return row_eventbox; + */ + return NULL; +} + +Eina_Bool +complete(const Arg *arg, void *data) +{ +#if 0 + char *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls; + char *str; + size_t listlen, len, cmdlen; + int i, spacepos; + Listelement *elementlist = NULL, *elementpointer; + Eina_Bool highlight = FALSE; + // GtkBox *row; + // GtkWidget *row_eventbox, *el; + // GtkBox *_table; + // GdkColor color; + // static GtkWidget *table, *top_border; + Evas_Object *row, *row_eventbox, *el, *_table; + Evas_Object *table, *top_border; + static char *prefix; + static char **suggestions; + static Evas_Object **widgets; + static int n = 0, m, current = -1; + App_Data *ad = data; + + str = elm_entry_entry_get(ad->url); + // str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox)); + len = strlen(str); + + /* Get the length of the list of commands for completion. We need this to + * malloc/realloc correctly. + */ + listlen = LENGTH(commands); + + if ((len == 0 || str[0] != ':') && arg->i != HideCompletion) + return TRUE; + /* + if (prefix) { + if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) { + gdk_color_parse(completionbgcolor[0], &color); + gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color); + current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n; + if ((arg->i == DirectionNext && current == 0) + || (arg->i == DirectionPrev && current == n - 1)) + current = -1; + } else { + free(widgets); + free(suggestions); + free(prefix); + gtk_widget_destroy(GTK_WIDGET(table)); + gtk_widget_destroy(GTK_WIDGET(top_border)); + table = NULL; + widgets = NULL; + suggestions = NULL; + prefix = NULL; + n = 0; + current = -1; + if (arg->i == HideCompletion) + return TRUE; + } + } else if (arg->i == HideCompletion) + return TRUE; + */ + if (!widgets) { + prefix = strdup(str); + // widgets = malloc(sizeof(GtkWidget*) * listlen); + widgets = malloc(sizeof(Evas_Object*) * listlen); + suggestions = malloc(sizeof(char*) * listlen); + top_border = gtk_event_box_new(); + gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1); + + // gdk_color_parse(completioncolor[2], &color); + // gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color); + elm_bg_color_set(top_border, completioncolor[2][0], completioncolor[2][1], completioncolor[2][2]); + + table = gtk_event_box_new(); + gdk_color_parse(completionbgcolor[0], &color); + _table = GTK_BOX(gtk_vbox_new(FALSE, 0)); + highlight = len > 1; + if (strchr(str, ' ') == NULL) { + /* command completion */ + listlen = LENGTH(commands); + for (i = 0; i < listlen; i++) { + if (commands[i].cmd == NULL) + break; + cmdlen = strlen(commands[i].cmd); + if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) { + p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen); + if (highlight) { + memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1); + memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1); + memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1); + p += sizeof(COMPLETION_TAG_CLOSE) - 1; + } + memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2); + row = GTK_BOX(gtk_hbox_new(FALSE, 0)); + row_eventbox = gtk_event_box_new(); + gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color); + el = gtk_label_new(NULL); + markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL); + free(s); + gtk_label_set_markup(GTK_LABEL(el), markup); + g_free(markup); + gtk_misc_set_alignment(GTK_MISC(el), 0, 0); + gtk_box_pack_start(row, el, TRUE, TRUE, 2); + gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row)); + gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0); + suggestions[n] = commands[i].cmd; + widgets[n++] = row_eventbox; + } + } + } else { + entry = (char *)malloc(512 * sizeof(char)); + if (entry == NULL) { + return FALSE; + } + memset(entry, 0, 512); + suggurls = malloc(sizeof(char*) * listlen); + if (suggurls == NULL) { + return FALSE; + } + spacepos = strcspn(str, " "); + searchfor = (str + spacepos + 1); + strncpy(command, (str + 1), spacepos - 1); + if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) { + /* browser settings */ + listlen = LENGTH(browsersettings); + for (i = 0; i < listlen; i++) { + if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) { + /* match */ + fill_suggline(suggline, command, browsersettings[i].name); + /* FIXME(HP): This memory is never freed */ + suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1); + strncpy(suggurls[n], suggline, 512); + suggestions[n] = suggurls[n]; + row_eventbox = fill_eventbox(suggline); + gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0); + widgets[n++] = row_eventbox; + } + + } + } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) { + /* completion on tags */ + spacepos = strcspn(str, " "); + searchfor = (str + spacepos + 1); + elementlist = complete_list(searchfor, 1, elementlist); + } else { + /* URL completion: bookmarks */ + elementlist = complete_list(searchfor, 0, elementlist); + m = count_list(elementlist); + if (m < MAX_LIST_SIZE) { + /* URL completion: history */ + elementlist = complete_list(searchfor, 2, elementlist); + } + } + elementpointer = elementlist; + while (elementpointer != NULL) { + fill_suggline(suggline, command, elementpointer->element); + /* FIXME(HP): This memory is never freed */ + suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1); + strncpy(suggurls[n], suggline, 512); + suggestions[n] = suggurls[n]; + row_eventbox = fill_eventbox(suggline); + gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0); + widgets[n++] = row_eventbox; + elementpointer = elementpointer->next; + if (n >= MAX_LIST_SIZE) + break; + } + free_list(elementlist); + if (suggurls != NULL) { + free(suggurls); + suggurls = NULL; + } + if (entry != NULL) { + free(entry); + entry = NULL; + } + } + /* TA: FIXME - this needs rethinking entirely. */ + { + GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n); + if (widgets_temp == NULL && widgets == NULL) { + fprintf(stderr, "Couldn't realloc() widgets\n"); + exit(1); + } + widgets = widgets_temp; + char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n); + if (suggestions_temp == NULL && suggestions == NULL) { + fprintf(stderr, "Couldn't realloc() suggestions\n"); + exit(1); + } + suggestions = suggestions_temp; + } + if (!n) { + gdk_color_parse(completionbgcolor[1], &color); + gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color); + el = gtk_label_new(NULL); + gtk_misc_set_alignment(GTK_MISC(el), 0, 0); + markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL); + gtk_label_set_markup(GTK_LABEL(el), markup); + g_free(markup); + gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0); + } + gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0); + gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table)); + gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0); + gtk_widget_show_all(GTK_WIDGET(window)); + if (!n) + return TRUE; + current = arg->i == DirectionPrev ? n - 1 : 0; + } + if (current != -1) { + gdk_color_parse(completionbgcolor[2], &color); + gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color); + s = g_strconcat(":", suggestions[current], NULL); + gtk_entry_set_text(GTK_ENTRY(inputbox), s); + g_free(s); + } else + gtk_entry_set_text(GTK_ENTRY(inputbox), prefix); + gtk_editable_set_position(GTK_EDITABLE(inputbox), -1); +#endif + return TRUE; +} + +Eina_Bool +descend(const Arg *arg, void *data) +{ + App_Data *ad = data; + // char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new; + const char *source = elm_web_uri_get(ad->current_web); + const char *p = &source[0]; + char *new; + + int i, len; + ad->count = ad->count ? ad->count : 1; + + if (!source) + return TRUE; + if (arg->i == Rootdir) { + for (i = 0; i < 3; i++) /* get to the third slash */ + if (!(p = strchr(++p, '/'))) + return TRUE; /* if we cannot find it quit */ + } else { + len = strlen(source); + if (!len) /* if string is empty quit */ + return TRUE; + p = source + len; /* start at the end */ + if (*(p - 1) == '/') /* /\/$/ is not an additional level */ + ++(ad->count); + for (i = 0; i < ad->count; i++) + while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */ + if (p == source) /* if we reach the first char pointer quit */ + return TRUE; + ++p; /* since we do p-- in the while, we are pointing at + the char before the slash, so +1 */ + } + len = p - source + 1; /* new length = end - start + 1 */ + new = malloc(len + 1); + memcpy(new, source, len); + new[len] = '\0'; + // webkit_web_view_load_uri(webview, new); + elm_web_uri_set(ad->current_web, new); + free(new); + return TRUE; +} + +Eina_Bool +input(const Arg *arg, void *data) +{ + // int pos = 0; + const char *url; + // int index = Info; + Arg a; + App_Data *ad = data; + ad->count = 0; + + /* if inputbox hidden, show it again */ + // if (!gtk_widget_get_visible(inputbox)) + // gtk_widget_set_visible(inputbox, TRUE); + if (!evas_object_visible_get(ad->url)) + evas_object_show(ad->url); + + update_state(data); + + /* Set the colour and font back to the default, so that we don't still + * maintain a red colour from a warning from an end of search indicator, + * etc. + */ + // set_widget_font_and_color(ad->url, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]); + + /* to avoid things like :open URL :open URL2 or :open :open URL */ + // gtk_entry_set_text(GTK_ENTRY(inputbox), ""); + elm_entry_entry_set(ad->url, ""); + + /* + gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos); + if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview))) + gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos); + + gtk_widget_grab_focus(inputbox); + gtk_editable_set_position(GTK_EDITABLE(inputbox), -1); + */ + + elm_entry_entry_set(ad->url, arg->s); + if (arg->i & InsertCurrentURL && (url = elm_web_uri_get(ad->current_web))) + elm_entry_entry_append(ad->url, url); + + elm_object_focus_set(ad->url, EINA_TRUE); + elm_entry_cursor_end_set(ad->url); + + if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') { + ad->mode = ModeHints; + memset(ad->followTarget, 0, 8); + strncpy(ad->followTarget, "current", 8); + a.i = Silent; + + switch (arg->s[0]) { + case '.': + a.s = strdup("hints.createHints('', 'f');"); + break; + + case ',': + a.s = strdup("hints.createHints('', 'F');"); + break; + + case ';': + a.s = NULL; + if (arg->s[1]) { + + switch (arg->s[1]) { + case 's': + a.s = strdup("hints.createHints('', 's');"); + break; + case 'y': + a.s = strdup("hints.createHints('', 'y');"); + break; + case 'o': + a.s = strdup("hints.createHints('', 'f');"); + break; + case 't': case 'w': + a.s = strdup("hints.createHints('', 'F');"); + break; + case 'O': case 'T': case 'W': + a.s = strdup("hints.createHints('', 'O');"); + break; + case 'i': + a.s = strdup("hints.createHints('', 'i');"); + break; + case 'I': + a.s = strdup("hints.createHints('', 'I');"); + break; + } + + } + break; + } + + ad->count = 0; + if (a.s) { + script(&a, data); + free(a.s); + } + } + + return TRUE; +} + +Eina_Bool +navigate(const Arg *arg, void *data) +{ + App_Data *ad = data; + if (arg->i & NavigationForwardBack) + // webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1)); + elm_web_navigate(ad->current_web, (arg->i == NavigationBack ? -1 : 1) * (ad->count ? ad->count : 1)); + else if (arg->i & NavigationReloadActions) + // (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview); + (arg->i == NavigationReload ? elm_web_reload : elm_web_reload_full)(ad->current_web); + else + // webkit_web_view_stop_loading(webview); + elm_web_stop(ad->current_web); + return TRUE; +} + +Eina_Bool +number(const Arg *arg, void *data) +{ + App_Data *ad = data; + // const char *source = webkit_web_view_get_uri(webview); + const char *source = elm_web_uri_get(ad->current_web); + char *uri, *p, *new; + int number, diff = (ad->count ? ad->count : 1) * (arg->i == Increment ? 1 : -1); + + if (!source) + return TRUE; + uri = g_strdup(source); /* copy string */ + p =& uri[0]; + while(*p != '\0') /* goto the end of the string */ + ++p; + --p; + while(*p >= '0' && *p <= '9') /* go back until non number char is reached */ + --p; + if (*(++p) == '\0') { /* if no numbers were found abort */ + free(uri); + return TRUE; + } + number = atoi(p) + diff; /* apply diff on number */ + *p = '\0'; + new = strdup_printf("%s%d", uri, number); /* create new uri */ + // webkit_web_view_load_uri(webview, new); + elm_web_uri_set(ad->current_web, new); + free(new); + free(uri); + return TRUE; +} + +Eina_Bool +open_arg(const Arg *arg, void *data) +{ + // char *argv[64]; + char *s = arg->s, *p = NULL, *new; + Arg a = { .i = NavigationReload }; + int len; + char *search_uri, *search_term; + App_Data *ad = data; + + /* + if (embed) { + argv[0] = *args; + argv[1] = "-e"; + argv[2] = winid; + argv[3] = arg->s; + argv[4] = NULL; + } else { + argv[0] = *args; + argv[1] = arg->s; + argv[2] = NULL; + } + */ + + if (!arg->s) + navigate(&a, data); + else if (arg->i == TargetCurrent) { + while(*s == ' ') /* strip leading whitespace */ + ++s; + p = (s + strlen(s) - 1); + while(*p == ' ') /* strip trailing whitespace */ + --p; + *(p + 1) = '\0'; + len = strlen(s); + new = NULL; + /* check for external handlers */ + if (open_handler(s)) + return TRUE; + /* check for search engines */ + p = strchr(s, ' '); + if (p) { /* check for search engines */ + *p = '\0'; + search_uri = find_uri_for_searchengine(s); + if (search_uri != NULL) { + search_term = soup_uri_encode(p+1, "&"); + new = strdup_printf(search_uri, search_term); + free(search_term); + } + *p = ' '; + } + if (!new) { + if (len > 3 && strstr(s, "://")) { /* valid url? */ + p = new = g_malloc(len + 1); + while(*s != '\0') { /* strip whitespaces */ + if (*s != ' ') + *(p++) = *s; + ++s; + } + *p = '\0'; + } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */ + new = g_malloc(sizeof("file://") + len); + strcpy(new, "file://"); + memcpy(&new[sizeof("file://") - 1], s, len + 1); + } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */ + search_uri = find_uri_for_searchengine(defaultsearch); + if (search_uri != NULL) { + search_term = soup_uri_encode(s, "&"); + new = g_strdup_printf(search_uri, search_term); + g_free(search_term); + } + } else { /* prepend "http://" */ + new = g_malloc(sizeof("http://") + len); + strcpy(new, "http://"); + memcpy(&new[sizeof("http://") - 1], s, len + 1); + } + } + // webkit_web_view_load_uri(webview, new); + elm_web_uri_set(ad->current_web, new); + free(new); + } // else + // g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); + return TRUE; +} + +Eina_Bool +open_remembered(const Arg *arg, void *data) +{ + App_Data *ad = data; + + Arg a = {arg->i, ad->rememberedURI}; + + if (strcmp(ad->rememberedURI, "")) { + open_arg(&a, data); + } + return TRUE; +} + +Eina_Bool +yank(const Arg *arg, void *data) { + /* + const char *url, *feedback, *content; + + if (arg->i & SourceSelection) { + webkit_web_view_copy_clipboard(webview); + if (arg->i & ClipboardPrimary) + content = gtk_clipboard_wait_for_text(clipboards[0]); + if (!content && arg->i & ClipboardGTK) + content = gtk_clipboard_wait_for_text(clipboards[1]); + if (content) { + feedback = g_strconcat("Yanked ", content, NULL); + g_free((gpointer *)content); + give_feedback(feedback); + g_free((gpointer *)feedback); + } + } else { + if (arg->i & SourceURL) { + url = webkit_web_view_get_uri(webview); + } else { + url = arg->s; + } + if (!url) + return TRUE; + feedback = g_strconcat("Yanked ", url, NULL); + give_feedback(feedback); + if (arg->i & ClipboardPrimary) + gtk_clipboard_set_text(clipboards[0], url, -1); + if (arg->i & ClipboardGTK) + gtk_clipboard_set_text(clipboards[1], url, -1); + } + */ + return TRUE; +} + +Eina_Bool +paste(const Arg *arg, void *data) { + /* + Arg a = { .i = arg->i & TargetNew, .s = NULL }; + + // If we're over a link, open it in a new target. + if (strlen(rememberedURI) > 0) { + Arg new_target = { .i = TargetNew, .s = arg->s }; + open_arg(&new_target); + return TRUE; + } + + if (arg->i & ClipboardPrimary) + a.s = gtk_clipboard_wait_for_text(clipboards[0]); + if (!a.s && arg->i & ClipboardGTK) + a.s = gtk_clipboard_wait_for_text(clipboards[1]); + if (a.s) { + open_arg(&a); + g_free(a.s); + } + */ + return TRUE; +} + +Eina_Bool +tab_quit(const Arg *arg, void *data) +{ + FILE *f; + char *filename; + App_Data *ad = data; + if (!ad->current_web) + return EINA_TRUE; + + // const char *uri = webkit_web_view_get_uri(webview); + const char *uri = elm_web_uri_get(ad->current_web); + if (uri != NULL) { + /* write last URL into status file for recreation with "u" */ + filename = strdup_printf(CLOSED_URL_FILENAME); + f = fopen(filename, "w"); + free(filename); + if (f != NULL) { + fprintf(f, "%s", uri); + fclose(f); + } + } + + evas_object_del(ad->current_web); + // elm_naviframe_item_pop(ad->naviframe); + // Elm_Object_Item *obj_item = elm_naviframe_top_item_get(ad->naviframe); + // if (obj_item) ad->current_web = elm_object_item_widget_get(obj_item); + // if (obj_item) ad->current_web = elm_object_item_part_content_get(obj_item, NULL); + // else quit(NULL, data); + + return EINA_TRUE; +} + +Eina_Bool +quit(const Arg *arg, void *data) +{ + App_Data *ad = data; + ad->exiting = EINA_TRUE; + elm_shutdown(); + return 0; +} + +Eina_Bool +revive(const Arg *arg, void *data) +{ + App_Data *ad = data; + + FILE *f; + char *filename; + char buffer[512] = ""; + Arg a = { .i = TargetNew, .s = NULL }; + /* get the URL of the window which has been closed last */ + filename = strdup_printf(CLOSED_URL_FILENAME); + f = fopen(filename, "r"); + free(filename); + if (f != NULL) { + fgets(buffer, 512, f); + fclose(f); + } + if (strlen(buffer) > 0) { + a.s = buffer; + open_arg(&a, data); + return TRUE; + } + return FALSE; +} + +Eina_Bool +print_frame(const Arg *arg, void *data) +{ + /* can webkit-efl even do this? + * + WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview); + webkit_web_frame_print (frame); + */ + return TRUE; +} + +Eina_Bool +search(const Arg *arg, void *data) +{ + App_Data *ad = data; + + ad->count = ad->count ? ad->count : 1; + Eina_Bool success, direction = arg->i & DirectionPrev; + Arg a; + + if (arg->s) { + free(ad->search_handle); + ad->search_handle = strdup(arg->s); + } + if (!ad->search_handle) + return TRUE; + if (arg->i & DirectionAbsolute) + ad->search_direction = direction; + else + direction ^= ad->search_direction; + do { + // success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE); + success = elm_web_text_search(ad->current_web, ad->search_handle, arg->i & CaseSensitive, direction, FALSE); + if (!success) { + if (arg->i & Wrapping) { + // success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE); + success = elm_web_text_search(ad->current_web, ad->search_handle, arg->i & CaseSensitive, direction, EINA_TRUE); + if (success) { + a.i = Warning; + a.s = strdup_printf("search hit %s, continuing at %s", + direction ? "BOTTOM" : "TOP", + direction ? "TOP" : "BOTTOM"); + echo(&a, data); + free(a.s); + } else + break; + } else + break; + } + } while(--(ad->count)); + if (!success) { + a.i = Error; + a.s = strdup_printf("<bold>Pattern not found: %s", ad->search_handle); + echo(&a, data); + free(a.s); + } + return TRUE; +} + +Eina_Bool +set(const Arg *arg, void *data) +{ + App_Data *ad = data; + Arg a = { .i = Info | NoAutoHide }; + + switch (arg->i) { + case ModeNormal: + if (ad->search_handle) { + ad->search_handle = NULL; + // webkit_web_view_unmark_text_matches(webview); + elm_web_text_matches_unmark_all(ad->current_web); + } + // gtk_entry_set_text(GTK_ENTRY(inputbox), ""); + // gtk_widget_grab_focus(GTK_WIDGET(webview)); + elm_entry_entry_set(ad->url, ""); + elm_object_focus_set(ad->current_web, EINA_TRUE); + break; + case ModePassThrough: + a.s = strdup("-- PASS THROUGH --"); + echo(&a, data); + free(a.s); + break; + case ModeSendKey: + a.s = strdup("-- PASS TROUGH (next) --"); + echo(&a, data); + free(a.s); + break; + case ModeInsert: /* should not be called manually but automatically */ + a.s = strdup("-- INSERT --"); + echo(&a, data); + free(a.s); + break; + default: + return TRUE; + } + ad->mode = arg->i; + return TRUE; +} + +/* +gchar* +jsapi_ref_to_string(JSContextRef context, JSValueRef ref) { + JSStringRef string_ref; + gchar *string; + size_t length; + + string_ref = JSValueToStringCopy(context, ref, NULL); + length = JSStringGetMaximumUTF8CStringSize(string_ref); + string = g_new(gchar, length); + JSStringGetUTF8CString(string_ref, string, length); + JSStringRelease(string_ref); + return string; +} + +void +jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) { + WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview); + JSGlobalContextRef context = webkit_web_frame_get_global_context(frame); + JSStringRef str; + JSValueRef val, exception; + + str = JSStringCreateWithUTF8CString(script); + val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception); + JSStringRelease(str); + if (!val) + *message = jsapi_ref_to_string(context, exception); + else + *value = jsapi_ref_to_string(context, val); +} +*/ + +Eina_Bool +quickmark(const Arg *a, void *data) +{ + int i, b; + App_Data *ad = data; + b = atoi(a->s); + char *fn = strdup_printf(QUICKMARK_FILE); + FILE *fp; + fp = fopen(fn, "r"); + free(fn); + fn = NULL; + char buf[100]; + + if (fp != NULL && b < 10) { + for( i=0; i < b; ++i ) { + if (feof(fp)) { + break; + } + fgets(buf, 100, fp); + } + char *ptr = strrchr(buf, '\n'); + *ptr = '\0'; + Arg x = { .s = buf }; + if (strlen(buf)) + return open_arg(&x, data); + else { + x.i = Error; + x.s = strdup_printf("Quickmark %d not defined", b); + echo(&x, data); + free(x.s); + return EINA_FALSE; + } + } else { return EINA_FALSE; } +} + +Eina_Bool +script(const Arg *arg, void *data) +{ + // gchar *value = NULL, *message = NULL; + const char *value; + char text[1024] = ""; + Arg a; + // WebKitNetworkRequest *request; + // WebKitDownload *download; + App_Data *ad = data; + Evas_Object *view = elm_web_webkit_view_get(ad->current_web); + Evas_Object *frame = ewk_view_frame_main_get(view); + + if (!arg->s) { + set_error("Missing argument.", data); + return EINA_FALSE; + } + /* + jsapi_evaluate_script(arg->s, &value, &message); + if (message) { + set_error(message); + g_free(message); + return FALSE; + } + g_free(message); + */ + value = ewk_frame_script_execute(frame, arg->s); + if (arg->i != Silent && value) { + a.i = arg->i; + a.s = strdup(value); + echo(&a, data); + free(a.s); + } + /* switch mode according to scripts return value */ + if (value) { + if (strncmp(value, "done;", 5) == 0) { + a.i = ModeNormal; + set(&a, data); + } else if (strncmp(value, "insert;", 7) == 0) { + a.i = ModeInsert; + set(&a, data); + ad->manual_focus = TRUE; + } else if (strncmp(value, "save;", 5) == 0) { + /* forced download */ + a.i = ModeNormal; + set(&a, data); + printf("script() js returned save, not implemented.\n"); + // request = webkit_network_request_new((value + 5)); + // download = webkit_download_new(request); + // webview_download_cb(webview, download, (gpointer *)NULL); + } else if (strncmp(value, "yank;", 5) == 0) { + /* yank link URL to clipboard */ + a.i = ModeNormal; + set(&a, echo); + printf("script() js returned yank, not implemented.\n"); + // a.i = ClipboardPrimary | ClipboardGTK; + // a.s = (value + 5); + // yank(&a); + } else if (strncmp(value, "colon;", 6) == 0) { + /* use link URL for colon command */ + // strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(inputbox)), 1023); + strncpy(text, elm_entry_entry_get(ad->url), 1023); + a.i = ModeNormal; + set(&a, data); + switch (text[1]) { + case 'O': + // a.s = g_strconcat(":open ", (value + 6), NULL); + a.s = strdup_printf(":open %s", value + 6); + break; + case 'T': case 'W': + // a.s = g_strconcat(":tabopen ", (value + 6), NULL); + a.s = strdup_printf(":tabopen %s", value + 6); + break; + } + if (a.s) { + input(&a, data); + free(a.s); + } + } else if (strncmp(value, "error;", 6) == 0) { + a.i = Error; + set(&a, data); + } + } + // free(value); + eina_stringshare_del(value); + return TRUE; +} + +Eina_Bool +scroll(const Arg *arg, void *data) +{ + // GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v; + // int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust); + // float val = gtk_adjustment_get_value(adjust) / max * 100; + + int x, y, w, h, page_w, page_h; + float val; + Eina_Bool horizontal = EINA_FALSE; + App_Data *ad = data; + Evas_Object *view = elm_web_webkit_view_get(ad->current_web); + Evas_Object *frame = ewk_view_frame_main_get(view); + + ewk_frame_visible_content_geometry_get(frame, EINA_FALSE, &x, &y, &page_w, &page_h); + ewk_frame_scroll_pos_get(frame, &x, &y); + ewk_frame_scroll_size_get(frame, &w, &h); + + if (arg->i & OrientationHoriz) + horizontal = EINA_TRUE; + + val = (float) (horizontal? x : y) / h * 100; + + int direction = (arg->i & (1 << 2)) ? 1 : -1; + + if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) { + if (arg->i & ScrollMove) { + int total = direction * /* direction */ + ((arg->i & UnitLine || (arg->i & UnitBuffer && ad->count)) ? (scrollstep * (ad->count ? ad->count : 1)) : ( + // arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 : + arg->i & UnitBuffer ? (horizontal ? page_w : page_h) / 2 : + // (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) - + (ad->count ? ad->count : 1) * ((horizontal ? page_w : page_h) - + // (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0))))); + ((horizontal ? page_w : page_h) > pagingkeep ? pagingkeep : 0)))); + + // gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) + + if (horizontal) + ewk_frame_scroll_set(frame, x + total, y); + else + ewk_frame_scroll_set(frame, x, y + total); + + } + else { + // gtk_adjustment_set_value(adjust, + // ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust)); + if (horizontal) + ewk_frame_scroll_set(frame, ((direction == 1) ? w : 0), y); + else + ewk_frame_scroll_set(frame, x, ((direction == 1) ? h : 0)); + } + update_state(data); + } + return TRUE; +} + +Eina_Bool +zoom(const Arg *arg, void *data) +{ + App_Data *ad = data; + + // webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0); + // webkit_web_view_set_zoom_level(ad->current_tab->web, (arg->i & ZoomOut) ? + elm_web_zoom_set(ad->current_web, (arg->i & ZoomOut) ? + // webkit_web_view_get_zoom_level(webview) + + elm_web_zoom_get(ad->current_web) + + (((float)(ad->count ? ad->count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * ad->zoomstep) : + (ad->count ? (float)ad->count / 100.0 : 1.0)); + return TRUE; +} + +/* +Eina_Bool +fake_key_event(const Arg *a, void *data) { + if(!embed) { + return FALSE; + } + Arg err; + err.i = Error; + Display *xdpy; + if ( (xdpy = XOpenDisplay(NULL)) == NULL ) { + err.s = g_strdup("Couldn't find the XDisplay."); + echo(&err); + g_free(err.s); + return FALSE; + } + + XKeyEvent xk; + xk.display = xdpy; + xk.subwindow = None; + xk.time = CurrentTime; + xk.same_screen = True; + xk.x = xk.y = xk.x_root = xk.y_root = 1; + xk.window = embed; + xk.state = a->i; + + if( ! a->s ) { + err.s = g_strdup("Zero pointer as argument! Check your config.h"); + echo(&err); + g_free(err.s); + return FALSE; + } + + KeySym keysym; + if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) { + err.s = g_strdup_printf("Couldn't translate %s to keysym", a->s ); + echo(&err); + g_free(err.s); + return FALSE; + } + + if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) { + err.s = g_strdup("Couldn't translate keysym to keycode"); + echo(&err); + g_free(err.s); + return FALSE; + } + + xk.type = KeyPress; + if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) { + err.s = g_strdup("XSendEvent failed"); + echo(&err); + g_free(err.s); + return FALSE; + } + XFlush(xdpy); + + return TRUE; +} +*/ + +Eina_Bool +bookmark(const Arg *arg, void *data) +{ + FILE *f; + char *filename; + App_Data *ad = data; + const char *uri = elm_web_uri_get(ad->current_web); + const char *title = elm_web_title_get(ad->current_web); + filename = strdup_printf(BOOKMARKS_STORAGE_FILENAME); + f = fopen(filename, "a"); + free(filename); + if (uri == NULL || strlen(uri) == 0) { + set_error("No URI found to bookmark.", data); + return EINA_FALSE; + } + if (f != NULL) { + fprintf(f, "%s", uri); + if (title != NULL) { + fprintf(f, "%s", " "); + fprintf(f, "%s", title); + } + if (arg->s && strlen(arg->s)) { + build_taglist(arg, f); + } + fprintf(f, "%s", "\n"); + fclose(f); + give_feedback("Bookmark saved", data); + return EINA_TRUE; + } else { + set_error("Bookmarks file not found.", data); + return EINA_FALSE; + } +} + +Eina_Bool +view_source(const Arg * arg, void *data) +{ + printf("view_source() source mode not implemented yet.\n"); + /* + gboolean current_mode = webkit_web_view_get_view_source_mode(webview); + webkit_web_view_set_view_source_mode(webview, !current_mode); + webkit_web_view_reload(webview); + */ + return TRUE; +} + +Eina_Bool +focus_input(const Arg *arg, void *data) +{ + static Arg a; + App_Data *ad = data; + + a.s = strdup("hints.focusInput();"); + a.i = Silent; + script(&a, data); + free(a.s); + update_state(data); + ad->manual_focus = TRUE; + return TRUE; +} + +/* +Eina_Bool +browser_settings(const Arg *arg, void *data) { + char line[255]; + if (!arg->s) { + set_error("Missing argument."); + return FALSE; + } + strncpy(line, arg->s, 254); + if (process_set_line(line)) + return TRUE; + else { + set_error("Invalid setting."); + return FALSE; + } +} +*/ + +void +toggle_proxy(Eina_Bool onoff) +{ + // SoupURI *proxy_uri; + char *filename, *new; + + if (onoff == FALSE) { + // g_object_set(session, "proxy-uri", NULL, NULL); + ewk_network_proxy_uri_set(NULL); + } else { + filename = getenv("http_proxy"); + + /* Fallthrough to checking HTTP_PROXY as well, since this can also be + * defined. + */ + if (filename == NULL) + filename = getenv("HTTP_PROXY"); + + if (filename != NULL && 0 < strlen(filename)) { + new = strstr(filename, "http://") ? strdup(filename) : strdup_printf("http://%s", filename); + // proxy_uri = soup_uri_new(new); + + // g_object_set(session, "proxy-uri", proxy_uri, NULL); + printf("toggle_proxy(%i, %s)\n", onoff, new); + ewk_network_proxy_uri_set(new); + + // soup_uri_free(proxy_uri); + free(new); + } + } +} + +Eina_Bool +process_set_line(char *line) { +#if 0 + char *c; + int listlen, i; + gboolean boolval; + WebKitWebSettings *settings; + + settings = webkit_web_view_get_settings(webview); + my_pair.line = line; + c = search_word(0); + if (!strlen(my_pair.what)) + return FALSE; + + while (isspace(*c) && *c) + c++; + + if (*c == ':' || *c == '=') + c++; + + my_pair.line = c; + c = search_word(1); + + listlen = LENGTH(browsersettings); + for (i = 0; i < listlen; i++) { + if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) { + /* mandatory argument not provided */ + if (strlen(my_pair.value) == 0) + return FALSE; + /* process qmark? */ + if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) { + return (process_save_qmark(my_pair.value, webview)); + } + /* interpret boolean values */ + if (browsersettings[i].boolval) { + if (strncmp(my_pair.value, "on", 2) == 0 || strncmp(my_pair.value, "true", 4) == 0 || strncmp(my_pair.value, "ON", 2) == 0 || strncmp(my_pair.value, "TRUE", 4) == 0) { + boolval = TRUE; + } else if (strncmp(my_pair.value, "off", 3) == 0 || strncmp(my_pair.value, "false", 5) == 0 || strncmp(my_pair.value, "OFF", 3) == 0 || strncmp(my_pair.value, "FALSE", 5) == 0) { + boolval = FALSE; + } else { + return FALSE; + } + } else if (browsersettings[i].colourval) { + /* interpret as hexadecimal colour */ + if (!parse_colour(my_pair.value)) { + return FALSE; + } + } + if (browsersettings[i].var != NULL) { + strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE); + if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) { + /* in this case, \0 will not have been copied */ + browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0'; + /* in case this string is also used for a webkit setting, make sure it's consistent */ + my_pair.value[MAX_SETTING_SIZE - 1] = '\0'; + give_feedback("String too long; automatically truncated!"); + } + } + if (strlen(browsersettings[i].webkit) > 0) { + /* activate appropriate webkit setting */ + if (browsersettings[i].boolval) { + g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL); + } else if (browsersettings[i].intval) { + g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL); + } else { + g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL); + } + webkit_web_view_set_settings(webview, settings); + } + + if (strlen(my_pair.what) == 14) { + if (strncmp("acceptlanguage", my_pair.what, 14) == 0) { + g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL); + } else if (strncmp("completioncase", my_pair.what, 14) == 0) { + complete_case_sensitive = boolval; + } + } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) { + toggle_proxy(boolval); + } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) { + toggle_scrollbars(boolval); + } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) { + gtk_widget_set_visible(GTK_WIDGET(statusbar), boolval); + } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) { + gtk_widget_set_visible(inputbox, boolval); + } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) { + escape_input_on_load = boolval; + } + + /* SSL certificate checking */ + if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) { + if (boolval) { + strict_ssl = TRUE; + g_object_set(G_OBJECT(session), "ssl-strict", TRUE, NULL); + } else { + strict_ssl = FALSE; + g_object_set(G_OBJECT(session), "ssl-strict", FALSE, NULL); + } + } + if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) { + g_object_set(G_OBJECT(session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL); + } + if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) { + set_default_winsize(my_pair.value); + } + + /* reload page? */ + if (browsersettings[i].reload) + webkit_web_view_reload(webview); + return TRUE; + } + } +#endif + return FALSE; +} + +Eina_Bool +search_tag(const Arg * a, void *data) +{ + FILE *f; + char *filename; + App_Data *ad = data; + const char *tag = a->s; + char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE]; + int t, i, intag, k; + + if (!tag) { + /* The user must give us something to load up. */ + set_error("Bookmark tag required with this option.", data); + return FALSE; + } + + if (strlen(tag) > MAXTAGSIZE) { + set_error("Tag too long.", data); + return FALSE; + } + + filename = strdup_printf(BOOKMARKS_STORAGE_FILENAME); + f = fopen(filename, "r"); + free(filename); + if (f == NULL) { + set_error("Couldn't open bookmarks file.", data); + return FALSE; + } + while (fgets(s, BUFFERSIZE-1, f)) { + intag = 0; + t = strlen(s) - 1; + while (isspace(s[t])) + t--; + if (s[t] != ']') continue; + while (t > 0) { + if (s[t] == ']') { + if (!intag) + intag = t; + else + intag = 0; + } else { + if (s[t] == '[') { + if (intag) { + i = 0; + k = t + 1; + while (k < intag) + foundtag[i++] = s[k++]; + foundtag[i] = '\0'; + /* foundtag now contains the tag */ + if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) { + i = 0; + while (isspace(s[i])) i++; + k = 0; + while (s[i] && !isspace(s[i])) url[k++] = s[i++]; + url[k] = '\0'; + Arg x = { .i = TargetNew, .s = url }; + open_arg(&x, data); + } + } + intag = 0; + } + } + t--; + } + } + return EINA_TRUE; +} + +Eina_Bool +list(const Arg *arg, void *data) +{ + char *url, *count_str; + App_Data *ad = data; + + Eina_List *l; + Tab_Data *td; + Evas_Object *icon, *row, *label; + Evas *e = evas_object_evas_get(ad->win); + + elm_box_clear(ad->event_box); + + EINA_LIST_FOREACH(ad->buffer_list, l, td) { + // EINA_LIST_FOREACH(elm_naviframe_items_get(ad->naviframe), l, i) { + // web = elm_object_item_part_content_get(i, NULL); + url = strdup_printf("<font=Monospace font_size=12>%s", elm_web_uri_get(td->web)); + + row = elm_box_add(ad->win); + elm_box_homogeneous_set(row, EINA_FALSE); + elm_box_horizontal_set(row, EINA_TRUE); + evas_object_size_hint_weight_set(row, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(row, 0.0, EVAS_HINT_FILL); + elm_box_pack_end(ad->event_box, row); + evas_object_show(row); + + label = elm_label_add(ad->win); + evas_object_size_hint_weight_set(row, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(row, 0.0, EVAS_HINT_FILL); + elm_box_pack_end(row, label); + evas_object_show(label); + count_str = strdup_printf("<font=Monospace font_size=12>%i ", td->buf_number); + elm_object_text_set(label, count_str); + free(count_str); + + // e = evas_object_evas_get(ad->win); + icon = ewk_settings_icon_database_icon_object_get(elm_web_uri_get(td->web), e); + // evas_object_image_filled_set(icon, EINA_FALSE); + // int w, h; + + // evas_object_image_scale_hint_set(icon, EVAS_IMAGE_SCALE_HINT_NONE); + evas_object_size_hint_weight_set(icon, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(icon, 0.0, EVAS_HINT_FILL); + + // evas_object_image_size_get(icon, &w, &h); + // printf("icon for %s has size (%i, %i)\n", url, w, h); + + evas_object_size_hint_max_set(icon, 16, 16); + evas_object_size_hint_min_set(icon, 16, 16); + // evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 0, 0); + elm_box_pack_end(row, icon); + evas_object_show(icon); + + label = elm_label_add(ad->win); + evas_object_size_hint_weight_set(row, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(row, 0.0, EVAS_HINT_FILL); + elm_box_pack_end(row, label); + evas_object_show(label); + + // url = elm_web_uri_get(web); + elm_object_text_set(label, url); + free(url); + } + + evas_object_show(ad->event_box); + + + return EINA_TRUE; +} + +Eina_Bool +switch_buffer(const Arg *arg, void *data) +{ + App_Data *ad = data; + Eina_List *l; + Tab_Data *td; + // int arg_count = atoi(arg->s); + + + EINA_LIST_FOREACH(ad->buffer_list, l, td) { + /* + if (arg_count == td->buf_number) { + buffer_current_set(td); + return EINA_TRUE; + } + */ + if (ad->count == td->buf_number) { + buffer_current_set(td); + return EINA_TRUE; + } + } + + return EINA_FALSE; +} + +Eina_Bool +inspector(const Arg *arg, void *data) +{ + App_Data *ad = data; + Evas_Object *view = elm_web_webkit_view_get(ad->current_web); + + printf("inspector(), arg-> is %i!\n", arg->i); + + if (arg->i & ShowInspector && !ewk_view_web_inspector_view_get(view)) { + ewk_view_setting_enable_developer_extras_set(view, EINA_TRUE); + + Evas_Object *inspector_view = ewk_view_single_add(evas_object_evas_get(ad->win)); + ewk_view_theme_set(inspector_view, "/usr/share/ewebkit-0/themes/default.edj"); + + + // Evas_Object *web = elm_web_add(ad->win); + // elm_web_inwin_mode_set(web, EINA_TRUE); + evas_object_size_hint_weight_set(inspector_view, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(inspector_view, EVAS_HINT_FILL, EVAS_HINT_FILL); + + // // Evas_Object *inspector_view = elm_web_webkit_view_get(web); + + ewk_view_web_inspector_view_set(view, inspector_view); + evas_object_show(inspector_view); + + + elm_box_pack_end(ad->web_inspector, inspector_view); + + ewk_view_web_inspector_show(view); + + evas_object_size_hint_weight_set(ad->web_inspector, EVAS_HINT_EXPAND, 1.0); + evas_object_show(ad->web_inspector); + } + else if (arg->i == HideInspector && ewk_view_web_inspector_view_get(view)) { + ewk_view_web_inspector_close(view); + ewk_view_setting_enable_developer_extras_set(view, EINA_FALSE); + elm_box_clear(ad->web_inspector); + evas_object_size_hint_weight_set(ad->web_inspector, EVAS_HINT_EXPAND, 0.0); + } + + return EINA_TRUE; +} + diff --git a/commands.h b/commands.h @@ -0,0 +1,99 @@ + +// #include "config.h" +// #include "utilities.h" + +Eina_Bool complete(const Arg *, void *); +Eina_Bool script(const Arg *, void *); +Eina_Bool open_arg(const Arg *, void *); +Eina_Bool quit(const Arg *, void *); +Eina_Bool add_tab(const Arg *, void *); +Eina_Bool view_source(const Arg *, void *); +Eina_Bool bookmark(const Arg *, void *); +Eina_Bool search_tag(const Arg *, void *); +Eina_Bool print_frame(const Arg *, void *); +Eina_Bool scroll(const Arg *arg, void *); +Eina_Bool open_remembered(const Arg *, void *); +// Eina_Bool browser_settings(const Arg *, void *); +Eina_Bool navigate(const Arg *arg, void *); +Eina_Bool yank(const Arg *, void *); +Eina_Bool zoom(const Arg *, void *); +Eina_Bool descend(const Arg *, void *); +Eina_Bool paste(const Arg *, void *); +Eina_Bool number(const Arg *, void *data); +Eina_Bool search(const Arg *, void *); +Eina_Bool input(const Arg *, void *); +Eina_Bool focus_input(const Arg *, void *); +Eina_Bool quickmark(const Arg *, void *); +Eina_Bool tab_quit(const Arg *, void *); +Eina_Bool revive(const Arg *, void *); +Eina_Bool set(const Arg *, void *); +Eina_Bool list(const Arg *, void *); +Eina_Bool switch_buffer(const Arg *, void *); +Eina_Bool inspector(const Arg *, void *); +// static Eina_Bool fake_key_event(const Arg *, void *); + +Eina_Bool process_set_line(char *line); +void toggle_proxy(Eina_Bool onoff); + +extern char startpage[MAX_SETTING_SIZE]; + +/* command mapping */ +static Command commands[COMMANDSIZE] = { + /* command, function, argument */ + { "b", switch_buffer, {0} }, + { "ba", navigate, {NavigationBack} }, + { "back", navigate, {NavigationBack} }, + { "ec", script, {Info} }, + { "echo", script, {Info} }, + { "echoe", script, {Error} }, + { "echoerr", script, {Error} }, + { "fw", navigate, {NavigationForward} }, + { "fo", navigate, {NavigationForward} }, + { "forward", navigate, {NavigationForward} }, + { "javascript", script, {Silent} }, + { "o", open_arg, {TargetCurrent} }, + { "open", open_arg, {TargetCurrent} }, + { "q", quit, {0} }, + { "quit", quit, {0} }, + { "re", navigate, {NavigationReload} }, + { "re!", navigate, {NavigationForceReload} }, + { "reload", navigate, {NavigationReload} }, + { "reload!", navigate, {NavigationForceReload} }, + { "qt", search_tag, {0} }, + { "st", navigate, {NavigationCancel} }, + { "stop", navigate, {NavigationCancel} }, + { "t", add_tab, {0} }, + { "tabopen", open_arg, {TargetNew} }, + { "print", print_frame, {0} }, + { "bma", bookmark, {0} }, + { "bookmark", bookmark, {0} }, + { "source", view_source, {0} }, +// { "set", browser_settings, {0} }, +// { "map", mappings, {0} }, + { "jumpleft", scroll, {ScrollJumpTo | DirectionLeft} }, + { "jumpright", scroll, {ScrollJumpTo | DirectionRight} }, + { "jumptop", scroll, {ScrollJumpTo | DirectionTop} }, + { "jumpbottom", scroll, {ScrollJumpTo | DirectionBottom} }, + { "pageup", scroll, {ScrollMove | DirectionTop | UnitPage} }, + { "pagedown", scroll, {ScrollMove | DirectionBottom | UnitPage} }, + { "navigationback", navigate, {NavigationBack} }, + { "navigationforward", navigate, {NavigationForward} }, + { "scrollleft", scroll, {ScrollMove | DirectionLeft | UnitLine} }, + { "scrollright", scroll, {ScrollMove | DirectionRight | UnitLine} }, + { "scrollup", scroll, {ScrollMove | DirectionTop | UnitLine} }, + { "scrolldown", scroll, {ScrollMove | DirectionBottom | UnitLine} }, + { "ls", list, {0} }, +}; + + + +/* mouse bindings + you can use MOUSE_BUTTON_1 to MOUSE_BUTTON_5 +*/ +//static Mouse mouse[] = { + /* modmask, modkey, button, function, argument */ +// {0, 0, 2, paste, {TargetCurrent | ClipboardPrimary | ClipboardGTK, rememberedURI} }, +// {"Control", 0, 2, paste, {TargetNew | ClipboardPrimary | ClipboardGTK} }, +// {"Control", 0, 1, open_remembered, {TargetNew} }, +//}; + diff --git a/config.h b/config.h @@ -0,0 +1,155 @@ +/* + (c) 2009 by Leon Winter + (c) 2009-2012 by Hannes Schueller + (c) 2009-2010 by Matto Fransen + (c) 2010-2011 by Hans-Peter Deifel + (c) 2010-2011 by Thomas Adam + (c) 2011 by Albert Kim + see LICENSE file +*/ + +/* Vimprobable version number */ +#define VERSION "1" +#define INTERNAL_VERSION "Evi/"VERSION + +/* general settings */ +char startpage[MAX_SETTING_SIZE] = "http://www.getaddrinfo.net/"; +// static char useragent[MAX_SETTING_SIZE] = "Vimprobable2/" VERSION; +// static char acceptlanguage[MAX_SETTING_SIZE] = ""; +static const Eina_Bool enablePlugins = TRUE; /* TRUE keeps plugins enabled */ +static const Eina_Bool enableJava = TRUE; /* FALSE disables Java applets */ +static const Eina_Bool enablePagecache = FALSE; /* TRUE turns on the page cache. */ +static Eina_Bool escape_input_on_load = TRUE; /* TRUE will disable automatic focusing of input fields via Javascript*/ + +/* appearance */ +// static char statusbgcolor[MAX_SETTING_SIZE] = "#000"; /* background color for status bar */ +// static char statuscolor[MAX_SETTING_SIZE] = "#FFF"; /* color for status bar */ +// static char sslbgcolor[MAX_SETTING_SIZE] = "#b0ff00"; /* background color for status bar with SSL url */ +// static char sslinvalidbgcolor[MAX_SETTING_SIZE]= "#ff0000"; /* background color for status bar with unverified SSL url */ +// static char sslcolor[MAX_SETTING_SIZE] = "#0F0"; /* color for status bar with SSL url */ + + /* normal, warning, error */ +static const char *urlboxfont[] = { "monospace normal 8", "monospace normal 8", "monospace bold 8"}; +static const char *urlboxcolor[] = { NULL, "#ff0000", "#ffffff" }; +static const char *urlboxbgcolor[] = { NULL, NULL, "#ff0000" }; + + /* normal, error */ +// static const char *completionfont[] = { "monospace normal 8", "monospace bold 8" }; + /* topborder color */ +static const unsigned char completioncolor[][3] = { {0, 0, 0}, {255, 0, 255}, {0, 0, 0} }; +/* current row background */ +static const unsigned char completionbgcolor[][3] = { {255, 255, 255}, {255, 255, 255}, {255, 240, 0} }; +/* pango markup for prefix highliting: opening, closing */ +#define COMPLETION_TAG_OPEN "<b>" +#define COMPLETION_TAG_CLOSE "</b>" + +static const char statusfont[] = "monospace bold 8"; /* font for status bar */ +static const int progressbartick = 20; +static const char progressborderleft = '['; +static const char progressbartickchar = '='; +static const char progressbarcurrent = '>'; +static const char progressbarspacer = ' '; +static const char progressborderright = ']'; + +/* external handlers: + * the handle (first string) contain what the handled links have to start with + * the handlers (second string) contain the external applications which should be called for this sort of link + * %s can be used as a placeholder for the link argument after the handler + * e.g.: "mailto:user@example.org + * "handle" is "mailto:" + * "%s" will translate to "user@example.org" + */ +static URIHandler uri_handlers[] = { + { "mailto:", "x-terminal-emulator -e mutt %s" }, + { "ftp://", "x-terminal-emulator -e wget ftp://%s" }, +}; + +/* cookies */ +#define ENABLE_COOKIE_SUPPORT +#define COOKIES_STORAGE_FILENAME "%s/evi/cookies", config_base +#define COOKIES_STORAGE_READONLY FALSE /* if TRUE new cookies will be lost if you quit */ + +/* downloads directory */ +#define DOWNLOADS_PATH "%s", getenv("HOME") + +/* font size */ +#define DEFAULT_FONT_SIZE 12 + +/* user styles */ +#define USER_STYLESHEET "%s/evi/style.css", config_base + +/* ssl */ +// static Eina_Bool strict_ssl = TRUE; /* FALSE will accept any SSL certificate at face value */ +static char ca_bundle[MAX_SETTING_SIZE] = "/etc/ssl/certs/ca-certificates.crt"; + +/* proxy */ +static const Eina_Bool use_proxy = TRUE; /* TRUE if you're going to use a proxy (whose address + is specified in http_proxy environment variable), false otherwise */ +/* scrolling */ +unsigned int scrollstep = 40; /* cursor difference in pixel */ +unsigned int pagingkeep = 40; /* pixels kept when paging */ +#define DISABLE_SCROLLBAR + +/* searching */ +#define ENABLE_MATCH_HIGHLITING +static const int searchoptions = CaseInsensitive | Wrapping; +Eina_Bool complete_case_sensitive = TRUE; + +/* search engines */ +static Searchengine searchengines[] = { + { "d", "https://duckduckgo.com/?q=%s&t=Vimprobable" }, + { "i", "http://ixquick.com/do/metasearch.pl?query=%s" }, + { "w", "https://secure.wikimedia.org/wikipedia/en/w/index.php?title=Special%%3ASearch&search=%s&go=Go" }, + { "wd", "https://secure.wikimedia.org/wikipedia/de/w/index.php?title=Special%%3ASearch&search=%s&go=Go" }, + { "dd", "https://duckduckgo.com/html/?q=%s&t=Vimprobable" }, +}; + +char defaultsearch[MAX_SETTING_SIZE] = "d"; + +#if 0 +/* settings (arguments of :set command) */ +static Setting browsersettings[] = { +/* public name, internal variwebkit setting integer ? bool? colour? reload page? */ + { "useragent", useragent, "user-agent", FALSE, FALSE, FALSE, FALSE }, + { "scripts", NULL, "enable-scripts", FALSE, TRUE, FALSE, FALSE }, + { "plugins", NULL, "enable-plugins", FALSE, TRUE, FALSE, FALSE }, + { "pagecache", NULL, "enable-page-cache", FALSE, TRUE, FALSE, FALSE }, + { "java", NULL, "enable-java-applet", FALSE, TRUE, FALSE, FALSE }, + { "images", NULL, "auto-load-images", FALSE, TRUE, FALSE, FALSE }, + { "shrinkimages", NULL, "auto-shrink-images", FALSE, TRUE, FALSE, FALSE }, + { "cursivefont", NULL, "cursive-font-family", FALSE, FALSE, FALSE, FALSE }, + { "defaultencoding", NULL, "default-encoding", FALSE, FALSE, FALSE, FALSE }, + { "defaultfont", NULL, "default-font-family", FALSE, FALSE, FALSE, FALSE }, + { "fontsize", NULL, "default-font-size", TRUE, FALSE, FALSE, FALSE }, + { "monofontsize", NULL, "default-monospace-font-size", TRUE, FALSE, FALSE, FALSE }, + { "caret", NULL, "enable-caret-browsing", FALSE, TRUE, FALSE, FALSE }, + { "fantasyfont", NULL, "fantasy-font-family", FALSE, FALSE, FALSE, FALSE }, + { "minimumfontsize", NULL, "minimum-font-size", TRUE, FALSE, FALSE, FALSE }, + { "monofont", NULL, "monospace-font-family", FALSE, FALSE, FALSE, FALSE }, + { "backgrounds", NULL, "print-backgrounds", FALSE, TRUE, FALSE, FALSE }, + { "sansfont", NULL, "sans-serif-font-family", FALSE, FALSE, FALSE, FALSE }, + { "seriffont", NULL, "serif-font-family", FALSE, FALSE, FALSE, FALSE }, + { "stylesheet", NULL, "user-stylesheet-uri", FALSE, FALSE, FALSE, FALSE }, + { "resizetextareas", NULL, "resizable-text-areas", FALSE, TRUE, FALSE, FALSE }, + { "webinspector", NULL, "enable-developer-extras", FALSE, TRUE, FALSE, FALSE }, + + { "homepage", startpage, "", FALSE, FALSE, FALSE, FALSE }, + { "statusbgcolor", statusbgcolor,"", FALSE, FALSE, TRUE, TRUE }, + { "statuscolor", statuscolor, "", FALSE, FALSE, TRUE, TRUE }, + { "sslbgcolor", sslbgcolor, "", FALSE, FALSE, TRUE, TRUE }, + { "sslcolor", sslcolor, "", FALSE, FALSE, TRUE, TRUE }, + { "acceptlanguage", acceptlanguage, "", FALSE, FALSE, FALSE, FALSE }, + { "defaultsearch", defaultsearch, "", FALSE, FALSE, FALSE, FALSE }, + { "qmark", NULL, "", FALSE, FALSE, FALSE, FALSE }, + { "proxy", NULL, "", FALSE, TRUE, FALSE, FALSE }, + { "windowsize", NULL, "", FALSE, FALSE, FALSE, FALSE }, + { "scrollbars", NULL, "", FALSE, TRUE, FALSE, FALSE }, + { "statusbar", NULL, "", FALSE, TRUE, FALSE, FALSE }, + { "inputbox", NULL, "", FALSE, TRUE, FALSE, FALSE }, + { "completioncase", NULL, "", FALSE, TRUE, FALSE, FALSE }, + { "escapeinput", NULL, "", FALSE, TRUE, FALSE, FALSE }, + { "strictssl", NULL, "", FALSE, TRUE, FALSE, FALSE }, + { "cabundle", ca_bundle, "", FALSE, FALSE, FALSE, FALSE }, +}; +#endif + diff --git a/data/desktop/viking.desktop b/data/desktop/viking.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Encoding=UTF-8 +Type=Application +Name=Evi +GenericName=Enlightenment Web Browser +Comment=Browse the web with vi like keybindings +Exec=evi +Icon=browser +StartupWMClass=MPlayer +Categories=Application;WebBrowser; diff --git a/data/desktop/viking.jpg b/data/desktop/viking.jpg Binary files differ. diff --git a/hinting.js b/hinting.js @@ -0,0 +1,455 @@ +/* + (c) 2009 by Leon Winter + (c) 2009, 2010 by Hannes Schueller + (c) 2010 by Hans-Peter Deifel + (c) 2011 by Daniel Carl + see LICENSE file +*/ +function Hints() { + var config = { + maxAllowedHints: 500, + hintCss: "z-index:100000;font-family:monospace;font-size:10px;" + + "font-weight:bold;color:white;background-color:red;" + + "padding:0px 1px;position:absolute;", + hintClass: "hinting_mode_hint", + hintClassFocus: "hinting_mode_hint_focus", + elemBackground: "#ff0", + elemBackgroundFocus: "#8f0", + elemColor: "#000" + }; + + var hintContainer; + var currentFocusNum = 1; + var hints = []; + var mode; + + this.createHints = function(inputText, hintMode) + { + if (hintMode) { + mode = hintMode; + } + + var topwin = window; + var top_height = topwin.innerHeight; + var top_width = topwin.innerWidth; + var xpath_expr; + + var hintCount = 0; + this.clearHints(); + + function helper (win, offsetX, offsetY) { + var doc = win.document; + + var win_height = win.height; + var win_width = win.width; + + /* Bounds */ + var minX = offsetX < 0 ? -offsetX : 0; + var minY = offsetY < 0 ? -offsetY : 0; + var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width; + var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height; + + var scrollX = win.scrollX; + var scrollY = win.scrollY; + + hintContainer = doc.createElement("div"); + hintContainer.id = "hint_container"; + + xpath_expr = _getXpathXpression(inputText); + + var res = doc.evaluate(xpath_expr, doc, + function (p) { + return "http://www.w3.org/1999/xhtml"; + }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + /* generate basic hint element which will be cloned and updated later */ + var hintSpan = doc.createElement("span"); + hintSpan.setAttribute("class", config.hintClass); + hintSpan.style.cssText = config.hintCss; + + /* due to the different XPath result type, we will need two counter variables */ + var rect, elem, text, node, show_text; + for (var i = 0; i < res.snapshotLength; i++) + { + if (hintCount >= config.maxAllowedHints) + break; + + elem = res.snapshotItem(i); + rect = elem.getBoundingClientRect(); + if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY) + continue; + + var style = topwin.getComputedStyle(elem, ""); + if (style.display == "none" || style.visibility != "visible") + continue; + + var leftpos = Math.max((rect.left + scrollX), scrollX); + var toppos = Math.max((rect.top + scrollY), scrollY); + + /* making this block DOM compliant */ + var hint = hintSpan.cloneNode(false); + hint.setAttribute("id", "vimprobablehint" + hintCount); + hint.style.left = leftpos + "px"; + hint.style.top = toppos + "px"; + text = doc.createTextNode(hintCount + 1); + hint.appendChild(text); + + hintContainer.appendChild(hint); + hintCount++; + hints.push({ + elem: elem, + number: hintCount, + span: hint, + background: elem.style.background, + foreground: elem.style.color} + ); + + /* make the link black to ensure it's readable */ + elem.style.color = config.elemColor; + elem.style.background = config.elemBackground; + } + + doc.documentElement.appendChild(hintContainer); + + /* recurse into any iframe or frame element */ + var frameTags = ["frame","iframe"]; + for (var f = 0; f < frameTags.length; ++f) { + var frames = doc.getElementsByTagName(frameTags[f]); + for (var i = 0, nframes = frames.length; i < nframes; ++i) { + elem = frames[i]; + rect = elem.getBoundingClientRect(); + if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY) + continue; + helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top); + } + } + } + + helper(topwin, 0, 0); + + this.clearFocus(); + this.focusHint(1); + if (hintCount == 1) { + /* just one hinted element - might as well follow it */ + return this.fire(1); + } + }; + + /* set focus on hint with given number */ + this.focusHint = function(n) + { + /* reset previous focused hint */ + var hint = _getHintByNumber(currentFocusNum); + if (hint !== null) { + hint.elem.className = hint.elem.className.replace(config.hintClassFocus, config.hintClass); + hint.elem.style.background = config.elemBackground; + } + + currentFocusNum = n; + + /* mark new hint as focused */ + var hint = _getHintByNumber(currentFocusNum); + if (hint !== null) { + hint.elem.className = hint.elem.className.replace(config.hintClass, config.hintClassFocus); + hint.elem.style.background = config.elemBackgroundFocus; + } + }; + + /* set focus to next avaiable hint */ + this.focusNextHint = function() + { + var index = _getHintIdByNumber(currentFocusNum); + + if (typeof(hints[index + 1]) != "undefined") { + this.focusHint(hints[index + 1].number); + } else { + this.focusHint(hints[0].number); + } + }; + + /* set focus to previous avaiable hint */ + this.focusPreviousHint = function() + { + var index = _getHintIdByNumber(currentFocusNum); + if (index != 0 && typeof(hints[index - 1].number) != "undefined") { + this.focusHint(hints[index - 1].number); + } else { + this.focusHint(hints[hints.length - 1].number); + } + }; + + /* filters hints matching given number */ + this.updateHints = function(n) + { + if (n == 0) { + return this.createHints(); + } + /* remove none matching hints */ + var remove = []; + for (var i = 0; i < hints.length; ++i) { + var hint = hints[i]; + if (0 != hint.number.toString().indexOf(n.toString())) { + remove.push(hint.number); + } + } + + for (var i = 0; i < remove.length; ++i) { + _removeHint(remove[i]); + } + + if (hints.length === 1) { + return this.fire(hints[0].number); + } else { + return this.focusHint(n); + } + }; + + this.clearFocus = function() + { + if (document.activeElement && document.activeElement.blur) { + document.activeElement.blur(); + } + }; + + /* remove all hints and set previous style to them */ + this.clearHints = function() + { + if (hints.length == 0) { + return; + } + for (var i = 0; i < hints.length; ++i) { + var hint = hints[i]; + if (typeof(hint.elem) != "undefined") { + hint.elem.style.background = hint.background; + hint.elem.style.color = hint.foreground; + hint.span.parentNode.removeChild(hint.span); + } + } + hints = []; + hintContainer.parentNode.removeChild(hintContainer); + window.onkeyup = null; + }; + + /* fires the modeevent on hint with given number */ + this.fire = function(n) + { + var doc, result; + if (!n) { + var n = currentFocusNum; + } + var hint = _getHintByNumber(n); + if (typeof(hint.elem) == "undefined") + return "done;"; + + var el = hint.elem; + var tag = el.nodeName.toLowerCase(); + + this.clearHints(); + + if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") { + el.focus(); + if (tag == "input" || tag == "textarea") { + return "insert;" + } + return "done;"; + } + + switch (mode) + { + case "f": case "i": result = _open(el); break; + case "F": case "I": result = _openNewWindow(el); break; + case "s": result = "save;" + _getElemtSource(el); break; + case "y": result = "yank;" + _getElemtSource(el); break; + case "O": result = "colon;" + _getElemtSource(el); break; + default: result = _getElemtSource(el); + } + + return result; + }; + + this.focusInput = function() + { + if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object") + return; + + /* prefixing html: will result in namespace error */ + var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea"; + var r = document.evaluate(hinttags, document, + function(p) { + return "http://www.w3.org/1999/xhtml"; + }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + var i; + var j = 0; + var k = 0; + var first = null; + for (i = 0; i < r.snapshotLength; i++) { + var elem = r.snapshotItem(i); + if (k == 0) { + if (elem.style.display != "none" && elem.style.visibility != "hidden") { + first = elem; + } else { + k--; + } + } + if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") { + elem.focus(); + var tag = elem.nodeName.toLowerCase(); + if (tag == "textarea" || tag == "input") { + return "insert;"; + } + break; + } + if (elem == document.activeElement) { + j = 1; + } + k++; + } + /* no appropriate field found focused - focus the first one */ + if (j == 0 && first !== null) { + first.focus(); + var tag = elem.nodeName.toLowerCase(); + if (tag == "textarea" || tag == "input") { + return "insert;"; + } + } + }; + + /* retrieves text content fro given element */ + function _getTextFromElement(el) + { + if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) { + text = el.value; + } else if (el instanceof HTMLSelectElement) { + if (el.selectedIndex >= 0) { + text = el.item(el.selectedIndex).text; + } else{ + text = ""; + } + } else { + text = el.textContent; + } + return text.toLowerCase();; + } + + /* retrieves the hint for given hint number */ + function _getHintByNumber(n) + { + var index = _getHintIdByNumber(n); + if (index !== null) { + return hints[index]; + } + return null; + } + + /* retrieves the id of hint with given number */ + function _getHintIdByNumber(n) + { + for (var i = 0; i < hints.length; ++i) { + var hint = hints[i]; + if (hint.number === n) { + return i; + } + } + return null; + } + + /* removes hint with given number from hints array */ + function _removeHint(n) + { + var index = _getHintIdByNumber(n); + if (index === null) { + return; + } + var hint = hints[index]; + if (hint.number === n) { + hint.elem.style.background = hint.background; + hint.elem.style.color = hint.foreground; + hint.span.parentNode.removeChild(hint.span); + + /* remove hints from all hints */ + hints.splice(index, 1); + } + } + + /* opens given element */ + function _open(elem) + { + if (elem.target == "_blank") { + elem.removeAttribute("target"); + } + _clickElement(elem); + return "done;"; + } + + /* opens given element into new window */ + function _openNewWindow(elem) + { + var oldTarget = elem.target; + + /* set target to open in new window */ + elem.target = "_blank"; + _clickElement(elem); + elem.target = oldTarget; + + return "done;"; + } + + /* fire moudedown and click event on given element */ + function _clickElement(elem) + { + doc = elem.ownerDocument; + view = elem.contentWindow; + + var evObj = doc.createEvent("MouseEvents"); + evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null); + elem.dispatchEvent(evObj); + + var evObj = doc.createEvent("MouseEvents"); + evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null); + elem.dispatchEvent(evObj); + } + + /* retrieves the url of given element */ + function _getElemtSource(elem) + { + var url = elem.href || elem.src; + return url; + } + + /* retrieves the xpath expression according to mode */ + function _getXpathXpression(text) + { + var expr; + if (typeof(text) == "undefined") { + text = ""; + } + switch (mode) { + case "f": + case "F": + if (text == "") { + expr = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a[href] | //area | //textarea | //button | //select"; + } else { + expr = "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + text + "')] | //input[not(@type='hidden') and contains(., '" + text + "')] | //a[@href and contains(., '" + text + "')] | //area[contains(., '" + text + "')] | //textarea[contains(., '" + text + "')] | //button[contains(@value, '" + text + "')] | //select[contains(., '" + text + "')]"; + } + break; + case "i": + case "I": + if (text == "") { + expr = "//img[@src]"; + } else { + expr = "//img[@src and contains(., '" + text + "')]"; + } + break; + default: + if (text == "") { + expr = "//*[@role='link' or @href] | //a[href] | //area | //img[not(ancestor::a)]"; + } else { + expr = "//*[(@role='link' or @href) and contains(., '" + text + "')] | //a[@href and contains(., '" + text + "')] | //area[contains(., '" + text + "')] | //img[not(ancestor::a) and contains(., '" + text + "')]"; + } + break; + } + return expr; + } + +} +hints = new Hints(); diff --git a/jsmn/Makefile b/jsmn/Makefile @@ -0,0 +1,26 @@ +# You can put your build options here +-include config.mk + +all: libjsmn.a + +libjsmn.a: jsmn.o + $(AR) rc $@ $^ + +%.o: %.c jsmn.h + $(CC) -c $(CFLAGS) $< -o $@ + +test: jsmn_test + ./jsmn_test + +jsmn_test: jsmn_test.o + $(CC) -L. -ljsmn $< -o $@ + +jsmn_test.o: jsmn_test.c libjsmn.a + +clean: + rm -f jsmn.o jsmn_test.o + rm -f jsmn_test + rm -f libjsmn.a + +.PHONY: all clean test + diff --git a/jsmn/jsmn.c b/jsmn/jsmn.c @@ -0,0 +1,226 @@ +#include <stdlib.h> + +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + unsigned int i; + for (i = parser->toknext; i < num_tokens; i++) { + if (tokens[i].start == -1 && tokens[i].end == -1) { + parser->toknext = i + 1; + return &tokens[i]; + } + } + return NULL; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case '\t' : case '\r' : case '\n' : case ' ' : case ':': +#endif + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); + parser->pos--; + return JSMN_SUCCESS; +} + +/** + * Filsl next token with JSON string. + */ +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); + return JSMN_SUCCESS; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\') { + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + /* TODO */ + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, + unsigned int num_tokens) { + jsmnerr_t r; + int i; + unsigned int tokindex; + jsmntok_t *token; + + /* initialize the rest of tokens (they could be reallocated) */ + for (tokindex = parser->toknext; tokindex < num_tokens; tokindex++) { + jsmn_fill_token(&tokens[tokindex], JSMN_PRIMITIVE, -1, -1); + } + + for (; js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } + break; + case '\"': + r = jsmn_parse_string(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + + } + } + + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + + return JSMN_SUCCESS; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + diff --git a/jsmn/jsmn.h b/jsmn/jsmn.h @@ -0,0 +1,64 @@ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 +} jsmntype_t; + +typedef enum { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3, + /* Everything was fine */ + JSMN_SUCCESS = 0 +} jsmnerr_t; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + int toknext; /* next token to allocate */ + int toksuper; /* suporior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, unsigned int num_tokens); + +#endif /* __JSMN_H_ */ diff --git a/keymap.h b/keymap.h @@ -0,0 +1,137 @@ +/* + (c) 2009 by Leon Winter + (c) 2009-2011 by Hannes Schueller + (c) 2009-2010 by Matto Fransen + (c) 2010-2011 by Hans-Peter Deifel + (c) 2010-2011 by Thomas Adam + (c) 2011 by Albert Kim + see LICENSE file +*/ + +#ifndef _KEYMAP +#define _KEYMAP + +/* key bindings for normal mode + Note: GDK_VoidSymbol is a wildcard so it matches on every modkey +*/ +static Key keys[] = { + /* modmask, modkey, key, function, argument */ + { 0, 'q', "1", quickmark, { .s = "1" } }, + { 0, 'q', "2", quickmark, { .s = "2" } }, + { 0, 'q', "3", quickmark, { .s = "3" } }, + { 0, 'q', "4", quickmark, { .s = "4" } }, + { 0, 'q', "5", quickmark, { .s = "5" } }, + { 0, 'q', "6", quickmark, { .s = "6" } }, + { 0, 'q', "7", quickmark, { .s = "7" } }, + { 0, 'q', "8", quickmark, { .s = "8" } }, + { 0, 'q', "9", quickmark, { .s = "9" } }, + { 0, 0, "0", scroll, {ScrollJumpTo | DirectionLeft} }, + { 0, 0, "$", scroll, {ScrollJumpTo | DirectionRight} }, + { 0, 'g', "g", scroll, {ScrollJumpTo | DirectionTop} }, + { 0, 0, "G", scroll, {ScrollJumpTo | DirectionBottom} }, + { 0, 0, "h", scroll, {ScrollMove | DirectionLeft | UnitLine} }, + { 0, 0, "j", scroll, {ScrollMove | DirectionBottom | UnitLine} }, + { 0, 0, "k", scroll, {ScrollMove | DirectionTop | UnitLine} }, + { 0, 0, "l", scroll, {ScrollMove | DirectionRight | UnitLine} }, + { 0, 0, " ", scroll, {ScrollMove | DirectionBottom | UnitPage} }, + { "Shift", 0, " ", scroll, {ScrollMove | DirectionTop | UnitPage} }, + { "Control", 0, "b", scroll, {ScrollMove | DirectionTop | UnitPage} }, + { "Control", 0, "f", scroll, {ScrollMove | DirectionBottom | UnitPage} }, + { "Control", 0, "d", scroll, {ScrollMove | DirectionBottom | UnitBuffer} }, + { "Control", 0, "u", scroll, {ScrollMove | DirectionTop | UnitBuffer} }, + { "Control", 0, "e", scroll, {ScrollMove | DirectionBottom | UnitLine} }, + { "Control", 0, "y", scroll, {ScrollMove | DirectionTop | UnitLine} }, +/* + { 0, "g", "t", fake_key_event, { .s = "l", .i = ShiftMask + ControlMask } }, + { 0, "g", "T", fake_key_event, { .s = "h", .i = ShiftMask + ControlMask } }, + { 0, "g", "1", fake_key_event, { .s = "1", .i = ControlMask } }, + { 0, "g", "2", fake_key_event, { .s = "2", .i = ControlMask } }, + { 0, "g", "3", fake_key_event, { .s = "3", .i = ControlMask } }, + { 0, "g", "4", fake_key_event, { .s = "4", .i = ControlMask } }, + { 0, "g", "5", fake_key_event, { .s = "5", .i = ControlMask } }, + { 0, "g", "6", fake_key_event, { .s = "6", .i = ControlMask } }, + { 0, "g", "7", fake_key_event, { .s = "7", .i = ControlMask } }, + { 0, "g", "8", fake_key_event, { .s = "8", .i = ControlMask } }, + { 0, "g", "9", fake_key_event, { .s = "9", .i = ControlMask } }, + { 0, "g", "0", fake_key_event, { .s = "0", .i = ControlMask } }, +*/ + { "Control", 0, "i", navigate, {NavigationBack} }, + { "Control", 0, "o", navigate, {NavigationForward} }, + { 0, 0, "H", navigate, {NavigationBack} }, + { 0, 0, "L", navigate, {NavigationForward} }, + { 0, 0, "r", navigate, {NavigationReload} }, + { 0, 0, "R", navigate, {NavigationForceReload} }, + { "Control", 0, "c", navigate, {NavigationCancel} }, + + { 0, 0, "plus", zoom, {ZoomIn | ZoomText} }, + { 0, 0, "minus", zoom, {ZoomOut | ZoomText} }, +// { 0, 0, GDK_KP_Add, zoom, {ZoomIn | ZoomText} }, +// { 0, 0, GDK_KP_Subtract, zoom, {ZoomOut | ZoomText} }, + { 0, 'z', "i", zoom, {ZoomIn | ZoomText} }, + { 0, 'z', "o", zoom, {ZoomOut | ZoomText} }, + { 0, 'z', "z", zoom, {ZoomReset | ZoomText} }, + { 0, 'z', "I", zoom, {ZoomIn | ZoomFullContent} }, + { 0, 'z', "O", zoom, {ZoomOut | ZoomFullContent} }, + { 0, 'z', "Z", zoom, {ZoomReset | ZoomFullContent} }, + + { 0, 0, "y", yank, {SourceURL | ClipboardPrimary | ClipboardGTK} }, + { 0, 0, "Y", yank, {SourceSelection| ClipboardPrimary | ClipboardGTK} }, + + { 0, 'g', "u", descend, {NthSubdir} }, + { 0, 'g', "U", descend, {Rootdir} }, + + { 0, 'g', "h", open_arg, {TargetCurrent, startpage} }, + { 0, 'g', "H", open_arg, {TargetNew, startpage} }, + + { 0, 0, "p", paste, {TargetCurrent | ClipboardPrimary | ClipboardGTK} }, + { 0, 0, "P", paste, {TargetNew | ClipboardPrimary | ClipboardGTK} }, + + { "Control", 0, "a", number, {Increment} }, + { "Control", 0, "x", number, {Decrement} }, + + { 0, 0, "n", search, {DirectionNext | CaseInsensitive | Wrapping} }, + { 0, 0, "N", search, {DirectionPrev | CaseInsensitive | Wrapping} }, + + { 0, 0, "colon", input, {.s = ":" } }, + { 0, 0, "o", input, {.s = ":open "} }, + { 0, 0, "O", input, {.s = ":open ", .i = InsertCurrentURL} }, + { 0, 0, "t", add_tab, {.s = ":open "} }, + { 0, 0, "T", input, {.s = ":tabopen ", .i = InsertCurrentURL} }, + { 0, 0, "slash", input, {.s = "/"} }, +// { 0, 0, GDK_KP_Divide, input, {.s = "/"} }, + { 0, 0, "question", input, {.s = "?"} }, + + { 0, 0, "period", input, {.s = "."} }, + { 0, 0, "comma", input, {.s = ","} }, + { 0, ';', "i", input, {.s = ";i"} }, + { 0, ';', "s", input, {.s = ";s"} }, + { 0, ';', "y", input, {.s = ";y"} }, + { 0, ';', "o", input, {.s = ";o"} }, + { 0, ';', "t", input, {.s = ";t"} }, + { 0, ';', "w", input, {.s = ";w"} }, + { 0, ';', "I", input, {.s = ";I"} }, + { 0, ';', "O", input, {.s = ";O"} }, + { 0, ';', "T", input, {.s = ";T"} }, + { 0, ';', "W", input, {.s = ";W"} }, + + { 0, '*', "Escape", set, {ModeNormal} }, + { "Control", '*', "bracketleft", set, {ModeNormal} }, + { "Control", 0, "z", set, {ModePassThrough} }, + { "Control", 0, "v", set, {ModeSendKey} }, + { 0, 0, "f", input, {.s = "."} }, + { 0, 0, "F", input, {.s = ","} }, + + { 0, 'g', "i", focus_input,{} }, + { 0, 0, "u", revive, {} }, + + { 0, 0, "x", tab_quit, {0} }, + // { 0, 0, "X", revive_last_tab, {0} }, + { 0, 0, "b", switch_buffer, {0}}, + { 0, 0, "i", inspector, { .i = ShowInspector}}, + { 0, 0, "I", inspector, { .i = HideInspector}}, + /* leave this last line as last */ + { 0, 0, 0, 0, {0} }, +}; + + +#endif diff --git a/main.c b/main.c @@ -0,0 +1,1533 @@ +/* + (c) 2009 by Leon Winter + (c) 2009-2012 by Hannes Schueller + (c) 2009-2010 by Matto Fransen + (c) 2010-2011 by Hans-Peter Deifel + (c) 2010-2011 by Thomas Adam + (c) 2011 by Albert Kim + (c) 2011 by Daniel Carl + (c) 2012 by Matthew Carter + see LICENSE file +*/ + +#define _GNU_SOURCE +#include <Elementary.h> +#include <EWebKit.h> + +#include "viking.h" +#include "main.h" +#include "commands.h" +#include "utilities.h" +#include "javascript.h" +#include "config.h" +#include "keymap.h" + +#include <stdio.h> /* asprintf() */ +#include <string.h> /* strcmp */ +#include <stdlib.h> /* free() */ + + +EAPI_MAIN int elm_main(int argc, char *argv[]); + +#define IS_ESCAPE(ev) ((!strcmp(ev->keyname, "Escape")) || \ + (!strcmp(ev->keyname, "[") && evas_key_modifier_is_set(ev->modifiers, "Control"))) + + +/* callbacks here */ +static void inputbox_activate_cb (void *, Evas_Object *, void *); +static void inputbox_changed_cb (void *, Evas_Object *, void *); +static Eina_Bool commandhistoryfetch (const Arg *, void *); +static void inputbox_keypress_cb (void *, Evas *, Evas_Object *, void *); +static void inputbox_keyrelease_cb (void *, Evas *, Evas_Object *, void *); + +static void webview_console_cb(void *, Evas_Object *, const char *, unsigned int, const char *); +static void webview_hoverlink_cb (void *, Evas_Object *, void*); +static void webview_hoverlink_out_cb (void *, Evas_Object *, void*); +static void webview_load_committed_cb (void *, Evas_Object *, void*); +static void webview_load_finished_cb (void *, Evas_Object *, void*); +static void history(void *); +static void webview_progress_changed_cb(void *, Evas_Object *, void *); +static void webview_title_changed_cb (void *, Evas_Object *, void *); +static void webview_download_cb (void *, Evas_Object *, void *); +static void inputmethod_changed_cb (void *, Evas_Object *, void *); +static void icon_received_cb (void *, Evas_Object *, void *); +static void webview_inspector_cb (void *, Evas_Object *, void *); +static Eina_Bool process_keypress (void *, void*); +static void webview_keypress_cb (void *, Evas *, Evas_Object *, void *); +static void webview_mousewheel_cb (void *, Evas *, Evas_Object *, void *); + +/* +static WebKitWebView* inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view); +static Eina_Bool webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request, char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data); +static void webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec); +static Eina_Bool webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data); +static WebKitWebView* webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data); +*/ + + +/* functions */ +static void ascii_bar(int total, int state, char *string); +static void update_url(const char *, void *); +// static char *jsapi_ref_to_string(JSContextRef context, JSValueRef ref); +// static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message, void *data); +static void set_widget_font_and_color(Evas_Object *widget, const char *font_str, const char *bg_color_str, const char *fg_color_str); + + + +static void +ascii_bar(int total, int state, char *string) +{ + int i; + + for (i = 0; i < state; i++) + string[i] = progressbartickchar; + string[i++] = progressbarcurrent; + for (; i < total; i++) + string[i] = progressbarspacer; + string[i] = '\0'; +} + +void +update_state(void *data) +{ + // char *markup; + App_Data *ad = data; + Evas_Object *view = elm_web_webkit_view_get(ad->current_web); + Evas_Object *frame = ewk_view_frame_main_get(view); + // int download_count = g_list_length(activeDownloads); + int download_count = eina_list_count(ad->active_downloads); + // GString *status = g_string_new(""); + char status[512] = ""; + + /* construct the status line */ + + strcat(status, "<font=Monospace font_size=12 color=#FFF backing=on backing_color=#000>"); + + /* count, modkey and input buffer */ + // g_string_append_printf(status, "%.0d", count); + char *count_formatted = strdup_printf("%.0d", ad->count); + strcat(status, count_formatted); + free(count_formatted); + + // if (ad->current_modkey) g_string_append_c(status, ad->current_modkey); + if (ad->current_modkey) strcat(status, &ad->current_modkey); + + if (ad->active_downloads) { + // g_string_append_printf(status, " %d active %s", download_count, + // (download_count == 1) ? "download" : "downloads"); + char *download_status = strdup_printf(" %d active %s", download_count, + (download_count == 1) ? "download" : "downloads"); + strcat(status, download_status); + free(download_status); + } + + /* the progressbar */ + int progress = -1; + char progressbar[progressbartick + 1]; + + /* + if (activeDownloads) { + progress = 0; + Eina_List *ptr; + + for (ptr = activeDownloads; ptr; ptr = eina_list_next(ptr)) { + progress += 100 * webkit_download_get_progress(ptr->data); + } + + progress /= download_count; + + } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED + && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) { + + // progress = webkit_web_view_get_progress(webview) * 100; + } + */ + + if (ewk_view_load_progress_get(view) != 0.0f) + progress = ewk_view_load_progress_get(view) * 100; + + if (progress >= 0) { + ascii_bar(progressbartick, progress * progressbartick / 100, progressbar); + // g_string_append_printf(status, " %c%s%c", + // progressborderleft, progressbar, progressborderright); + char *load_progress = strdup_printf(" %c%s%c", + progressborderleft, progressbar, progressborderright); + strcat(status, load_progress); + free(load_progress); + } + + /* put a notification if we are zoomed */ + double zoom = elm_web_zoom_get(ad->current_web); + if (zoom != 1) { + char *zoom_str = strdup_printf(" %0.2lfx", zoom); + strcat(status, zoom_str); + free(zoom_str); + } + + /* always show the current scroll position */ + int x, y, w, h, val; + ewk_frame_scroll_pos_get(frame, &x, &y); + ewk_frame_scroll_size_get(frame, &w, &h); + + val = (int) ((float) y / h * 100); + + if (h == 0) + strcat(status, " All"); + else if (val == 0) + strcat(status, " Top"); + else if (val == 100) + strcat(status, " Bot"); + else { + char *percent_scrolled = strdup_printf(" %d%%", val); + strcat(status, percent_scrolled); + free(percent_scrolled); + } + + elm_object_text_set(ad->status_state, status); + + int state_width, window_width; + evas_object_geometry_get(ad->status_state, NULL, NULL, &state_width, NULL); + evas_object_geometry_get(ad->win, NULL, NULL, &window_width, NULL); + + elm_label_wrap_width_set(ad->status_url, window_width - state_width); + + // update_url(elm_web_uri_get(ad->current_web), data); + // elm_box_recalculate(ad->status_bar); + // recalculate_status_bar(data); +} + +static void +update_url(const char *uri, void *data) +{ + char *markup; + char before[] = " ["; + char after[] = "]"; + + if (uri == NULL) + return; + + // Evas_Object *view, *frame; + App_Data *ad = data; + Eina_Bool back = elm_web_back_possible_get(ad->current_web); + Eina_Bool fwd = elm_web_forward_possible_get(ad->current_web); + + Evas_Object *view = elm_web_webkit_view_get(ad->current_web); + Evas_Object *frame = ewk_view_frame_main_get(view); + char *url_color = "#FFF"; + + if (!strncmp(uri, "https://", 8)) { + Ewk_Certificate_Status cert_status = ewk_frame_certificate_status_get(frame); + if (cert_status & EWK_CERTIFICATE_STATUS_TRUSTED) + url_color = "#0F0"; + else if (cert_status & EWK_CERTIFICATE_STATUS_UNTRUSTED) + url_color = "#F00"; + } + + if (!back && !fwd) + before[0] = after[0] = '\0'; + + markup = strdup_printf("<font=Monospace font_size=12 color=%s backing=on backing_color=#000>%s%s%s%s%s", + url_color, uri, before, back ? "+" : "", fwd ? "-" : "", after); + + + int state_width, window_width; + evas_object_geometry_get(ad->status_state, NULL, NULL, &state_width, NULL); + evas_object_geometry_get(ad->win, NULL, NULL, &window_width, NULL); + elm_label_wrap_width_set(ad->status_url, window_width - state_width); + + elm_object_text_set(ad->status_url, markup); + // elm_label_line_wrap_set(ad->status_url, ELM_WRAP_CHAR); + // elm_label_ellipsis_set(ad->status_url, EINA_TRUE); + + free(markup); +} + +Eina_Bool +echo(const Arg *arg, void *data) +{ + App_Data *ad = data; + int index = !arg->s ? 0 : arg->i & (~NoAutoHide); + + if (index < Info || index > Error) + return TRUE; + +// printf("echo(%s)\n", arg->s); + // if (!gtk_widget_is_focus(GTK_WIDGET(inputbox))) { +// if (!elm_object_focus_get(ad->url)) { + set_widget_font_and_color(ad->url, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]); + // gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s); + elm_entry_entry_set(ad->url, !arg->s ? "" : arg->s); +// } + + return TRUE; +} + +void +webview_title_changed_cb(void *data, Evas_Object *obj, void *event_info) +{ + /* + const char *title = ""; + Ewk_Text_With_Direction *dir_text = event_info; + if (event_info) title = dir_text->string; + char buf[20] = ""; + + if (title) + strncpy(buf, title, sizeof(buf) - 1); + elm_object_item_text_set(td->tab, buf); + */ +} + +void +webview_progress_changed_cb(void *data, Evas_Object *obj, void *event_info) +{ + Tab_Data *td = data; + /* *event_info is a double between 0.0 and 1.0 */ + update_state(td->app); +} + +void +webview_load_committed_cb(void *data, Evas_Object *obj, void *event_info) +{ + Arg a = { .i = Silent, .s = strdup(JS_SETUP_HINTS) }; + Tab_Data *td = data; + + // Evas_Object *view = elm_web_webkit_view_get(td->web); + // Evas_Object *frame = ewk_view_frame_main_get(view); + + // userscript_hooks_start(elm_web_uri_get(td->web)); + + script(&a, td->app); + free(a.s); + + /* + FILE *fp = fopen("/home/kyle/.config/evi/userscript/adblockplus/contentScript1.js", "r"); + if (!fp) + printf("error opening file.\n"); + fseek(fp, 0L, SEEK_END); + int len = ftell(fp); + char *buf = malloc(len); + rewind(fp); + fread(buf, len, 1, fp); + fclose(fp); + + ewk_frame_script_execute(frame, buf); + + free(buf); + */ + + if (td->app->mode == ModeInsert || td->app->mode == ModeHints) { + Arg a = { .i = ModeNormal }; + set(&a, td->app); + } + td->app->manual_focus = FALSE; +} + +void +history(void *data) +{ + FILE *f; + char *filename; + // const char *uri = webkit_web_view_get_uri(webview); + // const char *title = webkit_web_view_get_title(webview); + App_Data *ad = data; + const char *uri = elm_web_uri_get(ad->current_web); + const char *title = elm_web_title_get(ad->current_web); + char *entry, buffer[512], *new; + int n, i = 0; + Eina_Bool finished = EINA_FALSE; + if (uri != NULL) { + if (title != NULL) { + entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char)); + memset(entry, 0, strlen(uri) + strlen(title) + 2); + } else { + entry = malloc((strlen(uri) + 1) * sizeof(char)); + memset(entry, 0, strlen(uri) + 1); + } + if (entry != NULL) { + strncpy(entry, uri, strlen(uri)); + if (title != NULL) { + strncat(entry, " ", 1); + strncat(entry, title, strlen(title)); + } + n = strlen(entry); + filename = strdup_printf(HISTORY_STORAGE_FILENAME); + f = fopen(filename, "r"); + if (f != NULL) { + new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1); + if (new != NULL) { + memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1); + /* newest entries go on top */ + strncpy(new, entry, strlen(entry)); + strncat(new, "\n", 1); + /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */ + while (finished != TRUE) { + if ((char *)NULL == fgets(buffer, 512, f)) { + /* check if end of file was reached / error occured */ + if (!feof(f)) { + break; + } + /* end of file reached */ + finished = TRUE; + continue; + } + /* compare line (-1 because of newline character) */ + if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) { + /* if the URI is already in history; we put it on top and skip it here */ + strncat(new, buffer, 512); + i++; + } + if ((i + 1) >= HISTORY_MAX_ENTRIES) { + break; + } + } + fclose(f); + } + f = fopen(filename, "w"); + free(filename); + if (f != NULL) { + fprintf(f, "%s", new); + fclose(f); + } + if (new != NULL) { + free(new); + new = NULL; + } + } + } + if (entry != NULL) { + free(entry); + entry = NULL; + } + } +} + +void +webview_load_finished_cb(void *data, Evas_Object *obj, void *event_info) +{ + /* WebKitWebSettings *settings = webkit_web_view_get_settings(webview); */ + Tab_Data *td = data; + App_Data *ad = td->app; + Eina_Bool scripts = 1; + Elm_Web_Frame_Load_Error *frame_error = event_info; + +// Evas_Object *view = elm_web_webkit_view_get(td->web); +// Evas_Object *frame = ewk_view_frame_main_get(view); + + if (frame_error) + fprintf(stderr, "Error http code %i on domain %s,\n%s\n", frame_error->code, frame_error->failing_url, frame_error->description); + + /* g_object_get(settings, "enable-scripts", &scripts, NULL); */ + if (escape_input_on_load && scripts && !ad->manual_focus && !elm_object_focus_get(ad->url)) { + Arg a = { .i = Silent, .s = strdup("hints.clearFocus();") }; + script(&a, ad); + free(a.s); + a.i = ModeNormal; + a.s = NULL; + set(&a, ad); + } + if (HISTORY_MAX_ENTRIES > 0) + history(ad); + update_state(ad); + + // userscript_hooks_end(elm_web_uri_get(td->web)); + + /* + FILE *fp = fopen("/home/kyle/.config/evi/userscript/adblockplus/contentScript2.js", "r"); + if (!fp) + printf("error opening file.\n"); + fseek(fp, 0L, SEEK_END); + int len = ftell(fp); + char *buf = malloc(len); + rewind(fp); + fread(buf, len, 1, fp); + fclose(fp); + + ewk_frame_script_execute(frame, buf); + + free(buf); + */ + + + // elm_object_focus_set(td->web, EINA_TRUE); +} + +#if 0 +void +webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec) { + /* retrieve the URI of the temporary webview */ + Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(temp_view) }; + /* clean up */ + webkit_web_view_stop_loading(temp_view); + gtk_widget_destroy(GTK_WIDGET(temp_view)); + /* open the requested window */ + open_arg(&a); +} + +static WebKitWebView * +webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) { + /* create a temporary webview to execute the script in */ + WebKitWebView *temp_view = WEBKIT_WEB_VIEW(webkit_web_view_new()); + /* wait until the new webview receives its new URI */ + g_object_connect(temp_view, "signal::notify::uri", G_CALLBACK(webview_open_js_window_cb), NULL, NULL); + return temp_view; +} + +Eina_Bool +webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request, + WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) { + Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) }; + open_arg(&a); + webkit_web_policy_decision_ignore(decision); + return TRUE; +} +#endif + +/* +Eina_Bool +webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request, + char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) { + if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) { + webkit_web_policy_decision_download(decision); + return TRUE; + } else { + return FALSE; + } +} + +static WebKitWebView* +inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) { + gchar* inspector_title; + GtkWidget* inspector_window; + GtkWidget* inspector_view; + + // just enough code to show the inspector - no signal handling etc. + inspector_title = strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view)); + if (embed) { + inspector_window = gtk_plug_new(embed); + } else { + inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2"); + } + gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title); + free(inspector_title); + inspector_view = webkit_web_view_new(); + gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view); + gtk_widget_show_all(inspector_window); + return WEBKIT_WEB_VIEW(inspector_view); +} +*/ + + +static void +webview_inspector_cb(void *data, Evas_Object *obj, void *event_info) +{ + // Tab_Data *td = data; + + printf("would have show inspector!\n"); + +} + +void +download_complete_cb(void *data, const char *file, int status) +{ + App_Data *ad = data; + Arg a; + a.i = Info; + a.s = strdup_printf("Download %s finished, status %i", file, status); + echo(&a, data); + free(a.s); + + ad->active_downloads = eina_list_remove(ad->active_downloads, file); +} + +int +download_progress_cb(void *data, const char *file, long int dltotal, long int dlnow, long int ultotal, long int ulnow) +{ + printf("download_progress() file %s done %f\n", file, (float)dlnow/dltotal); + update_state(data); + + return 0; +} + +static void +webview_download_cb(void *data, Evas_Object *obj, void *event_info) +{ + Tab_Data *td = data; + App_Data *ad = td->app; + + Ewk_Download *dl = event_info; + char *suggested_name = strdup(strrchr(dl->url, '/')); + + char *full_path = strdup_printf("/home/kyle/%s", suggested_name); + + ecore_file_download(dl->url, full_path, download_complete_cb, + download_progress_cb, ad, NULL); + + ad->active_downloads = eina_list_append(ad->active_downloads, strdup(suggested_name)); + + free(full_path); + free(suggested_name); + + update_state(ad); +} + +static void +webview_mousewheel_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + Tab_Data *td = data; + update_state(td->app); +} + +static Eina_Bool +process_keypress(void *event_info, void *data) +{ + KeyList *walk; + Key key; + // int keyval; + // GdkModifierType irrelevant; + App_Data *ad = data; + Evas_Event_Key_Down *ev = event_info; + + // Get a mask of modifiers that shouldn't be considered for this event. + // E.g.: It shouldn't matter whether ';' is shifted or not. + // gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode, + // event->state, event->group, &keyval, NULL, NULL, &irrelevant); + + walk = ad->keylistroot; + while (walk != NULL) { + key = walk->Element; + // if (current->Element.mask == (CLEAN(event->state) & ~irrelevant) + if ((key.mask == 0 || evas_key_modifier_is_set(ev->modifiers, key.mask)) + && (key.modkey == ad->current_modkey + || (!key.modkey && !ad->current_modkey) + || key.modkey == '*' ) // wildcard + && !strcmp(key.key, ev->key) + && key.func) + if (key.func(&key.arg, data)) { + // printf("process_keypress() function for mask = %s modkey = %c key = %s called.\n", key.mask, ad->current_modkey, key.key); + ad->current_modkey = ad->count = 0; + update_state(data); + return TRUE; + } + walk = walk->next; + } + return FALSE; +} + +static void +webview_keypress_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + Arg a = { .i = ModeNormal, .s = NULL }; + // int keyval; + Evas_Event_Key_Down *ev = event_info; + Tab_Data *td = data; + App_Data *ad = td->app; + // GdkModifierType irrelevant; + + /* Get a mask of modifiers that shouldn't be considered for this event. + * E.g.: It shouldn't matter whether ';' is shifted or not. */ + // gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode, + // event->state, event->group, &keyval, NULL, NULL, &irrelevant); + + switch (ad->mode) { + case ModeNormal: + // if ((CLEAN(event->state) & ~irrelevant) == 0) { + if (IS_ESCAPE(ev)) { + a.i = Info; + a.s = strdup(""); + echo(&a, ad); + elm_box_clear(ad->event_box); + evas_object_hide(ad->event_box); + free(a.s); + } else if (ad->current_modkey == 0 && + (( strcmp(ev->key, "1") >= 0 && strcmp(ev->key, "9") <= 0) + || (!strcmp(ev->key, "0") && ad->count))) { + // ad->count = (ad->count ? ad->count * 10 : 0) + (ev->key - "0"); + ad->count = (ad->count ? ad->count * 10 : 0) + atoi(ev->key); + update_state(ad); + return; + } else if (strchr(ad->modkeys, ev->key[0]) && ad->current_modkey != ev->key[0]) { + ad->current_modkey = ev->key[0]; + // ad->current_modkey = strdup(ev->key); + + update_state(ad); + return; + } + // } + /* keybindings */ + if (process_keypress(event_info, ad) == TRUE) return; + + break; + case ModeInsert: + if (IS_ESCAPE(ev)) { + a.i = Silent; + a.s = strdup("hints.clearFocus();"); + script(&a, ad); + free(a.s); + a.i = ModeNormal; + set(&a, ad); + // ewk_view_input_method_state_set(td->web, FALSE); + return; + } + case ModePassThrough: + if (IS_ESCAPE(ev)) { + echo(&a, ad); + set(&a, ad); + return; + } + break; + case ModeSendKey: + echo(&a, ad); + set(&a, ad); + break; + } +} + +void +set_widget_font_and_color(Evas_Object *widget, const char *font_str, const char *bg_color_str, const char *fg_color_str) +{ + /* + GdkColor fg_color; + GdkColor bg_color; + PangoFontDescription *font; + + font = pango_font_description_from_string(font_str); + gtk_widget_modify_font(widget, font); + pango_font_description_free(font); + + if (fg_color_str) + gdk_color_parse(fg_color_str, &fg_color); + if (bg_color_str) + gdk_color_parse(bg_color_str, &bg_color); + + gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL); + gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL); + */ + + return; +} + +void +icon_received_cb(void *data, Evas_Object *obj, void *event_info) +{ + printf("icon_received_cb()\n"); +} + +void +webview_hoverlink_cb(void *data, Evas_Object *obj, void *event_info) +{ + Tab_Data *td = data; + const char *uri = elm_web_uri_get(td->web); + /* event_info is a char *link[2] where the first string contains the URL + * and the second the title of the link */ + char **link = event_info; + // char *markup; + + memset(td->app->rememberedURI, 0, 1024); + if (link && link[0]) { + // markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link[0]); + // markup = strdup_printf("<font=Monospace font_size=10 color=#FF0>%s", link[0]); + // gtk_label_set_markup(GTK_LABEL(td->app->status_url), markup); + // elm_object_text_set(td->app->status_url, markup); + update_url(link[0], td->app); + strcpy(td->app->rememberedURI, link[0]); + // free(markup); + } else + update_url(uri, td->app); +} + +static void +webview_hoverlink_out_cb(void *data, Evas_Object *obj, void *event_info) +{ + Tab_Data *td = data; + update_url(elm_web_uri_get(td->web), td->app); +} + +static void +webview_console_cb(void *data, Evas_Object *obj, const char *message, unsigned int line, const char *source) +{ + Arg a; + Tab_Data *td = data; + + /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */ + // if (gtk_window_has_toplevel_focus(window)) { + if (elm_object_focus_get(td->app->win)) { + if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) { + a.i = ModeNormal; + set(&a, td->app); + return; + } else if (!strcmp(message, "insertmode_on")) { + a.i = ModeInsert; + set(&a, td->app); + return; + } + } +} + +void +inputbox_activate_cb(void *data, Evas_Object *obj, void *event_info) +{ + const char *text; + App_Data *ad = data; + uint16_t length = strlen(elm_entry_entry_get(ad->url)); + Arg a; + Eina_Bool forward = FALSE; + + // printf("In inputbox_activate\n"); + + a.i = HideCompletion; + complete(&a, data); + if (length == 0) + return; + text = elm_entry_entry_get(ad->url); + if (length > 1 && text[0] == ':') { + process_line((text + 1), data); + } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) { + // webkit_web_view_unmark_text_matches(webview); + elm_web_text_matches_unmark_all(ad->current_web); + + // webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0); + // webkit_web_view_set_highlight_text_matches(webview, TRUE); + elm_web_text_matches_mark(ad->current_web, &text[1], EINA_FALSE, EINA_TRUE, 0); + + ad->count = 0; + ad->search_direction = forward; + ad->search_handle = strdup(&text[1]); + } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') { + a.i = Silent; + a.s = strdup("hints.fire();"); + script(&a, data); + free(a.s); + update_state(data); + } else + return; + if (!ad->echo_active) + // gtk_entry_set_text(entry, ""); + elm_entry_entry_set(ad->url, ""); + // gtk_widget_grab_focus(GTK_WIDGET(webview)); + /* process_line above may have deleted the tab, check if its still around */ + if (ad->current_web) + elm_object_focus_set(ad->current_web, EINA_TRUE); +} + +static Eina_Bool +commandhistoryfetch(const Arg *arg, void *data) +{ + App_Data *ad = data; + char *command; + const int length = eina_list_count(ad->commandhistory); + + if (length > 0) { + if (arg->i == DirectionPrev) { + ad->commandpointer = (length + ad->commandpointer - 1) % length; + } else { + ad->commandpointer = (length + ad->commandpointer + 1) % length; + } + + command = eina_list_nth(ad->commandhistory, ad->commandpointer); + // gtk_entry_set_text(GTK_ENTRY(inputbox), g_strconcat(":", command, NULL)); + // gtk_editable_set_position(GTK_EDITABLE(inputbox), -1); + command = strdup_printf(":%s", command); + elm_entry_entry_set(ad->url, command); + free(command); + elm_entry_cursor_line_end_set(ad->url); + return TRUE; + } + + return FALSE; +} + +static void +inputbox_keypress_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + Arg a; + int numval; + Evas_Event_Key_Down *ev = event_info; + App_Data *ad = data; + + if (ad->mode == ModeHints) { + // if (event->keyval == GDK_ISO_Left_Tab) { + if (!strcmp(ev->keyname, "Tab") && evas_key_modifier_is_set(ev->modifiers, "Shift")) { + a.i = Silent; + a.s = strdup("hints.focusPreviousHint();"); + script(&a, data); + free(a.s); + update_state(data); + return; + } + // if (event->keyval == GDK_Tab) { + if (!strcmp(ev->keyname, "Tab")) { + a.i = Silent; + a.s = strdup("hints.focusNextHint();"); + script(&a, data); + free(a.s); + update_state(data); + return; + } + // if (event->keyval == GDK_Return) { + if (!strcmp(ev->keyname, "Return")) { + a.i = Silent; + a.s = strdup_printf("hints.fire();"); + script(&a, data); + free(a.s); + update_state(data); + return; + } + } + if (!strcmp(ev->keyname, "[") || !strcmp(ev->keyname, "Escape")) { + a.i = HideCompletion; + complete(&a, data); + a.i = ModeNormal; + ad->commandpointer = 0; + set(&a, data); + return; + } + else if (!strcmp(ev->keyname, "Tab") && evas_key_modifier_is_set(ev->modifiers, "Shift")) { + a.i = DirectionPrev; + complete(&a, data); + return; + } + else if (!strcmp(ev->keyname, "Tab")) { + a.i = DirectionNext; + complete(&a, data); + return; + } + else if (!strcmp(ev->keyname, "Up")) { + a.i = DirectionPrev; + commandhistoryfetch(&a, data); + return; + } + else if (!strcmp(ev->keyname, "Down")) { + a.i = DirectionNext; + commandhistoryfetch(&a, data); + return; + } + + /* + switch (event->keyval) { + case GDK_bracketleft: + case GDK_Escape: + if (!IS_ESCAPE(event)) break; + a.i = HideCompletion; + complete(&a, data); + a.i = ModeNormal; + commandpointer = 0; + return set(&a, ad); + break; + case GDK_Tab: + a.i = DirectionNext; + return complete(&a, data); + break; + case GDK_Up: + a.i = DirectionPrev; + return commandhistoryfetch(&a); + break; + case GDK_Down: + a.i = DirectionNext; + return commandhistoryfetch(&a); + break; + case GDK_ISO_Left_Tab: + a.i = DirectionPrev; + return complete(&a, data); + break; + } + */ + + if (ad->mode == ModeHints) { +#if 0 + if ((CLEAN(event->state) & GDK_SHIFT_MASK) && + (CLEAN(event->state) & GDK_CONTROL_MASK) && + (event->keyval == GDK_BackSpace)) { + count /= 10; + a.i = Silent; + a.s = g_strdup_printf("hints.updateHints(%d);", count); + script(&a, data); + g_free(a.s); + update_state(); + return TRUE; + } +#endif + + // numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval)); + numval = atoi(ev->key); + if ((numval >= 1 && numval <= 9) || (numval == 0 && ad->count)) { + /* allow a zero as non-first number */ + ad->count = (ad->count ? ad->count * 10 : 0) + numval; + a.i = Silent; + a.s = strdup_printf("hints.updateHints(%d);", ad->count); + script(&a, data); + free(a.s); + update_state(data); + return; + } + } +} + +static void +inputmethod_changed_cb(void *data, Evas_Object *obj, void *event_info) +{ + const char *value; + Eina_Bool *enabled = event_info; + Tab_Data *td = data; + App_Data *ad = td->app; + Evas_Object *view = elm_web_webkit_view_get(td->web); + Evas_Object *frame = ewk_view_frame_main_get(view); + + if (ad->mode == ModeNormal && enabled) { + Arg a = { .i = ModeInsert }; + set(&a, ad); + ad->manual_focus = TRUE; + } else if (ad->mode == ModeInsert && !enabled) { + Arg a = { .i = ModeNormal }; + set(&a, ad); + } else { + // char *value = NULL, *message = NULL; + // jsapi_evaluate_script("window.getSelection().focusNode", &value, &message, ad); + value = ewk_frame_script_execute(frame, "window.getSelection().focusNode"); + if (value && !strcmp(value, "[object HTMLFormElement]")) { + Arg a = { .i = ModeInsert, .s = NULL }; + set(&a, ad); + ad->manual_focus = TRUE; + } + // free(value); + eina_stringshare_del(value); + // free(message); + } +} + +/* +static void +// notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) { +notify_event_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + int i; + // WebKitHitTestResult *result; + // WebKitHitTestResultContext context; + Ewk_Hit_Test *result; + Ewk_Hit_Test_Result_Context context; + + Evas_Event_Mouse_Up *ev = event_info; + Tab_Data *td = data; + App_Data *ad = td->app; + + Evas_Object *view = elm_web_webkit_view_get(td->web); + Evas_Object *frame = ewk_view_frame_main_get(view); + + // if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) { + if (ad->mode == ModeNormal) { + // handle mouse click events + for (i = 0; i < LENGTH(mouse); i++) { + // if (mouse[i].mask == CLEAN(event->button.state) + if ( (mouse[i].mask == 0 || evas_key_modifier_is_set(ev->modifiers, mouse[i].mask)) + // && (mouse[i].modkey == current_modkey + && ((ad->current_modkey && !strcmp(mouse[i].modkey, ad->current_modkey)) + || (!mouse[i].modkey && !ad->current_modkey) + // || mouse[i].modkey == GDK_VoidSymbol) // wildcard + || !strcmp(mouse[i].modkey, "*")) // wildcard + // && mouse[i].button == event->button.button + && (mouse[i].button & ev->button) + && mouse[i].func) { + if (mouse[i].func(&mouse[i].arg, ad)) { + // current_modkey = count = 0; + ad->current_modkey = NULL; + ad->count = 0; + update_state(ad); + return TRUE; + } + } + } + // result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event); + result = ewk_frame_hit_test_new(frame, ev->canvas.x, ev->canvas.y); + context = result->context; + + // g_object_get(result, "context", &context, NULL); + // if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) { + if (context & EWK_HIT_TEST_RESULT_CONTEXT_EDITABLE) { + Arg a = { .i = ModeInsert }; + set(&a, ad); + ad->manual_focus = TRUE; + } + // } else if (mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) { + } else if (ad->mode == ModeInsert) { + //result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event); + result = ewk_frame_hit_test_new(frame, ev->output.x, ev->output.y); + + // g_object_get(result, "context", &context, NULL); + // if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) { + if (!(context & EWK_HIT_TEST_RESULT_CONTEXT_EDITABLE)) { + Arg a = { .i = ModeNormal }; + set(&a, ad); + } + } else { + char *value = NULL, *message = NULL; + jsapi_evaluate_script("window.getSelection().focusNode", &value, &message, ad); + if (value && !strcmp(value, "[object HTMLFormElement]")) { + Arg a = { .i = ModeInsert, .s = NULL }; + set(&a, ad); + ad->manual_focus = TRUE; + } + free(value); + free(message); + } + ewk_frame_hit_test_free(result); + return FALSE; +} +*/ + +static void +inputbox_keyrelease_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + Arg a; + App_Data *ad = data; + // guint16 length = gtk_entry_get_text_length(entry); + uint16_t length = strlen(elm_entry_entry_get(ad->url)); + + if (!length) { + a.i = HideCompletion; + complete(&a, data); + a.i = ModeNormal; + set(&a, data); + } +} + +/* used for incremental search */ +static void +inputbox_changed_cb(void *data, Evas_Object *obj, void *event_info) +{ + Arg a; + App_Data *ad = data; + const char *text = elm_entry_entry_get(ad->url); + uint16_t length = strlen(text); + Eina_Bool forward = FALSE; + + /* Update incremental search if the user changes the search text. + * + * Note: gtk_widget_is_focus() is a poor way to check if the change comes + * from the user. But if the entry is focused and the text is set + * through gtk_entry_set_text() in some asyncrounous operation, + * I would consider that a bug. + */ + + // if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) { + if (elm_object_focus_get(ad->url) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) { + // webkit_web_view_unmark_text_matches(webview); + elm_web_text_matches_unmark_all(ad->current_web); + + // webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping); + elm_web_text_search(ad->current_web, &text[1], EINA_TRUE, forward, EINA_FALSE); + return; + // } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 && + } else if (elm_object_focus_get(ad->url) && length >= 1 && + (text[0] == '.' || text[0] == ',' || text[0] == ';')) { + a.i = Silent; + switch (text[0]) { + case '.': + // a.s = strconcat("hints.createHints('", text + 1, "', 'f');", NULL); + a.s = strdup_printf("hints.createHints('%s', 'f');", text + 1); + break; + + case ',': + // a.s = strconcat("hints.createHints('", text + 1, "', 'F');", NULL); + a.s = strdup_printf("hints.createHints('%s', 'F');", text + 1); + break; + + case ';': + a.s = NULL; + switch (text[1]) { + case 's': + // a.s = strconcat("hints.createHints('", text + 2, "', 's');", NULL); + a.s = strdup_printf("hints.createHints('%s', 's');", text + 2); + break; + case 'y': + // a.s = strconcat("hints.createHints('", text + 2, "', 'y');", NULL); + a.s = strdup_printf("hints.createHints('%s', 'y');", text + 2); + break; + case 'o': + // a.s = strconcat("hints.createHints('", text + 2, "', 'f');", NULL); + a.s = strdup_printf("hints.createHints('%s', 'f');", text + 2); + break; + case 't': case 'w': + // a.s = strconcat("hints.createHints('", text + 2, "', 'F');", NULL); + a.s = strdup_printf("hints.createHints('%s', 'F');", text + 2); + break; + case 'O': case 'T': case 'W': + // a.s = strconcat("hints.createHints('", text + 2, "', 'O');", NULL); + a.s = strdup_printf("hints.createHints('%s', 'O');", text + 2); + break; + case 'i': + // a.s = strconcat("hints.createHints('", text + 2, "', 'i');", NULL); + a.s = strdup_printf("hints.createHints('%s', 'i');", text + 2); + break; + case 'I': + // a.s = strconcat("hints.createHints('", text + 2, "', 'I');", NULL); + a.s = strdup_printf("hints.createHints('%s', 'I');", text + 2); + break; + } + break; + } + ad->count = 0; + if (a.s) { + script(&a, data); + free(a.s); + } + + return; + } else if (length == 0 && ad->followTarget[0]) { + ad->mode = ModeNormal; + a.i = Silent; + a.s = strdup("hints.clearHints();"); + script(&a, data); + free(a.s); + ad->count = 0; + update_state(data); + } +} + + + +Tab_Data *tab_add(App_Data *); + +static Evas_Object * +webview_create_window_cb(void *data, Evas_Object *obj, Eina_Bool js, const Elm_Web_Window_Features *wf) +{ + App_Data *ad = data; + Tab_Data *td; + + td = tab_add(ad); + buffer_current_set(td); + return td->web; +} + +static void +_win_del_request_cb(void *data, Evas_Object *obj, void *event_info) +{ + App_Data *ad = data; + ad->exiting = EINA_TRUE; +} + +void +buffer_current_set(Tab_Data *td) +{ + App_Data *ad = td->app; + // const char *uri; + Evas_Object *view; + + if (td->web == td->app->current_web) + return; + + if (ad->current_web) { + view = elm_web_webkit_view_get(ad->current_web); + ewk_view_visibility_state_set(view, EWK_PAGE_VISIBILITY_STATE_HIDDEN, 0); + } + + ad->current_web = td->web; + + if (ad->current_web) { + view = elm_web_webkit_view_get(ad->current_web); + ewk_view_visibility_state_set(view, EWK_PAGE_VISIBILITY_STATE_VISIBLE, 0); + } + + // uri = elm_web_uri_get(td->web); + update_url(elm_web_uri_get(td->web), td->app); + // elm_object_text_set(td->app->url, uri); + // if (uri) update_url(uri, td->app); + + + elm_naviframe_item_simple_promote(td->app->naviframe, td->web); +} + +static void +_web_free_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) +{ + Tab_Data *td = data; + App_Data *ad = td->app; + elm_naviframe_item_pop(ad->naviframe); + + if (!ad->exiting) + ad->current_web = NULL; + + ad->buffer_list = eina_list_remove(ad->buffer_list, td); + + if (eina_list_count(ad->buffer_list)) { + buffer_current_set(eina_list_nth(ad->buffer_list, 0)); + } + + free(td); +} + +static void +_uri_changed_cb(void *data, Evas_Object *obj, void *event_info) +{ + Tab_Data *td = data; + const char *uri = event_info; + + if (td->web != td->app->current_web) + return; + + update_url(uri, td->app); + // elm_object_text_set(td->app->url, uri); +} + +/* +static void +_tb_item_del_cb(void *data, Evas_Object *obj, void *event_info) +{ + Tab_Data *td = data; + // if (!td->app->exiting && !elm_toolbar_selected_item_get(obj)) + if (!td->app->exiting) + { + // td->app->current_tab = NULL; + // elm_entry_icon_visible_set(td->app->url, EINA_FALSE); + // if (td->app->search_box) + // evas_object_del(td->app->search_box); + } + // td->tab = NULL; +} +*/ + +Eina_Bool +add_tab(const Arg *arg, void *data) +{ + App_Data *ad = data; + Tab_Data *td = tab_add(ad); + buffer_current_set(td); + elm_object_text_set(ad->status_url, ""); + elm_object_text_set(ad->status_state, ""); + input(arg, data); + // elm_object_focus_set(ad->url, EINA_TRUE); + + return EINA_TRUE; +} + +Tab_Data * +tab_add(App_Data *ad) +{ + Tab_Data *td; + + td = calloc(1, sizeof(Tab_Data)); + if (!td) return NULL; + + ad->buf_total++; + + td->app = ad; + td->web = elm_web_add(ad->win); + td->buf_number = ad->buf_total; + td->inspector_enabled = EINA_FALSE; + + ad->buffer_list = eina_list_append(ad->buffer_list, td); + + elm_web_window_create_hook_set(td->web, webview_create_window_cb, ad); + elm_web_console_message_hook_set(td->web, webview_console_cb, td); + elm_web_useragent_set(td->web, "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7"); + elm_web_history_enabled_set(td->web, EINA_TRUE); + elm_web_tab_propagate_set(td->web, EINA_FALSE); + + elm_web_inwin_mode_set(td->web, EINA_TRUE); + evas_object_size_hint_weight_set(td->web, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(td->web, EVAS_HINT_FILL, EVAS_HINT_FILL); + + elm_naviframe_item_simple_push(ad->naviframe, td->web); + // elm_object_item_del_cb_set(td->tab, _tb_item_del_cb); + + evas_object_data_set(td->web, "tab_data", td); + + /* signals */ + evas_object_smart_callback_add(td->web, "title,changed", webview_title_changed_cb, td); + evas_object_smart_callback_add(td->web, "uri,changed", _uri_changed_cb, td); + evas_object_smart_callback_add(td->web, "load,progress", webview_progress_changed_cb, td); + evas_object_smart_callback_add(td->web, "load,started", webview_load_committed_cb, td); + evas_object_smart_callback_add(td->web, "load,finished", webview_load_finished_cb, td); + evas_object_smart_callback_add(td->web, "download,request", webview_download_cb, td); + evas_object_smart_callback_add(td->web, "link,hover,in", webview_hoverlink_cb, td); + evas_object_smart_callback_add(td->web, "link,hover,out", webview_hoverlink_out_cb, td); + evas_object_smart_callback_add(td->web, "inputmethod,changed", inputmethod_changed_cb, td); + // evas_object_smart_callback_add(td->web, "xss,detected", xss_detected_cb, td); + evas_object_smart_callback_add(td->web, "icon,changed", icon_received_cb, td); + evas_object_smart_callback_add(td->web, "inspector,view,create", webview_inspector_cb, td); + + evas_object_event_callback_add(td->web, EVAS_CALLBACK_FREE, _web_free_cb, td); + evas_object_event_callback_add(td->web, EVAS_CALLBACK_KEY_DOWN, webview_keypress_cb, td); + // evas_object_event_callback_add(td->web, EVAS_CALLBACK_MOUSE_UP, notify_event_cb, td); + evas_object_event_callback_add(td->web, EVAS_CALLBACK_MOUSE_WHEEL, webview_mousewheel_cb, td); + + // Evas_Object *view = elm_web_webkit_view_get(ad->current_web); + +// ewk_view_setting_include_link_in_focus_chain_set(view, EINA_FALSE); + + // static Ewk_View_Smart_Class api = EWK_VIEW_SMART_CLASS_INIT_NAME_VERSION("evi"); + // ewk_view_tiled_smart_set(&api); + + return td; +} + + +EAPI_MAIN int +elm_main(int argc, char *argv[]) +{ + Evas_Object *win, *bg, *box, *status_bar, *url, *naviframe, *status_url; + Evas_Object *status_state, *event_box, *web_inspector; + Evas *e; + Evas_Modifier_Mask mask; + Arg a; + App_Data *ad; + unsigned int i; + + if (!elm_need_web()) + return -1; + + ad = calloc(1, sizeof(App_Data)); + if (!ad) return -1; + + elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); + + ewk_network_tls_ca_certificates_path_set(ca_bundle); + + win = elm_win_add(NULL, "evi", ELM_WIN_BASIC); + elm_win_autodel_set(win, EINA_TRUE); + // elm_win_shaped_set(win, EINA_TRUE); + // elm_win_screen_constrain_set(win, EINA_TRUE); + + ad->modkeys = calloc(LENGTH(keys) + 1, sizeof(char)); + ad->modkeys[0] = '\0'; + e = evas_object_evas_get(win); + + for (i = 0; i < LENGTH(keys); i++) { + if (keys[i].modkey && !strchr(ad->modkeys, keys[i].modkey)) { + // strcat(ad->modkeys, keys[i].modkey); + ad->modkeys[strlen(ad->modkeys)] = keys[i].modkey; + ad->modkeys[strlen(ad->modkeys) + 1] = '\0'; + } + + mask = evas_key_modifier_mask_get(e, keys[i].mask); + if (keys[i].key && !evas_object_key_grab(win, keys[i].key, mask, 0, EINA_FALSE)) + fprintf(stderr, "Could not grab trigger for mask = %s, key = %s\n", keys[i].mask , keys[i].key); + } + + fprintf(stderr, "Modkey array looks like %s\n", ad->modkeys); + + evas_object_smart_callback_add(win, "delete,request", _win_del_request_cb, ad); + + ewk_settings_icon_database_path_set("./"); + ewk_cookies_file_set("cookies.txt"); + + bg = elm_bg_add(win); + evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, bg); + // elm_bg_color_set(bg, 255, 255, 255); + evas_object_show(bg); + + box = elm_box_add(win); + evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, box); + evas_object_show(box); + + naviframe = elm_naviframe_add(win); + evas_object_size_hint_weight_set(naviframe, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(naviframe, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_pack_end(box, naviframe); + evas_object_show(naviframe); + + /* status bar container */ + status_bar = elm_box_add(win); + elm_box_homogeneous_set(status_bar, EINA_FALSE); + elm_box_horizontal_set(status_bar, EINA_TRUE); + evas_object_size_hint_weight_set(status_bar, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(status_bar, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_pack_end(box, status_bar); + evas_object_show(status_bar); + + /* status bar url */ + status_url = elm_label_add(win); + evas_object_size_hint_weight_set(status_url, 0.0, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(status_url, 0.0, EVAS_HINT_FILL); + elm_box_pack_end(status_bar, status_url); + evas_object_show(status_url); + + /* status bar state (load progress, mod keys, scroll %) */ + status_state = elm_label_add(win); + evas_object_size_hint_weight_set(status_state, 1.0, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(status_state, 1.0, EVAS_HINT_FILL); + elm_box_pack_end(status_bar, status_state); + evas_object_show(status_state); + + /* invisible until activated web inspector */ + web_inspector = elm_box_add(win); + evas_object_size_hint_align_set(web_inspector, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_pack_end(box, web_inspector); + evas_object_hide(web_inspector); + + /* invisible event box, populated on tab completions and :ls */ + event_box = elm_box_add(win); + evas_object_size_hint_align_set(event_box, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_pack_end(box, event_box); + evas_object_hide(event_box); + + /* main entry widget */ + url = elm_entry_add(win); + elm_entry_single_line_set(url, EINA_TRUE); + elm_entry_scrollable_set(url, EINA_TRUE); + evas_object_size_hint_weight_set(url, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(url, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_box_pack_end(box, url); + elm_entry_text_style_user_push(url, "DEFAULT='font=Monospace font_size=12'"); + evas_object_show(url); + + evas_object_smart_callback_add(url, "activated", inputbox_activate_cb, ad); + evas_object_smart_callback_add(url, "changed", inputbox_changed_cb, ad); + evas_object_event_callback_add(url, EVAS_CALLBACK_KEY_DOWN, inputbox_keypress_cb, ad); + evas_object_event_callback_add(url, EVAS_CALLBACK_KEY_UP, inputbox_keyrelease_cb, ad); + + ad->win = win; + ad->main_box = box; + ad->naviframe = naviframe; + ad->url = url; + ad->status_url = status_url; + ad->status_state = status_state; + ad->event_box = event_box; + ad->web_inspector = web_inspector; + + ad->buf_total = 0; + + a.i = TargetCurrent; + a.s = strdup_printf(":open %s", startpage); + add_tab(&a, ad); + free(a.s); + + evas_object_resize(win, 480, 640); + evas_object_show(win); + + make_searchengines_list(searchengines, LENGTH(searchengines)); + make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers)); + + ad->keylistroot = make_keyslist(); + + memset(ad->rememberedURI, 0, 1024); + memset(ad->followTarget, 0, 8); + ad->error_msg = NULL; + ad->config_base = "./"; + ad->manual_focus = FALSE; + ad->zoomstep = 0.1f; + ad->current_modkey = 0; + + ad->commandhistory = NULL; + ad->commandpointer = 0; + + ad->mode = ModeNormal; + ad->count = 0; + ad->echo_active = EINA_TRUE; + + + toggle_proxy(use_proxy); + + elm_run(); + elm_shutdown(); + ewk_shutdown(); + + free(ad->modkeys); + eina_list_free(ad->buffer_list); + + return 0; +} + +ELM_MAIN() + diff --git a/main.h b/main.h @@ -0,0 +1,74 @@ +/* + (c) 2009 by Leon Winter + (c) 2009, 2010 by Hannes Schueller + (c) 2009, 2010 by Matto Fransen + (c) 2010 by Hans-Peter Deifel + (c) 2010 by Thomas Adam + see LICENSE file +*/ + +#ifndef MAIN_H +#define MAIN_H + +typedef struct _Tab_Data Tab_Data; + +void update_state(void *); +Eina_Bool echo(const Arg *arg, void *); +void buffer_current_set(Tab_Data *td); + +typedef struct +{ + Evas_Object *win; + Evas_Object *main_box; + Evas_Object *naviframe; + Evas_Object *url; /* "inputbox" */ + Evas_Object *event_box; + Evas_Object *status_bar; + Evas_Object *status_url; + Evas_Object *status_state; + Evas_Object *web_inspector; + + unsigned int buf_total; + + char current_modkey; + char *modkeys; + KeyList *keylistroot; + + unsigned int mode; + unsigned int count; + float zoomstep; + char *search_handle; + Eina_Bool search_direction; + Eina_Bool echo_active; + // WebKitWebInspector *inspector; + + char rememberedURI[1024]; + char followTarget[8]; + char *error_msg; + char *config_base; + Eina_Bool manual_focus; + + Eina_List *buffer_list; + Eina_List *active_downloads; + Eina_List *commandhistory; + int commandpointer; + + // SoupSession *session; + + Evas_Object *current_web; + + Eina_Bool exiting : 1; +} App_Data; + +struct _Tab_Data +{ + Evas_Object *web; + App_Data *app; + unsigned int buf_number; + + Eina_Bool inspector_enabled; + Evas_Object *web_inspector; +}; + +#endif + diff --git a/utilities.c b/utilities.c @@ -0,0 +1,1070 @@ +/* + (c) 2009 by Leon Winter + (c) 2009-2012 by Hannes Schueller + (c) 2009-2010 by Matto Fransen + (c) 2010-2011 by Hans-Peter Deifel + (c) 2010-2011 by Thomas Adam + see LICENSE file +*/ + + + +#define _GNU_SOURCE +#include <Elementary.h> + +#include <stdlib.h> /* malloc */ +#include <stdarg.h> /* vsnprintf */ +#include <stdio.h> /* vsnprintf */ +#include <ctype.h> /* tolower */ +#include <unistd.h> /* realloc */ + +#include "viking.h" +#include "main.h" +#include "utilities.h" +#include "commands.h" +#include "keymap.h" + +#include "jsmn/jsmn.h" + +/* these all need to go */ +extern Eina_Bool complete_case_sensitive; +extern char *config_base; + +static Eina_List *dynamic_searchengines = NULL, *dynamic_uri_handlers = NULL; + + + +void add_modkeys(char key); +int get_modkey(char key); +void strdown(char *str); + +char *strdup_printf(const char *fmt, ...) +{ + va_list args; + va_start (args,fmt); + + /* arbitrary size */ + char *buf = malloc(1024); + + vsnprintf(buf, 1024, fmt, args); + va_end(args); + + return buf; +} + +void strdown(char *str) +{ + int i; + + for (i = 0; str[i]; i++) { + str[i] = tolower(str[i]); + } +} + +char * +search_word(int whichword) +{ + int k = 0; + static char word[240]; + char *c = my_pair.line; + + while (isspace(*c) && *c) + c++; + + switch (whichword) { + case 0: + while (*c && !isspace (*c) && *c != '=' && k < 240) { + word[k++] = *c; + c++; + } + word[k] = '\0'; + strncpy(my_pair.what, word, 20); + break; + case 1: + while (*c && k < 240) { + word[k++] = *c; + c++; + } + word[k] = '\0'; + strncpy(my_pair.value, word, 240); + break; + } + + return c; +} + +void save_command_history(char *line, void *data) +{ + char *c = line; + App_Data *ad = data; + + while (isspace(*c) && *c) + c++; + if (!strlen(c)) + return; + + // if (COMMANDHISTSIZE <= g_list_length(commandhistory)) { + if (COMMANDHISTSIZE <= eina_list_count(ad->commandhistory)) { + /* if list is too long - remove items from beginning */ + // commandhistory = g_list_delete_link(commandhistory, g_list_first(commandhistory)); + ad->commandhistory = eina_list_remove(ad->commandhistory, eina_list_nth(ad->commandhistory, 0)); + } + // commandhistory = g_list_append(commandhistory, g_strdup(c)); + ad->commandhistory = eina_list_append(ad->commandhistory, strdup(c)); +} + +/* +Eina_Bool +process_save_qmark(const char *bm, WebKitWebView *webview) +{ + FILE *fp; + const char *filename; + const char *uri = webkit_web_view_get_uri(webview); + char qmarks[10][101]; + char buf[100]; + int i, mark, l=0; + Arg a; + mark = -1; + mark = atoi(bm); + if ( mark < 1 || mark > 9 ) + { + a.i = Error; + a.s = g_strdup_printf("Invalid quickmark, only 1-9"); + echo(&a); + g_free(a.s); + return TRUE; + } + if ( uri == NULL ) return FALSE; + for( i=0; i < 9; ++i ) strcpy( qmarks[i], ""); + + filename = g_strdup_printf(QUICKMARK_FILE); + + // get current quickmarks + + fp = fopen(filename, "r"); + if (fp != NULL){ + for( i=0; i < 10; ++i ) { + if (feof(fp)) { + break; + } + fgets(buf, 100, fp); + l = 0; + while (buf[l] && l < 100 && buf[l] != '\n') { + qmarks[i][l]=buf[l]; + l++; + } + qmarks[i][l]='\0'; + } + fclose(fp); + } + + // save quickmarks + strcpy( qmarks[mark-1], uri ); + fp = fopen(filename, "w"); + g_free((gpointer *)filename); + if (fp == NULL) return FALSE; + for( i=0; i < 10; ++i ) + fprintf(fp, "%s\n", qmarks[i]); + fclose(fp); + a.i = Error; + a.s = g_strdup_printf("Saved as quickmark %d: %s", mark, uri); + echo(&a); + g_free(a.s); + + return TRUE; +} +*/ + +KeyList * +make_keyslist(void) +{ + int i; + KeyList *ptr, *current; + KeyList *keylistroot = NULL; + + ptr = NULL; + current = NULL; + i = 0; + while ( keys[i].key != 0 ) + { + current = malloc(sizeof(KeyList)); + if (current == NULL) { + printf("Not enough memory\n"); + exit(-1); + } + current->Element = keys[i]; + current->next = NULL; + if (keylistroot == NULL) keylistroot = current; + if (ptr != NULL) ptr->next = current; + ptr = current; + i++; + } + + printf("make_keylist() processed %i keys.\n", i); + return keylistroot; +} + +Eina_Bool +parse_colour(char *color) { + char goodcolor[8]; + int colorlen; + + colorlen = (int)strlen(color); + + goodcolor[0] = '#'; + goodcolor[7] = '\0'; + + /* help the user a bit by making string like + #a10 and strings like ffffff full 6digit + strings with # in front :) + */ + + if (color[0] == '#') { + switch (colorlen) { + case 7: + strncpy(goodcolor, color, 7); + break; + case 4: + goodcolor[1] = color[1]; + goodcolor[2] = color[1]; + goodcolor[3] = color[2]; + goodcolor[4] = color[2]; + goodcolor[5] = color[3]; + goodcolor[6] = color[3]; + break; + case 2: + goodcolor[1] = color[1]; + goodcolor[2] = color[1]; + goodcolor[3] = color[1]; + goodcolor[4] = color[1]; + goodcolor[5] = color[1]; + goodcolor[6] = color[1]; + break; + } + } else { + switch (colorlen) { + case 6: + strncpy(&goodcolor[1], color, 6); + break; + case 3: + goodcolor[1] = color[0]; + goodcolor[2] = color[0]; + goodcolor[3] = color[1]; + goodcolor[4] = color[1]; + goodcolor[5] = color[2]; + goodcolor[6] = color[2]; + break; + case 1: + goodcolor[1] = color[0]; + goodcolor[2] = color[0]; + goodcolor[3] = color[0]; + goodcolor[4] = color[0]; + goodcolor[5] = color[0]; + goodcolor[6] = color[0]; + break; + } + } + + if (strlen (goodcolor) != 7) { + return FALSE; + } else { + strncpy(color, goodcolor, 8); + return TRUE; + } +} + +Eina_Bool +process_line_arg(const Arg *arg, void *data) { + return process_line(arg->s, data); +} + +#if 0 +Eina_Bool +changemapping(Key *search_key, int maprecord, char *cmd) { + KeyList *current, *newkey; + Arg a = { .s = cmd }; + + /* sanity check */ + if (maprecord < 0 && cmd == NULL) { + /* possible states: + * - maprecord >= 0 && cmd == NULL: mapping to internal symbol + * - maprecord < 0 && cmd != NULL: mapping to command line + * - maprecord >= 0 && cmd != NULL: cmd will be ignored, treated as mapping to internal symbol + * - anything else (gets in here): an error, hence we return FALSE */ + return FALSE; + } + + // current = keylistroot; + current = NULL; + + if (current) + while (current->next != NULL) { + if ( + current->Element.mask == search_key->mask && + current->Element.modkey == search_key->modkey && + current->Element.key == search_key->key + ) { + if (maprecord >= 0) { + /* mapping to an internal signal */ + current->Element.func = commands[maprecord].func; + current->Element.arg = commands[maprecord].arg; + } else { + /* mapping to a command line */ + current->Element.func = process_line_arg; + current->Element.arg = a; + } + return TRUE; + } + current = current->next; + } + newkey = malloc(sizeof(KeyList)); + if (newkey == NULL) { + printf("Not enough memory\n"); + exit (-1); + } + newkey->Element.mask = search_key->mask; + newkey->Element.modkey = search_key->modkey; + newkey->Element.key = search_key->key; + if (maprecord >= 0) { + /* mapping to an internal signal */ + newkey->Element.func = commands[maprecord].func; + newkey->Element.arg = commands[maprecord].arg; + } else { + /* mapping to a command line */ + newkey->Element.func = process_line_arg; + newkey->Element.arg = a; + } + add_modkeys(newkey->Element.modkey); + + newkey->next = NULL; + + // if (keylistroot == NULL) keylistroot = newkey; + + if (current != NULL) current->next = newkey; + + return TRUE; +} + +void add_modkeys(char key) +{ + unsigned int k, len; + extern char *modkeys; + len = strlen( modkeys ); + while (k < len ) + { + if ( modkeys[k] == key ) return; + k++; + } + modkeys = realloc(modkeys, len + 1); + modkeys[len++] = key; + modkeys[len] = '\0'; +} +#endif + +Eina_Bool +mappings(const Arg *arg, void *data) { + char line[255]; + + if (!arg->s) { + set_error("Missing argument.", data); + return FALSE; + } + strncpy(line, arg->s, 254); + if (process_map_line(line)) + return TRUE; + else { + set_error("Invalid mapping.", data); + return FALSE; + } +} + +int +get_modkey(char key) { + /* + switch (key) { + case '1': + return GDK_MOD1_MASK; + case '2': + return GDK_MOD2_MASK; + case '3': + return GDK_MOD3_MASK; + case '4': + return GDK_MOD4_MASK; + case '5': + return GDK_MOD5_MASK; + default: + return FALSE; + } + */ + return 0; +} + +Eina_Bool +process_mapping(char *keystring, int maprecord, char *cmd) { +#if 0 + Key search_key; + + search_key.mask = 0; + search_key.modkey = 0; + search_key.key = 0; + + if (strlen(keystring) == 1) { + search_key.key = keystring[0]; + } + + if (strlen(keystring) == 2) { + search_key.modkey= keystring[0]; + search_key.key = keystring[1]; + } + + /* process stuff like <S-v> for Shift-v or <C-v> for Ctrl-v (strlen == 5), + stuff like <S-v>a for Shift-v,a or <C-v>a for Ctrl-v,a (strlen == 6 && keystring[4] == '>') + stuff like <M1-v> for Mod1-v (strlen == 6 && keystring[5] == '>') + or stuff like <M1-v>a for Mod1-v,a (strlen = 7) + */ + if ( + ((strlen(keystring) == 5 || strlen(keystring) == 6) && keystring[0] == '<' && keystring[4] == '>') || + ((strlen(keystring) == 6 || strlen(keystring) == 7) && keystring[0] == '<' && keystring[5] == '>') + ) { + switch (toupper(keystring[1])) { + case 'S': + search_key.mask = "Shift"; + if (strlen(keystring) == 5) { + keystring[3] = toupper(keystring[3]); + } else { + keystring[3] = tolower(keystring[3]); + keystring[5] = toupper(keystring[5]); + } + break; + case 'C': + search_key.mask = "Control"; + break; + case 'M': + // search_key.mask = get_modkey(keystring[2]); + break; + } + if (!search_key.mask) + return FALSE; + if (strlen(keystring) == 5) { + search_key.key = keystring[3]; + } else if (strlen(keystring) == 7) { + search_key.modkey = keystring[4]; + search_key.key = keystring[6]; + } else { + if (!strcmp(search_key.mask, "Shift") || !strcmp(search_key.mask, "Control")) { + search_key.modkey = keystring[3]; + search_key.key = keystring[5]; + } else { + search_key.key = keystring[4]; + } + } + } + + /* process stuff like a<S-v> for a,Shift-v or a<C-v> for a,Ctrl-v (strlen == 6) + or stuff like a<M1-v> for a,Mod1-v (strlen == 7) + */ + if ( + (strlen(keystring) == 6 && keystring[1] == '<' && keystring[5] == '>') || + (strlen(keystring) == 7 && keystring[1] == '<' && keystring[6] == '>') + ) { + switch (toupper(keystring[2])) { + case 'S': + search_key.mask = "Shift"; + keystring[4] = toupper(keystring[4]); + break; + case 'C': + search_key.mask = "Control"; + break; + case 'M': + // search_key.mask = get_modkey(keystring[3]); + break; + } + if (!search_key.mask) + return FALSE; + search_key.modkey= keystring[0]; + if (strlen(keystring) == 6) { + search_key.key = keystring[4]; + } else { + search_key.key = keystring[5]; + } + } + // return (changemapping(&search_key, maprecord, cmd)); +#endif + return EINA_TRUE; +} + +Eina_Bool +process_map_line(char *line) { + int listlen, i; + char *c, *cmd; + my_pair.line = line; + c = search_word(0); + + if (!strlen(my_pair.what)) + return FALSE; + while (isspace(*c) && *c) + c++; + + if (*c == ':' || *c == '=') + c++; + my_pair.line = c; + c = search_word(1); + if (!strlen(my_pair.value)) + return FALSE; + listlen = LENGTH(commands); + for (i = 0; i < listlen; i++) { + /* commands is fixed size */ + if (commands[i].cmd == NULL) + break; + if (strlen(commands[i].cmd) == strlen(my_pair.value) && strncmp(commands[i].cmd, my_pair.value, strlen(my_pair.value)) == 0) { + /* map to an internal symbol */ + return process_mapping(my_pair.what, i, NULL); + } + } + /* if this is reached, the mapping is not for one of the internal symbol - test for command line structure */ + if (strlen(my_pair.value) > 1 && strncmp(my_pair.value, ":", 1) == 0) { + /* The string begins with a colon, like a command line, but it's not _just_ a colon, + * i.e. increasing the pointer by one will not go 'out of bounds'. + * We don't actually check that the command line after the = is valid. + * This is user responsibility, the worst case is the new mapping simply doing nothing. + * Since we will pass the command to the same function which also handles the config file lines, + * we have to strip the colon itself (a colon counts as a commented line there - like in vim). + * Last, but not least, the second argument being < 0 signifies to the function that this is a + * command line mapping, not a mapping to an existing internal symbol. */ + cmd = (char *)malloc(sizeof(char) * strlen(my_pair.value)); + memset(cmd, 0, strlen(my_pair.value)); + strncpy(cmd, (my_pair.value + 1), strlen(my_pair.value) - 1); + return process_mapping(my_pair.what, -1, cmd); + } + return FALSE; +} + +Eina_Bool +build_taglist(const Arg *arg, FILE *f) { + int k = 0, in_tag = 0; + int t = 0, marker = 0; + char foundtab[MAXTAGSIZE+1]; + while (arg->s[k]) { + if (!isspace(arg->s[k]) && !in_tag) { + in_tag = 1; + marker = k; + } + if (isspace(arg->s[k]) && in_tag) { + /* found a tag */ + t = 0; + while (marker < k && t < MAXTAGSIZE) foundtab[t++] = arg->s[marker++]; + foundtab[t] = '\0'; + fprintf(f, " [%s]", foundtab); + in_tag = 0; + } + k++; + } + if (in_tag) { + t = 0; + while (marker < strlen(arg->s) && t < MAXTAGSIZE) foundtab[t++] = arg->s[marker++]; + foundtab[t] = '\0'; + fprintf(f, " [%s]", foundtab ); + } + return TRUE; +} + +void +set_error(const char *error, void *data) { + App_Data *ad = data; + /* it should never happen that set_error is called more than once, + * but to avoid any potential memory leaks, we ignore any subsequent + * error if the current one has not been shown */ + if (ad->error_msg == NULL) { + ad->error_msg = strdup_printf("%s", error); + } +} + +void +give_feedback(const char *feedback, void *data) +{ + Arg a = { .i = Info }; + + a.s = strdup_printf("%s", feedback); + echo(&a, data); + free(a.s); +} + +Listelement * +complete_list(const char *searchfor, const int mode, Listelement *elementlist, void *data) +{ + FILE *f; + char *filename; + Listelement *candidatelist = NULL, *candidatepointer = NULL; + char s[255] = "", readelement[MAXTAGSIZE + 1] = ""; + int i, t, n = 0; + App_Data *ad = data; + + if (mode == 2) { + /* open in history file */ + filename = strdup_printf(HISTORY_STORAGE_FILENAME); + } else { + /* open in bookmark file (for tags and bookmarks) */ + filename = strdup_printf(BOOKMARKS_STORAGE_FILENAME); + } + f = fopen(filename, "r"); + if (f == NULL) { + free(filename); + return (elementlist); + } + + while (fgets(s, 254, f)) { + if (mode == 1) { + /* just tags (could be more than one per line) */ + i = 0; + while (s[i] && i < 254) { + while (s[i] != '[' && s[i]) + i++; + if (s[i] != '[') + continue; + i++; + t = 0; + while (s[i] != ']' && s[i] && t < MAXTAGSIZE) + readelement[t++] = s[i++]; + readelement[t] = '\0'; + candidatelist = add_list(readelement, candidatelist); + i++; + } + } else { + /* complete string (bookmarks & history) */ + candidatelist = add_list(s, candidatelist); + } + candidatepointer = candidatelist; + while (candidatepointer != NULL) { + strncpy(s, candidatepointer->element, sizeof(s)); + if (!complete_case_sensitive) { + strdown(s); + } + if (!strlen(searchfor) || strstr(s, searchfor) != NULL) { + /* only use string up to the first space */ + memset(readelement, 0, MAXTAGSIZE + 1); + if (strchr(candidatepointer->element, ' ') != NULL) { + i = strcspn(candidatepointer->element, " "); + if (i > MAXTAGSIZE) + i = MAXTAGSIZE; + strncpy(readelement, candidatepointer->element, i); + } else { + strncpy(readelement, candidatepointer->element, MAXTAGSIZE); + } + /* in the case of URLs without title, remove the line break */ + if (readelement[strlen(readelement) - 1] == '\n') { + readelement[strlen(readelement) - 1] = '\0'; + } + elementlist = add_list(readelement, elementlist); + n = count_list(elementlist); + } + if (n >= MAX_LIST_SIZE) + break; + candidatepointer = candidatepointer->next; + } + free_list(candidatelist); + candidatelist = NULL; + if (n >= MAX_LIST_SIZE) + break; + } + free(filename); + return (elementlist); +} + +Listelement * +add_list(const char *element, Listelement *elementlist) +{ + int n, i = 0; + Listelement *newelement, *elementpointer, *lastelement; + + if (elementlist == NULL) { /* first element */ + newelement = malloc(sizeof(Listelement)); + if (newelement == NULL) + return (elementlist); + strncpy(newelement->element, element, 254); + newelement->next = NULL; + return newelement; + } + elementpointer = elementlist; + n = strlen(element); + + /* check if element is already in list */ + while (elementpointer != NULL) { + if (strlen(elementpointer->element) == n && + strncmp(elementpointer->element, element, n) == 0) + return (elementlist); + lastelement = elementpointer; + elementpointer = elementpointer->next; + i++; + } + /* add to list */ + newelement = malloc(sizeof(Listelement)); + if (newelement == NULL) + return (elementlist); + lastelement->next = newelement; + strncpy(newelement->element, element, 254); + newelement->next = NULL; + return elementlist; +} + +void +free_list(Listelement *elementlist) +{ + Listelement *elementpointer; + + while (elementlist != NULL) { + elementpointer = elementlist->next; + free(elementlist); + elementlist = elementpointer; + } +} + +int +count_list(Listelement *elementlist) +{ + Listelement *elementpointer = elementlist; + int n = 0; + + while (elementpointer != NULL) { + n++; + elementpointer = elementpointer->next; + } + + return n; +} + +/* split the string at the first occurence of whitespace and return the + * position of the second half. + * Unlike strtok, the substrings can be empty and the second string is + * stripped of trailing and leading whitespace. + * Return -1 if `string' contains no whitespace */ +static int split_string_at_whitespace(char *string) +{ + int index = strcspn(string, "\n\t "); + if (string[index] != '\0') { + string[index++] = 0; + // g_strstrip(string+index); + return index; + } + return -1; +} + +/* return TRUE, if the string contains exactly one unescaped %s and no other + * printf directives */ +static Eina_Bool sanity_check_search_url(const char *string) +{ + int was_percent_char = 0, percent_s_count = 0; + + for (; *string; string++) { + switch (*string) { + case '%': + was_percent_char = !was_percent_char; + break; + case 's': + if (was_percent_char) + percent_s_count++; + was_percent_char = 0; + break; + default: + if (was_percent_char) + return FALSE; + was_percent_char = 0; + break; + } + } + + return !was_percent_char && percent_s_count == 1; +} + +void make_searchengines_list(Searchengine *searchengines, int length) +{ + int i; + for (i = 0; i < length; i++, searchengines++) { + dynamic_searchengines = eina_list_prepend(dynamic_searchengines, searchengines); + } +} + +/* find a searchengine with a given handle and return its URI or NULL if + * nothing is found. + * The returned string is internal and must not be freed or modified. */ +char *find_uri_for_searchengine(const char *handle) +{ + Eina_List *l; + + if (dynamic_searchengines != NULL) { + for (l = dynamic_searchengines; l; l = eina_list_next(l)) { + Searchengine *s = (Searchengine*)l->data; + if (!strcmp(s->handle, handle)) { + return s->uri; + } + } + } + + return NULL; +} + +Eina_Bool +process_line(const char *line, void *data) +{ + const char *c = line; + char *command_hist; + int i; + App_Data *ad = data; + size_t len, length = strlen(line); + Eina_Bool found = EINA_FALSE, success = EINA_FALSE; + Arg a; + + while (isspace(*c)) + c++; + /* Ignore blank lines. */ + if (c[0] == '\0') + return EINA_TRUE; + + command_hist = strdup(c); + for (i = 0; i < LENGTH(commands); i++) { + if (commands[i].cmd == NULL) + break; + len = strlen(commands[i].cmd); + if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) { + found = EINA_TRUE; + a.i = commands[i].arg.i; + //printf("length, len, c, commands[i].arg.s = %zu, %zu, %s, %s\n", + // length, len, c, commands[i].arg.s); + if (length > len + 1) + a.s = strdup(&c[len + 1]); + else if (!(length > len + 1) && commands[i].arg.s) + a.s = strdup(commands[i].arg.s); + else + a.s = NULL; + success = commands[i].func(&a, data); + free(a.s); + break; + } + } + + save_command_history(command_hist, data); + free(command_hist); + + if (!found) { + a.i = Error; + a.s = strdup_printf("Not a browser command: %s", c); + echo(&a, data); + free(a.s); + } else if (!success) { + a.i = Error; + if (ad->error_msg != NULL) { + a.s = strdup_printf("%s", ad->error_msg); + free(ad->error_msg); + ad->error_msg = NULL; + } else { + a.s = strdup_printf("Unknown error. Please file a bug report!"); + } + echo(&a, data); + free(a.s); + } + return success; +} + +enum ConfigFileError +read_rcfile(const char *config, void *data) +{ + int t, linum = 0, index; + char s[255], *buffer; + FILE *fpin; + Eina_Bool found_malformed_lines = FALSE; + Searchengine *new; + URIHandler *newhandler; + + if (access(config, F_OK) != 0) + return FILE_NOT_FOUND; + + fpin = fopen(config, "r"); + if (fpin == NULL) + return READING_FAILED; + + while (fgets(s, 254, fpin)) { + linum++; + /* + * ignore lines that begin with #, / and such + */ + if (!isalpha(s[0])) + continue; + t = strlen(s); + s[t - 1] = '\0'; + if (strncmp(s, "searchengine", 12) == 0) { + buffer = (s + 12); + while (buffer[0] == ' ') + buffer++; + /* split line at whitespace */ + index = split_string_at_whitespace(buffer); + if (index < 0 || buffer[0] == '\0' || buffer[index] == '\0' + || !sanity_check_search_url(buffer+index)) { + fprintf(stderr, "searchengines: syntax error on line %d\n", linum); + found_malformed_lines = TRUE; + continue; + } + new = malloc(sizeof(Searchengine)); + if (new == NULL) { + fprintf(stderr, "Memory exhausted while loading search engines.\n"); + exit(EXIT_FAILURE); + } + new->handle = strdup(buffer); + new->uri = strdup(buffer+index); + dynamic_searchengines = eina_list_prepend(dynamic_searchengines, new); + } else if (strncmp(s, "handler", 7) == 0) { + buffer = (s + 7); + while (buffer[0] == ' ') + buffer++; + /* split line at whitespace */ + index = split_string_at_whitespace(buffer); + if (index < 0 || buffer[0] == '\0' || buffer[index] == '\0' + || !sanity_check_search_url(buffer+index)) { /* this sanity check is also valid for handler definitions */ + fprintf(stderr, "URI handlers: syntax error on line %d\n", linum); + found_malformed_lines = TRUE; + continue; + } + newhandler = malloc(sizeof(URIHandler)); + if (newhandler == NULL) { + fprintf(stderr, "Memory exhausted while loading uri handlers.\n"); + exit(EXIT_FAILURE); + } + newhandler->handle = strdup(buffer); + newhandler->handler = strdup(buffer+index); + dynamic_uri_handlers = eina_list_prepend(dynamic_uri_handlers, newhandler); + } else { + if (!process_line(s, data)) + found_malformed_lines = TRUE; + } + } + fclose(fpin); + return found_malformed_lines ? SYNTAX_ERROR : SUCCESS; +} + +void make_uri_handlers_list(URIHandler *uri_handlers, int length) +{ + int i; + for (i = 0; i < length; i++, uri_handlers++) { + dynamic_uri_handlers = eina_list_prepend(dynamic_uri_handlers, uri_handlers); + } +} + +Eina_Bool +open_handler(char *uri) { + /* + char *argv[64]; + char *p = NULL, *arg, arg_temp[MAX_SETTING_SIZE], *temp, temp2[MAX_SETTING_SIZE] = "", *temp3; + int j; + Eina_List *l; + + p = strchr(uri, ':'); + if (p) { + if (dynamic_uri_handlers != NULL) { + for (l = dynamic_uri_handlers; l; l = eina_list_next(l)) { + URIHandler *s = (URIHandler *)l->data; + if (strlen(uri) >= strlen(s->handle) && strncmp(s->handle, uri, strlen(s->handle)) == 0) { + if (strlen(s->handler) > 0) { + arg = (uri + strlen(s->handle)); + strncpy(temp2, s->handler, MAX_SETTING_SIZE); + temp = strtok(temp2, " "); + j = 0; + while (temp != NULL) { + if (strstr(temp, "%s")) { + temp3 = temp; + memset(arg_temp, 0, MAX_SETTING_SIZE); + while (strncmp(temp3, "%s", 2) != 0) { + strncat(arg_temp, temp3, 1); + temp3++; + } + strcat(arg_temp, arg); + temp3++; + temp3++; + strcat(arg_temp, temp3); + argv[j] = arg_temp; + } else { + argv[j] = temp; + } + temp = strtok(NULL, " "); + j++; + } + argv[j] = NULL; + // g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); + } + return TRUE; + } + } + } + } + */ + return FALSE; +} + +void +parse(const char *buf) +{ + int r; + jsmn_parser p; + jsmntok_t tok[10]; + + jsmn_init(&p); + r = jsmn_parse(&p, buf, tok, 10); + + for (int i = 0; i < 10; i++) { + // printf("token %i starts @ %i, ends @ %i, size %i, type %i\n", i, tok[i].start, tok[i].end, tok[i].size, tok[i].type); + char *tmp = strndup(buf + tok[i].start, tok[i].end - tok[i].start); + // printf("token %i = %s\n", i, tmp); + free(tmp); + } + + // printf("parser pos is %i\n", p.pos); + +} + +void +userscript_hooks_start(const char *uri) +{ + // const char *js; + + printf("userscript_hooks_start() uri = %s\n", uri); + + char *base_dir = "/home/kyle/.config/evi/userscript/"; + // Eina_List *files_to_parse; + Eina_List *l; + Eina_List *userscript_dir = ecore_file_ls(base_dir); + char *path; + char full_path[1024] = ""; + + EINA_LIST_FOREACH(userscript_dir, l, path) { + + memset(full_path, 0, 1024); + strcat(full_path, base_dir); + strcat(full_path, path); + + if (ecore_file_is_dir(full_path)) { + strcat(full_path, "/manifest.json"); + + if (ecore_file_can_read(full_path)) { + + printf("parsing %s ...\n", full_path); + + FILE *fp = fopen(full_path, "r"); + fseek(fp, 0L, SEEK_END); + int len = ftell(fp); + char *buf = malloc(len); + rewind(fp); + fread(buf, len, 1, fp); + fclose(fp); + + parse(buf); + + free(buf); + // files_to_parse = eina_list_append(files_to_parse, full_path); + // printf("would have parsed %s\n", full_path); + } + } + } +} + +void +userscript_hooks_end(const char *uri) +{ +} + diff --git a/utilities.h b/utilities.h @@ -0,0 +1,43 @@ +/* + (c) 2009 by Leon Winter + (c) 2009-2012 by Hannes Schueller + (c) 2009-2010 by Matto Fransen + (c) 2010-2011 by Hans-Peter Deifel + (c) 2010-2011 by Thomas Adam + see LICENSE file +*/ + +/* config file */ +#define RCFILE "%s/evi/evirc", config_base + +/* max entries in command history */ +#define COMMANDHISTSIZE 50 + +char *strdup_printf(const char *, ...); +enum ConfigFileError read_rcfile(const char *config, void *); +void save_command_history(char *line, void *); +// Eina_Bool process_save_qmark(const char *bm, WebKitWebView *webview); +KeyList *make_keyslist(void); +Eina_Bool parse_colour(char *color); +Eina_Bool mappings(const Arg *, void *); +Eina_Bool process_mapping(char *keystring, int maprecord, char *cmd); +Eina_Bool process_map_line(char *line); +Eina_Bool changemapping(Key *search_key, int maprecord, char *cmd); +Eina_Bool process_line(const char *line, void *); +Eina_Bool process_line_arg(const Arg *, void *); +Eina_Bool build_taglist(const Arg *arg, FILE *f); +void set_error(const char *error, void *); +void give_feedback(const char *feedback, void *); +Listelement * complete_list(const char *, const int, Listelement *, void *); +Listelement * add_list(const char *element, Listelement *elementlist); +int count_list(Listelement *elementlist); +void free_list(Listelement *elementlist); +char * search_word(int whichword); +void userscript_hooks_start(const char*); +void userscript_hooks_end(const char*); + +char *find_uri_for_searchengine(const char *handle); +void make_searchengines_list(Searchengine *searchengines, int length); +void make_uri_handlers_list(URIHandler *uri_handlers, int length); +Eina_Bool open_handler(char *uri); + diff --git a/viking.h b/viking.h @@ -0,0 +1,197 @@ +/* + (c) 2009 by Leon Winter + (c) 2009-2012 by Hannes Schueller + (c) 2009-2010 by Matto Fransen + (c) 2010-2011 by Hans-Peter Deifel + (c) 2010-2011 by Thomas Adam + see LICENSE file +*/ + +/* macros */ +#define LENGTH(x) (sizeof(x)/sizeof(x[0])) + +/* enums */ +enum { ModeNormal, ModePassThrough, ModeSendKey, ModeInsert, ModeHints }; /* modes */ +enum { TargetCurrent, TargetNew }; /* target */ +/* bitmask, + 1 << 0: 0 = jumpTo, 1 = scroll + 1 << 1: 0 = top/down, 1 = left/right + 1 << 2: 0 = top/left, 1 = bottom/right + 1 << 3: 0 = paging/halfpage, 1 = line + 1 << 4: 0 = paging, 1 = halfpage aka buffer +*/ +enum { ScrollJumpTo, ScrollMove }; +enum { OrientationVert, OrientationHoriz = (1 << 1) }; +enum { DirectionTop, + DirectionBottom = (1 << 2), + DirectionLeft = OrientationHoriz, + DirectionRight = OrientationHoriz | (1 << 2) +}; +enum { UnitPage, + UnitLine = (1 << 3), + UnitBuffer = (1 << 4) +}; +/* bitmask: + 1 << 0: 0 = Reload/Cancel 1 = Forward/Back + Forward/Back: + 1 << 1: 0 = Forward 1 = Back + Reload/Cancel: + 1 << 1: 0 = Cancel 1 = Force/Normal Reload + 1 << 2: 0 = Force Reload 1 = Reload +*/ +enum { NavigationForwardBack = 1, NavigationReloadActions = (1 << 1) }; +enum { NavigationCancel, + NavigationBack = (NavigationForwardBack | 1 << 1), + NavigationForward = (NavigationForwardBack), + NavigationReload = (NavigationReloadActions | 1 << 2), + NavigationForceReload = NavigationReloadActions +}; +/* bitmask: + 1 << 1: ClipboardPrimary (X11) + 1 << 2: ClipboardGTK + 1 << 3: SourceURL + 1 << 4: SourceSelection +*/ +enum { ClipboardPrimary = 1 << 1, ClipboardGTK = 1 << 2 }; +enum { SourceSelection = 1 << 4, SourceURL = 1 << 3 }; +/* bitmask: + 1 << 0: 0 = ZoomReset 1 = ZoomIn/Out + 1 << 1: 0 = ZoomOut 1 = ZoomIn + 1 << 2: 0 = TextZoom 1 = FullContentZoom +*/ +enum { ZoomReset, + ZoomOut, + ZoomIn = ZoomOut | (1 << 1) +}; +enum { ZoomText, ZoomFullContent = (1 << 2) }; +/* bitmask: + 0 = Info, 1 = Warning, 2 = Error + 1 << 2: 0 = AutoHide 1 = NoAutoHide + relevant for script: + 1 << 3: 1 = Silent (no echo) +*/ +enum { Info, Warning, Error, NoAutoHide = 1 << 2, Silent = 1 << 3 }; +enum { NthSubdir, Rootdir }; +enum { InsertCurrentURL = 1 }; +enum { Increment, Decrement }; +/* bitmask: + 1 << 0: 0 = DirectionNext 1 = DirectionPrev (Relative) + 1 << 0: 0 = DirectionBackwards 1 = DirectionForward (Absolute) + + 1 << 1: 0 = DirectionRelative 1 = DirectionAbsolute + + 1 << 2: 0 = CaseInsensitive 1 = CaseSensitive + 1 << 3: 0 = No Wrapping 1 = Wrapping +*/ +enum { DirectionRelative, DirectionAbsolute = 1 << 1 }; +enum { DirectionNext, DirectionPrev, HideCompletion }; +enum { DirectionBackwards = DirectionAbsolute, DirectionForward = + (1 << 0) | DirectionAbsolute }; +enum { CaseInsensitive, CaseSensitive = 1 << 2 }; +enum { Wrapping = 1 << 3 }; +enum { HideInspector, ShowInspector }; + +/* structs here */ +typedef struct { + int i; + char *s; +} Arg; + +typedef struct { + const char *mask; + char modkey; + const char *key; + Eina_Bool(*func) (const Arg * func, void *); + Arg arg; +} Key; + +typedef struct { + void *next; + Key Element; +} KeyList; + +typedef struct { + const char *mask; + const char *modkey; + unsigned int button; + Eina_Bool(*func) (const Arg *, void *); + const Arg arg; +} Mouse; + +typedef struct { + char *name; + char (*var); + char *webkit; + Eina_Bool intval; + Eina_Bool boolval; + Eina_Bool colourval; + Eina_Bool reload; +} Setting; + +typedef struct { + char *cmd; + Eina_Bool(*func) (const Arg *, void *); + const Arg arg; +} Command; + +typedef struct { + char *handle; + char *uri; +} Searchengine; + +typedef struct { + char *handle; + char *handler; +} URIHandler; + +struct map_pair { + char *line; + char what[20]; + char value[240]; +} my_pair; + +typedef struct { + void *next; + char element[255]; +} Listelement; + +enum ConfigFileError { + SUCCESS = 0, + FILE_NOT_FOUND = -1, + READING_FAILED = -2, + SYNTAX_ERROR = -3 +}; + + +/* constants */ +#define MOUSE_BUTTON_1 1 +#define MOUSE_BUTTON_2 2 +#define MOUSE_BUTTON_3 3 +#define MOUSE_BUTTON_4 4 +#define MOUSE_BUTTON_5 5 +#define BUFFERSIZE 255 +#define MAXTAGSIZE 200 + +/* bookmarks */ +#define BOOKMARKS_STORAGE_FILENAME "%s/vimprobable/bookmarks", ad->config_base + +/* quickmarks */ +#define QUICKMARK_FILE "%s/vimprobable/quickmarks", ad->config_base + +/* history */ +#define HISTORY_MAX_ENTRIES 1000 +#define HISTORY_STORAGE_FILENAME "%s/vimprobable/history", ad->config_base +#define CLOSED_URL_FILENAME "%s/vimprobable/closed", ad->config_base + +/* Command size */ +#define COMMANDSIZE 1024 + +/* maximum size of internal string variable handled by :set + * if you set this to anything lower than 8, colour values + * will stop working */ +#define MAX_SETTING_SIZE 1024 + +/* completion list size */ +#define MAX_LIST_SIZE 40 + +