Adding visual feedback for areas externally edited.
[vimprobable2.git] / main.c
blob5acf509dc79aae649420f489c99698843f3f03e2
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 navigate(const Arg *arg);
71 static gboolean number(const Arg *arg);
72 static gboolean open_arg(const Arg *arg);
73 static gboolean open_remembered(const Arg *arg);
74 static gboolean paste(const Arg *arg);
75 static gboolean quickmark(const Arg *arg);
76 static gboolean quit(const Arg *arg);
77 static gboolean revive(const Arg *arg);
78 static gboolean print_frame(const Arg *arg);
79 static gboolean search(const Arg *arg);
80 static gboolean set(const Arg *arg);
81 static gboolean script(const Arg *arg);
82 static gboolean scroll(const Arg *arg);
83 static gboolean search_tag(const Arg *arg);
84 static gboolean yank(const Arg *arg);
85 static gboolean view_source(const Arg * arg);
86 static gboolean zoom(const Arg *arg);
87 static gboolean fake_key_event(const Arg *arg);
89 static void update_url(const char *uri);
90 static void setup_modkeys(void);
91 static void setup_gui(void);
92 static void setup_settings(void);
93 static void setup_signals(void);
94 static void ascii_bar(int total, int state, char *string);
95 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
96 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
97 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
98 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
99 const char *bg_color_str, const char *fg_color_str);
101 static gboolean history(void);
102 static gboolean process_set_line(char *line);
103 void save_command_history(char *line);
104 void toggle_proxy(gboolean onoff);
105 void toggle_scrollbars(gboolean onoff);
106 void set_default_winsize(const char * const size);
108 gboolean process_keypress(GdkEventKey *event);
109 void fill_suggline(char * suggline, const char * command, const char *fill_with);
110 GtkWidget * fill_eventbox(const char * completion_line);
111 static void mop_up(void);
113 #include "main.h"
115 /* variables */
116 static GtkWindow *window;
117 static GtkWidget *viewport;
118 static GtkBox *box;
119 static GtkScrollbar *scroll_h;
120 static GtkScrollbar *scroll_v;
121 static GtkAdjustment *adjust_h;
122 static GtkAdjustment *adjust_v;
123 static GtkWidget *inputbox;
124 static GtkWidget *eventbox;
125 static GtkBox *statusbar;
126 static GtkWidget *status_url;
127 static GtkWidget *status_state;
128 static WebKitWebView *webview;
129 static SoupSession *session;
130 static GtkClipboard *clipboards[2];
131 static GdkKeymap *keymap;
133 static char **args;
134 static unsigned int mode = ModeNormal;
135 static unsigned int count = 0;
136 static float zoomstep;
137 char *modkeys;
138 static char current_modkey;
139 static char *search_handle;
140 static gboolean search_direction;
141 static gboolean echo_active = TRUE;
142 WebKitWebInspector *inspector;
144 static GdkNativeWindow embed = 0;
145 static char *configfile = NULL;
146 static char *winid = NULL;
148 static char rememberedURI[1024] = "";
149 static char followTarget[8] = "";
150 char *error_msg = NULL;
151 char *config_base = NULL;
152 static gboolean manual_focus = FALSE;
154 GList *activeDownloads;
156 #include "config.h"
157 #include "keymap.h"
159 GList *commandhistory = NULL;
160 int commandpointer = 0;
162 KeyList *keylistroot = NULL;
164 /* Cookie support. */
165 #ifdef ENABLE_COOKIE_SUPPORT
166 static SoupCookieJar *session_cookie_jar = NULL;
167 static SoupCookieJar *file_cookie_jar = NULL;
168 static time_t cookie_timeout = 4800;
169 static char *cookie_store;
170 static void setup_cookies(void);
171 static char *get_cookies(SoupURI *soup_uri);
172 static void load_all_cookies(void);
173 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
174 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
175 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
176 #endif
177 /* callbacks */
178 void
179 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
180 quit(NULL);
183 void
184 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
185 gtk_window_set_title(window, title);
188 void
189 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
190 #ifdef ENABLE_GTK_PROGRESS_BAR
191 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
192 #endif
193 update_state();
196 #ifdef ENABLE_WGET_PROGRESS_BAR
197 void
198 ascii_bar(int total, int state, char *string) {
199 int i;
201 for (i = 0; i < state; i++)
202 string[i] = progressbartickchar;
203 string[i++] = progressbarcurrent;
204 for (; i < total; i++)
205 string[i] = progressbarspacer;
206 string[i] = '\0';
208 #endif
210 void
211 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
212 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
213 const char *uri = webkit_web_view_get_uri(webview);
215 update_url(uri);
216 script(&a);
217 g_free(a.s);
219 if (mode == ModeInsert || mode == ModeHints) {
220 Arg a = { .i = ModeNormal };
221 set(&a);
223 manual_focus = FALSE;
226 void
227 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
228 WebKitWebSettings *settings = webkit_web_view_get_settings(webview);
229 gboolean scripts;
231 g_object_get(settings, "enable-scripts", &scripts, NULL);
232 if (escape_input_on_load && scripts && !manual_focus && !gtk_widget_is_focus(inputbox)) {
233 Arg a = { .i = Silent, .s = g_strdup("hints.clearFocus();") };
234 script(&a);
235 g_free(a.s);
236 a.i = ModeNormal;
237 a.s = NULL;
238 set(&a);
240 if (HISTORY_MAX_ENTRIES > 0)
241 history();
242 update_state();
245 void
246 webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec) {
247 /* retrieve the URI of the temporary webview */
248 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(temp_view) };
249 /* clean up */
250 webkit_web_view_stop_loading(temp_view);
251 gtk_widget_destroy(GTK_WIDGET(temp_view));
252 /* open the requested window */
253 open_arg(&a);
256 static WebKitWebView *
257 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
258 /* create a temporary webview to execute the script in */
259 WebKitWebView *temp_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
260 /* wait until the new webview receives its new URI */
261 g_object_connect(temp_view, "signal::notify::uri", G_CALLBACK(webview_open_js_window_cb), NULL, NULL);
262 return temp_view;
265 gboolean
266 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
267 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
268 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
269 open_arg(&a);
270 webkit_web_policy_decision_ignore(decision);
271 return TRUE;
274 gboolean
275 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
276 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
277 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
278 webkit_web_policy_decision_download(decision);
279 return TRUE;
280 } else {
281 return FALSE;
285 static WebKitWebView*
286 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
287 gchar* inspector_title;
288 GtkWidget* inspector_window;
289 GtkWidget* inspector_view;
291 /* just enough code to show the inspector - no signal handling etc. */
292 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
293 if (embed) {
294 inspector_window = gtk_plug_new(embed);
295 } else {
296 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
297 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
299 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
300 g_free(inspector_title);
301 inspector_view = webkit_web_view_new();
302 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
303 gtk_widget_show_all(inspector_window);
304 return WEBKIT_WEB_VIEW(inspector_view);
307 gboolean
308 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
309 const gchar *filename;
310 gchar *uri, *path;
311 uint32_t size;
312 Arg a;
313 WebKitDownloadStatus status;
315 filename = webkit_download_get_suggested_filename(download);
316 if (filename == NULL || strlen(filename) == 0) {
317 filename = "vimprobable_download";
319 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
320 uri = g_strconcat("file://", path, NULL);
321 webkit_download_set_destination_uri(download, uri);
322 g_free(uri);
323 size = (uint32_t)webkit_download_get_total_size(download);
324 a.i = Info;
325 if (size > 0)
326 a.s = g_strdup_printf("Download %s started (expected size: %u bytes)...", filename, size);
327 else
328 a.s = g_strdup_printf("Download %s started (unknown size)...", filename);
329 echo(&a);
330 g_free(a.s);
331 activeDownloads = g_list_prepend(activeDownloads, download);
332 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
333 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
334 status = webkit_download_get_status(download);
335 if (status == WEBKIT_DOWNLOAD_STATUS_CREATED)
336 webkit_download_start(download);
337 update_state();
338 return TRUE;
341 gboolean
342 blank_cb(void) {
343 return TRUE;
346 void
347 download_progress(WebKitDownload *d, GParamSpec *pspec) {
348 Arg a;
349 WebKitDownloadStatus status = webkit_download_get_status(d);
351 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
352 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
353 a.i = Error;
354 a.s = g_strdup_printf("Error while downloading %s", webkit_download_get_suggested_filename(d));
355 echo(&a);
356 } else {
357 a.i = Info;
358 a.s = g_strdup_printf("Download %s finished", webkit_download_get_suggested_filename(d));
359 echo(&a);
361 g_free(a.s);
362 activeDownloads = g_list_remove(activeDownloads, d);
364 update_state();
368 gboolean
369 process_keypress(GdkEventKey *event) {
370 KeyList *current;
371 guint keyval;
372 GdkModifierType irrelevant;
374 /* Get a mask of modifiers that shouldn't be considered for this event.
375 * E.g.: It shouldn't matter whether ';' is shifted or not. */
376 gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
377 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
379 current = keylistroot;
381 while (current != NULL) {
382 if (current->Element.mask == (CLEAN(event->state) & ~irrelevant)
383 && (current->Element.modkey == current_modkey
384 || (!current->Element.modkey && !current_modkey)
385 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
386 && current->Element.key == keyval
387 && current->Element.func)
388 if (current->Element.func(&current->Element.arg)) {
389 current_modkey = count = 0;
390 update_state();
391 return TRUE;
393 current = current->next;
395 return FALSE;
398 gboolean
399 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
400 Arg a = { .i = ModeNormal, .s = NULL };
401 guint keyval;
402 GdkModifierType irrelevant;
404 /* Get a mask of modifiers that shouldn't be considered for this event.
405 * E.g.: It shouldn't matter whether ';' is shifted or not. */
406 gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
407 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
409 switch (mode) {
410 case ModeNormal:
411 if ((CLEAN(event->state) & ~irrelevant) == 0) {
412 if (IS_ESCAPE(event)) {
413 a.i = Info;
414 a.s = g_strdup("");
415 echo(&a);
416 g_free(a.s);
417 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
418 || (event->keyval == GDK_0 && count))) {
419 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
420 update_state();
421 return TRUE;
422 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
423 current_modkey = event->keyval;
424 update_state();
425 return TRUE;
428 /* keybindings */
429 if (process_keypress(event) == TRUE) return TRUE;
431 break;
432 case ModeInsert:
433 if (IS_ESCAPE(event)) {
434 a.i = Silent;
435 a.s = g_strdup("hints.clearFocus();");
436 script(&a);
437 g_free(a.s);
438 a.i = ModeNormal;
439 return set(&a);
440 } else if (CLEAN(event->state) & GDK_CONTROL_MASK) {
441 /* keybindings of non-printable characters */
442 if (process_keypress(event) == TRUE) return TRUE;
444 case ModePassThrough:
445 if (IS_ESCAPE(event)) {
446 echo(&a);
447 set(&a);
448 return TRUE;
450 break;
451 case ModeSendKey:
452 echo(&a);
453 set(&a);
454 break;
456 return FALSE;
459 void
460 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
461 const char *fg_color_str) {
462 GdkColor fg_color;
463 GdkColor bg_color;
464 PangoFontDescription *font;
466 font = pango_font_description_from_string(font_str);
467 gtk_widget_modify_font(widget, font);
468 pango_font_description_free(font);
470 if (fg_color_str)
471 gdk_color_parse(fg_color_str, &fg_color);
472 if (bg_color_str)
473 gdk_color_parse(bg_color_str, &bg_color);
475 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
476 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
478 return;
481 void
482 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
483 const char *uri = webkit_web_view_get_uri(webview);
484 char *markup;
486 memset(rememberedURI, 0, 1024);
487 if (link) {
488 markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link);
489 gtk_label_set_markup(GTK_LABEL(status_url), markup);
490 strncpy(rememberedURI, link, 1024);
491 g_free(markup);
492 } else
493 update_url(uri);
496 gboolean
497 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
498 Arg a;
500 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
501 if (gtk_window_has_toplevel_focus(window)) {
502 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
503 a.i = ModeNormal;
504 return set(&a);
505 } else if (!strcmp(message, "insertmode_on")) {
506 a.i = ModeInsert;
507 return set(&a);
510 return FALSE;
513 void
514 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
515 char *text;
516 guint16 length = gtk_entry_get_text_length(entry);
517 Arg a;
518 gboolean success = FALSE, forward = FALSE;
520 a.i = HideCompletion;
521 complete(&a);
522 if (length == 0)
523 return;
524 text = (char*)gtk_entry_get_text(entry);
525 if (length > 1 && text[0] == ':') {
526 success = process_line((text + 1));
527 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
528 webkit_web_view_unmark_text_matches(webview);
529 #ifdef ENABLE_MATCH_HIGHLITING
530 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
531 webkit_web_view_set_highlight_text_matches(webview, TRUE);
532 #endif
533 count = 0;
534 #ifndef ENABLE_INCREMENTAL_SEARCH
535 a.s =& text[1];
536 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
537 search(&a);
538 #else
539 search_direction = forward;
540 search_handle = g_strdup(&text[1]);
541 #endif
542 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
543 a.i = Silent;
544 a.s = g_strdup_printf("hints.fire();");
545 script(&a);
546 g_free(a.s);
547 update_state();
548 } else
549 return;
550 if (!echo_active)
551 gtk_entry_set_text(entry, "");
552 gtk_widget_grab_focus(GTK_WIDGET(webview));
555 gboolean
556 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
557 Arg a;
558 int numval;
560 if (mode == ModeHints) {
561 if (event->keyval == GDK_Tab) {
562 a.i = Silent;
563 a.s = g_strdup_printf("hints.focusNextHint();");
564 script(&a);
565 g_free(a.s);
566 update_state();
567 return TRUE;
569 if (event->keyval == GDK_ISO_Left_Tab) {
570 a.i = Silent;
571 a.s = g_strdup_printf("hints.focusPreviousHint();");
572 script(&a);
573 g_free(a.s);
574 update_state();
575 return TRUE;
577 if (event->keyval == GDK_Return) {
578 a.i = Silent;
579 a.s = g_strdup_printf("hints.fire();");
580 script(&a);
581 g_free(a.s);
582 update_state();
583 return TRUE;
586 switch (event->keyval) {
587 case GDK_bracketleft:
588 case GDK_Escape:
589 if (!IS_ESCAPE(event)) break;
590 a.i = HideCompletion;
591 complete(&a);
592 a.i = ModeNormal;
593 commandpointer = 0;
594 return set(&a);
595 break;
596 case GDK_Tab:
597 a.i = DirectionNext;
598 return complete(&a);
599 break;
600 case GDK_Up:
601 a.i = DirectionPrev;
602 return commandhistoryfetch(&a);
603 break;
604 case GDK_Down:
605 a.i = DirectionNext;
606 return commandhistoryfetch(&a);
607 break;
608 case GDK_ISO_Left_Tab:
609 a.i = DirectionPrev;
610 return complete(&a);
611 break;
614 if (mode == ModeHints) {
615 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
616 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
617 (event->keyval == GDK_BackSpace)) {
618 count /= 10;
619 a.i = Silent;
620 a.s = g_strdup_printf("hints.updateHints(%d);", count);
621 script(&a);
622 g_free(a.s);
623 update_state();
624 return TRUE;
627 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
628 if ((numval >= 1 && numval <= 9) || (numval == 0 && count)) {
629 /* allow a zero as non-first number */
630 count = (count ? count * 10 : 0) + numval;
631 a.i = Silent;
632 a.s = g_strdup_printf("hints.updateHints(%d);", count);
633 script(&a);
634 g_free(a.s);
635 update_state();
636 return TRUE;
640 return FALSE;
643 gboolean
644 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
645 int i;
646 WebKitHitTestResult *result;
647 WebKitHitTestResultContext context;
648 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
649 /* handle mouse click events */
650 for (i = 0; i < LENGTH(mouse); i++) {
651 if (mouse[i].mask == CLEAN(event->button.state)
652 && (mouse[i].modkey == current_modkey
653 || (!mouse[i].modkey && !current_modkey)
654 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
655 && mouse[i].button == event->button.button
656 && mouse[i].func) {
657 if (mouse[i].func(&mouse[i].arg)) {
658 current_modkey = count = 0;
659 update_state();
660 return TRUE;
664 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
665 g_object_get(result, "context", &context, NULL);
666 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
667 Arg a = { .i = ModeInsert };
668 set(&a);
669 manual_focus = TRUE;
671 } else if (mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) {
672 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
673 g_object_get(result, "context", &context, NULL);
674 if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) {
675 Arg a = { .i = ModeNormal };
676 set(&a);
678 } else {
679 gchar *value = NULL, *message = NULL;
680 jsapi_evaluate_script("window.getSelection().focusNode", &value, &message);
681 if (value && !strcmp(value, "[object HTMLFormElement]")) {
682 Arg a = { .i = ModeInsert, .s = NULL };
683 set(&a);
684 manual_focus = TRUE;
686 g_free(value);
687 g_free(message);
689 return FALSE;
692 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
693 Arg a;
694 guint16 length = gtk_entry_get_text_length(entry);
696 if (!length) {
697 a.i = HideCompletion;
698 complete(&a);
699 a.i = ModeNormal;
700 return set(&a);
702 return FALSE;
705 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
706 Arg a;
707 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
708 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
709 gboolean forward = FALSE;
711 /* Update incremental search if the user changes the search text.
713 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
714 * from the user. But if the entry is focused and the text is set
715 * through gtk_entry_set_text() in some asyncrounous operation,
716 * I would consider that a bug.
719 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
720 webkit_web_view_unmark_text_matches(webview);
721 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
722 return TRUE;
723 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
724 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
725 a.i = Silent;
726 switch (text[0]) {
727 case '.':
728 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
729 break;
731 case ',':
732 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
733 break;
735 case ';':
736 a.s = NULL;
737 switch (text[1]) {
738 case 's':
739 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
740 break;
741 case 'y':
742 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
743 break;
744 case 'o':
745 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
746 break;
747 case 't': case 'w':
748 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
749 break;
750 case 'O': case 'T': case 'W':
751 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
752 break;
753 case 'i':
754 a.s = g_strconcat("hints.createHints('", text + 2, "', 'i');", NULL);
755 break;
756 case 'I':
757 a.s = g_strconcat("hints.createHints('", text + 2, "', 'I');", NULL);
758 break;
760 break;
762 count = 0;
763 if (a.s) {
764 script(&a);
765 g_free(a.s);
768 return TRUE;
769 } else if (length == 0 && followTarget[0]) {
770 mode = ModeNormal;
771 a.i = Silent;
772 a.s = g_strdup("hints.clearHints();");
773 script(&a);
774 g_free(a.s);
775 count = 0;
776 update_state();
779 return FALSE;
782 /* funcs here */
784 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
785 memset(suggline, 0, 512);
786 strncpy(suggline, command, 512);
787 strncat(suggline, " ", 1);
788 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
791 GtkWidget * fill_eventbox(const char * completion_line) {
792 GtkBox * row;
793 GtkWidget *row_eventbox, *el;
794 GdkColor color;
795 char *markup, *markup_tmp;
797 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
798 row_eventbox = gtk_event_box_new();
799 gdk_color_parse(completionbgcolor[0], &color);
800 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
801 el = gtk_label_new(NULL);
802 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
803 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
804 markup_tmp, "</span>", NULL);
805 gtk_label_set_markup(GTK_LABEL(el), markup);
806 g_free(markup_tmp);
807 g_free(markup);
808 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
809 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
810 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
811 return row_eventbox;
814 gboolean
815 complete(const Arg *arg) {
816 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
817 size_t listlen, len, cmdlen;
818 int i, spacepos;
819 Listelement *elementlist = NULL, *elementpointer;
820 gboolean highlight = FALSE;
821 GtkBox *row;
822 GtkWidget *row_eventbox, *el;
823 GtkBox *_table;
824 GdkColor color;
825 static GtkWidget *table, *top_border;
826 static char *prefix;
827 static char **suggestions;
828 static GtkWidget **widgets;
829 static int n = 0, m, current = -1;
831 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
832 len = strlen(str);
834 /* Get the length of the list of commands for completion. We need this to
835 * malloc/realloc correctly.
837 listlen = LENGTH(commands);
839 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
840 return TRUE;
841 if (prefix) {
842 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
843 gdk_color_parse(completionbgcolor[0], &color);
844 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
845 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
846 if ((arg->i == DirectionNext && current == 0)
847 || (arg->i == DirectionPrev && current == n - 1))
848 current = -1;
849 } else {
850 free(widgets);
851 free(suggestions);
852 free(prefix);
853 gtk_widget_destroy(GTK_WIDGET(table));
854 gtk_widget_destroy(GTK_WIDGET(top_border));
855 table = NULL;
856 widgets = NULL;
857 suggestions = NULL;
858 prefix = NULL;
859 n = 0;
860 current = -1;
861 if (arg->i == HideCompletion)
862 return TRUE;
864 } else if (arg->i == HideCompletion)
865 return TRUE;
866 if (!widgets) {
867 prefix = g_strdup(str);
868 widgets = malloc(sizeof(GtkWidget*) * listlen);
869 suggestions = malloc(sizeof(char*) * listlen);
870 top_border = gtk_event_box_new();
871 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
872 gdk_color_parse(completioncolor[2], &color);
873 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
874 table = gtk_event_box_new();
875 gdk_color_parse(completionbgcolor[0], &color);
876 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
877 highlight = len > 1;
878 if (strchr(str, ' ') == NULL) {
879 /* command completion */
880 listlen = LENGTH(commands);
881 for (i = 0; i < listlen; i++) {
882 if (commands[i].cmd == NULL)
883 break;
884 cmdlen = strlen(commands[i].cmd);
885 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
886 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
887 if (highlight) {
888 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
889 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
890 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
891 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
893 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
894 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
895 row_eventbox = gtk_event_box_new();
896 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
897 el = gtk_label_new(NULL);
898 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
899 free(s);
900 gtk_label_set_markup(GTK_LABEL(el), markup);
901 g_free(markup);
902 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
903 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
904 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
905 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
906 suggestions[n] = commands[i].cmd;
907 widgets[n++] = row_eventbox;
910 } else {
911 entry = (char *)malloc(512 * sizeof(char));
912 if (entry == NULL) {
913 return FALSE;
915 memset(entry, 0, 512);
916 suggurls = malloc(sizeof(char*) * listlen);
917 if (suggurls == NULL) {
918 return FALSE;
920 spacepos = strcspn(str, " ");
921 searchfor = (str + spacepos + 1);
922 strncpy(command, (str + 1), spacepos - 1);
923 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
924 /* browser settings */
925 listlen = LENGTH(browsersettings);
926 for (i = 0; i < listlen; i++) {
927 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
928 /* match */
929 fill_suggline(suggline, command, browsersettings[i].name);
930 /* FIXME(HP): This memory is never freed */
931 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
932 strncpy(suggurls[n], suggline, 512);
933 suggestions[n] = suggurls[n];
934 row_eventbox = fill_eventbox(suggline);
935 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
936 widgets[n++] = row_eventbox;
940 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
941 /* completion on tags */
942 spacepos = strcspn(str, " ");
943 searchfor = (str + spacepos + 1);
944 elementlist = complete_list(searchfor, 1, elementlist);
945 } else {
946 /* URL completion: bookmarks */
947 elementlist = complete_list(searchfor, 0, elementlist);
948 m = count_list(elementlist);
949 if (m < MAX_LIST_SIZE) {
950 /* URL completion: history */
951 elementlist = complete_list(searchfor, 2, elementlist);
954 elementpointer = elementlist;
955 while (elementpointer != NULL) {
956 fill_suggline(suggline, command, elementpointer->element);
957 /* FIXME(HP): This memory is never freed */
958 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
959 strncpy(suggurls[n], suggline, 512);
960 suggestions[n] = suggurls[n];
961 row_eventbox = fill_eventbox(suggline);
962 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
963 widgets[n++] = row_eventbox;
964 elementpointer = elementpointer->next;
965 if (n >= MAX_LIST_SIZE)
966 break;
968 free_list(elementlist);
969 if (suggurls != NULL) {
970 free(suggurls);
971 suggurls = NULL;
973 if (entry != NULL) {
974 free(entry);
975 entry = NULL;
978 /* TA: FIXME - this needs rethinking entirely. */
980 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
981 if (widgets_temp == NULL && widgets == NULL) {
982 fprintf(stderr, "Couldn't realloc() widgets\n");
983 exit(1);
985 widgets = widgets_temp;
986 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
987 if (suggestions_temp == NULL && suggestions == NULL) {
988 fprintf(stderr, "Couldn't realloc() suggestions\n");
989 exit(1);
991 suggestions = suggestions_temp;
993 if (!n) {
994 gdk_color_parse(completionbgcolor[1], &color);
995 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
996 el = gtk_label_new(NULL);
997 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
998 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
999 gtk_label_set_markup(GTK_LABEL(el), markup);
1000 g_free(markup);
1001 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
1003 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
1004 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
1005 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
1006 gtk_widget_show_all(GTK_WIDGET(window));
1007 if (!n)
1008 return TRUE;
1009 current = arg->i == DirectionPrev ? n - 1 : 0;
1011 if (current != -1) {
1012 gdk_color_parse(completionbgcolor[2], &color);
1013 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
1014 s = g_strconcat(":", suggestions[current], NULL);
1015 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
1016 g_free(s);
1017 } else
1018 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
1019 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1020 return TRUE;
1023 gboolean
1024 descend(const Arg *arg) {
1025 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
1026 int i, len;
1027 count = count ? count : 1;
1029 if (!source)
1030 return TRUE;
1031 if (arg->i == Rootdir) {
1032 for (i = 0; i < 3; i++) /* get to the third slash */
1033 if (!(p = strchr(++p, '/')))
1034 return TRUE; /* if we cannot find it quit */
1035 } else {
1036 len = strlen(source);
1037 if (!len) /* if string is empty quit */
1038 return TRUE;
1039 p = source + len; /* start at the end */
1040 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
1041 ++count;
1042 for (i = 0; i < count; i++)
1043 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
1044 if (p == source) /* if we reach the first char pointer quit */
1045 return TRUE;
1046 ++p; /* since we do p-- in the while, we are pointing at
1047 the char before the slash, so +1 */
1049 len = p - source + 1; /* new length = end - start + 1 */
1050 new = malloc(len + 1);
1051 memcpy(new, source, len);
1052 new[len] = '\0';
1053 webkit_web_view_load_uri(webview, new);
1054 free(new);
1055 return TRUE;
1058 gboolean
1059 echo(const Arg *arg) {
1060 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
1062 if (index < Info || index > Error)
1063 return TRUE;
1065 if (!gtk_widget_is_focus(GTK_WIDGET(inputbox))) {
1066 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1067 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
1070 return TRUE;
1073 gboolean
1074 input(const Arg *arg) {
1075 int pos = 0;
1076 count = 0;
1077 const char *url;
1078 int index = Info;
1079 Arg a;
1081 /* if inputbox hidden, show it again */
1082 if (!gtk_widget_get_visible(inputbox))
1083 gtk_widget_set_visible(inputbox, TRUE);
1085 update_state();
1087 /* Set the colour and font back to the default, so that we don't still
1088 * maintain a red colour from a warning from an end of search indicator,
1089 * etc.
1091 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1093 /* to avoid things like :open URL :open URL2 or :open :open URL */
1094 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1095 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1096 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
1097 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1099 gtk_widget_grab_focus(inputbox);
1100 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1102 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1103 mode = ModeHints;
1104 memset(followTarget, 0, 8);
1105 strncpy(followTarget, "current", 8);
1106 a.i = Silent;
1107 switch (arg->s[0]) {
1108 case '.':
1109 a.s = g_strdup("hints.createHints('', 'f');");
1110 break;
1112 case ',':
1113 a.s = g_strdup("hints.createHints('', 'F');");
1114 break;
1116 case ';':
1117 a.s = NULL;
1118 if (arg->s[1]) {
1119 switch (arg->s[1]) {
1120 case 's':
1121 a.s = g_strdup("hints.createHints('', 's');");
1122 break;
1123 case 'y':
1124 a.s = g_strdup("hints.createHints('', 'y');");
1125 break;
1126 case 'o':
1127 a.s = g_strdup("hints.createHints('', 'f');");
1128 break;
1129 case 't': case 'w':
1130 a.s = g_strdup("hints.createHints('', 'F');");
1131 break;
1132 case 'O': case 'T': case 'W':
1133 a.s = g_strdup("hints.createHints('', 'O');");
1134 break;
1135 case 'i':
1136 a.s = g_strdup("hints.createHints('', 'i');");
1137 break;
1138 case 'I':
1139 a.s = g_strdup("hints.createHints('', 'I');");
1140 break;
1143 break;
1145 count = 0;
1146 if (a.s) {
1147 script(&a);
1148 g_free(a.s);
1152 return TRUE;
1155 gboolean
1156 navigate(const Arg *arg) {
1157 if (arg->i & NavigationForwardBack)
1158 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
1159 else if (arg->i & NavigationReloadActions)
1160 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
1161 else
1162 webkit_web_view_stop_loading(webview);
1163 return TRUE;
1166 gboolean
1167 number(const Arg *arg) {
1168 const char *source = webkit_web_view_get_uri(webview);
1169 char *uri, *p, *new;
1170 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1172 if (!source)
1173 return TRUE;
1174 uri = g_strdup(source); /* copy string */
1175 p =& uri[0];
1176 while(*p != '\0') /* goto the end of the string */
1177 ++p;
1178 --p;
1179 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1180 --p;
1181 if (*(++p) == '\0') { /* if no numbers were found abort */
1182 free(uri);
1183 return TRUE;
1185 number = atoi(p) + diff; /* apply diff on number */
1186 *p = '\0';
1187 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1188 webkit_web_view_load_uri(webview, new);
1189 g_free(new);
1190 free(uri);
1191 return TRUE;
1194 gboolean
1195 open_arg(const Arg *arg) {
1196 char *argv[64];
1197 char *s = arg->s, *p = NULL, *new;
1198 Arg a = { .i = NavigationReload };
1199 int len;
1200 char *search_uri, *search_term;
1202 if (embed) {
1203 argv[0] = *args;
1204 argv[1] = "-e";
1205 argv[2] = winid;
1206 argv[3] = arg->s;
1207 argv[4] = NULL;
1208 } else {
1209 argv[0] = *args;
1210 argv[1] = arg->s;
1211 argv[2] = NULL;
1214 if (!arg->s)
1215 navigate(&a);
1216 else if (arg->i == TargetCurrent) {
1217 while(*s == ' ') /* strip leading whitespace */
1218 ++s;
1219 p = (s + strlen(s) - 1);
1220 while(*p == ' ') /* strip trailing whitespace */
1221 --p;
1222 *(p + 1) = '\0';
1223 len = strlen(s);
1224 new = NULL;
1225 /* check for external handlers */
1226 if (open_handler(s))
1227 return TRUE;
1228 /* check for search engines */
1229 p = strchr(s, ' ');
1230 if (p) { /* check for search engines */
1231 *p = '\0';
1232 search_uri = find_uri_for_searchengine(s);
1233 if (search_uri != NULL) {
1234 search_term = soup_uri_encode(p+1, "&");
1235 new = g_strdup_printf(search_uri, search_term);
1236 g_free(search_term);
1238 *p = ' ';
1240 if (!new) {
1241 if (len > 3 && strstr(s, "://")) { /* valid url? */
1242 p = new = g_malloc(len + 1);
1243 while(*s != '\0') { /* strip whitespaces */
1244 if (*s != ' ')
1245 *(p++) = *s;
1246 ++s;
1248 *p = '\0';
1249 } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */
1250 new = g_malloc(sizeof("file://") + len);
1251 strcpy(new, "file://");
1252 memcpy(&new[sizeof("file://") - 1], s, len + 1);
1253 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1254 search_uri = find_uri_for_searchengine(defaultsearch);
1255 if (search_uri != NULL) {
1256 search_term = soup_uri_encode(s, "&");
1257 new = g_strdup_printf(search_uri, search_term);
1258 g_free(search_term);
1260 } else { /* prepend "http://" */
1261 new = g_malloc(sizeof("http://") + len);
1262 strcpy(new, "http://");
1263 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1266 webkit_web_view_load_uri(webview, new);
1267 g_free(new);
1268 } else
1269 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1270 return TRUE;
1273 gboolean
1274 open_remembered(const Arg *arg)
1276 Arg a = {arg->i, rememberedURI};
1278 if (strcmp(rememberedURI, "")) {
1279 open_arg(&a);
1281 return TRUE;
1284 gboolean
1285 yank(const Arg *arg) {
1286 const char *url, *feedback, *content;
1288 if (arg->i & SourceSelection) {
1289 webkit_web_view_copy_clipboard(webview);
1290 if (arg->i & ClipboardPrimary)
1291 content = gtk_clipboard_wait_for_text(clipboards[0]);
1292 if (!content && arg->i & ClipboardGTK)
1293 content = gtk_clipboard_wait_for_text(clipboards[1]);
1294 if (content) {
1295 feedback = g_strconcat("Yanked ", content, NULL);
1296 g_free((gpointer *)content);
1297 give_feedback(feedback);
1298 g_free((gpointer *)feedback);
1300 } else {
1301 if (arg->i & SourceURL) {
1302 url = webkit_web_view_get_uri(webview);
1303 } else {
1304 url = arg->s;
1306 if (!url)
1307 return TRUE;
1308 feedback = g_strconcat("Yanked ", url, NULL);
1309 give_feedback(feedback);
1310 if (arg->i & ClipboardPrimary)
1311 gtk_clipboard_set_text(clipboards[0], url, -1);
1312 if (arg->i & ClipboardGTK)
1313 gtk_clipboard_set_text(clipboards[1], url, -1);
1315 return TRUE;
1318 gboolean
1319 paste(const Arg *arg) {
1320 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1322 /* If we're over a link, open it in a new target. */
1323 if (strlen(rememberedURI) > 0) {
1324 Arg new_target = { .i = TargetNew, .s = arg->s };
1325 open_arg(&new_target);
1326 return TRUE;
1329 if (arg->i & ClipboardPrimary)
1330 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1331 if (!a.s && arg->i & ClipboardGTK)
1332 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1333 if (a.s) {
1334 open_arg(&a);
1335 g_free(a.s);
1337 return TRUE;
1340 gboolean
1341 quit(const Arg *arg) {
1342 FILE *f;
1343 const char *filename;
1344 const char *uri = webkit_web_view_get_uri(webview);
1345 if (uri != NULL) {
1346 /* write last URL into status file for recreation with "u" */
1347 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1348 f = fopen(filename, "w");
1349 g_free((gpointer *)filename);
1350 if (f != NULL) {
1351 fprintf(f, "%s", uri);
1352 fclose(f);
1355 gtk_main_quit();
1356 return TRUE;
1359 gboolean
1360 revive(const Arg *arg) {
1361 FILE *f;
1362 const char *filename;
1363 char buffer[512] = "";
1364 Arg a = { .i = TargetNew, .s = NULL };
1365 /* get the URL of the window which has been closed last */
1366 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1367 f = fopen(filename, "r");
1368 g_free((gpointer *)filename);
1369 if (f != NULL) {
1370 fgets(buffer, 512, f);
1371 fclose(f);
1373 if (strlen(buffer) > 0) {
1374 a.s = buffer;
1375 open_arg(&a);
1376 return TRUE;
1378 return FALSE;
1381 static
1382 gboolean print_frame(const Arg *arg)
1384 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1385 webkit_web_frame_print (frame);
1386 return TRUE;
1389 gboolean
1390 search(const Arg *arg) {
1391 count = count ? count : 1;
1392 gboolean success, direction = arg->i & DirectionPrev;
1393 Arg a;
1395 if (arg->s) {
1396 free(search_handle);
1397 search_handle = g_strdup(arg->s);
1399 if (!search_handle)
1400 return TRUE;
1401 if (arg->i & DirectionAbsolute)
1402 search_direction = direction;
1403 else
1404 direction ^= search_direction;
1405 do {
1406 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1407 if (!success) {
1408 if (arg->i & Wrapping) {
1409 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1410 if (success) {
1411 a.i = Warning;
1412 a.s = g_strdup_printf("search hit %s, continuing at %s",
1413 direction ? "BOTTOM" : "TOP",
1414 direction ? "TOP" : "BOTTOM");
1415 echo(&a);
1416 g_free(a.s);
1417 } else
1418 break;
1419 } else
1420 break;
1422 } while(--count);
1423 if (!success) {
1424 a.i = Error;
1425 a.s = g_strdup_printf("Pattern not found: %s", search_handle);
1426 echo(&a);
1427 g_free(a.s);
1429 return TRUE;
1432 gboolean
1433 set(const Arg *arg) {
1434 Arg a = { .i = Info | NoAutoHide };
1436 switch (arg->i) {
1437 case ModeNormal:
1438 if (search_handle) {
1439 search_handle = NULL;
1440 webkit_web_view_unmark_text_matches(webview);
1442 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1443 gtk_widget_grab_focus(GTK_WIDGET(webview));
1444 break;
1445 case ModePassThrough:
1446 a.s = g_strdup("-- PASS THROUGH --");
1447 echo(&a);
1448 g_free(a.s);
1449 break;
1450 case ModeSendKey:
1451 a.s = g_strdup("-- PASS TROUGH (next) --");
1452 echo(&a);
1453 g_free(a.s);
1454 break;
1455 case ModeInsert: /* should not be called manually but automatically */
1456 a.s = g_strdup("-- INSERT --");
1457 echo(&a);
1458 g_free(a.s);
1459 break;
1460 default:
1461 return TRUE;
1463 mode = arg->i;
1464 return TRUE;
1467 gchar*
1468 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1469 JSStringRef string_ref;
1470 gchar *string;
1471 size_t length;
1473 string_ref = JSValueToStringCopy(context, ref, NULL);
1474 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1475 string = g_new(gchar, length);
1476 JSStringGetUTF8CString(string_ref, string, length);
1477 JSStringRelease(string_ref);
1478 return string;
1481 void
1482 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1483 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1484 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1485 JSStringRef str;
1486 JSValueRef val, exception;
1488 str = JSStringCreateWithUTF8CString(script);
1489 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1490 JSStringRelease(str);
1491 if (!val)
1492 *message = jsapi_ref_to_string(context, exception);
1493 else
1494 *value = jsapi_ref_to_string(context, val);
1497 gboolean
1498 quickmark(const Arg *a) {
1499 int i, b;
1500 b = atoi(a->s);
1501 char *fn = g_strdup_printf(QUICKMARK_FILE);
1502 FILE *fp;
1503 fp = fopen(fn, "r");
1504 g_free(fn);
1505 fn = NULL;
1506 char buf[100];
1508 if (fp != NULL && b < 10) {
1509 for( i=0; i < b; ++i ) {
1510 if (feof(fp)) {
1511 break;
1513 fgets(buf, 100, fp);
1515 char *ptr = strrchr(buf, '\n');
1516 *ptr = '\0';
1517 Arg x = { .s = buf };
1518 if (strlen(buf))
1519 return open_arg(&x);
1520 else {
1521 x.i = Error;
1522 x.s = g_strdup_printf("Quickmark %d not defined", b);
1523 echo(&x);
1524 g_free(x.s);
1525 return false;
1527 } else { return false; }
1530 gboolean
1531 script(const Arg *arg) {
1532 gchar *value = NULL, *message = NULL;
1533 char text[1024] = "";
1534 Arg a;
1535 WebKitNetworkRequest *request;
1536 WebKitDownload *download;
1538 if (!arg->s) {
1539 set_error("Missing argument.");
1540 return FALSE;
1542 jsapi_evaluate_script(arg->s, &value, &message);
1543 if (message) {
1544 set_error(message);
1545 g_free(message);
1546 return FALSE;
1548 g_free(message);
1549 if (arg->i != Silent && value) {
1550 a.i = arg->i;
1551 a.s = g_strdup(value);
1552 echo(&a);
1553 g_free(a.s);
1555 /* switch mode according to scripts return value */
1556 if (value) {
1557 if (strncmp(value, "done;", 5) == 0) {
1558 a.i = ModeNormal;
1559 set(&a);
1560 } else if (strncmp(value, "insert;", 7) == 0) {
1561 a.i = ModeInsert;
1562 set(&a);
1563 manual_focus = TRUE;
1564 } else if (strncmp(value, "save;", 5) == 0) {
1565 /* forced download */
1566 a.i = ModeNormal;
1567 set(&a);
1568 request = webkit_network_request_new((value + 5));
1569 download = webkit_download_new(request);
1570 webview_download_cb(webview, download, (gpointer *)NULL);
1571 } else if (strncmp(value, "yank;", 5) == 0) {
1572 /* yank link URL to clipboard */
1573 a.i = ModeNormal;
1574 set(&a);
1575 a.i = ClipboardPrimary | ClipboardGTK;
1576 a.s = (value + 5);
1577 yank(&a);
1578 } else if (strncmp(value, "colon;", 6) == 0) {
1579 /* use link URL for colon command */
1580 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(inputbox)), 1023);
1581 a.i = ModeNormal;
1582 set(&a);
1583 switch (text[1]) {
1584 case 'O':
1585 a.s = g_strconcat(":open ", (value + 6), NULL);
1586 break;
1587 case 'T': case 'W':
1588 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1589 break;
1591 if (a.s) {
1592 input(&a);
1593 g_free(a.s);
1595 } else if (strncmp(value, "error;", 6) == 0) {
1596 a.i = Error;
1597 set(&a);
1600 g_free(value);
1601 return TRUE;
1604 gboolean
1605 scroll(const Arg *arg) {
1606 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1607 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1608 float val = gtk_adjustment_get_value(adjust) / max * 100;
1609 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1611 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1612 if (arg->i & ScrollMove)
1613 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1614 direction * /* direction */
1615 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1616 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1617 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1618 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1619 else
1620 gtk_adjustment_set_value(adjust,
1621 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1622 update_state();
1624 return TRUE;
1627 gboolean
1628 zoom(const Arg *arg) {
1629 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1630 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1631 webkit_web_view_get_zoom_level(webview) +
1632 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1633 (count ? (float)count / 100.0 : 1.0));
1634 return TRUE;
1637 gboolean
1638 fake_key_event(const Arg *a) {
1639 if(!embed) {
1640 return FALSE;
1642 Arg err;
1643 err.i = Error;
1644 Display *xdpy;
1645 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1646 err.s = g_strdup("Couldn't find the XDisplay.");
1647 echo(&err);
1648 g_free(err.s);
1649 return FALSE;
1652 XKeyEvent xk;
1653 xk.display = xdpy;
1654 xk.subwindow = None;
1655 xk.time = CurrentTime;
1656 xk.same_screen = True;
1657 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1658 xk.window = embed;
1659 xk.state = a->i;
1661 if( ! a->s ) {
1662 err.s = g_strdup("Zero pointer as argument! Check your config.h");
1663 echo(&err);
1664 g_free(err.s);
1665 return FALSE;
1668 KeySym keysym;
1669 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1670 err.s = g_strdup_printf("Couldn't translate %s to keysym", a->s );
1671 echo(&err);
1672 g_free(err.s);
1673 return FALSE;
1676 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1677 err.s = g_strdup("Couldn't translate keysym to keycode");
1678 echo(&err);
1679 g_free(err.s);
1680 return FALSE;
1683 xk.type = KeyPress;
1684 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1685 err.s = g_strdup("XSendEvent failed");
1686 echo(&err);
1687 g_free(err.s);
1688 return FALSE;
1690 XFlush(xdpy);
1692 return TRUE;
1696 gboolean
1697 commandhistoryfetch(const Arg *arg) {
1698 const int length = g_list_length(commandhistory);
1700 if (length > 0) {
1701 if (arg->i == DirectionPrev) {
1702 commandpointer = (length + commandpointer - 1) % length;
1703 } else {
1704 commandpointer = (length + commandpointer + 1) % length;
1707 const char* command = (char *)g_list_nth_data(commandhistory, commandpointer);
1708 gtk_entry_set_text(GTK_ENTRY(inputbox), g_strconcat(":", command, NULL));
1709 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1710 return TRUE;
1713 return FALSE;
1716 gboolean
1717 bookmark(const Arg *arg) {
1718 FILE *f;
1719 const char *filename;
1720 const char *uri = webkit_web_view_get_uri(webview);
1721 const char *title = webkit_web_view_get_title(webview);
1722 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1723 f = fopen(filename, "a");
1724 g_free((gpointer *)filename);
1725 if (uri == NULL || strlen(uri) == 0) {
1726 set_error("No URI found to bookmark.");
1727 return FALSE;
1729 if (f != NULL) {
1730 fprintf(f, "%s", uri);
1731 if (title != NULL) {
1732 fprintf(f, "%s", " ");
1733 fprintf(f, "%s", title);
1735 if (arg->s && strlen(arg->s)) {
1736 build_taglist(arg, f);
1738 fprintf(f, "%s", "\n");
1739 fclose(f);
1740 give_feedback( "Bookmark saved" );
1741 return TRUE;
1742 } else {
1743 set_error("Bookmarks file not found.");
1744 return FALSE;
1748 gboolean
1749 history() {
1750 FILE *f;
1751 const char *filename;
1752 const char *uri = webkit_web_view_get_uri(webview);
1753 const char *title = webkit_web_view_get_title(webview);
1754 char *entry, buffer[512], *new;
1755 int n, i = 0;
1756 gboolean finished = FALSE;
1757 if (uri != NULL) {
1758 if (title != NULL) {
1759 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1760 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1761 } else {
1762 entry = malloc((strlen(uri) + 1) * sizeof(char));
1763 memset(entry, 0, strlen(uri) + 1);
1765 if (entry != NULL) {
1766 strncpy(entry, uri, strlen(uri));
1767 if (title != NULL) {
1768 strncat(entry, " ", 1);
1769 strncat(entry, title, strlen(title));
1771 n = strlen(entry);
1772 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1773 f = fopen(filename, "r");
1774 if (f != NULL) {
1775 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1776 if (new != NULL) {
1777 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1778 /* newest entries go on top */
1779 strncpy(new, entry, strlen(entry));
1780 strncat(new, "\n", 1);
1781 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1782 while (finished != TRUE) {
1783 if ((char *)NULL == fgets(buffer, 512, f)) {
1784 /* check if end of file was reached / error occured */
1785 if (!feof(f)) {
1786 break;
1788 /* end of file reached */
1789 finished = TRUE;
1790 continue;
1792 /* compare line (-1 because of newline character) */
1793 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1794 /* if the URI is already in history; we put it on top and skip it here */
1795 strncat(new, buffer, 512);
1796 i++;
1798 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1799 break;
1802 fclose(f);
1804 f = fopen(filename, "w");
1805 g_free((gpointer *)filename);
1806 if (f != NULL) {
1807 fprintf(f, "%s", new);
1808 fclose(f);
1810 if (new != NULL) {
1811 free(new);
1812 new = NULL;
1816 if (entry != NULL) {
1817 free(entry);
1818 entry = NULL;
1821 return TRUE;
1824 static gboolean
1825 view_source(const Arg * arg) {
1826 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1827 webkit_web_view_set_view_source_mode(webview, !current_mode);
1828 webkit_web_view_reload(webview);
1829 return TRUE;
1832 /* open an external editor defined by the protocol handler for
1833 vimprobableedit on a text box or similar */
1834 static gboolean
1835 open_editor(const Arg *arg) {
1836 char *text = NULL;
1837 gboolean success;
1838 GPid child_pid;
1839 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1840 gchar *temp_file_name = g_strdup("/tmp/vimprobableeditXXXXXX");
1841 int temp_file_handle = -1;
1843 /* check if active element is suitable for text editing */
1844 jsapi_evaluate_script("document.activeElement.tagName", &value, &message);
1845 if (value == NULL)
1846 return FALSE;
1847 tag = g_strdup(value);
1848 if (strcmp(tag, "INPUT") == 0) {
1849 /* extra check: type == text */
1850 jsapi_evaluate_script("document.activeElement.type", &value, &message);
1851 if (strcmp(value, "text") != 0) {
1852 g_free(value);
1853 g_free(message);
1854 return FALSE;
1856 } else if (strcmp(tag, "TEXTAREA") != 0) {
1857 g_free(value);
1858 g_free(message);
1859 return FALSE;
1861 jsapi_evaluate_script("document.activeElement.value", &value, &message);
1862 text = g_strdup(value);
1863 if (text == NULL) {
1864 g_free(value);
1865 g_free(message);
1866 return FALSE;
1869 /* write text into temporary file */
1870 temp_file_handle = mkstemp(temp_file_name);
1871 if (temp_file_handle == -1) {
1872 message = g_strdup_printf("Could not create temporary file: %s",
1873 strerror(errno));
1874 give_feedback(message);
1875 g_free(value);
1876 g_free(message);
1877 g_free(text);
1878 return FALSE;
1880 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
1881 message = g_strdup_printf("Short write to temporary file: %s",
1882 strerror(errno));
1883 give_feedback(message);
1884 g_free(value);
1885 g_free(message);
1886 g_free(text);
1887 return FALSE;
1889 close(temp_file_handle);
1890 g_free(text);
1892 /* spawn editor */
1893 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
1894 success = open_handler_pid(edit_url, &child_pid);
1895 g_free(edit_url);
1896 if (!success) {
1897 give_feedback("External editor open failed (no handler for"
1898 " vimprobableedit protocol?)");
1899 unlink(temp_file_name);
1900 g_free(value);
1901 g_free(message);
1902 return FALSE;
1905 /* mark the active text box as "under processing" */
1906 jsapi_evaluate_script(
1907 "document.activeElement.disabled = true;"
1908 "document.activeElement.originalBackground = "
1909 " document.activeElement.style.background;"
1910 "document.activeElement.style.background = '#aaaaaa';"
1911 ,&value, &message);
1913 g_child_watch_add(child_pid, _resume_from_editor, temp_file_name);
1915 /* temp_file_name is freed in _resume_from_editor */
1916 g_free(value);
1917 g_free(message);
1918 g_free(tag);
1919 return TRUE;
1923 /* pick up from where open_editor left the work to the glib event loop.
1925 This is called when the external editor exits.
1927 The data argument points to allocated memory containing the temporary file
1928 name. */
1929 void
1930 _resume_from_editor(GPid child_pid, int child_status, gpointer data) {
1931 FILE *fp;
1932 GString *new_text = g_string_new("");
1933 g_spawn_close_pid(child_pid);
1934 gchar *value = NULL, *message = NULL;
1935 gchar *temp_file_name = data;
1936 gchar buffer[255] = "";
1938 jsapi_evaluate_script(
1939 "document.activeElement.disabled = true;"
1940 "document.activeElement.style.background = '#aaaaaa';"
1941 ,&value, &message);
1943 if (child_status) {
1944 give_feedback("External editor returned with non-zero status,"
1945 " discarding edits.");
1946 goto error_exit;
1949 /* re-read the new contents of the file and put it into the HTML element */
1950 if (!access(temp_file_name, R_OK) == 0) {
1951 message = g_strdup_printf("Could not access temporary file: %s",
1952 strerror(errno));
1953 goto error_exit;
1955 fp = fopen(temp_file_name, "r");
1956 if (fp == NULL) {
1957 /* this would be too weird to even emit an error message */
1958 goto error_exit;
1960 jsapi_evaluate_script("document.activeElement.value = '';",
1961 &value, &message);
1962 new_text = g_string_append(new_text, "\"");
1963 while (fgets(buffer, 254, fp)) {
1964 if (buffer[strlen(buffer)-1] == '\n') {
1965 /* encode line breaks into the string as Javascript does not like actual line breaks */
1966 new_text = g_string_append_len(
1967 new_text, buffer, strlen(buffer) - 1);
1968 new_text = g_string_append(new_text, "\\n");
1969 } else {
1970 new_text = g_string_append(new_text, buffer);
1973 new_text = g_string_append(new_text, "\"");
1974 fclose(fp);
1975 /* FIXME: Is the memory returned by g_strconcat actually freed? */
1976 jsapi_evaluate_script(g_strconcat("document.activeElement.value = ",
1977 new_text->str, ";", NULL), &value, &message);
1979 /* Fall through, error and normal exit are identical */
1980 error_exit:
1981 if (new_text) {
1982 g_string_free(new_text, TRUE);
1985 jsapi_evaluate_script(
1986 "document.activeElement.disabled = false;"
1987 "document.activeElement.style.background ="
1988 " document.activeElement.originalBackground;"
1989 ,&value, &message);
1991 unlink(temp_file_name);
1992 g_free(temp_file_name);
1993 g_free(value);
1994 g_free(message);
1997 static gboolean
1998 focus_input(const Arg *arg) {
1999 static Arg a;
2001 a.s = g_strdup("hints.focusInput();");
2002 a.i = Silent;
2003 script(&a);
2004 g_free(a.s);
2005 update_state();
2006 manual_focus = TRUE;
2007 return TRUE;
2010 static gboolean
2011 browser_settings(const Arg *arg) {
2012 char line[255];
2013 if (!arg->s) {
2014 set_error("Missing argument.");
2015 return FALSE;
2017 strncpy(line, arg->s, 254);
2018 if (process_set_line(line))
2019 return TRUE;
2020 else {
2021 set_error("Invalid setting.");
2022 return FALSE;
2026 char *
2027 search_word(int whichword) {
2028 int k = 0;
2029 static char word[240];
2030 char *c = my_pair.line;
2032 while (isspace(*c) && *c)
2033 c++;
2035 switch (whichword) {
2036 case 0:
2037 while (*c && !isspace (*c) && *c != '=' && k < 240) {
2038 word[k++] = *c;
2039 c++;
2041 word[k] = '\0';
2042 strncpy(my_pair.what, word, 20);
2043 break;
2044 case 1:
2045 while (*c && k < 240) {
2046 word[k++] = *c;
2047 c++;
2049 word[k] = '\0';
2050 strncpy(my_pair.value, word, 240);
2051 break;
2054 return c;
2057 static gboolean
2058 process_set_line(char *line) {
2059 char *c;
2060 int listlen, i;
2061 gboolean boolval;
2062 WebKitWebSettings *settings;
2064 settings = webkit_web_view_get_settings(webview);
2065 my_pair.line = line;
2066 c = search_word(0);
2067 if (!strlen(my_pair.what))
2068 return FALSE;
2070 while (isspace(*c) && *c)
2071 c++;
2073 if (*c == ':' || *c == '=')
2074 c++;
2076 my_pair.line = c;
2077 c = search_word(1);
2079 listlen = LENGTH(browsersettings);
2080 for (i = 0; i < listlen; i++) {
2081 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
2082 /* mandatory argument not provided */
2083 if (strlen(my_pair.value) == 0)
2084 return FALSE;
2085 /* process qmark? */
2086 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
2087 return (process_save_qmark(my_pair.value, webview));
2089 /* interpret boolean values */
2090 if (browsersettings[i].boolval) {
2091 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) {
2092 boolval = TRUE;
2093 } 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) {
2094 boolval = FALSE;
2095 } else {
2096 return FALSE;
2098 } else if (browsersettings[i].colourval) {
2099 /* interpret as hexadecimal colour */
2100 if (!parse_colour(my_pair.value)) {
2101 return FALSE;
2104 if (browsersettings[i].var != NULL) {
2105 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
2106 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
2107 /* in this case, \0 will not have been copied */
2108 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
2109 /* in case this string is also used for a webkit setting, make sure it's consistent */
2110 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
2111 give_feedback("String too long; automatically truncated!");
2114 if (strlen(browsersettings[i].webkit) > 0) {
2115 /* activate appropriate webkit setting */
2116 if (browsersettings[i].boolval) {
2117 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
2118 } else if (browsersettings[i].intval) {
2119 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
2120 } else {
2121 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
2123 webkit_web_view_set_settings(webview, settings);
2126 if (strlen(my_pair.what) == 14) {
2127 if (strncmp("acceptlanguage", my_pair.what, 14) == 0) {
2128 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2129 } else if (strncmp("completioncase", my_pair.what, 14) == 0) {
2130 complete_case_sensitive = boolval;
2132 } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
2133 toggle_proxy(boolval);
2134 } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) {
2135 toggle_scrollbars(boolval);
2136 } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) {
2137 gtk_widget_set_visible(GTK_WIDGET(statusbar), boolval);
2138 } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) {
2139 gtk_widget_set_visible(inputbox, boolval);
2140 } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) {
2141 escape_input_on_load = boolval;
2144 /* SSL certificate checking */
2145 if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) {
2146 if (boolval) {
2147 strict_ssl = TRUE;
2148 g_object_set(G_OBJECT(session), "ssl-strict", TRUE, NULL);
2149 } else {
2150 strict_ssl = FALSE;
2151 g_object_set(G_OBJECT(session), "ssl-strict", FALSE, NULL);
2154 if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) {
2155 g_object_set(G_OBJECT(session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL);
2157 if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) {
2158 set_default_winsize(my_pair.value);
2161 /* reload page? */
2162 if (browsersettings[i].reload)
2163 webkit_web_view_reload(webview);
2164 return TRUE;
2167 return FALSE;
2170 gboolean
2171 process_line(char *line) {
2172 char *c = line, *command_hist;
2173 int i;
2174 size_t len, length = strlen(line);
2175 gboolean found = FALSE, success = FALSE;
2176 Arg a;
2178 while (isspace(*c))
2179 c++;
2180 /* Ignore blank lines. */
2181 if (c[0] == '\0')
2182 return TRUE;
2184 command_hist = g_strdup(c);
2185 for (i = 0; i < LENGTH(commands); i++) {
2186 if (commands[i].cmd == NULL)
2187 break;
2188 len = strlen(commands[i].cmd);
2189 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
2190 found = TRUE;
2191 a.i = commands[i].arg.i;
2192 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
2193 success = commands[i].func(&a);
2194 g_free(a.s);
2195 break;
2199 save_command_history(command_hist);
2200 g_free(command_hist);
2202 if (!found) {
2203 a.i = Error;
2204 a.s = g_strdup_printf("Not a browser command: %s", c);
2205 echo(&a);
2206 g_free(a.s);
2207 } else if (!success) {
2208 a.i = Error;
2209 if (error_msg != NULL) {
2210 a.s = g_strdup_printf("%s", error_msg);
2211 g_free(error_msg);
2212 error_msg = NULL;
2213 } else {
2214 a.s = g_strdup_printf("Unknown error. Please file a bug report!");
2216 echo(&a);
2217 g_free(a.s);
2219 return success;
2222 static gboolean
2223 search_tag(const Arg * a) {
2224 FILE *f;
2225 const char *filename;
2226 const char *tag = a->s;
2227 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
2228 int t, i, intag, k;
2230 if (!tag) {
2231 /* The user must give us something to load up. */
2232 set_error("Bookmark tag required with this option.");
2233 return FALSE;
2236 if (strlen(tag) > MAXTAGSIZE) {
2237 set_error("Tag too long.");
2238 return FALSE;
2241 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
2242 f = fopen(filename, "r");
2243 g_free((gpointer *)filename);
2244 if (f == NULL) {
2245 set_error("Couldn't open bookmarks file.");
2246 return FALSE;
2248 while (fgets(s, BUFFERSIZE-1, f)) {
2249 intag = 0;
2250 t = strlen(s) - 1;
2251 while (isspace(s[t]))
2252 t--;
2253 if (s[t] != ']') continue;
2254 while (t > 0) {
2255 if (s[t] == ']') {
2256 if (!intag)
2257 intag = t;
2258 else
2259 intag = 0;
2260 } else {
2261 if (s[t] == '[') {
2262 if (intag) {
2263 i = 0;
2264 k = t + 1;
2265 while (k < intag)
2266 foundtag[i++] = s[k++];
2267 foundtag[i] = '\0';
2268 /* foundtag now contains the tag */
2269 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2270 i = 0;
2271 while (isspace(s[i])) i++;
2272 k = 0;
2273 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2274 url[k] = '\0';
2275 Arg x = { .i = TargetNew, .s = url };
2276 open_arg(&x);
2279 intag = 0;
2282 t--;
2285 return TRUE;
2288 void
2289 toggle_proxy(gboolean onoff) {
2290 SoupURI *proxy_uri;
2291 char *filename, *new;
2293 if (onoff == FALSE) {
2294 g_object_set(session, "proxy-uri", NULL, NULL);
2295 } else {
2296 filename = (char *)g_getenv("http_proxy");
2298 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2299 * defined.
2301 if (filename == NULL)
2302 filename = (char *)g_getenv("HTTP_PROXY");
2304 if (filename != NULL && 0 < strlen(filename)) {
2305 new = g_strrstr(filename, "http://") ? g_strdup(filename) : g_strdup_printf("http://%s", filename);
2306 proxy_uri = soup_uri_new(new);
2308 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2310 soup_uri_free(proxy_uri);
2311 g_free(new);
2316 void
2317 toggle_scrollbars(gboolean onoff) {
2318 if (onoff == TRUE) {
2319 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
2320 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
2321 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2322 } else {
2323 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2324 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2325 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2327 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
2329 return;
2332 void set_default_winsize(const char * const size) {
2333 char *p;
2334 int x = 640, y = 480;
2336 x = strtol(size, &p, 10);
2337 if (errno == ERANGE || x <= 0) {
2338 x = 640;
2339 goto out;
2342 if (p == size || strlen(size) == p - size)
2343 goto out;
2345 y = strtol(p + 1, NULL, 10);
2346 if (errno == ERANGE || y <= 0)
2347 y = 480;
2349 out:
2350 gtk_window_resize(GTK_WINDOW(window), x, y);
2353 void
2354 update_url(const char *uri) {
2355 gboolean ssl = g_str_has_prefix(uri, "https://");
2356 GdkColor color;
2357 WebKitWebFrame *frame;
2358 WebKitWebDataSource *src;
2359 WebKitNetworkRequest *request;
2360 SoupMessage *msg;
2361 gboolean ssl_ok;
2362 char *sslactivecolor;
2363 gchar *markup;
2364 #ifdef ENABLE_HISTORY_INDICATOR
2365 char before[] = " [";
2366 char after[] = "]";
2367 gboolean back = webkit_web_view_can_go_back(webview);
2368 gboolean fwd = webkit_web_view_can_go_forward(webview);
2370 if (!back && !fwd)
2371 before[0] = after[0] = '\0';
2372 #endif
2373 markup = g_markup_printf_escaped(
2374 #ifdef ENABLE_HISTORY_INDICATOR
2375 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2376 before, back ? "+" : "", fwd ? "-" : "", after
2377 #else
2378 "<span font=\"%s\">%s</span>", statusfont, uri
2379 #endif
2381 gtk_label_set_markup(GTK_LABEL(status_url), markup);
2382 g_free(markup);
2383 if (ssl) {
2384 frame = webkit_web_view_get_main_frame(webview);
2385 src = webkit_web_frame_get_data_source(frame);
2386 request = webkit_web_data_source_get_request(src);
2387 msg = webkit_network_request_get_message(request);
2388 ssl_ok = soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED;
2389 if (ssl_ok)
2390 sslactivecolor = sslbgcolor;
2391 else
2392 sslactivecolor = sslinvalidbgcolor;
2394 gdk_color_parse(ssl ? sslactivecolor : statusbgcolor, &color);
2395 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
2396 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2397 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
2398 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
2401 void
2402 update_state() {
2403 char *markup;
2404 int download_count = g_list_length(activeDownloads);
2405 GString *status = g_string_new("");
2407 /* construct the status line */
2409 /* count, modkey and input buffer */
2410 g_string_append_printf(status, "%.0d", count);
2411 if (current_modkey) g_string_append_c(status, current_modkey);
2413 /* the number of active downloads */
2414 if (activeDownloads) {
2415 g_string_append_printf(status, " %d active %s", download_count,
2416 (download_count == 1) ? "download" : "downloads");
2419 #ifdef ENABLE_WGET_PROGRESS_BAR
2420 /* the progressbar */
2422 int progress = -1;
2423 char progressbar[progressbartick + 1];
2425 if (activeDownloads) {
2426 progress = 0;
2427 GList *ptr;
2429 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
2430 progress += 100 * webkit_download_get_progress(ptr->data);
2433 progress /= download_count;
2435 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
2436 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
2438 progress = webkit_web_view_get_progress(webview) * 100;
2441 if (progress >= 0) {
2442 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2443 g_string_append_printf(status, " %c%s%c",
2444 progressborderleft, progressbar, progressborderright);
2447 #endif
2449 /* and the current scroll position */
2451 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
2452 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
2454 if (max == 0)
2455 g_string_append(status, " All");
2456 else if (val == 0)
2457 g_string_append(status, " Top");
2458 else if (val == 100)
2459 g_string_append(status, " Bot");
2460 else
2461 g_string_append_printf(status, " %d%%", val);
2465 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2466 gtk_label_set_markup(GTK_LABEL(status_state), markup);
2467 g_free(markup);
2469 g_string_free(status, TRUE);
2472 void
2473 setup_modkeys() {
2474 unsigned int i;
2475 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2476 char *ptr = modkeys;
2478 for (i = 0; i < LENGTH(keys); i++)
2479 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
2480 *(ptr++) = keys[i].modkey;
2481 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
2484 void
2485 setup_gui() {
2486 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2487 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2488 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2489 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2490 if (embed) {
2491 window = GTK_WINDOW(gtk_plug_new(embed));
2492 } else {
2493 window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2494 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2496 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2497 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2498 inputbox = gtk_entry_new();
2499 webview = (WebKitWebView*)webkit_web_view_new();
2500 statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2501 eventbox = gtk_event_box_new();
2502 status_url = gtk_label_new(NULL);
2503 status_state = gtk_label_new(NULL);
2504 GdkColor bg;
2505 PangoFontDescription *font;
2506 GdkGeometry hints = { 1, 1 };
2507 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2509 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2510 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2511 setup_settings();
2512 gdk_color_parse(statusbgcolor, &bg);
2513 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2514 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2515 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2517 keymap = gdk_keymap_get_default();
2519 #ifdef DISABLE_SCROLLBAR
2520 viewport = gtk_scrolled_window_new(NULL, NULL);
2521 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2522 #else
2523 /* Ensure we still see scrollbars. */
2524 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2525 #endif
2527 setup_signals();
2528 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2530 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2531 * titlebars, we can still scroll.
2533 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2535 font = pango_font_description_from_string(urlboxfont[0]);
2536 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2537 pango_font_description_free(font);
2538 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2539 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2540 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2541 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2542 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2543 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2544 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2545 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2546 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2547 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2548 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2549 gtk_widget_grab_focus(GTK_WIDGET(webview));
2550 gtk_widget_show_all(GTK_WIDGET(window));
2551 set_widget_font_and_color(inputbox, urlboxfont[0], urlboxbgcolor[0], urlboxcolor[0]);
2552 g_object_set(gtk_widget_get_settings(inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
2555 void
2556 setup_settings() {
2557 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2558 char *filename, *file_url;
2560 session = webkit_get_default_session();
2561 g_object_set(G_OBJECT(session), "ssl-ca-file", ca_bundle, NULL);
2562 g_object_set(G_OBJECT(session), "ssl-strict", strict_ssl, NULL);
2563 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2564 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2565 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2566 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2567 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2568 filename = g_strdup_printf(USER_STYLESHEET);
2569 file_url = g_strdup_printf("file://%s", filename);
2570 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2571 g_free(file_url);
2572 g_free(filename);
2573 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2574 g_object_get(G_OBJECT(settings), "zoom-step", &zoomstep, NULL);
2575 webkit_web_view_set_settings(webview, settings);
2577 /* proxy */
2578 toggle_proxy(use_proxy);
2581 void
2582 setup_signals() {
2583 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
2584 #ifdef ENABLE_COOKIE_SUPPORT
2585 /* Headers. */
2586 g_signal_connect_after(G_OBJECT(session), "request-started", G_CALLBACK(new_generic_request), NULL);
2587 #endif
2588 /* Accept-language header */
2589 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2590 /* window */
2591 g_object_connect(G_OBJECT(window),
2592 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2593 NULL);
2594 /* frame */
2595 g_signal_connect(G_OBJECT(frame),
2596 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2597 /* webview */
2598 g_object_connect(G_OBJECT(webview),
2599 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2600 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2601 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2602 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2603 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2604 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2605 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2606 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2607 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2608 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2609 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2610 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2611 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2612 NULL);
2613 /* webview adjustment */
2614 g_object_connect(G_OBJECT(adjust_v),
2615 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2616 NULL);
2617 /* inputbox */
2618 g_object_connect(G_OBJECT(inputbox),
2619 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2620 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2621 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2622 #ifdef ENABLE_INCREMENTAL_SEARCH
2623 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2624 #endif
2625 NULL);
2626 /* inspector */
2627 g_signal_connect(G_OBJECT(inspector),
2628 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2631 #ifdef ENABLE_COOKIE_SUPPORT
2632 void
2633 setup_cookies()
2635 if (file_cookie_jar)
2636 g_object_unref(file_cookie_jar);
2638 if (session_cookie_jar)
2639 g_object_unref(session_cookie_jar);
2641 session_cookie_jar = soup_cookie_jar_new();
2643 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2645 load_all_cookies();
2647 g_signal_connect(G_OBJECT(file_cookie_jar), "changed",
2648 G_CALLBACK(update_cookie_jar), NULL);
2650 return;
2653 /* TA: XXX - we should be using this callback for any header-requests we
2654 * receive (hence the name "new_generic_request" -- but for now, its use
2655 * is limited to handling cookies.
2657 void
2658 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused)
2660 SoupMessageHeaders *soup_msg_h;
2661 SoupURI *uri;
2662 char *cookie_str;
2664 soup_msg_h = soup_msg->request_headers;
2665 soup_message_headers_remove(soup_msg_h, "Cookie");
2666 uri = soup_message_get_uri(soup_msg);
2667 if ((cookie_str = get_cookies(uri))) {
2668 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2669 g_free(cookie_str);
2672 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2674 return;
2677 char *
2678 get_cookies(SoupURI *soup_uri) {
2679 char *cookie_str;
2681 cookie_str = soup_cookie_jar_get_cookies(file_cookie_jar, soup_uri, TRUE);
2683 return cookie_str;
2686 void
2687 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2689 GSList *resp_cookie = NULL, *cookie_list;
2690 SoupCookie *cookie;
2692 cookie_list = soup_cookies_from_response(soup_msg);
2693 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
2695 SoupDate *soup_date;
2696 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2698 if (cookie_timeout && cookie->expires == NULL) {
2699 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2700 soup_cookie_set_expires(cookie, soup_date);
2701 soup_date_free(soup_date);
2703 soup_cookie_jar_add_cookie(file_cookie_jar, cookie);
2706 soup_cookies_free(cookie_list);
2708 return;
2711 void
2712 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2714 if (!new) {
2715 /* Nothing to do. */
2716 return;
2719 SoupCookie *copy;
2720 copy = soup_cookie_copy(new);
2722 soup_cookie_jar_add_cookie(session_cookie_jar, copy);
2724 return;
2727 void
2728 load_all_cookies(void)
2730 GSList *cookie_list;
2731 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2733 /* Put them back in the session store. */
2734 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2735 cookie_list = cookies_from_file;
2737 for (; cookies_from_file;
2738 cookies_from_file = cookies_from_file->next)
2740 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2743 soup_cookies_free(cookies_from_file);
2744 g_slist_free(cookie_list);
2746 return;
2749 #endif
2751 void
2752 mop_up(void) {
2753 /* Free up any nasty globals before exiting. */
2754 #ifdef ENABLE_COOKIE_SUPPORT
2755 if (cookie_store)
2756 g_free(cookie_store);
2757 #endif
2758 return;
2762 main(int argc, char *argv[]) {
2763 static Arg a;
2764 static char url[256] = "";
2765 static gboolean ver = false;
2766 static gboolean configfile_exists = FALSE;
2767 static const char *cfile = NULL;
2768 static GOptionEntry opts[] = {
2769 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2770 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2771 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2772 { NULL }
2774 static GError *err;
2775 args = argv;
2777 /* command line argument: version */
2778 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2779 g_printerr("can't init gtk: %s\n", err->message);
2780 g_error_free(err);
2781 return EXIT_FAILURE;
2784 if (ver) {
2785 printf("%s\n", INTERNAL_VERSION);
2786 return EXIT_SUCCESS;
2789 if( getenv("XDG_CONFIG_HOME") )
2790 config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
2791 else
2792 config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
2794 if (cfile)
2795 configfile = g_strdup(cfile);
2796 else
2797 configfile = g_strdup_printf(RCFILE);
2799 if (!g_thread_supported())
2800 g_thread_init(NULL);
2802 if (winid) {
2803 if (strncmp(winid, "0x", 2) == 0) {
2804 embed = strtol(winid, NULL, 16);
2805 } else {
2806 embed = atoi(winid);
2810 setup_modkeys();
2811 make_keyslist();
2812 setup_gui();
2813 #ifdef ENABLE_COOKIE_SUPPORT
2814 setup_cookies();
2815 #endif
2817 make_searchengines_list(searchengines, LENGTH(searchengines));
2818 make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers));
2820 /* Check if the specified file exists. */
2821 /* And only warn the user, if they explicitly asked for a config on the
2822 * command line.
2824 if (!(access(configfile, F_OK) == 0) && cfile) {
2825 char *feedback_str;
2827 feedback_str = g_strdup_printf("Config file '%s' doesn't exist", cfile);
2828 give_feedback(feedback_str);
2829 g_free(feedback_str);
2830 } else if ((access(configfile, F_OK) == 0))
2831 configfile_exists = true;
2833 /* read config file */
2834 /* But only report errors if we failed, and the file existed. */
2835 if ((SUCCESS != read_rcfile(configfile)) && configfile_exists) {
2836 a.i = Error;
2837 a.s = g_strdup_printf("Error in config file '%s'", configfile);
2838 echo(&a);
2839 g_free(a.s);
2840 g_free(configfile);
2843 /* command line argument: URL */
2844 if (argc > 1) {
2845 strncpy(url, argv[argc - 1], 255);
2846 } else {
2847 strncpy(url, startpage, 255);
2850 a.i = TargetCurrent;
2851 a.s = url;
2852 open_arg(&a);
2853 gtk_main();
2855 mop_up();
2857 return EXIT_SUCCESS;