This removes the give_feedback function by a new a litte more flexible
[vimprobable2.git] / main.c
blob0f9b1e42c038de2fbb9692f4963771ca4ab1005c
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 update_url(const char *uri);
91 static void setup_modkeys(void);
92 static void setup_gui(void);
93 static void setup_settings(void);
94 static void setup_signals(void);
95 static void ascii_bar(int total, int state, char *string);
96 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
97 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
98 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
99 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
100 const char *bg_color_str, const char *fg_color_str);
102 static gboolean history(void);
103 static gboolean process_set_line(char *line);
104 void save_command_history(char *line);
105 void toggle_proxy(gboolean onoff);
106 void toggle_scrollbars(gboolean onoff);
107 void set_default_winsize(const char * const size);
109 gboolean process_keypress(GdkEventKey *event);
110 void fill_suggline(char * suggline, const char * command, const char *fill_with);
111 GtkWidget * fill_eventbox(const char * completion_line);
112 static void mop_up(void);
114 #include "main.h"
116 /* variables */
117 static GtkWindow *window;
118 static GtkWidget *viewport;
119 static GtkBox *box;
120 static GtkScrollbar *scroll_h;
121 static GtkScrollbar *scroll_v;
122 static GtkAdjustment *adjust_h;
123 static GtkAdjustment *adjust_v;
124 static GtkWidget *inputbox;
125 static GtkWidget *eventbox;
126 static GtkBox *statusbar;
127 static GtkWidget *status_url;
128 static GtkWidget *status_state;
129 static WebKitWebView *webview;
130 static SoupSession *session;
131 static GtkClipboard *clipboards[2];
132 static GdkKeymap *keymap;
134 static char **args;
135 static unsigned int mode = ModeNormal;
136 static unsigned int count = 0;
137 static float zoomstep;
138 char *modkeys;
139 static char current_modkey;
140 static char *search_handle;
141 static gboolean search_direction;
142 static gboolean echo_active = TRUE;
143 static WebKitWebInspector *inspector;
145 static GdkNativeWindow embed = 0;
146 static char *configfile = NULL;
147 static char *winid = NULL;
149 static char rememberedURI[1024] = "";
150 static char followTarget[8] = "";
151 char *error_msg = NULL;
152 char *config_base = NULL;
153 static gboolean manual_focus = FALSE;
155 GList *activeDownloads;
157 #include "config.h"
158 #include "keymap.h"
160 GList *commandhistory = NULL;
161 int commandpointer = 0;
163 KeyList *keylistroot = NULL;
165 /* Cookie support. */
166 #ifdef ENABLE_COOKIE_SUPPORT
167 static SoupCookieJar *session_cookie_jar = NULL;
168 static SoupCookieJar *file_cookie_jar = NULL;
169 static time_t cookie_timeout = 4800;
170 static char *cookie_store;
171 static void setup_cookies(void);
172 static char *get_cookies(SoupURI *soup_uri);
173 static void load_all_cookies(void);
174 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
175 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
176 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
177 #endif
178 /* callbacks */
179 void
180 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
181 quit(NULL);
184 void
185 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
186 gtk_window_set_title(window, title);
189 void
190 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
191 #ifdef ENABLE_GTK_PROGRESS_BAR
192 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
193 #endif
194 update_state();
197 #ifdef ENABLE_WGET_PROGRESS_BAR
198 void
199 ascii_bar(int total, int state, char *string) {
200 int i;
202 for (i = 0; i < state; i++)
203 string[i] = progressbartickchar;
204 string[i++] = progressbarcurrent;
205 for (; i < total; i++)
206 string[i] = progressbarspacer;
207 string[i] = '\0';
209 #endif
211 void
212 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
213 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
214 const char *uri = webkit_web_view_get_uri(webview);
216 update_url(uri);
217 script(&a);
218 g_free(a.s);
220 if (mode == ModeInsert || mode == ModeHints) {
221 Arg a = { .i = ModeNormal };
222 set(&a);
224 manual_focus = FALSE;
227 void
228 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
229 WebKitWebSettings *settings = webkit_web_view_get_settings(webview);
230 gboolean scripts;
232 g_object_get(settings, "enable-scripts", &scripts, NULL);
233 if (escape_input_on_load && scripts && !manual_focus && !gtk_widget_is_focus(inputbox)) {
234 Arg a = { .i = Silent, .s = g_strdup("hints.clearFocus();") };
235 script(&a);
236 g_free(a.s);
237 a.i = ModeNormal;
238 a.s = NULL;
239 set(&a);
241 if (HISTORY_MAX_ENTRIES > 0)
242 history();
243 update_state();
246 void
247 webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec) {
248 /* retrieve the URI of the temporary webview */
249 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(temp_view) };
250 /* clean up */
251 webkit_web_view_stop_loading(temp_view);
252 gtk_widget_destroy(GTK_WIDGET(temp_view));
253 /* open the requested window */
254 open_arg(&a);
257 static WebKitWebView *
258 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
259 if (rememberedURI != NULL && strlen(rememberedURI) > 0) {
260 if (strncmp(rememberedURI, "javascript:", 11) != 0) {
261 Arg a = { .i = TargetNew, .s = rememberedURI };
262 open_arg(&a);
263 return NULL;
266 /* create a temporary webview to execute the script in */
267 WebKitWebView *temp_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
268 /* wait until the new webview receives its new URI */
269 g_object_connect(temp_view, "signal::notify::uri", G_CALLBACK(webview_open_js_window_cb), NULL, NULL);
270 return temp_view;
273 gboolean
274 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
275 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
276 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
277 open_arg(&a);
278 webkit_web_policy_decision_ignore(decision);
279 return TRUE;
282 gboolean
283 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
284 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
285 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
286 webkit_web_policy_decision_download(decision);
287 return TRUE;
288 } else {
289 return FALSE;
293 static WebKitWebView*
294 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
295 gchar* inspector_title;
296 GtkWidget* inspector_window;
297 GtkWidget* inspector_view;
299 /* just enough code to show the inspector - no signal handling etc. */
300 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
301 if (embed) {
302 inspector_window = gtk_plug_new(embed);
303 } else {
304 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
305 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
307 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
308 g_free(inspector_title);
309 inspector_view = webkit_web_view_new();
310 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
311 gtk_widget_show_all(inspector_window);
312 return WEBKIT_WEB_VIEW(inspector_view);
315 gboolean
316 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
317 const gchar *filename;
318 gchar *uri, *path;
319 uint32_t size;
320 WebKitDownloadStatus status;
322 filename = webkit_download_get_suggested_filename(download);
323 if (filename == NULL || strlen(filename) == 0) {
324 filename = "vimprobable_download";
326 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
327 uri = g_strconcat("file://", path, NULL);
328 webkit_download_set_destination_uri(download, uri);
329 g_free(uri);
330 size = (uint32_t)webkit_download_get_total_size(download);
331 if (size > 0)
332 echo_message(Info, "Download %s started (expected size: %u bytes)...", filename, size);
333 else
334 echo_message(Info, "Download %s started (unknown size)...", filename);
335 activeDownloads = g_list_prepend(activeDownloads, download);
336 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
337 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
338 status = webkit_download_get_status(download);
339 if (status == WEBKIT_DOWNLOAD_STATUS_CREATED)
340 webkit_download_start(download);
341 update_state();
342 return TRUE;
345 gboolean
346 blank_cb(void) {
347 return TRUE;
350 void
351 download_progress(WebKitDownload *d, GParamSpec *pspec) {
352 WebKitDownloadStatus status = webkit_download_get_status(d);
354 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
355 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
356 echo_message(Error, "Error while downloading %s", webkit_download_get_suggested_filename(d));
357 } else {
358 echo_message(Info, "Download %s finished", webkit_download_get_suggested_filename(d));
360 activeDownloads = g_list_remove(activeDownloads, d);
362 update_state();
366 gboolean
367 process_keypress(GdkEventKey *event) {
368 KeyList *current;
369 guint keyval;
370 GdkModifierType irrelevant;
372 /* Get a mask of modifiers that shouldn't be considered for this event.
373 * E.g.: It shouldn't matter whether ';' is shifted or not. */
374 gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
375 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
377 current = keylistroot;
379 while (current != NULL) {
380 if (current->Element.mask == (CLEAN(event->state) & ~irrelevant)
381 && (current->Element.modkey == current_modkey
382 || (!current->Element.modkey && !current_modkey)
383 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
384 && current->Element.key == keyval
385 && current->Element.func)
386 if (current->Element.func(&current->Element.arg)) {
387 current_modkey = count = 0;
388 update_state();
389 return TRUE;
391 current = current->next;
393 return FALSE;
396 gboolean
397 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
398 Arg a = { .i = ModeNormal, .s = NULL };
399 guint keyval;
400 GdkModifierType irrelevant;
402 /* Get a mask of modifiers that shouldn't be considered for this event.
403 * E.g.: It shouldn't matter whether ';' is shifted or not. */
404 gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
405 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
407 switch (mode) {
408 case ModeNormal:
409 if ((CLEAN(event->state) & ~irrelevant) == 0) {
410 if (IS_ESCAPE(event)) {
411 echo_message(Info, "");
412 g_free(a.s);
413 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
414 || (event->keyval == GDK_0 && count))) {
415 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
416 update_state();
417 return TRUE;
418 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
419 current_modkey = event->keyval;
420 update_state();
421 return TRUE;
424 /* keybindings */
425 if (process_keypress(event) == TRUE) return TRUE;
427 break;
428 case ModeInsert:
429 if (IS_ESCAPE(event)) {
430 a.i = Silent;
431 a.s = g_strdup("hints.clearFocus();");
432 script(&a);
433 g_free(a.s);
434 a.i = ModeNormal;
435 return set(&a);
436 } else if (CLEAN(event->state) & GDK_CONTROL_MASK) {
437 /* keybindings of non-printable characters */
438 if (process_keypress(event) == TRUE) return TRUE;
440 case ModePassThrough:
441 if (IS_ESCAPE(event)) {
442 echo_message(Info, "");
443 set(&a);
444 return TRUE;
446 break;
447 case ModeSendKey:
448 echo_message(Info, "");
449 set(&a);
450 break;
452 return FALSE;
455 void
456 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
457 const char *fg_color_str) {
458 GdkColor fg_color;
459 GdkColor bg_color;
460 PangoFontDescription *font;
462 font = pango_font_description_from_string(font_str);
463 gtk_widget_modify_font(widget, font);
464 pango_font_description_free(font);
466 if (fg_color_str)
467 gdk_color_parse(fg_color_str, &fg_color);
468 if (bg_color_str)
469 gdk_color_parse(bg_color_str, &bg_color);
471 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
472 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
474 return;
477 void
478 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
479 const char *uri = webkit_web_view_get_uri(webview);
480 char *markup;
482 memset(rememberedURI, 0, 1024);
483 if (link) {
484 markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link);
485 gtk_label_set_markup(GTK_LABEL(status_url), markup);
486 strncpy(rememberedURI, link, 1024);
487 g_free(markup);
488 } else
489 update_url(uri);
492 gboolean
493 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
494 Arg a;
496 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
497 if (gtk_window_has_toplevel_focus(window)) {
498 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
499 a.i = ModeNormal;
500 return set(&a);
501 } else if (!strcmp(message, "insertmode_on")) {
502 a.i = ModeInsert;
503 return set(&a);
506 return FALSE;
509 void
510 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
511 char *text;
512 guint16 length = gtk_entry_get_text_length(entry);
513 Arg a;
514 gboolean success = FALSE, forward = FALSE;
516 a.i = HideCompletion;
517 complete(&a);
518 if (length == 0)
519 return;
520 text = (char*)gtk_entry_get_text(entry);
521 if (length > 1 && text[0] == ':') {
522 success = process_line((text + 1));
523 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
524 webkit_web_view_unmark_text_matches(webview);
525 #ifdef ENABLE_MATCH_HIGHLITING
526 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
527 webkit_web_view_set_highlight_text_matches(webview, TRUE);
528 #endif
529 count = 0;
530 #ifndef ENABLE_INCREMENTAL_SEARCH
531 a.s =& text[1];
532 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
533 search(&a);
534 #else
535 search_direction = forward;
536 search_handle = g_strdup(&text[1]);
537 #endif
538 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
539 a.i = Silent;
540 a.s = g_strdup_printf("hints.fire();");
541 script(&a);
542 g_free(a.s);
543 update_state();
544 } else
545 return;
546 if (!echo_active)
547 gtk_entry_set_text(entry, "");
548 gtk_widget_grab_focus(GTK_WIDGET(webview));
551 gboolean
552 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
553 Arg a;
554 int numval;
556 if (mode == ModeHints) {
557 if (event->keyval == GDK_Tab) {
558 a.i = Silent;
559 a.s = g_strdup_printf("hints.focusNextHint();");
560 script(&a);
561 g_free(a.s);
562 update_state();
563 return TRUE;
565 if (event->keyval == GDK_ISO_Left_Tab) {
566 a.i = Silent;
567 a.s = g_strdup_printf("hints.focusPreviousHint();");
568 script(&a);
569 g_free(a.s);
570 update_state();
571 return TRUE;
573 if (event->keyval == GDK_Return) {
574 a.i = Silent;
575 a.s = g_strdup_printf("hints.fire();");
576 script(&a);
577 g_free(a.s);
578 update_state();
579 return TRUE;
582 switch (event->keyval) {
583 case GDK_bracketleft:
584 case GDK_Escape:
585 if (!IS_ESCAPE(event)) break;
586 a.i = HideCompletion;
587 complete(&a);
588 a.i = ModeNormal;
589 commandpointer = 0;
590 return set(&a);
591 break;
592 case GDK_Tab:
593 a.i = DirectionNext;
594 return complete(&a);
595 break;
596 case GDK_Up:
597 a.i = DirectionPrev;
598 return commandhistoryfetch(&a);
599 break;
600 case GDK_Down:
601 a.i = DirectionNext;
602 return commandhistoryfetch(&a);
603 break;
604 case GDK_ISO_Left_Tab:
605 a.i = DirectionPrev;
606 return complete(&a);
607 break;
610 if (mode == ModeHints) {
611 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
612 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
613 (event->keyval == GDK_BackSpace)) {
614 count /= 10;
615 a.i = Silent;
616 a.s = g_strdup_printf("hints.updateHints(%d);", count);
617 script(&a);
618 g_free(a.s);
619 update_state();
620 return TRUE;
623 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
624 if ((numval >= 1 && numval <= 9) || (numval == 0 && count)) {
625 /* allow a zero as non-first number */
626 count = (count ? count * 10 : 0) + numval;
627 a.i = Silent;
628 a.s = g_strdup_printf("hints.updateHints(%d);", count);
629 script(&a);
630 g_free(a.s);
631 update_state();
632 return TRUE;
636 return FALSE;
639 gboolean
640 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
641 int i;
642 WebKitHitTestResult *result;
643 WebKitHitTestResultContext context;
644 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
645 /* handle mouse click events */
646 for (i = 0; i < LENGTH(mouse); i++) {
647 if (mouse[i].mask == CLEAN(event->button.state)
648 && (mouse[i].modkey == current_modkey
649 || (!mouse[i].modkey && !current_modkey)
650 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
651 && mouse[i].button == event->button.button
652 && mouse[i].func) {
653 if (mouse[i].func(&mouse[i].arg)) {
654 current_modkey = count = 0;
655 update_state();
656 return TRUE;
660 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
661 g_object_get(result, "context", &context, NULL);
662 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
663 Arg a = { .i = ModeInsert };
664 set(&a);
665 manual_focus = TRUE;
667 } else if (mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) {
668 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
669 g_object_get(result, "context", &context, NULL);
670 if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) {
671 Arg a = { .i = ModeNormal };
672 set(&a);
674 } else {
675 gchar *value = NULL, *message = NULL;
676 jsapi_evaluate_script("window.getSelection().focusNode", &value, &message);
677 if (value && !strcmp(value, "[object HTMLFormElement]")) {
678 Arg a = { .i = ModeInsert, .s = NULL };
679 set(&a);
680 manual_focus = TRUE;
682 g_free(value);
683 g_free(message);
685 return FALSE;
688 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
689 Arg a;
690 guint16 length = gtk_entry_get_text_length(entry);
692 if (!length) {
693 a.i = HideCompletion;
694 complete(&a);
695 a.i = ModeNormal;
696 return set(&a);
698 return FALSE;
701 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
702 Arg a;
703 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
704 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
705 gboolean forward = FALSE;
707 /* Update incremental search if the user changes the search text.
709 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
710 * from the user. But if the entry is focused and the text is set
711 * through gtk_entry_set_text() in some asyncrounous operation,
712 * I would consider that a bug.
715 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
716 webkit_web_view_unmark_text_matches(webview);
717 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
718 return TRUE;
719 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
720 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
721 a.i = Silent;
722 switch (text[0]) {
723 case '.':
724 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
725 break;
727 case ',':
728 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
729 break;
731 case ';':
732 a.s = NULL;
733 switch (text[1]) {
734 case 's':
735 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
736 break;
737 case 'y':
738 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
739 break;
740 case 'o':
741 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
742 break;
743 case 't': case 'w':
744 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
745 break;
746 case 'O': case 'T': case 'W':
747 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
748 break;
749 case 'i':
750 a.s = g_strconcat("hints.createHints('", text + 2, "', 'i');", NULL);
751 break;
752 case 'I':
753 a.s = g_strconcat("hints.createHints('", text + 2, "', 'I');", NULL);
754 break;
756 break;
758 count = 0;
759 if (a.s) {
760 script(&a);
761 g_free(a.s);
764 return TRUE;
765 } else if (length == 0 && followTarget[0]) {
766 mode = ModeNormal;
767 a.i = Silent;
768 a.s = g_strdup("hints.clearHints();");
769 script(&a);
770 g_free(a.s);
771 count = 0;
772 update_state();
775 return FALSE;
778 /* funcs here */
780 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
781 memset(suggline, 0, 512);
782 strncpy(suggline, command, 512);
783 strncat(suggline, " ", 1);
784 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
787 GtkWidget * fill_eventbox(const char * completion_line) {
788 GtkBox * row;
789 GtkWidget *row_eventbox, *el;
790 GdkColor color;
791 char *markup, *markup_tmp;
793 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
794 row_eventbox = gtk_event_box_new();
795 gdk_color_parse(completionbgcolor[0], &color);
796 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
797 el = gtk_label_new(NULL);
798 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
799 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
800 markup_tmp, "</span>", NULL);
801 gtk_label_set_markup(GTK_LABEL(el), markup);
802 g_free(markup_tmp);
803 g_free(markup);
804 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
805 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
806 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
807 return row_eventbox;
810 gboolean
811 complete(const Arg *arg) {
812 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
813 size_t listlen, len, cmdlen;
814 int i, spacepos;
815 Listelement *elementlist = NULL, *elementpointer;
816 gboolean highlight = FALSE;
817 GtkBox *row;
818 GtkWidget *row_eventbox, *el;
819 GtkBox *_table;
820 GdkColor color;
821 static GtkWidget *table, *top_border;
822 static char *prefix;
823 static char **suggestions;
824 static GtkWidget **widgets;
825 static int n = 0, m, current = -1;
827 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
828 len = strlen(str);
830 /* Get the length of the list of commands for completion. We need this to
831 * malloc/realloc correctly.
833 listlen = LENGTH(commands);
835 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
836 return TRUE;
837 if (prefix) {
838 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
839 gdk_color_parse(completionbgcolor[0], &color);
840 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
841 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
842 if ((arg->i == DirectionNext && current == 0)
843 || (arg->i == DirectionPrev && current == n - 1))
844 current = -1;
845 } else {
846 free(widgets);
847 free(suggestions);
848 free(prefix);
849 gtk_widget_destroy(GTK_WIDGET(table));
850 gtk_widget_destroy(GTK_WIDGET(top_border));
851 table = NULL;
852 widgets = NULL;
853 suggestions = NULL;
854 prefix = NULL;
855 n = 0;
856 current = -1;
857 if (arg->i == HideCompletion)
858 return TRUE;
860 } else if (arg->i == HideCompletion)
861 return TRUE;
862 if (!widgets) {
863 prefix = g_strdup(str);
864 widgets = malloc(sizeof(GtkWidget*) * listlen);
865 suggestions = malloc(sizeof(char*) * listlen);
866 top_border = gtk_event_box_new();
867 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
868 gdk_color_parse(completioncolor[2], &color);
869 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
870 table = gtk_event_box_new();
871 gdk_color_parse(completionbgcolor[0], &color);
872 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
873 highlight = len > 1;
874 if (strchr(str, ' ') == NULL) {
875 /* command completion */
876 listlen = LENGTH(commands);
877 for (i = 0; i < listlen; i++) {
878 if (commands[i].cmd == NULL)
879 break;
880 cmdlen = strlen(commands[i].cmd);
881 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
882 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
883 if (highlight) {
884 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
885 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
886 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
887 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
889 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
890 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
891 row_eventbox = gtk_event_box_new();
892 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
893 el = gtk_label_new(NULL);
894 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
895 free(s);
896 gtk_label_set_markup(GTK_LABEL(el), markup);
897 g_free(markup);
898 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
899 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
900 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
901 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
902 suggestions[n] = commands[i].cmd;
903 widgets[n++] = row_eventbox;
906 } else {
907 entry = (char *)malloc(512 * sizeof(char));
908 if (entry == NULL) {
909 return FALSE;
911 memset(entry, 0, 512);
912 suggurls = malloc(sizeof(char*) * listlen);
913 if (suggurls == NULL) {
914 return FALSE;
916 spacepos = strcspn(str, " ");
917 searchfor = (str + spacepos + 1);
918 strncpy(command, (str + 1), spacepos - 1);
919 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
920 /* browser settings */
921 listlen = LENGTH(browsersettings);
922 for (i = 0; i < listlen; i++) {
923 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
924 /* match */
925 fill_suggline(suggline, command, browsersettings[i].name);
926 /* FIXME(HP): This memory is never freed */
927 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
928 strncpy(suggurls[n], suggline, 512);
929 suggestions[n] = suggurls[n];
930 row_eventbox = fill_eventbox(suggline);
931 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
932 widgets[n++] = row_eventbox;
936 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
937 /* completion on tags */
938 spacepos = strcspn(str, " ");
939 searchfor = (str + spacepos + 1);
940 elementlist = complete_list(searchfor, 1, elementlist);
941 } else {
942 /* URL completion: bookmarks */
943 elementlist = complete_list(searchfor, 0, elementlist);
944 m = count_list(elementlist);
945 if (m < MAX_LIST_SIZE) {
946 /* URL completion: history */
947 elementlist = complete_list(searchfor, 2, elementlist);
950 elementpointer = elementlist;
951 while (elementpointer != NULL) {
952 fill_suggline(suggline, command, elementpointer->element);
953 /* FIXME(HP): This memory is never freed */
954 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
955 strncpy(suggurls[n], suggline, 512);
956 suggestions[n] = suggurls[n];
957 row_eventbox = fill_eventbox(suggline);
958 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
959 widgets[n++] = row_eventbox;
960 elementpointer = elementpointer->next;
961 if (n >= MAX_LIST_SIZE)
962 break;
964 free_list(elementlist);
965 if (suggurls != NULL) {
966 free(suggurls);
967 suggurls = NULL;
969 if (entry != NULL) {
970 free(entry);
971 entry = NULL;
974 /* TA: FIXME - this needs rethinking entirely. */
976 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
977 if (widgets_temp == NULL && widgets == NULL) {
978 fprintf(stderr, "Couldn't realloc() widgets\n");
979 exit(1);
981 widgets = widgets_temp;
982 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
983 if (suggestions_temp == NULL && suggestions == NULL) {
984 fprintf(stderr, "Couldn't realloc() suggestions\n");
985 exit(1);
987 suggestions = suggestions_temp;
989 if (!n) {
990 gdk_color_parse(completionbgcolor[1], &color);
991 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
992 el = gtk_label_new(NULL);
993 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
994 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
995 gtk_label_set_markup(GTK_LABEL(el), markup);
996 g_free(markup);
997 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
999 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
1000 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
1001 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
1002 gtk_widget_show_all(GTK_WIDGET(window));
1003 if (!n)
1004 return TRUE;
1005 current = arg->i == DirectionPrev ? n - 1 : 0;
1007 if (current != -1) {
1008 gdk_color_parse(completionbgcolor[2], &color);
1009 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
1010 s = g_strconcat(":", suggestions[current], NULL);
1011 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
1012 g_free(s);
1013 } else
1014 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
1015 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1016 return TRUE;
1019 gboolean
1020 descend(const Arg *arg) {
1021 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
1022 int i, len;
1023 count = count ? count : 1;
1025 if (!source)
1026 return TRUE;
1027 if (arg->i == Rootdir) {
1028 for (i = 0; i < 3; i++) /* get to the third slash */
1029 if (!(p = strchr(++p, '/')))
1030 return TRUE; /* if we cannot find it quit */
1031 } else {
1032 len = strlen(source);
1033 if (!len) /* if string is empty quit */
1034 return TRUE;
1035 p = source + len; /* start at the end */
1036 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
1037 ++count;
1038 for (i = 0; i < count; i++)
1039 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
1040 if (p == source) /* if we reach the first char pointer quit */
1041 return TRUE;
1042 ++p; /* since we do p-- in the while, we are pointing at
1043 the char before the slash, so +1 */
1045 len = p - source + 1; /* new length = end - start + 1 */
1046 new = malloc(len + 1);
1047 memcpy(new, source, len);
1048 new[len] = '\0';
1049 webkit_web_view_load_uri(webview, new);
1050 free(new);
1051 return TRUE;
1054 gboolean
1055 echo(const Arg *arg) {
1056 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
1058 if (index < Info || index > Error)
1059 return TRUE;
1061 if (!gtk_widget_is_focus(GTK_WIDGET(inputbox))) {
1062 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1063 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
1066 return TRUE;
1069 static gboolean
1070 open_inspector(const Arg * arg) {
1071 gboolean inspect_enabled;
1072 WebKitWebSettings *settings;
1073 Arg a;
1075 settings = webkit_web_view_get_settings(webview);
1076 g_object_get(G_OBJECT(settings), "enable-developer-extras", &inspect_enabled, NULL);
1077 if (inspect_enabled) {
1078 webkit_web_inspector_show(inspector);
1079 } else {
1080 a.i = Error;
1081 a.s = g_strdup("Webinspector is not enabled");
1082 echo(&a);
1083 g_free(a.s);
1087 gboolean
1088 input(const Arg *arg) {
1089 int pos = 0;
1090 count = 0;
1091 const char *url;
1092 int index = Info;
1093 Arg a;
1095 /* if inputbox hidden, show it again */
1096 if (!gtk_widget_get_visible(inputbox))
1097 gtk_widget_set_visible(inputbox, TRUE);
1099 update_state();
1101 /* Set the colour and font back to the default, so that we don't still
1102 * maintain a red colour from a warning from an end of search indicator,
1103 * etc.
1105 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1107 /* to avoid things like :open URL :open URL2 or :open :open URL */
1108 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1109 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1110 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
1111 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1113 gtk_widget_grab_focus(inputbox);
1114 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1116 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1117 mode = ModeHints;
1118 memset(followTarget, 0, 8);
1119 strncpy(followTarget, "current", 8);
1120 a.i = Silent;
1121 switch (arg->s[0]) {
1122 case '.':
1123 a.s = g_strdup("hints.createHints('', 'f');");
1124 break;
1126 case ',':
1127 a.s = g_strdup("hints.createHints('', 'F');");
1128 break;
1130 case ';':
1131 a.s = NULL;
1132 if (arg->s[1]) {
1133 switch (arg->s[1]) {
1134 case 's':
1135 a.s = g_strdup("hints.createHints('', 's');");
1136 break;
1137 case 'y':
1138 a.s = g_strdup("hints.createHints('', 'y');");
1139 break;
1140 case 'o':
1141 a.s = g_strdup("hints.createHints('', 'f');");
1142 break;
1143 case 't': case 'w':
1144 a.s = g_strdup("hints.createHints('', 'F');");
1145 break;
1146 case 'O': case 'T': case 'W':
1147 a.s = g_strdup("hints.createHints('', 'O');");
1148 break;
1149 case 'i':
1150 a.s = g_strdup("hints.createHints('', 'i');");
1151 break;
1152 case 'I':
1153 a.s = g_strdup("hints.createHints('', 'I');");
1154 break;
1157 break;
1159 count = 0;
1160 if (a.s) {
1161 script(&a);
1162 g_free(a.s);
1166 return TRUE;
1169 gboolean
1170 navigate(const Arg *arg) {
1171 if (arg->i & NavigationForwardBack)
1172 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
1173 else if (arg->i & NavigationReloadActions)
1174 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
1175 else
1176 webkit_web_view_stop_loading(webview);
1177 return TRUE;
1180 gboolean
1181 number(const Arg *arg) {
1182 const char *source = webkit_web_view_get_uri(webview);
1183 char *uri, *p, *new;
1184 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1186 if (!source)
1187 return TRUE;
1188 uri = g_strdup(source); /* copy string */
1189 p =& uri[0];
1190 while(*p != '\0') /* goto the end of the string */
1191 ++p;
1192 --p;
1193 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1194 --p;
1195 if (*(++p) == '\0') { /* if no numbers were found abort */
1196 free(uri);
1197 return TRUE;
1199 number = atoi(p) + diff; /* apply diff on number */
1200 *p = '\0';
1201 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1202 webkit_web_view_load_uri(webview, new);
1203 g_free(new);
1204 free(uri);
1205 return TRUE;
1208 gboolean
1209 open_arg(const Arg *arg) {
1210 char *argv[64];
1211 char *s = arg->s, *p = NULL, *new;
1212 Arg a = { .i = NavigationReload };
1213 int len;
1214 char *search_uri, *search_term;
1216 if (embed) {
1217 argv[0] = *args;
1218 argv[1] = "-e";
1219 argv[2] = winid;
1220 argv[3] = arg->s;
1221 argv[4] = NULL;
1222 } else {
1223 argv[0] = *args;
1224 argv[1] = arg->s;
1225 argv[2] = NULL;
1228 if (!arg->s)
1229 navigate(&a);
1230 else if (arg->i == TargetCurrent) {
1231 while(*s == ' ') /* strip leading whitespace */
1232 ++s;
1233 p = (s + strlen(s) - 1);
1234 while(*p == ' ') /* strip trailing whitespace */
1235 --p;
1236 *(p + 1) = '\0';
1237 len = strlen(s);
1238 new = NULL;
1239 /* check for external handlers */
1240 if (open_handler(s))
1241 return TRUE;
1242 /* check for search engines */
1243 p = strchr(s, ' ');
1244 if (p) { /* check for search engines */
1245 *p = '\0';
1246 search_uri = find_uri_for_searchengine(s);
1247 if (search_uri != NULL) {
1248 search_term = soup_uri_encode(p+1, "&");
1249 new = g_strdup_printf(search_uri, search_term);
1250 g_free(search_term);
1252 *p = ' ';
1254 if (!new) {
1255 if (len > 3 && strstr(s, "://")) { /* valid url? */
1256 p = new = g_malloc(len + 1);
1257 while(*s != '\0') { /* strip whitespaces */
1258 if (*s != ' ')
1259 *(p++) = *s;
1260 ++s;
1262 *p = '\0';
1263 } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */
1264 new = g_malloc(sizeof("file://") + len);
1265 strcpy(new, "file://");
1266 memcpy(&new[sizeof("file://") - 1], s, len + 1);
1267 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1268 search_uri = find_uri_for_searchengine(defaultsearch);
1269 if (search_uri != NULL) {
1270 search_term = soup_uri_encode(s, "&");
1271 new = g_strdup_printf(search_uri, search_term);
1272 g_free(search_term);
1274 } else { /* prepend "http://" */
1275 new = g_malloc(sizeof("http://") + len);
1276 strcpy(new, "http://");
1277 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1280 webkit_web_view_load_uri(webview, new);
1281 g_free(new);
1282 } else
1283 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1284 return TRUE;
1287 gboolean
1288 open_remembered(const Arg *arg)
1290 Arg a = {arg->i, rememberedURI};
1292 if (strcmp(rememberedURI, "")) {
1293 open_arg(&a);
1295 return TRUE;
1298 gboolean
1299 yank(const Arg *arg) {
1300 const char *url, *content;
1302 if (arg->i & SourceSelection) {
1303 webkit_web_view_copy_clipboard(webview);
1304 if (arg->i & ClipboardPrimary)
1305 content = gtk_clipboard_wait_for_text(clipboards[0]);
1306 if (!content && arg->i & ClipboardGTK)
1307 content = gtk_clipboard_wait_for_text(clipboards[1]);
1308 if (content) {
1309 echo_message(Info, "Yanked %s", content);
1310 g_free((gpointer *)content);
1312 } else {
1313 if (arg->i & SourceURL) {
1314 url = webkit_web_view_get_uri(webview);
1315 } else {
1316 url = arg->s;
1318 if (!url)
1319 return TRUE;
1321 echo_message(Info, "Yanked %s", url);
1322 if (arg->i & ClipboardPrimary)
1323 gtk_clipboard_set_text(clipboards[0], url, -1);
1324 if (arg->i & ClipboardGTK)
1325 gtk_clipboard_set_text(clipboards[1], url, -1);
1327 return TRUE;
1330 gboolean
1331 paste(const Arg *arg) {
1332 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1334 /* If we're over a link, open it in a new target. */
1335 if (strlen(rememberedURI) > 0) {
1336 Arg new_target = { .i = TargetNew, .s = arg->s };
1337 open_arg(&new_target);
1338 return TRUE;
1341 if (arg->i & ClipboardPrimary)
1342 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1343 if (!a.s && arg->i & ClipboardGTK)
1344 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1345 if (a.s) {
1346 open_arg(&a);
1347 g_free(a.s);
1349 return TRUE;
1352 gboolean
1353 quit(const Arg *arg) {
1354 FILE *f;
1355 const char *filename;
1356 const char *uri = webkit_web_view_get_uri(webview);
1357 if (uri != NULL) {
1358 /* write last URL into status file for recreation with "u" */
1359 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1360 f = fopen(filename, "w");
1361 g_free((gpointer *)filename);
1362 if (f != NULL) {
1363 fprintf(f, "%s", uri);
1364 fclose(f);
1367 gtk_main_quit();
1368 return TRUE;
1371 gboolean
1372 revive(const Arg *arg) {
1373 FILE *f;
1374 const char *filename;
1375 char buffer[512] = "";
1376 Arg a = { .i = TargetNew, .s = NULL };
1377 /* get the URL of the window which has been closed last */
1378 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1379 f = fopen(filename, "r");
1380 g_free((gpointer *)filename);
1381 if (f != NULL) {
1382 fgets(buffer, 512, f);
1383 fclose(f);
1385 if (strlen(buffer) > 0) {
1386 a.s = buffer;
1387 open_arg(&a);
1388 return TRUE;
1390 return FALSE;
1393 static
1394 gboolean print_frame(const Arg *arg)
1396 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1397 webkit_web_frame_print (frame);
1398 return TRUE;
1401 gboolean
1402 search(const Arg *arg) {
1403 count = count ? count : 1;
1404 gboolean success, direction = arg->i & DirectionPrev;
1406 if (arg->s) {
1407 free(search_handle);
1408 search_handle = g_strdup(arg->s);
1410 if (!search_handle)
1411 return TRUE;
1412 if (arg->i & DirectionAbsolute)
1413 search_direction = direction;
1414 else
1415 direction ^= search_direction;
1416 do {
1417 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1418 if (!success) {
1419 if (arg->i & Wrapping) {
1420 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1421 if (success) {
1422 echo_message(Warning, "search hit %s, continuing at %s",
1423 direction ? "BOTTOM" : "TOP",
1424 direction ? "TOP" : "BOTTOM");
1425 } else
1426 break;
1427 } else
1428 break;
1430 } while(--count);
1431 if (!success) {
1432 echo_message(Error, "Pattern not found: %s", search_handle);
1434 return TRUE;
1437 gboolean
1438 set(const Arg *arg) {
1439 switch (arg->i) {
1440 case ModeNormal:
1441 if (search_handle) {
1442 search_handle = NULL;
1443 webkit_web_view_unmark_text_matches(webview);
1445 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1446 gtk_widget_grab_focus(GTK_WIDGET(webview));
1447 break;
1448 case ModePassThrough:
1449 echo_message(Info | NoAutoHide, "-- PASS THROUGH --");
1450 break;
1451 case ModeSendKey:
1452 echo_message(Info | NoAutoHide, "-- PASS TROUGH (next) --");
1453 break;
1454 case ModeInsert: /* should not be called manually but automatically */
1455 echo_message(Info | NoAutoHide, "-- INSERT --");
1456 break;
1457 default:
1458 return TRUE;
1460 mode = arg->i;
1461 return TRUE;
1464 gchar*
1465 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1466 JSStringRef string_ref;
1467 gchar *string;
1468 size_t length;
1470 string_ref = JSValueToStringCopy(context, ref, NULL);
1471 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1472 string = g_new(gchar, length);
1473 JSStringGetUTF8CString(string_ref, string, length);
1474 JSStringRelease(string_ref);
1475 return string;
1478 void
1479 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1480 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1481 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1482 JSStringRef str;
1483 JSValueRef val, exception;
1485 str = JSStringCreateWithUTF8CString(script);
1486 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1487 JSStringRelease(str);
1488 if (!val)
1489 *message = jsapi_ref_to_string(context, exception);
1490 else
1491 *value = jsapi_ref_to_string(context, val);
1494 gboolean
1495 quickmark(const Arg *a) {
1496 int i, b;
1497 b = atoi(a->s);
1498 char *fn = g_strdup_printf(QUICKMARK_FILE);
1499 FILE *fp;
1500 fp = fopen(fn, "r");
1501 g_free(fn);
1502 fn = NULL;
1503 char buf[100];
1505 if (fp != NULL && b < 10) {
1506 for( i=0; i < b; ++i ) {
1507 if (feof(fp)) {
1508 break;
1510 fgets(buf, 100, fp);
1512 char *ptr = strrchr(buf, '\n');
1513 *ptr = '\0';
1514 if (strlen(buf)) {
1515 Arg x = { .s = buf };
1516 return open_arg(&x);
1517 } else {
1518 echo_message(Error, "Quickmark %d not defined", b);
1519 return false;
1521 } else { return false; }
1524 gboolean
1525 script(const Arg *arg) {
1526 gchar *value = NULL, *message = NULL;
1527 char text[1024] = "";
1528 Arg a;
1529 WebKitNetworkRequest *request;
1530 WebKitDownload *download;
1532 if (!arg->s) {
1533 set_error("Missing argument.");
1534 return FALSE;
1536 jsapi_evaluate_script(arg->s, &value, &message);
1537 if (message) {
1538 set_error(message);
1539 g_free(message);
1540 return FALSE;
1542 g_free(message);
1543 if (arg->i != Silent && value) {
1544 echo_message(arg->i, value);
1546 /* switch mode according to scripts return value */
1547 if (value) {
1548 if (strncmp(value, "done;", 5) == 0) {
1549 a.i = ModeNormal;
1550 set(&a);
1551 } else if (strncmp(value, "insert;", 7) == 0) {
1552 a.i = ModeInsert;
1553 set(&a);
1554 manual_focus = TRUE;
1555 } else if (strncmp(value, "save;", 5) == 0) {
1556 /* forced download */
1557 a.i = ModeNormal;
1558 set(&a);
1559 request = webkit_network_request_new((value + 5));
1560 download = webkit_download_new(request);
1561 webview_download_cb(webview, download, (gpointer *)NULL);
1562 } else if (strncmp(value, "yank;", 5) == 0) {
1563 /* yank link URL to clipboard */
1564 a.i = ModeNormal;
1565 set(&a);
1566 a.i = ClipboardPrimary | ClipboardGTK;
1567 a.s = (value + 5);
1568 yank(&a);
1569 } else if (strncmp(value, "colon;", 6) == 0) {
1570 /* use link URL for colon command */
1571 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(inputbox)), 1023);
1572 a.i = ModeNormal;
1573 set(&a);
1574 switch (text[1]) {
1575 case 'O':
1576 a.s = g_strconcat(":open ", (value + 6), NULL);
1577 break;
1578 case 'T': case 'W':
1579 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1580 break;
1582 if (a.s) {
1583 input(&a);
1584 g_free(a.s);
1586 } else if (strncmp(value, "error;", 6) == 0) {
1587 a.i = Error;
1588 set(&a);
1591 g_free(value);
1592 return TRUE;
1595 gboolean
1596 scroll(const Arg *arg) {
1597 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1598 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1599 float val = gtk_adjustment_get_value(adjust) / max * 100;
1600 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1602 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1603 if (arg->i & ScrollMove)
1604 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1605 direction * /* direction */
1606 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1607 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1608 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1609 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1610 else
1611 gtk_adjustment_set_value(adjust,
1612 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1613 update_state();
1615 return TRUE;
1618 gboolean
1619 zoom(const Arg *arg) {
1620 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1621 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1622 webkit_web_view_get_zoom_level(webview) +
1623 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1624 (count ? (float)count / 100.0 : 1.0));
1625 return TRUE;
1628 gboolean
1629 fake_key_event(const Arg *a) {
1630 if(!embed) {
1631 return FALSE;
1633 Display *xdpy;
1634 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1635 echo_message(Error, "Couldn't find the XDisplay.");
1636 return FALSE;
1639 XKeyEvent xk;
1640 xk.display = xdpy;
1641 xk.subwindow = None;
1642 xk.time = CurrentTime;
1643 xk.same_screen = True;
1644 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1645 xk.window = embed;
1646 xk.state = a->i;
1648 if( ! a->s ) {
1649 echo_message(Error, "Zero pointer as argument! Check your config.h");
1650 return FALSE;
1653 KeySym keysym;
1654 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1655 echo_message(Error, "Couldn't translate %s to keysym", a->s );
1656 return FALSE;
1659 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1660 echo_message(Error, "Couldn't translate keysym to keycode");
1661 return FALSE;
1664 xk.type = KeyPress;
1665 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1666 echo_message(Error, "XSendEvent failed");
1667 return FALSE;
1669 XFlush(xdpy);
1671 return TRUE;
1674 gboolean
1675 commandhistoryfetch(const Arg *arg) {
1676 const int length = g_list_length(commandhistory);
1678 if (length > 0) {
1679 if (arg->i == DirectionPrev) {
1680 commandpointer = (length + commandpointer - 1) % length;
1681 } else {
1682 commandpointer = (length + commandpointer + 1) % length;
1685 const char* command = (char *)g_list_nth_data(commandhistory, commandpointer);
1686 gtk_entry_set_text(GTK_ENTRY(inputbox), g_strconcat(":", command, NULL));
1687 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1688 return TRUE;
1691 return FALSE;
1694 gboolean
1695 bookmark(const Arg *arg) {
1696 FILE *f;
1697 const char *filename;
1698 const char *uri = webkit_web_view_get_uri(webview);
1699 const char *title = webkit_web_view_get_title(webview);
1700 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1701 f = fopen(filename, "a");
1702 g_free((gpointer *)filename);
1703 if (uri == NULL || strlen(uri) == 0) {
1704 set_error("No URI found to bookmark.");
1705 return FALSE;
1707 if (f != NULL) {
1708 fprintf(f, "%s", uri);
1709 if (title != NULL) {
1710 fprintf(f, "%s", " ");
1711 fprintf(f, "%s", title);
1713 if (arg->s && strlen(arg->s)) {
1714 build_taglist(arg, f);
1716 fprintf(f, "%s", "\n");
1717 fclose(f);
1718 echo_message(Info, "Bookmark saved");
1719 return TRUE;
1720 } else {
1721 set_error("Bookmarks file not found.");
1722 return FALSE;
1726 gboolean
1727 history() {
1728 FILE *f;
1729 const char *filename;
1730 const char *uri = webkit_web_view_get_uri(webview);
1731 const char *title = webkit_web_view_get_title(webview);
1732 char *entry, buffer[512], *new;
1733 int n, i = 0;
1734 gboolean finished = FALSE;
1735 if (uri != NULL) {
1736 if (title != NULL) {
1737 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1738 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1739 } else {
1740 entry = malloc((strlen(uri) + 1) * sizeof(char));
1741 memset(entry, 0, strlen(uri) + 1);
1743 if (entry != NULL) {
1744 strncpy(entry, uri, strlen(uri));
1745 if (title != NULL) {
1746 strncat(entry, " ", 1);
1747 strncat(entry, title, strlen(title));
1749 n = strlen(entry);
1750 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1751 f = fopen(filename, "r");
1752 if (f != NULL) {
1753 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1754 if (new != NULL) {
1755 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1756 /* newest entries go on top */
1757 strncpy(new, entry, strlen(entry));
1758 strncat(new, "\n", 1);
1759 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1760 while (finished != TRUE) {
1761 if ((char *)NULL == fgets(buffer, 512, f)) {
1762 /* check if end of file was reached / error occured */
1763 if (!feof(f)) {
1764 break;
1766 /* end of file reached */
1767 finished = TRUE;
1768 continue;
1770 /* compare line (-1 because of newline character) */
1771 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1772 /* if the URI is already in history; we put it on top and skip it here */
1773 strncat(new, buffer, 512);
1774 i++;
1776 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1777 break;
1780 fclose(f);
1782 f = fopen(filename, "w");
1783 g_free((gpointer *)filename);
1784 if (f != NULL) {
1785 fprintf(f, "%s", new);
1786 fclose(f);
1788 if (new != NULL) {
1789 free(new);
1790 new = NULL;
1794 if (entry != NULL) {
1795 free(entry);
1796 entry = NULL;
1799 return TRUE;
1802 static gboolean
1803 view_source(const Arg * arg) {
1804 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1805 webkit_web_view_set_view_source_mode(webview, !current_mode);
1806 webkit_web_view_reload(webview);
1807 return TRUE;
1810 /* open an external editor defined by the protocol handler for
1811 vimprobableedit on a text box or similar */
1812 static gboolean
1813 open_editor(const Arg *arg) {
1814 char *text = NULL;
1815 gboolean success;
1816 GPid child_pid;
1817 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1818 gchar *temp_file_name = g_strdup_printf("%s/vimprobableeditXXXXXX",
1819 temp_dir);
1820 int temp_file_handle = -1;
1822 /* check if active element is suitable for text editing */
1823 jsapi_evaluate_script("document.activeElement.tagName", &value, &message);
1824 if (value == NULL)
1825 return FALSE;
1826 tag = g_strdup(value);
1827 if (strcmp(tag, "INPUT") == 0) {
1828 /* extra check: type == text */
1829 jsapi_evaluate_script("document.activeElement.type", &value, &message);
1830 if (strcmp(value, "text") != 0) {
1831 g_free(value);
1832 g_free(message);
1833 return FALSE;
1835 } else if (strcmp(tag, "TEXTAREA") != 0) {
1836 g_free(value);
1837 g_free(message);
1838 return FALSE;
1840 jsapi_evaluate_script("document.activeElement.value", &value, &message);
1841 text = g_strdup(value);
1842 if (text == NULL) {
1843 g_free(value);
1844 g_free(message);
1845 return FALSE;
1848 /* write text into temporary file */
1849 temp_file_handle = mkstemp(temp_file_name);
1850 if (temp_file_handle == -1) {
1851 message = g_strdup_printf("Could not create temporary file: %s",
1852 strerror(errno));
1853 echo_message(Error, message);
1854 g_free(value);
1855 g_free(message);
1856 g_free(text);
1857 return FALSE;
1859 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
1860 message = g_strdup_printf("Short write to temporary file: %s",
1861 strerror(errno));
1862 echo_message(Error, message);
1863 g_free(value);
1864 g_free(message);
1865 g_free(text);
1866 return FALSE;
1868 close(temp_file_handle);
1869 g_free(text);
1871 /* spawn editor */
1872 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
1873 success = open_handler_pid(edit_url, &child_pid);
1874 g_free(edit_url);
1875 if (!success) {
1876 echo_message(Error, "External editor open failed (no handler for"
1877 " vimprobableedit protocol?)");
1878 unlink(temp_file_name);
1879 g_free(value);
1880 g_free(message);
1881 return FALSE;
1884 /* mark the active text box as "under processing" */
1885 jsapi_evaluate_script(
1886 "document.activeElement.disabled = true;"
1887 "document.activeElement.originalBackground = "
1888 " document.activeElement.style.background;"
1889 "document.activeElement.style.background = '#aaaaaa';"
1890 ,&value, &message);
1892 g_child_watch_add(child_pid, _resume_from_editor, temp_file_name);
1894 /* temp_file_name is freed in _resume_from_editor */
1895 g_free(value);
1896 g_free(message);
1897 g_free(tag);
1898 return TRUE;
1902 /* pick up from where open_editor left the work to the glib event loop.
1904 This is called when the external editor exits.
1906 The data argument points to allocated memory containing the temporary file
1907 name. */
1908 void
1909 _resume_from_editor(GPid child_pid, int child_status, gpointer data) {
1910 FILE *fp;
1911 GString *set_value_js = g_string_new(
1912 "document.activeElement.value = \"");
1913 g_spawn_close_pid(child_pid);
1914 gchar *value = NULL, *message = NULL;
1915 gchar *temp_file_name = data;
1916 gchar buffer[BUF_SIZE] = "";
1917 gchar *buf_ptr = buffer;
1918 int char_read;
1920 jsapi_evaluate_script(
1921 "document.activeElement.disabled = true;"
1922 "document.activeElement.style.background = '#aaaaaa';"
1923 ,&value, &message);
1925 if (child_status) {
1926 echo_message(Error, "External editor returned with non-zero status,"
1927 " discarding edits.");
1928 goto error_exit;
1931 /* re-read the new contents of the file and put it into the HTML element */
1932 if (!access(temp_file_name, R_OK) == 0) {
1933 message = g_strdup_printf("Could not access temporary file: %s",
1934 strerror(errno));
1935 goto error_exit;
1937 fp = fopen(temp_file_name, "r");
1938 if (fp == NULL) {
1939 /* this would be too weird to even emit an error message */
1940 goto error_exit;
1942 jsapi_evaluate_script("document.activeElement.value = '';",
1943 &value, &message);
1945 while (EOF != (char_read = fgetc(fp))) {
1946 if (char_read == '\n') {
1947 *buf_ptr++ = '\\';
1948 *buf_ptr++ = 'n';
1949 } else if (char_read == '"') {
1950 *buf_ptr++ = '\\';
1951 *buf_ptr++ = '"';
1952 } else {
1953 *buf_ptr++ = char_read;
1955 /* ship out as the buffer when space gets tight. This has
1956 fuzz to save on thinking, plus we have enough space for the
1957 trailing "; in any case. */
1958 if (buf_ptr-buffer>=BUF_SIZE-10) {
1959 *buf_ptr = 0;
1960 g_string_append(set_value_js, buffer);
1961 buf_ptr = buffer;
1964 *buf_ptr++ = '"';
1965 *buf_ptr++ = ';';
1966 *buf_ptr = 0;
1967 g_string_append(set_value_js, buffer);
1968 fclose(fp);
1970 jsapi_evaluate_script(set_value_js->str, &value, &message);
1972 /* Fall through, error and normal exit are identical */
1973 error_exit:
1974 jsapi_evaluate_script(
1975 "document.activeElement.disabled = false;"
1976 "document.activeElement.style.background ="
1977 " document.activeElement.originalBackground;"
1978 ,&value, &message);
1980 g_string_free(set_value_js, TRUE);
1981 unlink(temp_file_name);
1982 g_free(temp_file_name);
1983 g_free(value);
1984 g_free(message);
1987 static gboolean
1988 focus_input(const Arg *arg) {
1989 static Arg a;
1991 a.s = g_strdup("hints.focusInput();");
1992 a.i = Silent;
1993 script(&a);
1994 g_free(a.s);
1995 update_state();
1996 manual_focus = TRUE;
1997 return TRUE;
2000 static gboolean
2001 browser_settings(const Arg *arg) {
2002 char line[255];
2003 if (!arg->s) {
2004 set_error("Missing argument.");
2005 return FALSE;
2007 strncpy(line, arg->s, 254);
2008 if (process_set_line(line))
2009 return TRUE;
2010 else {
2011 set_error("Invalid setting.");
2012 return FALSE;
2016 char *
2017 search_word(int whichword) {
2018 int k = 0;
2019 static char word[240];
2020 char *c = my_pair.line;
2022 while (isspace(*c) && *c)
2023 c++;
2025 switch (whichword) {
2026 case 0:
2027 while (*c && !isspace (*c) && *c != '=' && k < 240) {
2028 word[k++] = *c;
2029 c++;
2031 word[k] = '\0';
2032 strncpy(my_pair.what, word, 20);
2033 break;
2034 case 1:
2035 while (*c && k < 240) {
2036 word[k++] = *c;
2037 c++;
2039 word[k] = '\0';
2040 strncpy(my_pair.value, word, 240);
2041 break;
2044 return c;
2047 static gboolean
2048 process_set_line(char *line) {
2049 char *c;
2050 int listlen, i;
2051 gboolean boolval;
2052 WebKitWebSettings *settings;
2054 settings = webkit_web_view_get_settings(webview);
2055 my_pair.line = line;
2056 c = search_word(0);
2057 if (!strlen(my_pair.what))
2058 return FALSE;
2060 while (isspace(*c) && *c)
2061 c++;
2063 if (*c == ':' || *c == '=')
2064 c++;
2066 my_pair.line = c;
2067 c = search_word(1);
2069 listlen = LENGTH(browsersettings);
2070 for (i = 0; i < listlen; i++) {
2071 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
2072 /* mandatory argument not provided */
2073 if (strlen(my_pair.value) == 0)
2074 return FALSE;
2075 /* process qmark? */
2076 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
2077 return (process_save_qmark(my_pair.value, webview));
2079 /* interpret boolean values */
2080 if (browsersettings[i].boolval) {
2081 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) {
2082 boolval = TRUE;
2083 } 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) {
2084 boolval = FALSE;
2085 } else {
2086 return FALSE;
2088 } else if (browsersettings[i].colourval) {
2089 /* interpret as hexadecimal colour */
2090 if (!parse_colour(my_pair.value)) {
2091 return FALSE;
2094 if (browsersettings[i].var != NULL) {
2095 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
2096 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
2097 /* in this case, \0 will not have been copied */
2098 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
2099 /* in case this string is also used for a webkit setting, make sure it's consistent */
2100 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
2101 echo_message(Info, "String too long; automatically truncated!");
2104 if (strlen(browsersettings[i].webkit) > 0) {
2105 /* activate appropriate webkit setting */
2106 if (browsersettings[i].boolval) {
2107 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
2108 } else if (browsersettings[i].intval) {
2109 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
2110 } else {
2111 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
2113 webkit_web_view_set_settings(webview, settings);
2116 if (strlen(my_pair.what) == 14) {
2117 if (strncmp("acceptlanguage", my_pair.what, 14) == 0) {
2118 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2119 } else if (strncmp("completioncase", my_pair.what, 14) == 0) {
2120 complete_case_sensitive = boolval;
2122 } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
2123 toggle_proxy(boolval);
2124 } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) {
2125 toggle_scrollbars(boolval);
2126 } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) {
2127 gtk_widget_set_visible(GTK_WIDGET(statusbar), boolval);
2128 } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) {
2129 gtk_widget_set_visible(inputbox, boolval);
2130 } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) {
2131 escape_input_on_load = boolval;
2134 /* SSL certificate checking */
2135 if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) {
2136 if (boolval) {
2137 strict_ssl = TRUE;
2138 g_object_set(G_OBJECT(session), "ssl-strict", TRUE, NULL);
2139 } else {
2140 strict_ssl = FALSE;
2141 g_object_set(G_OBJECT(session), "ssl-strict", FALSE, NULL);
2144 if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) {
2145 g_object_set(G_OBJECT(session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL);
2147 if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) {
2148 set_default_winsize(my_pair.value);
2151 /* reload page? */
2152 if (browsersettings[i].reload)
2153 webkit_web_view_reload(webview);
2154 return TRUE;
2157 return FALSE;
2160 gboolean
2161 process_line(char *line) {
2162 char *c = line, *command_hist;
2163 int i;
2164 size_t len, length = strlen(line);
2165 gboolean found = FALSE, success = FALSE;
2166 Arg a;
2168 while (isspace(*c))
2169 c++;
2170 /* Ignore blank lines. */
2171 if (c[0] == '\0')
2172 return TRUE;
2174 command_hist = g_strdup(c);
2175 for (i = 0; i < LENGTH(commands); i++) {
2176 if (commands[i].cmd == NULL)
2177 break;
2178 len = strlen(commands[i].cmd);
2179 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
2180 found = TRUE;
2181 a.i = commands[i].arg.i;
2182 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
2183 success = commands[i].func(&a);
2184 g_free(a.s);
2185 break;
2189 save_command_history(command_hist);
2190 g_free(command_hist);
2192 if (!found) {
2193 echo_message(Error, "Not a browser command: %s", c);
2194 } else if (!success) {
2195 if (error_msg != NULL) {
2196 echo_message(Error, error_msg);
2197 g_free(error_msg);
2198 error_msg = NULL;
2199 } else {
2200 echo_message(Error, "Unknown error. Please file a bug report!");
2203 return success;
2206 static gboolean
2207 search_tag(const Arg * a) {
2208 FILE *f;
2209 const char *filename;
2210 const char *tag = a->s;
2211 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
2212 int t, i, intag, k;
2214 if (!tag) {
2215 /* The user must give us something to load up. */
2216 set_error("Bookmark tag required with this option.");
2217 return FALSE;
2220 if (strlen(tag) > MAXTAGSIZE) {
2221 set_error("Tag too long.");
2222 return FALSE;
2225 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
2226 f = fopen(filename, "r");
2227 g_free((gpointer *)filename);
2228 if (f == NULL) {
2229 set_error("Couldn't open bookmarks file.");
2230 return FALSE;
2232 while (fgets(s, BUFFERSIZE-1, f)) {
2233 intag = 0;
2234 t = strlen(s) - 1;
2235 while (isspace(s[t]))
2236 t--;
2237 if (s[t] != ']') continue;
2238 while (t > 0) {
2239 if (s[t] == ']') {
2240 if (!intag)
2241 intag = t;
2242 else
2243 intag = 0;
2244 } else {
2245 if (s[t] == '[') {
2246 if (intag) {
2247 i = 0;
2248 k = t + 1;
2249 while (k < intag)
2250 foundtag[i++] = s[k++];
2251 foundtag[i] = '\0';
2252 /* foundtag now contains the tag */
2253 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2254 i = 0;
2255 while (isspace(s[i])) i++;
2256 k = 0;
2257 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2258 url[k] = '\0';
2259 Arg x = { .i = TargetNew, .s = url };
2260 open_arg(&x);
2263 intag = 0;
2266 t--;
2269 return TRUE;
2272 void
2273 toggle_proxy(gboolean onoff) {
2274 SoupURI *proxy_uri;
2275 char *filename, *new;
2277 if (onoff == FALSE) {
2278 g_object_set(session, "proxy-uri", NULL, NULL);
2279 } else {
2280 filename = (char *)g_getenv("http_proxy");
2282 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2283 * defined.
2285 if (filename == NULL)
2286 filename = (char *)g_getenv("HTTP_PROXY");
2288 if (filename != NULL && 0 < strlen(filename)) {
2289 new = g_strrstr(filename, "http://") ? g_strdup(filename) : g_strdup_printf("http://%s", filename);
2290 proxy_uri = soup_uri_new(new);
2292 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2294 soup_uri_free(proxy_uri);
2295 g_free(new);
2300 void
2301 toggle_scrollbars(gboolean onoff) {
2302 if (onoff == TRUE) {
2303 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
2304 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
2305 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2306 } else {
2307 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2308 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2309 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2311 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
2313 return;
2316 void set_default_winsize(const char * const size) {
2317 char *p;
2318 int x = 640, y = 480;
2320 x = strtol(size, &p, 10);
2321 if (errno == ERANGE || x <= 0) {
2322 x = 640;
2323 goto out;
2326 if (p == size || strlen(size) == p - size)
2327 goto out;
2329 y = strtol(p + 1, NULL, 10);
2330 if (errno == ERANGE || y <= 0)
2331 y = 480;
2333 out:
2334 gtk_window_resize(GTK_WINDOW(window), x, y);
2337 void
2338 update_url(const char *uri) {
2339 gboolean ssl = g_str_has_prefix(uri, "https://");
2340 GdkColor color;
2341 WebKitWebFrame *frame;
2342 WebKitWebDataSource *src;
2343 WebKitNetworkRequest *request;
2344 SoupMessage *msg;
2345 gboolean ssl_ok;
2346 char *sslactivecolor;
2347 gchar *markup;
2348 #ifdef ENABLE_HISTORY_INDICATOR
2349 char before[] = " [";
2350 char after[] = "]";
2351 gboolean back = webkit_web_view_can_go_back(webview);
2352 gboolean fwd = webkit_web_view_can_go_forward(webview);
2354 if (!back && !fwd)
2355 before[0] = after[0] = '\0';
2356 #endif
2357 markup = g_markup_printf_escaped(
2358 #ifdef ENABLE_HISTORY_INDICATOR
2359 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2360 before, back ? "+" : "", fwd ? "-" : "", after
2361 #else
2362 "<span font=\"%s\">%s</span>", statusfont, uri
2363 #endif
2365 gtk_label_set_markup(GTK_LABEL(status_url), markup);
2366 g_free(markup);
2367 if (ssl) {
2368 frame = webkit_web_view_get_main_frame(webview);
2369 src = webkit_web_frame_get_data_source(frame);
2370 request = webkit_web_data_source_get_request(src);
2371 msg = webkit_network_request_get_message(request);
2372 ssl_ok = soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED;
2373 if (ssl_ok)
2374 sslactivecolor = sslbgcolor;
2375 else
2376 sslactivecolor = sslinvalidbgcolor;
2378 gdk_color_parse(ssl ? sslactivecolor : statusbgcolor, &color);
2379 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
2380 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2381 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
2382 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
2385 void
2386 update_state() {
2387 char *markup;
2388 int download_count = g_list_length(activeDownloads);
2389 GString *status = g_string_new("");
2391 /* construct the status line */
2393 /* count, modkey and input buffer */
2394 g_string_append_printf(status, "%.0d", count);
2395 if (current_modkey) g_string_append_c(status, current_modkey);
2397 /* the number of active downloads */
2398 if (activeDownloads) {
2399 g_string_append_printf(status, " %d active %s", download_count,
2400 (download_count == 1) ? "download" : "downloads");
2403 #ifdef ENABLE_WGET_PROGRESS_BAR
2404 /* the progressbar */
2406 int progress = -1;
2407 char progressbar[progressbartick + 1];
2409 if (activeDownloads) {
2410 progress = 0;
2411 GList *ptr;
2413 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
2414 progress += 100 * webkit_download_get_progress(ptr->data);
2417 progress /= download_count;
2419 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
2420 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
2422 progress = webkit_web_view_get_progress(webview) * 100;
2425 if (progress >= 0) {
2426 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2427 g_string_append_printf(status, " %c%s%c",
2428 progressborderleft, progressbar, progressborderright);
2431 #endif
2433 /* and the current scroll position */
2435 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
2436 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
2438 if (max == 0)
2439 g_string_append(status, " All");
2440 else if (val == 0)
2441 g_string_append(status, " Top");
2442 else if (val == 100)
2443 g_string_append(status, " Bot");
2444 else
2445 g_string_append_printf(status, " %d%%", val);
2449 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2450 gtk_label_set_markup(GTK_LABEL(status_state), markup);
2451 g_free(markup);
2453 g_string_free(status, TRUE);
2456 void
2457 setup_modkeys() {
2458 unsigned int i;
2459 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2460 char *ptr = modkeys;
2462 for (i = 0; i < LENGTH(keys); i++)
2463 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
2464 *(ptr++) = keys[i].modkey;
2465 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
2468 void
2469 setup_gui() {
2470 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2471 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2472 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2473 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2474 if (embed) {
2475 window = GTK_WINDOW(gtk_plug_new(embed));
2476 } else {
2477 window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2478 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2480 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2481 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2482 inputbox = gtk_entry_new();
2483 webview = (WebKitWebView*)webkit_web_view_new();
2484 statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2485 eventbox = gtk_event_box_new();
2486 status_url = gtk_label_new(NULL);
2487 status_state = gtk_label_new(NULL);
2488 GdkColor bg;
2489 PangoFontDescription *font;
2490 GdkGeometry hints = { 1, 1 };
2491 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2493 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2494 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2495 setup_settings();
2496 gdk_color_parse(statusbgcolor, &bg);
2497 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2498 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2499 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2501 keymap = gdk_keymap_get_default();
2503 #ifdef DISABLE_SCROLLBAR
2504 viewport = gtk_scrolled_window_new(NULL, NULL);
2505 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2506 #else
2507 /* Ensure we still see scrollbars. */
2508 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2509 #endif
2511 setup_signals();
2512 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2514 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2515 * titlebars, we can still scroll.
2517 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2519 font = pango_font_description_from_string(urlboxfont[0]);
2520 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2521 pango_font_description_free(font);
2522 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2523 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2524 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2525 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2526 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2527 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2528 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2529 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2530 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2531 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2532 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2533 gtk_widget_grab_focus(GTK_WIDGET(webview));
2534 gtk_widget_show_all(GTK_WIDGET(window));
2535 set_widget_font_and_color(inputbox, urlboxfont[0], urlboxbgcolor[0], urlboxcolor[0]);
2536 g_object_set(gtk_widget_get_settings(inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
2539 void
2540 setup_settings() {
2541 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2542 char *filename, *file_url;
2544 session = webkit_get_default_session();
2545 g_object_set(G_OBJECT(session), "ssl-ca-file", ca_bundle, NULL);
2546 g_object_set(G_OBJECT(session), "ssl-strict", strict_ssl, NULL);
2547 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2548 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2549 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2550 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2551 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2552 filename = g_strdup_printf(USER_STYLESHEET);
2553 file_url = g_strdup_printf("file://%s", filename);
2554 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2555 g_free(file_url);
2556 g_free(filename);
2557 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2558 g_object_get(G_OBJECT(settings), "zoom-step", &zoomstep, NULL);
2559 webkit_web_view_set_settings(webview, settings);
2561 /* proxy */
2562 toggle_proxy(use_proxy);
2565 void
2566 setup_signals() {
2567 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
2568 #ifdef ENABLE_COOKIE_SUPPORT
2569 /* Headers. */
2570 g_signal_connect_after(G_OBJECT(session), "request-started", G_CALLBACK(new_generic_request), NULL);
2571 #endif
2572 /* Accept-language header */
2573 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2574 /* window */
2575 g_object_connect(G_OBJECT(window),
2576 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2577 NULL);
2578 /* frame */
2579 g_signal_connect(G_OBJECT(frame),
2580 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2581 /* webview */
2582 g_object_connect(G_OBJECT(webview),
2583 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2584 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2585 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2586 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2587 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2588 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2589 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2590 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2591 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2592 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2593 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2594 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2595 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2596 NULL);
2597 /* webview adjustment */
2598 g_object_connect(G_OBJECT(adjust_v),
2599 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2600 NULL);
2601 /* inputbox */
2602 g_object_connect(G_OBJECT(inputbox),
2603 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2604 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2605 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2606 #ifdef ENABLE_INCREMENTAL_SEARCH
2607 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2608 #endif
2609 NULL);
2610 /* inspector */
2611 g_signal_connect(G_OBJECT(inspector),
2612 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2615 #ifdef ENABLE_COOKIE_SUPPORT
2616 void
2617 setup_cookies()
2619 if (file_cookie_jar)
2620 g_object_unref(file_cookie_jar);
2622 if (session_cookie_jar)
2623 g_object_unref(session_cookie_jar);
2625 session_cookie_jar = soup_cookie_jar_new();
2627 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2629 load_all_cookies();
2631 g_signal_connect(G_OBJECT(file_cookie_jar), "changed",
2632 G_CALLBACK(update_cookie_jar), NULL);
2634 return;
2637 /* TA: XXX - we should be using this callback for any header-requests we
2638 * receive (hence the name "new_generic_request" -- but for now, its use
2639 * is limited to handling cookies.
2641 void
2642 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused)
2644 SoupMessageHeaders *soup_msg_h;
2645 SoupURI *uri;
2646 char *cookie_str;
2648 soup_msg_h = soup_msg->request_headers;
2649 soup_message_headers_remove(soup_msg_h, "Cookie");
2650 uri = soup_message_get_uri(soup_msg);
2651 if ((cookie_str = get_cookies(uri))) {
2652 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2653 g_free(cookie_str);
2656 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2658 return;
2661 char *
2662 get_cookies(SoupURI *soup_uri) {
2663 char *cookie_str;
2665 cookie_str = soup_cookie_jar_get_cookies(file_cookie_jar, soup_uri, TRUE);
2667 return cookie_str;
2670 void
2671 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2673 GSList *resp_cookie = NULL, *cookie_list;
2674 SoupCookie *cookie;
2676 cookie_list = soup_cookies_from_response(soup_msg);
2677 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
2679 SoupDate *soup_date;
2680 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2682 if (cookie_timeout && cookie->expires == NULL) {
2683 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2684 soup_cookie_set_expires(cookie, soup_date);
2685 soup_date_free(soup_date);
2687 soup_cookie_jar_add_cookie(file_cookie_jar, cookie);
2690 soup_cookies_free(cookie_list);
2692 return;
2695 void
2696 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2698 if (!new) {
2699 /* Nothing to do. */
2700 return;
2703 SoupCookie *copy;
2704 copy = soup_cookie_copy(new);
2706 soup_cookie_jar_add_cookie(session_cookie_jar, copy);
2708 return;
2711 void
2712 load_all_cookies(void)
2714 GSList *cookie_list;
2715 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2717 /* Put them back in the session store. */
2718 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2719 cookie_list = cookies_from_file;
2721 for (; cookies_from_file;
2722 cookies_from_file = cookies_from_file->next)
2724 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2727 soup_cookies_free(cookies_from_file);
2728 g_slist_free(cookie_list);
2730 return;
2733 #endif
2735 void
2736 mop_up(void) {
2737 /* Free up any nasty globals before exiting. */
2738 #ifdef ENABLE_COOKIE_SUPPORT
2739 if (cookie_store)
2740 g_free(cookie_store);
2741 #endif
2742 return;
2746 main(int argc, char *argv[]) {
2747 static Arg a;
2748 static char url[256] = "";
2749 static gboolean ver = false;
2750 static gboolean configfile_exists = FALSE;
2751 static const char *cfile = NULL;
2752 static GOptionEntry opts[] = {
2753 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2754 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2755 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2756 { NULL }
2758 static GError *err;
2759 args = argv;
2761 /* command line argument: version */
2762 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2763 g_printerr("can't init gtk: %s\n", err->message);
2764 g_error_free(err);
2765 return EXIT_FAILURE;
2768 if (ver) {
2769 printf("%s\n", INTERNAL_VERSION);
2770 return EXIT_SUCCESS;
2773 if (getenv("TMPDIR")) {
2774 strncpy(temp_dir, getenv("TMPDIR"), MAX_SETTING_SIZE);
2775 temp_dir[MAX_SETTING_SIZE-1] = 0;
2778 if( getenv("XDG_CONFIG_HOME") )
2779 config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
2780 else
2781 config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
2783 if (cfile)
2784 configfile = g_strdup(cfile);
2785 else
2786 configfile = g_strdup_printf(RCFILE);
2788 if (!g_thread_supported())
2789 g_thread_init(NULL);
2791 if (winid) {
2792 if (strncmp(winid, "0x", 2) == 0) {
2793 embed = strtol(winid, NULL, 16);
2794 } else {
2795 embed = atoi(winid);
2799 setup_modkeys();
2800 make_keyslist();
2801 setup_gui();
2802 #ifdef ENABLE_COOKIE_SUPPORT
2803 setup_cookies();
2804 #endif
2806 make_searchengines_list(searchengines, LENGTH(searchengines));
2807 make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers));
2809 /* Check if the specified file exists. */
2810 /* And only warn the user, if they explicitly asked for a config on the
2811 * command line.
2813 if (!(access(configfile, F_OK) == 0) && cfile) {
2814 echo_message(Info, "Config file '%s' doesn't exist", cfile);
2815 } else if ((access(configfile, F_OK) == 0))
2816 configfile_exists = true;
2818 /* read config file */
2819 /* But only report errors if we failed, and the file existed. */
2820 if ((SUCCESS != read_rcfile(configfile)) && configfile_exists) {
2821 echo_message(Error, "Error in config file '%s'", configfile);
2822 g_free(configfile);
2825 /* command line argument: URL */
2826 if (argc > 1) {
2827 strncpy(url, argv[argc - 1], 255);
2828 } else {
2829 strncpy(url, startpage, 255);
2832 a.i = TargetCurrent;
2833 a.s = url;
2834 open_arg(&a);
2835 gtk_main();
2837 mop_up();
2839 return EXIT_SUCCESS;