correct DuckDuckGo search engine definition
[vimprobable2.git] / main.c
blobb6568cd0316b372a5f07c0f1a7cc50b5432ce338
1 /*
2 (c) 2009 by Leon Winter
3 (c) 2009-2012 by Hannes Schueller
4 (c) 2009-2010 by Matto Fransen
5 (c) 2010-2011 by Hans-Peter Deifel
6 (c) 2010-2011 by Thomas Adam
7 (c) 2011 by Albert Kim
8 (c) 2011 by Daniel Carl
9 (c) 2012 by Matthew Carter
10 see LICENSE file
13 #include <X11/Xlib.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <errno.h>
17 #include <stdlib.h>
18 #include "includes.h"
19 #include "vimprobable.h"
20 #include "utilities.h"
21 #include "callbacks.h"
22 #include "javascript.h"
24 /* the CLEAN_MOD_*_MASK defines have all the bits set that will be stripped from the modifier bit field */
25 #define CLEAN_MOD_NUMLOCK_MASK (GDK_MOD2_MASK)
26 #define CLEAN_MOD_BUTTON_MASK (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
28 /* remove unused bits, numlock symbol and buttons from keymask */
29 #define CLEAN(mask) (mask & (GDK_MODIFIER_MASK) & ~(CLEAN_MOD_NUMLOCK_MASK) & ~(CLEAN_MOD_BUTTON_MASK))
31 #define IS_ESCAPE(event) (IS_ESCAPE_KEY(CLEAN(event->state), event->keyval))
32 #define IS_ESCAPE_KEY(s, k) ((s == 0 && k == GDK_Escape) || \
33 (s == GDK_CONTROL_MASK && k == GDK_bracketleft))
35 /* callbacks here */
36 static void inputbox_activate_cb(GtkEntry *entry, gpointer user_data);
37 static gboolean inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event);
38 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event);
39 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data);
40 static WebKitWebView* inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view);
41 static gboolean notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
42 static gboolean webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data);
43 static gboolean webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data);
44 static void webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data);
45 static gboolean webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event);
46 static void webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
47 static void webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
48 static gboolean webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
49 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data);
50 static void webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec);
51 static gboolean webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
52 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data);
53 static WebKitWebView* webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
54 static void webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data);
55 static void webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data);
56 static void window_destroyed_cb(GtkWidget *window, gpointer func_data);
57 static gboolean blank_cb(void);
59 /* functions */
60 static gboolean bookmark(const Arg *arg);
61 static gboolean browser_settings(const Arg *arg);
62 static gboolean commandhistoryfetch(const Arg *arg);
63 static gboolean complete(const Arg *arg);
64 static gboolean descend(const Arg *arg);
65 gboolean echo(const Arg *arg);
66 static gboolean focus_input(const Arg *arg);
67 static gboolean open_editor(const Arg *arg);
68 void _resume_from_editor(GPid child_pid, int status, gpointer data);
69 static gboolean input(const Arg *arg);
70 static gboolean open_inspector(const Arg * arg);
71 static gboolean navigate(const Arg *arg);
72 static gboolean number(const Arg *arg);
73 static gboolean open_arg(const Arg *arg);
74 static gboolean open_remembered(const Arg *arg);
75 static gboolean paste(const Arg *arg);
76 static gboolean quickmark(const Arg *arg);
77 static gboolean quit(const Arg *arg);
78 static gboolean revive(const Arg *arg);
79 static gboolean print_frame(const Arg *arg);
80 static gboolean search(const Arg *arg);
81 static gboolean set(const Arg *arg);
82 static gboolean script(const Arg *arg);
83 static gboolean scroll(const Arg *arg);
84 static gboolean search_tag(const Arg *arg);
85 static gboolean yank(const Arg *arg);
86 static gboolean view_source(const Arg * arg);
87 static gboolean zoom(const Arg *arg);
88 static gboolean fake_key_event(const Arg *arg);
90 static void clear_focus(void);
91 static void update_url(const char *uri);
92 static void setup_modkeys(void);
93 static void setup_gui(void);
94 static void setup_settings(void);
95 static void setup_signals(void);
96 static void ascii_bar(int total, int state, char *string);
97 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
98 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
99 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
100 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
101 const char *bg_color_str, const char *fg_color_str);
102 static void scripts_run_user_file(void);
104 static gboolean history(void);
105 static gboolean process_set_line(char *line);
106 void save_command_history(char *line);
107 void toggle_proxy(gboolean onoff);
108 void toggle_scrollbars(gboolean onoff);
109 void set_default_winsize(const char * const size);
111 gboolean process_keypress(GdkEventKey *event);
112 void fill_suggline(char * suggline, const char * command, const char *fill_with);
113 GtkWidget * fill_eventbox(const char * completion_line);
114 static void mop_up(void);
116 #include "main.h"
118 /* variables */
119 static GtkWindow *window;
120 static GtkWidget *viewport;
121 static GtkBox *box;
122 static GtkScrollbar *scroll_h;
123 static GtkScrollbar *scroll_v;
124 static GtkAdjustment *adjust_h;
125 static GtkAdjustment *adjust_v;
126 static GtkWidget *inputbox;
127 static GtkWidget *eventbox;
128 static GtkBox *statusbar;
129 static GtkWidget *status_url;
130 static GtkWidget *status_state;
131 static WebKitWebView *webview;
132 static SoupSession *session;
133 static GtkClipboard *clipboards[2];
134 static GdkKeymap *keymap;
136 static char **args;
137 static unsigned int mode = ModeNormal;
138 static unsigned int count = 0;
139 static float zoomstep;
140 char *modkeys;
141 static char current_modkey;
142 static char *search_handle;
143 static gboolean search_direction;
144 static gboolean echo_active = TRUE;
145 static WebKitWebInspector *inspector;
147 static GdkNativeWindow embed = 0;
148 static char *configfile = NULL;
149 static char *winid = NULL;
151 static char rememberedURI[1024] = "";
152 static char followTarget[8] = "";
153 char *error_msg = NULL;
154 char *config_base = NULL;
155 static gboolean manual_focus = FALSE;
157 GList *activeDownloads;
159 #include "config.h"
160 #include "keymap.h"
162 GList *commandhistory = NULL;
163 int commandpointer = 0;
165 KeyList *keylistroot = NULL;
167 /* Cookie support. */
168 #ifdef ENABLE_COOKIE_SUPPORT
169 static SoupCookieJar *session_cookie_jar = NULL;
170 static SoupCookieJar *file_cookie_jar = NULL;
171 static time_t cookie_timeout = 4800;
172 static char *cookie_store;
173 static void setup_cookies(void);
174 static char *get_cookies(SoupURI *soup_uri);
175 static void load_all_cookies(void);
176 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
177 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
178 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
179 #endif
180 /* callbacks */
181 void
182 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
183 quit(NULL);
186 void
187 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
188 gtk_window_set_title(window, title);
191 void
192 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
193 #ifdef ENABLE_GTK_PROGRESS_BAR
194 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
195 #endif
196 update_state();
199 #ifdef ENABLE_WGET_PROGRESS_BAR
200 void
201 ascii_bar(int total, int state, char *string) {
202 int i;
204 for (i = 0; i < state; i++)
205 string[i] = progressbartickchar;
206 string[i++] = progressbarcurrent;
207 for (; i < total; i++)
208 string[i] = progressbarspacer;
209 string[i] = '\0';
211 #endif
213 void
214 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
215 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
216 const char *uri = webkit_web_view_get_uri(webview);
218 update_url(uri);
219 script(&a);
220 g_free(a.s);
221 scripts_run_user_file();
223 if (mode == ModeInsert || mode == ModeHints) {
224 Arg a = { .i = ModeNormal };
225 set(&a);
227 manual_focus = FALSE;
230 void
231 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
232 WebKitWebSettings *settings = webkit_web_view_get_settings(webview);
233 gboolean scripts;
235 g_object_get(settings, "enable-scripts", &scripts, NULL);
236 if (escape_input_on_load && scripts && !manual_focus && !gtk_widget_is_focus(inputbox)) {
237 clear_focus();
239 if (HISTORY_MAX_ENTRIES > 0)
240 history();
241 update_state();
244 void
245 webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec) {
246 /* retrieve the URI of the temporary webview */
247 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(temp_view) };
248 /* clean up */
249 webkit_web_view_stop_loading(temp_view);
250 gtk_widget_destroy(GTK_WIDGET(temp_view));
251 /* open the requested window */
252 open_arg(&a);
255 static WebKitWebView *
256 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
257 if (rememberedURI != NULL && strlen(rememberedURI) > 0) {
258 if (strncmp(rememberedURI, "javascript:", 11) != 0) {
259 Arg a = { .i = TargetNew, .s = rememberedURI };
260 open_arg(&a);
261 return NULL;
264 /* create a temporary webview to execute the script in */
265 WebKitWebView *temp_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
266 /* wait until the new webview receives its new URI */
267 g_object_connect(temp_view, "signal::notify::uri", G_CALLBACK(webview_open_js_window_cb), NULL, NULL);
268 return temp_view;
271 gboolean
272 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
273 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
274 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
275 open_arg(&a);
276 webkit_web_policy_decision_ignore(decision);
277 return TRUE;
280 gboolean
281 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
282 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
283 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
284 webkit_web_policy_decision_download(decision);
285 return TRUE;
286 } else {
287 return FALSE;
291 static WebKitWebView*
292 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
293 gchar* inspector_title;
294 GtkWidget* inspector_window;
295 GtkWidget* inspector_view;
297 /* just enough code to show the inspector - no signal handling etc. */
298 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
299 if (embed) {
300 inspector_window = gtk_plug_new(embed);
301 } else {
302 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
303 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
305 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
306 g_free(inspector_title);
307 inspector_view = webkit_web_view_new();
308 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
309 gtk_widget_show_all(inspector_window);
310 return WEBKIT_WEB_VIEW(inspector_view);
313 gboolean
314 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
315 const gchar *filename;
316 gchar *uri, *path;
317 uint32_t size;
318 WebKitDownloadStatus status;
320 filename = webkit_download_get_suggested_filename(download);
321 if (filename == NULL || strlen(filename) == 0) {
322 filename = "vimprobable_download";
324 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
325 uri = g_strconcat("file://", path, NULL);
326 webkit_download_set_destination_uri(download, uri);
327 g_free(uri);
328 size = (uint32_t)webkit_download_get_total_size(download);
329 if (size > 0)
330 echo_message(Info, "Download %s started (expected size: %u bytes)...", filename, size);
331 else
332 echo_message(Info, "Download %s started (unknown size)...", filename);
333 activeDownloads = g_list_prepend(activeDownloads, download);
334 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
335 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
336 status = webkit_download_get_status(download);
337 if (status == WEBKIT_DOWNLOAD_STATUS_CREATED)
338 webkit_download_start(download);
339 update_state();
340 return TRUE;
343 gboolean
344 blank_cb(void) {
345 return TRUE;
348 void
349 download_progress(WebKitDownload *d, GParamSpec *pspec) {
350 WebKitDownloadStatus status = webkit_download_get_status(d);
352 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
353 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
354 echo_message(Error, "Error while downloading %s", webkit_download_get_suggested_filename(d));
355 } else {
356 echo_message(Info, "Download %s finished", webkit_download_get_suggested_filename(d));
358 activeDownloads = g_list_remove(activeDownloads, d);
360 update_state();
364 gboolean
365 process_keypress(GdkEventKey *event) {
366 KeyList *current;
367 guint keyval;
368 GdkModifierType irrelevant;
370 /* Get a mask of modifiers that shouldn't be considered for this event.
371 * E.g.: It shouldn't matter whether ';' is shifted or not. */
372 gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
373 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
375 current = keylistroot;
377 while (current != NULL) {
378 if (current->Element.mask == (CLEAN(event->state) & ~irrelevant)
379 && (current->Element.modkey == current_modkey
380 || (!current->Element.modkey && !current_modkey)
381 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
382 && current->Element.key == keyval
383 && current->Element.func)
384 if (current->Element.func(&current->Element.arg)) {
385 current_modkey = count = 0;
386 update_state();
387 return TRUE;
389 current = current->next;
391 return FALSE;
394 gboolean
395 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
396 Arg a = { .i = ModeNormal, .s = NULL };
397 guint keyval;
398 GdkModifierType irrelevant;
400 /* Get a mask of modifiers that shouldn't be considered for this event.
401 * E.g.: It shouldn't matter whether ';' is shifted or not. */
402 gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
403 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
405 switch (mode) {
406 case ModeNormal:
407 if ((CLEAN(event->state) & ~irrelevant) == 0) {
408 if (IS_ESCAPE(event)) {
409 echo_message(Info, "");
410 g_free(a.s);
411 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
412 || (event->keyval == GDK_0 && count))) {
413 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
414 update_state();
415 return TRUE;
416 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
417 current_modkey = event->keyval;
418 update_state();
419 return TRUE;
422 /* keybindings */
423 if (process_keypress(event) == TRUE) return TRUE;
425 break;
426 case ModeInsert:
427 if (IS_ESCAPE(event)) {
428 a.i = Silent;
429 a.s = g_strdup("hints.clearFocus();");
430 script(&a);
431 g_free(a.s);
432 a.i = ModeNormal;
433 return set(&a);
434 } else if (CLEAN(event->state) & GDK_CONTROL_MASK) {
435 /* keybindings of non-printable characters */
436 if (process_keypress(event) == TRUE) return TRUE;
438 case ModePassThrough:
439 if (IS_ESCAPE(event)) {
440 echo_message(Info, "");
441 set(&a);
442 return TRUE;
444 break;
445 case ModeSendKey:
446 echo_message(Info, "");
447 set(&a);
448 break;
450 return FALSE;
453 void
454 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
455 const char *fg_color_str) {
456 GdkColor fg_color;
457 GdkColor bg_color;
458 PangoFontDescription *font;
460 font = pango_font_description_from_string(font_str);
461 gtk_widget_modify_font(widget, font);
462 pango_font_description_free(font);
464 if (fg_color_str)
465 gdk_color_parse(fg_color_str, &fg_color);
466 if (bg_color_str)
467 gdk_color_parse(bg_color_str, &bg_color);
469 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
470 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
472 return;
475 void
476 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
477 const char *uri = webkit_web_view_get_uri(webview);
478 char *markup;
480 memset(rememberedURI, 0, 1024);
481 if (link) {
482 markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link);
483 gtk_label_set_markup(GTK_LABEL(status_url), markup);
484 strncpy(rememberedURI, link, 1024);
485 g_free(markup);
486 } else
487 update_url(uri);
490 gboolean
491 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
492 Arg a;
494 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
495 if (gtk_window_has_toplevel_focus(window)) {
496 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
497 a.i = ModeNormal;
498 return set(&a);
499 } else if (!strcmp(message, "insertmode_on")) {
500 a.i = ModeInsert;
501 return set(&a);
504 return FALSE;
507 void
508 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
509 char *text;
510 guint16 length = gtk_entry_get_text_length(entry);
511 Arg a;
512 gboolean success = FALSE, forward = FALSE;
514 a.i = HideCompletion;
515 complete(&a);
516 if (length == 0)
517 return;
518 text = (char*)gtk_entry_get_text(entry);
520 /* move focus from inputbox to print potential messages that could not be
521 * printed as long as the inputbox is focused */
522 gtk_widget_grab_focus(GTK_WIDGET(webview));
524 if (length > 1 && text[0] == ':') {
525 success = process_line((text + 1));
526 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
527 webkit_web_view_unmark_text_matches(webview);
528 #ifdef ENABLE_MATCH_HIGHLITING
529 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
530 webkit_web_view_set_highlight_text_matches(webview, TRUE);
531 #endif
532 count = 0;
533 #ifndef ENABLE_INCREMENTAL_SEARCH
534 a.s =& text[1];
535 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
536 search(&a);
537 #else
538 search_direction = forward;
539 search_handle = g_strdup(&text[1]);
540 #endif
541 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
542 a.i = Silent;
543 a.s = g_strdup_printf("hints.fire();");
544 script(&a);
545 g_free(a.s);
546 update_state();
547 } else
548 return;
549 if (!echo_active)
550 gtk_entry_set_text(entry, "");
551 gtk_widget_grab_focus(GTK_WIDGET(webview));
554 gboolean
555 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
556 Arg a;
557 int numval;
559 if (mode == ModeHints) {
560 if (event->keyval == GDK_Tab) {
561 a.i = Silent;
562 a.s = g_strdup_printf("hints.focusNextHint();");
563 script(&a);
564 g_free(a.s);
565 update_state();
566 return TRUE;
568 if (event->keyval == GDK_ISO_Left_Tab) {
569 a.i = Silent;
570 a.s = g_strdup_printf("hints.focusPreviousHint();");
571 script(&a);
572 g_free(a.s);
573 update_state();
574 return TRUE;
576 if (event->keyval == GDK_Return) {
577 a.i = Silent;
578 a.s = g_strdup_printf("hints.fire();");
579 script(&a);
580 g_free(a.s);
581 update_state();
582 return TRUE;
585 switch (event->keyval) {
586 case GDK_bracketleft:
587 case GDK_Escape:
588 if (!IS_ESCAPE(event)) break;
589 a.i = HideCompletion;
590 complete(&a);
591 a.i = ModeNormal;
592 commandpointer = 0;
593 return set(&a);
594 break;
595 case GDK_Tab:
596 a.i = DirectionNext;
597 return complete(&a);
598 break;
599 case GDK_Up:
600 a.i = DirectionPrev;
601 return commandhistoryfetch(&a);
602 break;
603 case GDK_Down:
604 a.i = DirectionNext;
605 return commandhistoryfetch(&a);
606 break;
607 case GDK_ISO_Left_Tab:
608 a.i = DirectionPrev;
609 return complete(&a);
610 break;
613 if (mode == ModeHints) {
614 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
615 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
616 (event->keyval == GDK_BackSpace)) {
617 count /= 10;
618 a.i = Silent;
619 a.s = g_strdup_printf("hints.updateHints(%d);", count);
620 script(&a);
621 g_free(a.s);
622 update_state();
623 return TRUE;
626 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
627 if ((numval >= 1 && numval <= 9) || (numval == 0 && count)) {
628 /* allow a zero as non-first number */
629 count = (count ? count * 10 : 0) + numval;
630 a.i = Silent;
631 a.s = g_strdup_printf("hints.updateHints(%d);", count);
632 script(&a);
633 g_free(a.s);
634 update_state();
635 return TRUE;
639 return FALSE;
642 gboolean
643 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
644 int i;
645 WebKitHitTestResult *result;
646 WebKitHitTestResultContext context;
647 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
648 /* handle mouse click events */
649 for (i = 0; i < LENGTH(mouse); i++) {
650 if (mouse[i].mask == CLEAN(event->button.state)
651 && (mouse[i].modkey == current_modkey
652 || (!mouse[i].modkey && !current_modkey)
653 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
654 && mouse[i].button == event->button.button
655 && mouse[i].func) {
656 if (mouse[i].func(&mouse[i].arg)) {
657 current_modkey = count = 0;
658 update_state();
659 return TRUE;
663 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
664 g_object_get(result, "context", &context, NULL);
665 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
666 Arg a = { .i = ModeInsert };
667 set(&a);
668 manual_focus = TRUE;
670 } else if (mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) {
671 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
672 g_object_get(result, "context", &context, NULL);
673 if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) {
674 Arg a = { .i = ModeNormal };
675 set(&a);
677 } else {
678 gchar *value = NULL, *message = NULL;
679 jsapi_evaluate_script("window.getSelection().focusNode", &value, &message);
680 if (value && !strcmp(value, "[object HTMLFormElement]")) {
681 Arg a = { .i = ModeInsert, .s = NULL };
682 set(&a);
683 manual_focus = TRUE;
685 g_free(value);
686 g_free(message);
688 return FALSE;
691 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
692 Arg a;
693 guint16 length = gtk_entry_get_text_length(entry);
695 if (!length) {
696 a.i = HideCompletion;
697 complete(&a);
698 a.i = ModeNormal;
699 return set(&a);
701 return FALSE;
704 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
705 Arg a;
706 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
707 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
708 gboolean forward = FALSE;
710 /* Update incremental search if the user changes the search text.
712 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
713 * from the user. But if the entry is focused and the text is set
714 * through gtk_entry_set_text() in some asyncrounous operation,
715 * I would consider that a bug.
718 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
719 webkit_web_view_unmark_text_matches(webview);
720 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
721 return TRUE;
722 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
723 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
724 a.i = Silent;
725 switch (text[0]) {
726 case '.':
727 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
728 break;
730 case ',':
731 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
732 break;
734 case ';':
735 a.s = NULL;
736 switch (text[1]) {
737 case 's':
738 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
739 break;
740 case 'y':
741 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
742 break;
743 case 'o':
744 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
745 break;
746 case 't': case 'w':
747 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
748 break;
749 case 'O': case 'T': case 'W':
750 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
751 break;
752 case 'i':
753 a.s = g_strconcat("hints.createHints('", text + 2, "', 'i');", NULL);
754 break;
755 case 'I':
756 a.s = g_strconcat("hints.createHints('", text + 2, "', 'I');", NULL);
757 break;
759 break;
761 count = 0;
762 if (a.s) {
763 script(&a);
764 g_free(a.s);
767 return TRUE;
768 } else if (length == 0 && followTarget[0]) {
769 mode = ModeNormal;
770 a.i = Silent;
771 a.s = g_strdup("hints.clearHints();");
772 script(&a);
773 g_free(a.s);
774 count = 0;
775 update_state();
778 return FALSE;
781 /* funcs here */
783 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
784 memset(suggline, 0, 512);
785 strncpy(suggline, command, 512);
786 strncat(suggline, " ", 1);
787 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
790 GtkWidget * fill_eventbox(const char * completion_line) {
791 GtkBox * row;
792 GtkWidget *row_eventbox, *el;
793 GdkColor color;
794 char *markup, *markup_tmp;
796 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
797 row_eventbox = gtk_event_box_new();
798 gdk_color_parse(completionbgcolor[0], &color);
799 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
800 el = gtk_label_new(NULL);
801 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
802 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
803 markup_tmp, "</span>", NULL);
804 gtk_label_set_markup(GTK_LABEL(el), markup);
805 g_free(markup_tmp);
806 g_free(markup);
807 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
808 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
809 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
810 return row_eventbox;
813 gboolean
814 complete(const Arg *arg) {
815 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
816 size_t listlen, len, cmdlen;
817 int i, spacepos;
818 Listelement *elementlist = NULL, *elementpointer;
819 gboolean highlight = FALSE;
820 GtkBox *row;
821 GtkWidget *row_eventbox, *el;
822 GtkBox *_table;
823 GdkColor color;
824 static GtkWidget *table, *top_border;
825 static char *prefix;
826 static char **suggestions;
827 static GtkWidget **widgets;
828 static int n = 0, m, current = -1;
830 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
831 len = strlen(str);
833 /* Get the length of the list of commands for completion. We need this to
834 * malloc/realloc correctly.
836 listlen = LENGTH(commands);
838 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
839 return TRUE;
840 if (prefix) {
841 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
842 gdk_color_parse(completionbgcolor[0], &color);
843 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
844 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
845 if ((arg->i == DirectionNext && current == 0)
846 || (arg->i == DirectionPrev && current == n - 1))
847 current = -1;
848 } else {
849 free(widgets);
850 free(suggestions);
851 free(prefix);
852 gtk_widget_destroy(GTK_WIDGET(table));
853 gtk_widget_destroy(GTK_WIDGET(top_border));
854 table = NULL;
855 widgets = NULL;
856 suggestions = NULL;
857 prefix = NULL;
858 n = 0;
859 current = -1;
860 if (arg->i == HideCompletion)
861 return TRUE;
863 } else if (arg->i == HideCompletion)
864 return TRUE;
865 if (!widgets) {
866 prefix = g_strdup(str);
867 widgets = malloc(sizeof(GtkWidget*) * listlen);
868 suggestions = malloc(sizeof(char*) * listlen);
869 top_border = gtk_event_box_new();
870 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
871 gdk_color_parse(completioncolor[2], &color);
872 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
873 table = gtk_event_box_new();
874 gdk_color_parse(completionbgcolor[0], &color);
875 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
876 highlight = len > 1;
877 if (strchr(str, ' ') == NULL) {
878 /* command completion */
879 listlen = LENGTH(commands);
880 for (i = 0; i < listlen; i++) {
881 if (commands[i].cmd == NULL)
882 break;
883 cmdlen = strlen(commands[i].cmd);
884 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
885 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
886 if (highlight) {
887 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
888 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
889 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
890 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
892 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
893 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
894 row_eventbox = gtk_event_box_new();
895 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
896 el = gtk_label_new(NULL);
897 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
898 free(s);
899 gtk_label_set_markup(GTK_LABEL(el), markup);
900 g_free(markup);
901 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
902 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
903 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
904 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
905 suggestions[n] = commands[i].cmd;
906 widgets[n++] = row_eventbox;
909 } else {
910 entry = (char *)malloc(512 * sizeof(char));
911 if (entry == NULL) {
912 return FALSE;
914 memset(entry, 0, 512);
915 suggurls = malloc(sizeof(char*) * listlen);
916 if (suggurls == NULL) {
917 return FALSE;
919 spacepos = strcspn(str, " ");
920 searchfor = (str + spacepos + 1);
921 strncpy(command, (str + 1), spacepos - 1);
922 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
923 /* browser settings */
924 listlen = LENGTH(browsersettings);
925 for (i = 0; i < listlen; i++) {
926 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
927 /* match */
928 fill_suggline(suggline, command, browsersettings[i].name);
929 /* FIXME(HP): This memory is never freed */
930 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
931 strncpy(suggurls[n], suggline, 512);
932 suggestions[n] = suggurls[n];
933 row_eventbox = fill_eventbox(suggline);
934 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
935 widgets[n++] = row_eventbox;
939 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
940 /* completion on tags */
941 spacepos = strcspn(str, " ");
942 searchfor = (str + spacepos + 1);
943 elementlist = complete_list(searchfor, 1, elementlist);
944 } else {
945 /* URL completion: bookmarks */
946 elementlist = complete_list(searchfor, 0, elementlist);
947 m = count_list(elementlist);
948 if (m < MAX_LIST_SIZE) {
949 /* URL completion: history */
950 elementlist = complete_list(searchfor, 2, elementlist);
953 elementpointer = elementlist;
954 while (elementpointer != NULL) {
955 fill_suggline(suggline, command, elementpointer->element);
956 /* FIXME(HP): This memory is never freed */
957 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
958 strncpy(suggurls[n], suggline, 512);
959 suggestions[n] = suggurls[n];
960 row_eventbox = fill_eventbox(suggline);
961 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
962 widgets[n++] = row_eventbox;
963 elementpointer = elementpointer->next;
964 if (n >= MAX_LIST_SIZE)
965 break;
967 free_list(elementlist);
968 if (suggurls != NULL) {
969 free(suggurls);
970 suggurls = NULL;
972 if (entry != NULL) {
973 free(entry);
974 entry = NULL;
977 /* TA: FIXME - this needs rethinking entirely. */
979 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
980 if (widgets_temp == NULL && widgets == NULL) {
981 fprintf(stderr, "Couldn't realloc() widgets\n");
982 exit(1);
984 widgets = widgets_temp;
985 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
986 if (suggestions_temp == NULL && suggestions == NULL) {
987 fprintf(stderr, "Couldn't realloc() suggestions\n");
988 exit(1);
990 suggestions = suggestions_temp;
992 if (!n) {
993 gdk_color_parse(completionbgcolor[1], &color);
994 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
995 el = gtk_label_new(NULL);
996 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
997 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
998 gtk_label_set_markup(GTK_LABEL(el), markup);
999 g_free(markup);
1000 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
1002 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
1003 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
1004 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
1005 gtk_widget_show_all(GTK_WIDGET(window));
1006 if (!n)
1007 return TRUE;
1008 current = arg->i == DirectionPrev ? n - 1 : 0;
1010 if (current != -1) {
1011 gdk_color_parse(completionbgcolor[2], &color);
1012 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
1013 s = g_strconcat(":", suggestions[current], NULL);
1014 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
1015 g_free(s);
1016 } else
1017 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
1018 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1019 return TRUE;
1022 gboolean
1023 descend(const Arg *arg) {
1024 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
1025 int i, len;
1026 count = count ? count : 1;
1028 if (!source)
1029 return TRUE;
1030 if (arg->i == Rootdir) {
1031 for (i = 0; i < 3; i++) /* get to the third slash */
1032 if (!(p = strchr(++p, '/')))
1033 return TRUE; /* if we cannot find it quit */
1034 } else {
1035 len = strlen(source);
1036 if (!len) /* if string is empty quit */
1037 return TRUE;
1038 p = source + len; /* start at the end */
1039 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
1040 ++count;
1041 for (i = 0; i < count; i++)
1042 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
1043 if (p == source) /* if we reach the first char pointer quit */
1044 return TRUE;
1045 ++p; /* since we do p-- in the while, we are pointing at
1046 the char before the slash, so +1 */
1048 len = p - source + 1; /* new length = end - start + 1 */
1049 new = malloc(len + 1);
1050 memcpy(new, source, len);
1051 new[len] = '\0';
1052 webkit_web_view_load_uri(webview, new);
1053 free(new);
1054 return TRUE;
1057 gboolean
1058 echo(const Arg *arg) {
1059 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
1061 if (index < Info || index > Error)
1062 return TRUE;
1064 if (!gtk_widget_is_focus(GTK_WIDGET(inputbox))) {
1065 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1066 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
1069 return TRUE;
1072 static gboolean
1073 open_inspector(const Arg * arg) {
1074 gboolean inspect_enabled;
1075 WebKitWebSettings *settings;
1077 settings = webkit_web_view_get_settings(webview);
1078 g_object_get(G_OBJECT(settings), "enable-developer-extras", &inspect_enabled, NULL);
1079 if (inspect_enabled) {
1080 webkit_web_inspector_show(inspector);
1081 return TRUE;
1082 } else {
1083 echo_message(Error, "Webinspector is not enabled");
1084 return FALSE;
1088 gboolean
1089 input(const Arg *arg) {
1090 int pos = 0;
1091 count = 0;
1092 const char *url;
1093 int index = Info;
1094 Arg a;
1096 /* if inputbox hidden, show it again */
1097 if (!gtk_widget_get_visible(inputbox))
1098 gtk_widget_set_visible(inputbox, TRUE);
1100 update_state();
1102 /* Set the colour and font back to the default, so that we don't still
1103 * maintain a red colour from a warning from an end of search indicator,
1104 * etc.
1106 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1108 /* to avoid things like :open URL :open URL2 or :open :open URL */
1109 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1110 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1111 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
1112 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1114 gtk_widget_grab_focus(inputbox);
1115 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1117 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1118 mode = ModeHints;
1119 memset(followTarget, 0, 8);
1120 strncpy(followTarget, "current", 8);
1121 a.i = Silent;
1122 switch (arg->s[0]) {
1123 case '.':
1124 a.s = g_strdup("hints.createHints('', 'f');");
1125 break;
1127 case ',':
1128 a.s = g_strdup("hints.createHints('', 'F');");
1129 break;
1131 case ';':
1132 a.s = NULL;
1133 if (arg->s[1]) {
1134 switch (arg->s[1]) {
1135 case 's':
1136 a.s = g_strdup("hints.createHints('', 's');");
1137 break;
1138 case 'y':
1139 a.s = g_strdup("hints.createHints('', 'y');");
1140 break;
1141 case 'o':
1142 a.s = g_strdup("hints.createHints('', 'f');");
1143 break;
1144 case 't': case 'w':
1145 a.s = g_strdup("hints.createHints('', 'F');");
1146 break;
1147 case 'O': case 'T': case 'W':
1148 a.s = g_strdup("hints.createHints('', 'O');");
1149 break;
1150 case 'i':
1151 a.s = g_strdup("hints.createHints('', 'i');");
1152 break;
1153 case 'I':
1154 a.s = g_strdup("hints.createHints('', 'I');");
1155 break;
1158 break;
1160 count = 0;
1161 if (a.s) {
1162 script(&a);
1163 g_free(a.s);
1167 return TRUE;
1170 gboolean
1171 navigate(const Arg *arg) {
1172 if (arg->i & NavigationForwardBack)
1173 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
1174 else if (arg->i & NavigationReloadActions)
1175 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
1176 else
1177 webkit_web_view_stop_loading(webview);
1178 return TRUE;
1181 gboolean
1182 number(const Arg *arg) {
1183 const char *source = webkit_web_view_get_uri(webview);
1184 char *uri, *p, *new;
1185 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1187 if (!source)
1188 return TRUE;
1189 uri = g_strdup(source); /* copy string */
1190 p =& uri[0];
1191 while(*p != '\0') /* goto the end of the string */
1192 ++p;
1193 --p;
1194 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1195 --p;
1196 if (*(++p) == '\0') { /* if no numbers were found abort */
1197 free(uri);
1198 return TRUE;
1200 number = atoi(p) + diff; /* apply diff on number */
1201 *p = '\0';
1202 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1203 webkit_web_view_load_uri(webview, new);
1204 g_free(new);
1205 free(uri);
1206 return TRUE;
1209 gboolean
1210 open_arg(const Arg *arg) {
1211 char *argv[64];
1212 char *s = arg->s, *p = NULL, *new;
1213 Arg a = { .i = NavigationReload };
1214 int len;
1215 char *search_uri, *search_term;
1217 if (embed) {
1218 argv[0] = *args;
1219 argv[1] = "-e";
1220 argv[2] = winid;
1221 argv[3] = arg->s;
1222 argv[4] = NULL;
1223 } else {
1224 argv[0] = *args;
1225 argv[1] = arg->s;
1226 argv[2] = NULL;
1229 if (!arg->s)
1230 navigate(&a);
1231 else if (arg->i == TargetCurrent) {
1232 while(*s == ' ') /* strip leading whitespace */
1233 ++s;
1234 p = (s + strlen(s) - 1);
1235 while(*p == ' ') /* strip trailing whitespace */
1236 --p;
1237 *(p + 1) = '\0';
1238 len = strlen(s);
1239 new = NULL;
1240 /* check for external handlers */
1241 if (open_handler(s))
1242 return TRUE;
1243 /* check for search engines */
1244 p = strchr(s, ' ');
1245 if (p) { /* check for search engines */
1246 *p = '\0';
1247 search_uri = find_uri_for_searchengine(s);
1248 if (search_uri != NULL) {
1249 search_term = soup_uri_encode(p+1, "&");
1250 new = g_strdup_printf(search_uri, search_term);
1251 g_free(search_term);
1253 *p = ' ';
1255 if (!new) {
1256 if (len > 3 && strstr(s, "://")) { /* valid url? */
1257 p = new = g_malloc(len + 1);
1258 while(*s != '\0') { /* strip whitespaces */
1259 if (*s != ' ')
1260 *(p++) = *s;
1261 ++s;
1263 *p = '\0';
1264 } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */
1265 new = g_malloc(sizeof("file://") + len);
1266 strcpy(new, "file://");
1267 memcpy(&new[sizeof("file://") - 1], s, len + 1);
1268 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1269 search_uri = find_uri_for_searchengine(defaultsearch);
1270 if (search_uri != NULL) {
1271 search_term = soup_uri_encode(s, "&");
1272 new = g_strdup_printf(search_uri, search_term);
1273 g_free(search_term);
1275 } else { /* prepend "http://" */
1276 new = g_malloc(sizeof("http://") + len);
1277 strcpy(new, "http://");
1278 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1281 webkit_web_view_load_uri(webview, new);
1282 g_free(new);
1283 } else
1284 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1285 return TRUE;
1288 gboolean
1289 open_remembered(const Arg *arg)
1291 Arg a = {arg->i, rememberedURI};
1293 if (strcmp(rememberedURI, "")) {
1294 open_arg(&a);
1296 return TRUE;
1299 gboolean
1300 yank(const Arg *arg) {
1301 const char *url, *content;
1303 if (arg->i & SourceSelection) {
1304 webkit_web_view_copy_clipboard(webview);
1305 if (arg->i & ClipboardPrimary)
1306 content = gtk_clipboard_wait_for_text(clipboards[0]);
1307 if (!content && arg->i & ClipboardGTK)
1308 content = gtk_clipboard_wait_for_text(clipboards[1]);
1309 if (content) {
1310 echo_message(Info, "Yanked %s", content);
1311 g_free((gpointer *)content);
1313 } else {
1314 if (arg->i & SourceURL) {
1315 url = webkit_web_view_get_uri(webview);
1316 } else {
1317 url = arg->s;
1319 if (!url)
1320 return TRUE;
1322 echo_message(Info, "Yanked %s", url);
1323 if (arg->i & ClipboardPrimary)
1324 gtk_clipboard_set_text(clipboards[0], url, -1);
1325 if (arg->i & ClipboardGTK)
1326 gtk_clipboard_set_text(clipboards[1], url, -1);
1328 return TRUE;
1331 gboolean
1332 paste(const Arg *arg) {
1333 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1335 /* If we're over a link, open it in a new target. */
1336 if (strlen(rememberedURI) > 0) {
1337 Arg new_target = { .i = TargetNew, .s = arg->s };
1338 open_arg(&new_target);
1339 return TRUE;
1342 if (arg->i & ClipboardPrimary)
1343 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1344 if (!a.s && arg->i & ClipboardGTK)
1345 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1346 if (a.s) {
1347 open_arg(&a);
1348 g_free(a.s);
1350 return TRUE;
1353 gboolean
1354 quit(const Arg *arg) {
1355 FILE *f;
1356 const char *filename;
1357 const char *uri = webkit_web_view_get_uri(webview);
1358 if (uri != NULL) {
1359 /* write last URL into status file for recreation with "u" */
1360 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1361 f = fopen(filename, "w");
1362 g_free((gpointer *)filename);
1363 if (f != NULL) {
1364 fprintf(f, "%s", uri);
1365 fclose(f);
1368 gtk_main_quit();
1369 return TRUE;
1372 gboolean
1373 revive(const Arg *arg) {
1374 FILE *f;
1375 const char *filename;
1376 char buffer[512] = "";
1377 Arg a = { .i = TargetNew, .s = NULL };
1378 /* get the URL of the window which has been closed last */
1379 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1380 f = fopen(filename, "r");
1381 g_free((gpointer *)filename);
1382 if (f != NULL) {
1383 fgets(buffer, 512, f);
1384 fclose(f);
1386 if (strlen(buffer) > 0) {
1387 a.s = buffer;
1388 open_arg(&a);
1389 return TRUE;
1391 return FALSE;
1394 static
1395 gboolean print_frame(const Arg *arg)
1397 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1398 webkit_web_frame_print (frame);
1399 return TRUE;
1402 gboolean
1403 search(const Arg *arg) {
1404 count = count ? count : 1;
1405 gboolean success, direction = arg->i & DirectionPrev;
1407 if (arg->s) {
1408 free(search_handle);
1409 search_handle = g_strdup(arg->s);
1411 if (!search_handle)
1412 return TRUE;
1413 if (arg->i & DirectionAbsolute)
1414 search_direction = direction;
1415 else
1416 direction ^= search_direction;
1417 do {
1418 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1419 if (!success) {
1420 if (arg->i & Wrapping) {
1421 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1422 if (success) {
1423 echo_message(Warning, "search hit %s, continuing at %s",
1424 direction ? "BOTTOM" : "TOP",
1425 direction ? "TOP" : "BOTTOM");
1426 } else
1427 break;
1428 } else
1429 break;
1431 } while(--count);
1432 if (!success) {
1433 echo_message(Error, "Pattern not found: %s", search_handle);
1435 return TRUE;
1438 gboolean
1439 set(const Arg *arg) {
1440 switch (arg->i) {
1441 case ModeNormal:
1442 if (search_handle) {
1443 search_handle = NULL;
1444 webkit_web_view_unmark_text_matches(webview);
1446 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1447 gtk_widget_grab_focus(GTK_WIDGET(webview));
1448 break;
1449 case ModePassThrough:
1450 echo_message(Info | NoAutoHide, "-- PASS THROUGH --");
1451 break;
1452 case ModeSendKey:
1453 echo_message(Info | NoAutoHide, "-- PASS TROUGH (next) --");
1454 break;
1455 case ModeInsert: /* should not be called manually but automatically */
1456 echo_message(Info | NoAutoHide, "-- INSERT --");
1457 break;
1458 default:
1459 return TRUE;
1461 mode = arg->i;
1462 return TRUE;
1465 gchar*
1466 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1467 JSStringRef string_ref;
1468 gchar *string;
1469 size_t length;
1471 string_ref = JSValueToStringCopy(context, ref, NULL);
1472 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1473 string = g_new(gchar, length);
1474 JSStringGetUTF8CString(string_ref, string, length);
1475 JSStringRelease(string_ref);
1476 return string;
1479 void
1480 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1481 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1482 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1483 JSStringRef str;
1484 JSValueRef val, exception;
1486 str = JSStringCreateWithUTF8CString(script);
1487 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1488 JSStringRelease(str);
1489 if (!val)
1490 *message = jsapi_ref_to_string(context, exception);
1491 else
1492 *value = jsapi_ref_to_string(context, val);
1495 gboolean
1496 quickmark(const Arg *a) {
1497 int i, b;
1498 b = atoi(a->s);
1499 char *fn = g_strdup_printf(QUICKMARK_FILE);
1500 FILE *fp;
1501 fp = fopen(fn, "r");
1502 g_free(fn);
1503 fn = NULL;
1504 char buf[100];
1506 if (fp != NULL && b < 10) {
1507 for( i=0; i < b; ++i ) {
1508 if (feof(fp)) {
1509 break;
1511 fgets(buf, 100, fp);
1513 char *ptr = strrchr(buf, '\n');
1514 *ptr = '\0';
1515 if (strlen(buf)) {
1516 Arg x = { .s = buf };
1517 return open_arg(&x);
1518 } else {
1519 echo_message(Error, "Quickmark %d not defined", b);
1520 return false;
1522 } else { return false; }
1525 gboolean
1526 script(const Arg *arg) {
1527 gchar *value = NULL, *message = NULL;
1528 char text[1024] = "";
1529 Arg a;
1530 WebKitNetworkRequest *request;
1531 WebKitDownload *download;
1533 if (!arg->s) {
1534 set_error("Missing argument.");
1535 return FALSE;
1537 jsapi_evaluate_script(arg->s, &value, &message);
1538 if (message) {
1539 set_error(message);
1540 g_free(message);
1541 return FALSE;
1543 g_free(message);
1544 if (arg->i != Silent && value) {
1545 echo_message(arg->i, value);
1547 /* switch mode according to scripts return value */
1548 if (value) {
1549 if (strncmp(value, "done;", 5) == 0) {
1550 a.i = ModeNormal;
1551 set(&a);
1552 } else if (strncmp(value, "insert;", 7) == 0) {
1553 a.i = ModeInsert;
1554 set(&a);
1555 manual_focus = TRUE;
1556 } else if (strncmp(value, "save;", 5) == 0) {
1557 /* forced download */
1558 a.i = ModeNormal;
1559 set(&a);
1560 request = webkit_network_request_new((value + 5));
1561 download = webkit_download_new(request);
1562 webview_download_cb(webview, download, (gpointer *)NULL);
1563 } else if (strncmp(value, "yank;", 5) == 0) {
1564 /* yank link URL to clipboard */
1565 a.i = ModeNormal;
1566 set(&a);
1567 a.i = ClipboardPrimary | ClipboardGTK;
1568 a.s = (value + 5);
1569 yank(&a);
1570 } else if (strncmp(value, "colon;", 6) == 0) {
1571 /* use link URL for colon command */
1572 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(inputbox)), 1023);
1573 a.i = ModeNormal;
1574 set(&a);
1575 switch (text[1]) {
1576 case 'O':
1577 a.s = g_strconcat(":open ", (value + 6), NULL);
1578 break;
1579 case 'T': case 'W':
1580 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1581 break;
1583 if (a.s) {
1584 input(&a);
1585 g_free(a.s);
1587 } else if (strncmp(value, "error;", 6) == 0) {
1588 a.i = Error;
1589 set(&a);
1592 g_free(value);
1593 return TRUE;
1596 gboolean
1597 scroll(const Arg *arg) {
1598 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1599 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1600 float val = gtk_adjustment_get_value(adjust) / max * 100;
1601 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1603 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1604 if (arg->i & ScrollMove)
1605 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1606 direction * /* direction */
1607 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1608 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1609 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1610 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1611 else
1612 gtk_adjustment_set_value(adjust,
1613 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1614 update_state();
1616 return TRUE;
1619 gboolean
1620 zoom(const Arg *arg) {
1621 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1622 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1623 webkit_web_view_get_zoom_level(webview) +
1624 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1625 (count ? (float)count / 100.0 : 1.0));
1626 return TRUE;
1629 gboolean
1630 fake_key_event(const Arg *a) {
1631 if(!embed) {
1632 return FALSE;
1634 Display *xdpy;
1635 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1636 echo_message(Error, "Couldn't find the XDisplay.");
1637 return FALSE;
1640 XKeyEvent xk;
1641 xk.display = xdpy;
1642 xk.subwindow = None;
1643 xk.time = CurrentTime;
1644 xk.same_screen = True;
1645 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1646 xk.window = embed;
1647 xk.state = a->i;
1649 if( ! a->s ) {
1650 echo_message(Error, "Zero pointer as argument! Check your config.h");
1651 return FALSE;
1654 KeySym keysym;
1655 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1656 echo_message(Error, "Couldn't translate %s to keysym", a->s );
1657 return FALSE;
1660 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1661 echo_message(Error, "Couldn't translate keysym to keycode");
1662 return FALSE;
1665 xk.type = KeyPress;
1666 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1667 echo_message(Error, "XSendEvent failed");
1668 return FALSE;
1670 XFlush(xdpy);
1672 return TRUE;
1675 gboolean
1676 commandhistoryfetch(const Arg *arg) {
1677 const int length = g_list_length(commandhistory);
1679 if (length > 0) {
1680 if (arg->i == DirectionPrev) {
1681 commandpointer = (length + commandpointer - 1) % length;
1682 } else {
1683 commandpointer = (length + commandpointer + 1) % length;
1686 const char* command = (char *)g_list_nth_data(commandhistory, commandpointer);
1687 gtk_entry_set_text(GTK_ENTRY(inputbox), g_strconcat(":", command, NULL));
1688 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1689 return TRUE;
1692 return FALSE;
1695 gboolean
1696 bookmark(const Arg *arg) {
1697 FILE *f;
1698 const char *filename;
1699 const char *uri = webkit_web_view_get_uri(webview);
1700 const char *title = webkit_web_view_get_title(webview);
1701 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1702 f = fopen(filename, "a");
1703 g_free((gpointer *)filename);
1704 if (uri == NULL || strlen(uri) == 0) {
1705 set_error("No URI found to bookmark.");
1706 return FALSE;
1708 if (f != NULL) {
1709 fprintf(f, "%s", uri);
1710 if (title != NULL) {
1711 fprintf(f, "%s", " ");
1712 fprintf(f, "%s", title);
1714 if (arg->s && strlen(arg->s)) {
1715 build_taglist(arg, f);
1717 fprintf(f, "%s", "\n");
1718 fclose(f);
1719 echo_message(Info, "Bookmark saved");
1720 return TRUE;
1721 } else {
1722 set_error("Bookmarks file not found.");
1723 return FALSE;
1727 gboolean
1728 history() {
1729 FILE *f;
1730 const char *filename;
1731 const char *uri = webkit_web_view_get_uri(webview);
1732 const char *title = webkit_web_view_get_title(webview);
1733 char *entry, buffer[512], *new;
1734 int n, i = 0;
1735 gboolean finished = FALSE;
1736 if (uri != NULL) {
1737 if (title != NULL) {
1738 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1739 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1740 } else {
1741 entry = malloc((strlen(uri) + 1) * sizeof(char));
1742 memset(entry, 0, strlen(uri) + 1);
1744 if (entry != NULL) {
1745 strncpy(entry, uri, strlen(uri));
1746 if (title != NULL) {
1747 strncat(entry, " ", 1);
1748 strncat(entry, title, strlen(title));
1750 n = strlen(entry);
1751 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1752 f = fopen(filename, "r");
1753 if (f != NULL) {
1754 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1755 if (new != NULL) {
1756 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1757 /* newest entries go on top */
1758 strncpy(new, entry, strlen(entry));
1759 strncat(new, "\n", 1);
1760 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1761 while (finished != TRUE) {
1762 if ((char *)NULL == fgets(buffer, 512, f)) {
1763 /* check if end of file was reached / error occured */
1764 if (!feof(f)) {
1765 break;
1767 /* end of file reached */
1768 finished = TRUE;
1769 continue;
1771 /* compare line (-1 because of newline character) */
1772 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1773 /* if the URI is already in history; we put it on top and skip it here */
1774 strncat(new, buffer, 512);
1775 i++;
1777 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1778 break;
1781 fclose(f);
1783 f = fopen(filename, "w");
1784 g_free((gpointer *)filename);
1785 if (f != NULL) {
1786 fprintf(f, "%s", new);
1787 fclose(f);
1789 if (new != NULL) {
1790 free(new);
1791 new = NULL;
1795 if (entry != NULL) {
1796 free(entry);
1797 entry = NULL;
1800 return TRUE;
1803 static gboolean
1804 view_source(const Arg * arg) {
1805 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1806 webkit_web_view_set_view_source_mode(webview, !current_mode);
1807 webkit_web_view_reload(webview);
1808 return TRUE;
1811 /* open an external editor defined by the protocol handler for
1812 vimprobableedit on a text box or similar */
1813 static gboolean
1814 open_editor(const Arg *arg) {
1815 char *text = NULL;
1816 gboolean success;
1817 GPid child_pid;
1818 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1819 gchar *temp_file_name = g_strdup_printf("%s/vimprobableeditXXXXXX",
1820 temp_dir);
1821 int temp_file_handle = -1;
1823 /* check if active element is suitable for text editing */
1824 jsapi_evaluate_script("document.activeElement.tagName", &value, &message);
1825 if (value == NULL)
1826 return FALSE;
1827 tag = g_strdup(value);
1828 if (strcmp(tag, "INPUT") == 0) {
1829 /* extra check: type == text */
1830 jsapi_evaluate_script("document.activeElement.type", &value, &message);
1831 if (strcmp(value, "text") != 0) {
1832 g_free(value);
1833 g_free(message);
1834 return FALSE;
1836 } else if (strcmp(tag, "TEXTAREA") != 0) {
1837 g_free(value);
1838 g_free(message);
1839 return FALSE;
1841 jsapi_evaluate_script("document.activeElement.value", &value, &message);
1842 text = g_strdup(value);
1843 if (text == NULL) {
1844 g_free(value);
1845 g_free(message);
1846 return FALSE;
1849 /* write text into temporary file */
1850 temp_file_handle = mkstemp(temp_file_name);
1851 if (temp_file_handle == -1) {
1852 message = g_strdup_printf("Could not create temporary file: %s",
1853 strerror(errno));
1854 echo_message(Error, message);
1855 g_free(value);
1856 g_free(message);
1857 g_free(text);
1858 return FALSE;
1860 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
1861 message = g_strdup_printf("Short write to temporary file: %s",
1862 strerror(errno));
1863 echo_message(Error, message);
1864 g_free(value);
1865 g_free(message);
1866 g_free(text);
1867 return FALSE;
1869 close(temp_file_handle);
1870 g_free(text);
1872 /* spawn editor */
1873 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
1874 success = open_handler_pid(edit_url, &child_pid);
1875 g_free(edit_url);
1876 if (!success) {
1877 echo_message(Error, "External editor open failed (no handler for"
1878 " vimprobableedit protocol?)");
1879 unlink(temp_file_name);
1880 g_free(value);
1881 g_free(message);
1882 return FALSE;
1885 /* mark the active text box as "under processing" */
1886 jsapi_evaluate_script(
1887 "document.activeElement.disabled = true;"
1888 "document.activeElement.originalBackground = "
1889 " document.activeElement.style.background;"
1890 "document.activeElement.style.background = '#aaaaaa';"
1891 ,&value, &message);
1893 g_child_watch_add(child_pid, _resume_from_editor, temp_file_name);
1895 /* temp_file_name is freed in _resume_from_editor */
1896 g_free(value);
1897 g_free(message);
1898 g_free(tag);
1899 return TRUE;
1903 /* pick up from where open_editor left the work to the glib event loop.
1905 This is called when the external editor exits.
1907 The data argument points to allocated memory containing the temporary file
1908 name. */
1909 void
1910 _resume_from_editor(GPid child_pid, int child_status, gpointer data) {
1911 FILE *fp;
1912 GString *set_value_js = g_string_new(
1913 "document.activeElement.value = \"");
1914 g_spawn_close_pid(child_pid);
1915 gchar *value = NULL, *message = NULL;
1916 gchar *temp_file_name = data;
1917 gchar buffer[BUF_SIZE] = "";
1918 gchar *buf_ptr = buffer;
1919 int char_read;
1921 jsapi_evaluate_script(
1922 "document.activeElement.disabled = true;"
1923 "document.activeElement.style.background = '#aaaaaa';"
1924 ,&value, &message);
1926 if (child_status) {
1927 echo_message(Error, "External editor returned with non-zero status,"
1928 " discarding edits.");
1929 goto error_exit;
1932 /* re-read the new contents of the file and put it into the HTML element */
1933 if (!access(temp_file_name, R_OK) == 0) {
1934 message = g_strdup_printf("Could not access temporary file: %s",
1935 strerror(errno));
1936 goto error_exit;
1938 fp = fopen(temp_file_name, "r");
1939 if (fp == NULL) {
1940 /* this would be too weird to even emit an error message */
1941 goto error_exit;
1943 jsapi_evaluate_script("document.activeElement.value = '';",
1944 &value, &message);
1946 while (EOF != (char_read = fgetc(fp))) {
1947 if (char_read == '\n') {
1948 *buf_ptr++ = '\\';
1949 *buf_ptr++ = 'n';
1950 } else if (char_read == '"') {
1951 *buf_ptr++ = '\\';
1952 *buf_ptr++ = '"';
1953 } else {
1954 *buf_ptr++ = char_read;
1956 /* ship out as the buffer when space gets tight. This has
1957 fuzz to save on thinking, plus we have enough space for the
1958 trailing "; in any case. */
1959 if (buf_ptr-buffer>=BUF_SIZE-10) {
1960 *buf_ptr = 0;
1961 g_string_append(set_value_js, buffer);
1962 buf_ptr = buffer;
1965 *buf_ptr++ = '"';
1966 *buf_ptr++ = ';';
1967 *buf_ptr = 0;
1968 g_string_append(set_value_js, buffer);
1969 fclose(fp);
1971 jsapi_evaluate_script(set_value_js->str, &value, &message);
1973 /* Fall through, error and normal exit are identical */
1974 error_exit:
1975 jsapi_evaluate_script(
1976 "document.activeElement.disabled = false;"
1977 "document.activeElement.style.background ="
1978 " document.activeElement.originalBackground;"
1979 ,&value, &message);
1981 g_string_free(set_value_js, TRUE);
1982 unlink(temp_file_name);
1983 g_free(temp_file_name);
1984 g_free(value);
1985 g_free(message);
1988 static gboolean
1989 focus_input(const Arg *arg) {
1990 static Arg a;
1992 a.s = g_strdup("hints.focusInput();");
1993 a.i = Silent;
1994 script(&a);
1995 g_free(a.s);
1996 update_state();
1997 manual_focus = TRUE;
1998 return TRUE;
2001 static void
2002 clear_focus(void) {
2003 static Arg a;
2005 a.s = g_strdup("hints.clearFocus();");
2006 a.i = Silent;
2007 script(&a);
2008 g_free(a.s);
2009 a.i = ModeNormal;
2010 a.s = NULL;
2011 set(&a);
2014 static gboolean
2015 browser_settings(const Arg *arg) {
2016 char line[255];
2017 if (!arg->s) {
2018 set_error("Missing argument.");
2019 return FALSE;
2021 strncpy(line, arg->s, 254);
2022 if (process_set_line(line))
2023 return TRUE;
2024 else {
2025 set_error("Invalid setting.");
2026 return FALSE;
2030 char *
2031 search_word(int whichword) {
2032 int k = 0;
2033 static char word[240];
2034 char *c = my_pair.line;
2036 while (isspace(*c) && *c)
2037 c++;
2039 switch (whichword) {
2040 case 0:
2041 while (*c && !isspace (*c) && *c != '=' && k < 240) {
2042 word[k++] = *c;
2043 c++;
2045 word[k] = '\0';
2046 strncpy(my_pair.what, word, 20);
2047 break;
2048 case 1:
2049 while (*c && k < 240) {
2050 word[k++] = *c;
2051 c++;
2053 word[k] = '\0';
2054 strncpy(my_pair.value, word, 240);
2055 break;
2058 return c;
2061 static gboolean
2062 process_set_line(char *line) {
2063 char *c;
2064 int listlen, i;
2065 gboolean boolval;
2066 WebKitWebSettings *settings;
2068 settings = webkit_web_view_get_settings(webview);
2069 my_pair.line = line;
2070 c = search_word(0);
2071 if (!strlen(my_pair.what))
2072 return FALSE;
2074 while (isspace(*c) && *c)
2075 c++;
2077 if (*c == ':' || *c == '=')
2078 c++;
2080 my_pair.line = c;
2081 c = search_word(1);
2083 listlen = LENGTH(browsersettings);
2084 for (i = 0; i < listlen; i++) {
2085 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
2086 /* mandatory argument not provided */
2087 if (strlen(my_pair.value) == 0)
2088 return FALSE;
2089 /* process qmark? */
2090 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
2091 return (process_save_qmark(my_pair.value, webview));
2093 /* interpret boolean values */
2094 if (browsersettings[i].boolval) {
2095 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) {
2096 boolval = TRUE;
2097 } 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) {
2098 boolval = FALSE;
2099 } else {
2100 return FALSE;
2102 } else if (browsersettings[i].colourval) {
2103 /* interpret as hexadecimal colour */
2104 if (!parse_colour(my_pair.value)) {
2105 return FALSE;
2108 if (browsersettings[i].var != NULL) {
2109 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
2110 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
2111 /* in this case, \0 will not have been copied */
2112 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
2113 /* in case this string is also used for a webkit setting, make sure it's consistent */
2114 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
2115 echo_message(Info, "String too long; automatically truncated!");
2118 if (strlen(browsersettings[i].webkit) > 0) {
2119 /* activate appropriate webkit setting */
2120 if (browsersettings[i].boolval) {
2121 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
2122 } else if (browsersettings[i].intval) {
2123 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
2124 } else {
2125 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
2127 webkit_web_view_set_settings(webview, settings);
2130 if (strlen(my_pair.what) == 14) {
2131 if (strncmp("acceptlanguage", my_pair.what, 14) == 0) {
2132 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2133 } else if (strncmp("completioncase", my_pair.what, 14) == 0) {
2134 complete_case_sensitive = boolval;
2136 } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
2137 toggle_proxy(boolval);
2138 } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) {
2139 toggle_scrollbars(boolval);
2140 } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) {
2141 gtk_widget_set_visible(GTK_WIDGET(statusbar), boolval);
2142 } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) {
2143 gtk_widget_set_visible(inputbox, boolval);
2144 } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) {
2145 escape_input_on_load = boolval;
2148 /* SSL certificate checking */
2149 if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) {
2150 if (boolval) {
2151 strict_ssl = TRUE;
2152 g_object_set(G_OBJECT(session), "ssl-strict", TRUE, NULL);
2153 } else {
2154 strict_ssl = FALSE;
2155 g_object_set(G_OBJECT(session), "ssl-strict", FALSE, NULL);
2158 if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) {
2159 g_object_set(G_OBJECT(session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL);
2161 if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) {
2162 set_default_winsize(my_pair.value);
2165 /* reload page? */
2166 if (browsersettings[i].reload)
2167 webkit_web_view_reload(webview);
2168 return TRUE;
2171 return FALSE;
2174 gboolean
2175 process_line(char *line) {
2176 char *c = line, *command_hist;
2177 int i;
2178 size_t len, length = strlen(line);
2179 gboolean found = FALSE, success = FALSE;
2180 Arg a;
2182 while (isspace(*c))
2183 c++;
2184 /* Ignore blank lines. */
2185 if (c[0] == '\0')
2186 return TRUE;
2188 command_hist = g_strdup(c);
2189 for (i = 0; i < LENGTH(commands); i++) {
2190 if (commands[i].cmd == NULL)
2191 break;
2192 len = strlen(commands[i].cmd);
2193 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
2194 found = TRUE;
2195 a.i = commands[i].arg.i;
2196 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
2197 success = commands[i].func(&a);
2198 g_free(a.s);
2199 break;
2203 save_command_history(command_hist);
2204 g_free(command_hist);
2206 if (!found) {
2207 echo_message(Error, "Not a browser command: %s", c);
2208 } else if (!success) {
2209 if (error_msg != NULL) {
2210 echo_message(Error, error_msg);
2211 g_free(error_msg);
2212 error_msg = NULL;
2213 } else {
2214 echo_message(Error, "Unknown error. Please file a bug report!");
2217 return success;
2220 static gboolean
2221 search_tag(const Arg * a) {
2222 FILE *f;
2223 const char *filename;
2224 const char *tag = a->s;
2225 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
2226 int t, i, intag, k;
2228 if (!tag) {
2229 /* The user must give us something to load up. */
2230 set_error("Bookmark tag required with this option.");
2231 return FALSE;
2234 if (strlen(tag) > MAXTAGSIZE) {
2235 set_error("Tag too long.");
2236 return FALSE;
2239 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
2240 f = fopen(filename, "r");
2241 g_free((gpointer *)filename);
2242 if (f == NULL) {
2243 set_error("Couldn't open bookmarks file.");
2244 return FALSE;
2246 while (fgets(s, BUFFERSIZE-1, f)) {
2247 intag = 0;
2248 t = strlen(s) - 1;
2249 while (isspace(s[t]))
2250 t--;
2251 if (s[t] != ']') continue;
2252 while (t > 0) {
2253 if (s[t] == ']') {
2254 if (!intag)
2255 intag = t;
2256 else
2257 intag = 0;
2258 } else {
2259 if (s[t] == '[') {
2260 if (intag) {
2261 i = 0;
2262 k = t + 1;
2263 while (k < intag)
2264 foundtag[i++] = s[k++];
2265 foundtag[i] = '\0';
2266 /* foundtag now contains the tag */
2267 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2268 i = 0;
2269 while (isspace(s[i])) i++;
2270 k = 0;
2271 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2272 url[k] = '\0';
2273 Arg x = { .i = TargetNew, .s = url };
2274 open_arg(&x);
2277 intag = 0;
2280 t--;
2283 return TRUE;
2286 void
2287 toggle_proxy(gboolean onoff) {
2288 SoupURI *proxy_uri;
2289 char *filename, *new;
2291 if (onoff == FALSE) {
2292 g_object_set(session, "proxy-uri", NULL, NULL);
2293 } else {
2294 filename = (char *)g_getenv("http_proxy");
2296 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2297 * defined.
2299 if (filename == NULL)
2300 filename = (char *)g_getenv("HTTP_PROXY");
2302 if (filename != NULL && 0 < strlen(filename)) {
2303 new = g_strrstr(filename, "http://") ? g_strdup(filename) : g_strdup_printf("http://%s", filename);
2304 proxy_uri = soup_uri_new(new);
2306 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2308 soup_uri_free(proxy_uri);
2309 g_free(new);
2314 void
2315 toggle_scrollbars(gboolean onoff) {
2316 if (onoff == TRUE) {
2317 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
2318 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
2319 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2320 } else {
2321 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2322 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2323 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2325 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
2327 return;
2330 void set_default_winsize(const char * const size) {
2331 char *p;
2332 int x = 640, y = 480;
2334 x = strtol(size, &p, 10);
2335 if (errno == ERANGE || x <= 0) {
2336 x = 640;
2337 goto out;
2340 if (p == size || strlen(size) == p - size)
2341 goto out;
2343 y = strtol(p + 1, NULL, 10);
2344 if (errno == ERANGE || y <= 0)
2345 y = 480;
2347 out:
2348 gtk_window_resize(GTK_WINDOW(window), x, y);
2351 void
2352 update_url(const char *uri) {
2353 gboolean ssl = g_str_has_prefix(uri, "https://");
2354 GdkColor color;
2355 WebKitWebFrame *frame;
2356 WebKitWebDataSource *src;
2357 WebKitNetworkRequest *request;
2358 SoupMessage *msg;
2359 gboolean ssl_ok;
2360 char *sslactivecolor;
2361 gchar *markup;
2362 #ifdef ENABLE_HISTORY_INDICATOR
2363 char before[] = " [";
2364 char after[] = "]";
2365 gboolean back = webkit_web_view_can_go_back(webview);
2366 gboolean fwd = webkit_web_view_can_go_forward(webview);
2368 if (!back && !fwd)
2369 before[0] = after[0] = '\0';
2370 #endif
2371 markup = g_markup_printf_escaped(
2372 #ifdef ENABLE_HISTORY_INDICATOR
2373 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2374 before, back ? "+" : "", fwd ? "-" : "", after
2375 #else
2376 "<span font=\"%s\">%s</span>", statusfont, uri
2377 #endif
2379 gtk_label_set_markup(GTK_LABEL(status_url), markup);
2380 g_free(markup);
2381 if (ssl) {
2382 frame = webkit_web_view_get_main_frame(webview);
2383 src = webkit_web_frame_get_data_source(frame);
2384 request = webkit_web_data_source_get_request(src);
2385 msg = webkit_network_request_get_message(request);
2386 ssl_ok = soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED;
2387 if (ssl_ok)
2388 sslactivecolor = sslbgcolor;
2389 else
2390 sslactivecolor = sslinvalidbgcolor;
2392 gdk_color_parse(ssl ? sslactivecolor : statusbgcolor, &color);
2393 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
2394 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2395 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
2396 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
2399 void
2400 update_state() {
2401 char *markup;
2402 int download_count = g_list_length(activeDownloads);
2403 GString *status = g_string_new("");
2405 /* construct the status line */
2407 /* count, modkey and input buffer */
2408 g_string_append_printf(status, "%.0d", count);
2409 if (current_modkey) g_string_append_c(status, current_modkey);
2411 /* the number of active downloads */
2412 if (activeDownloads) {
2413 g_string_append_printf(status, " %d active %s", download_count,
2414 (download_count == 1) ? "download" : "downloads");
2417 #ifdef ENABLE_WGET_PROGRESS_BAR
2418 /* the progressbar */
2420 int progress = -1;
2421 char progressbar[progressbartick + 1];
2423 if (activeDownloads) {
2424 progress = 0;
2425 GList *ptr;
2427 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
2428 progress += 100 * webkit_download_get_progress(ptr->data);
2431 progress /= download_count;
2433 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
2434 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
2436 progress = webkit_web_view_get_progress(webview) * 100;
2439 if (progress >= 0) {
2440 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2441 g_string_append_printf(status, " %c%s%c",
2442 progressborderleft, progressbar, progressborderright);
2445 #endif
2447 /* and the current scroll position */
2449 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
2450 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
2452 if (max == 0)
2453 g_string_append(status, " All");
2454 else if (val == 0)
2455 g_string_append(status, " Top");
2456 else if (val == 100)
2457 g_string_append(status, " Bot");
2458 else
2459 g_string_append_printf(status, " %d%%", val);
2463 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2464 gtk_label_set_markup(GTK_LABEL(status_state), markup);
2465 g_free(markup);
2467 g_string_free(status, TRUE);
2470 void
2471 setup_modkeys() {
2472 unsigned int i;
2473 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2474 char *ptr = modkeys;
2476 for (i = 0; i < LENGTH(keys); i++)
2477 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
2478 *(ptr++) = keys[i].modkey;
2479 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
2482 void
2483 setup_gui() {
2484 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2485 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2486 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2487 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2488 if (embed) {
2489 window = GTK_WINDOW(gtk_plug_new(embed));
2490 } else {
2491 window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2492 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2494 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2495 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2496 inputbox = gtk_entry_new();
2497 webview = (WebKitWebView*)webkit_web_view_new();
2498 statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2499 eventbox = gtk_event_box_new();
2500 status_url = gtk_label_new(NULL);
2501 status_state = gtk_label_new(NULL);
2502 GdkColor bg;
2503 PangoFontDescription *font;
2504 GdkGeometry hints = { 1, 1 };
2505 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2507 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2508 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2509 setup_settings();
2510 gdk_color_parse(statusbgcolor, &bg);
2511 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2512 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2513 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2515 keymap = gdk_keymap_get_default();
2517 #ifdef DISABLE_SCROLLBAR
2518 viewport = gtk_scrolled_window_new(NULL, NULL);
2519 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2520 #else
2521 /* Ensure we still see scrollbars. */
2522 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2523 #endif
2525 setup_signals();
2526 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2528 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2529 * titlebars, we can still scroll.
2531 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2533 font = pango_font_description_from_string(urlboxfont[0]);
2534 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2535 pango_font_description_free(font);
2536 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2537 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2538 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2539 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2540 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2541 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2542 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2543 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2544 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2545 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2546 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2547 gtk_widget_grab_focus(GTK_WIDGET(webview));
2548 gtk_widget_show_all(GTK_WIDGET(window));
2549 set_widget_font_and_color(inputbox, urlboxfont[0], urlboxbgcolor[0], urlboxcolor[0]);
2550 g_object_set(gtk_widget_get_settings(inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
2553 void
2554 setup_settings() {
2555 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2556 char *filename, *file_url;
2558 session = webkit_get_default_session();
2559 g_object_set(G_OBJECT(session), "ssl-ca-file", ca_bundle, NULL);
2560 g_object_set(G_OBJECT(session), "ssl-strict", strict_ssl, NULL);
2561 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2562 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2563 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2564 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2565 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2566 filename = g_strdup_printf(USER_STYLESHEET);
2567 file_url = g_strdup_printf("file://%s", filename);
2568 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2569 g_free(file_url);
2570 g_free(filename);
2571 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2572 g_object_get(G_OBJECT(settings), "zoom-step", &zoomstep, NULL);
2573 webkit_web_view_set_settings(webview, settings);
2575 /* proxy */
2576 toggle_proxy(use_proxy);
2579 void
2580 setup_signals() {
2581 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
2582 #ifdef ENABLE_COOKIE_SUPPORT
2583 /* Headers. */
2584 g_signal_connect_after(G_OBJECT(session), "request-started", G_CALLBACK(new_generic_request), NULL);
2585 #endif
2586 /* Accept-language header */
2587 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2588 /* window */
2589 g_object_connect(G_OBJECT(window),
2590 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2591 NULL);
2592 /* frame */
2593 g_signal_connect(G_OBJECT(frame),
2594 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2595 /* webview */
2596 g_object_connect(G_OBJECT(webview),
2597 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2598 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2599 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2600 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2601 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2602 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2603 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2604 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2605 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2606 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2607 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2608 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2609 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2610 NULL);
2611 /* webview adjustment */
2612 g_object_connect(G_OBJECT(adjust_v),
2613 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2614 NULL);
2615 /* inputbox */
2616 g_object_connect(G_OBJECT(inputbox),
2617 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2618 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2619 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2620 #ifdef ENABLE_INCREMENTAL_SEARCH
2621 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2622 #endif
2623 NULL);
2624 /* inspector */
2625 g_signal_connect(G_OBJECT(inspector),
2626 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2629 #ifdef ENABLE_USER_SCRIPTFILE
2630 static void
2631 scripts_run_user_file() {
2632 gchar *js = NULL, *user_scriptfile = NULL;
2633 GError *error = NULL;
2635 user_scriptfile = g_strdup_printf(USER_SCRIPTFILE);
2637 /* run the users script file */
2638 if (g_file_test(user_scriptfile, G_FILE_TEST_IS_REGULAR)
2639 && g_file_get_contents(user_scriptfile, &js, NULL, &error)) {
2641 gchar *value = NULL, *message = NULL;
2643 jsapi_evaluate_script(js, &value, &message);
2644 if (message) {
2645 fprintf(stderr, "%s", message);
2646 g_free(message);
2648 } else {
2649 fprintf(stderr, "Cannot open %s: %s\n", user_scriptfile, error ? error->message : "file not found");
2652 g_free(user_scriptfile);
2654 #endif
2656 #ifdef ENABLE_COOKIE_SUPPORT
2657 void
2658 setup_cookies()
2660 if (file_cookie_jar)
2661 g_object_unref(file_cookie_jar);
2663 if (session_cookie_jar)
2664 g_object_unref(session_cookie_jar);
2666 session_cookie_jar = soup_cookie_jar_new();
2668 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2670 load_all_cookies();
2672 g_signal_connect(G_OBJECT(file_cookie_jar), "changed",
2673 G_CALLBACK(update_cookie_jar), NULL);
2675 return;
2678 /* TA: XXX - we should be using this callback for any header-requests we
2679 * receive (hence the name "new_generic_request" -- but for now, its use
2680 * is limited to handling cookies.
2682 void
2683 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused)
2685 SoupMessageHeaders *soup_msg_h;
2686 SoupURI *uri;
2687 char *cookie_str;
2689 soup_msg_h = soup_msg->request_headers;
2690 soup_message_headers_remove(soup_msg_h, "Cookie");
2691 uri = soup_message_get_uri(soup_msg);
2692 if ((cookie_str = get_cookies(uri))) {
2693 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2694 g_free(cookie_str);
2697 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2699 return;
2702 char *
2703 get_cookies(SoupURI *soup_uri) {
2704 char *cookie_str;
2706 cookie_str = soup_cookie_jar_get_cookies(file_cookie_jar, soup_uri, TRUE);
2708 return cookie_str;
2711 void
2712 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2714 GSList *resp_cookie = NULL, *cookie_list;
2715 SoupCookie *cookie;
2717 cookie_list = soup_cookies_from_response(soup_msg);
2718 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
2720 SoupDate *soup_date;
2721 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2723 if (cookie_timeout && cookie->expires == NULL) {
2724 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2725 soup_cookie_set_expires(cookie, soup_date);
2726 soup_date_free(soup_date);
2728 soup_cookie_jar_add_cookie(file_cookie_jar, cookie);
2731 soup_cookies_free(cookie_list);
2733 return;
2736 void
2737 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2739 if (!new) {
2740 /* Nothing to do. */
2741 return;
2744 SoupCookie *copy;
2745 copy = soup_cookie_copy(new);
2747 soup_cookie_jar_add_cookie(session_cookie_jar, copy);
2749 return;
2752 void
2753 load_all_cookies(void)
2755 GSList *cookie_list;
2756 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2758 /* Put them back in the session store. */
2759 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2760 cookie_list = cookies_from_file;
2762 for (; cookies_from_file;
2763 cookies_from_file = cookies_from_file->next)
2765 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2768 soup_cookies_free(cookies_from_file);
2769 g_slist_free(cookie_list);
2771 return;
2774 #endif
2776 void
2777 mop_up(void) {
2778 /* Free up any nasty globals before exiting. */
2779 #ifdef ENABLE_COOKIE_SUPPORT
2780 if (cookie_store)
2781 g_free(cookie_store);
2782 #endif
2783 return;
2787 main(int argc, char *argv[]) {
2788 static Arg a;
2789 static char url[256] = "";
2790 static gboolean ver = false;
2791 static gboolean configfile_exists = FALSE;
2792 static const char *cfile = NULL;
2793 static GOptionEntry opts[] = {
2794 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2795 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2796 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2797 { NULL }
2799 static GError *err;
2800 args = argv;
2802 /* command line argument: version */
2803 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2804 g_printerr("can't init gtk: %s\n", err->message);
2805 g_error_free(err);
2806 return EXIT_FAILURE;
2809 if (ver) {
2810 printf("%s\n", INTERNAL_VERSION);
2811 return EXIT_SUCCESS;
2814 if (getenv("TMPDIR")) {
2815 strncpy(temp_dir, getenv("TMPDIR"), MAX_SETTING_SIZE);
2816 temp_dir[MAX_SETTING_SIZE-1] = 0;
2819 if( getenv("XDG_CONFIG_HOME") )
2820 config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
2821 else
2822 config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
2824 if (cfile)
2825 configfile = g_strdup(cfile);
2826 else
2827 configfile = g_strdup_printf(RCFILE);
2829 if (!g_thread_supported())
2830 g_thread_init(NULL);
2832 if (winid) {
2833 if (strncmp(winid, "0x", 2) == 0) {
2834 embed = strtol(winid, NULL, 16);
2835 } else {
2836 embed = atoi(winid);
2840 setup_modkeys();
2841 make_keyslist();
2842 setup_gui();
2843 #ifdef ENABLE_COOKIE_SUPPORT
2844 setup_cookies();
2845 #endif
2847 make_searchengines_list(searchengines, LENGTH(searchengines));
2848 make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers));
2850 /* Check if the specified file exists. */
2851 /* And only warn the user, if they explicitly asked for a config on the
2852 * command line.
2854 if (!(access(configfile, F_OK) == 0) && cfile) {
2855 echo_message(Info, "Config file '%s' doesn't exist", cfile);
2856 } else if ((access(configfile, F_OK) == 0))
2857 configfile_exists = true;
2859 /* read config file */
2860 /* But only report errors if we failed, and the file existed. */
2861 if ((SUCCESS != read_rcfile(configfile)) && configfile_exists) {
2862 echo_message(Error, "Error in config file '%s'", configfile);
2863 g_free(configfile);
2866 /* command line argument: URL */
2867 if (argc > 1) {
2868 strncpy(url, argv[argc - 1], 255);
2869 } else {
2870 strncpy(url, startpage, 255);
2873 a.i = TargetCurrent;
2874 a.s = url;
2875 open_arg(&a);
2876 gtk_main();
2878 mop_up();
2880 return EXIT_SUCCESS;