man page update: new hinting modes
[vimprobable2.git] / main.c
blobeb1d66fc2fa314b806bfff124151bfa3fd79495b
1 /*
2 (c) 2009 by Leon Winter
3 (c) 2009-2011 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 see LICENSE file
12 #include <X11/Xlib.h>
13 #include "includes.h"
14 #include "vimprobable.h"
15 #include "utilities.h"
16 #include "callbacks.h"
17 #include "javascript.h"
19 /* the CLEAN_MOD_*_MASK defines have all the bits set that will be stripped from the modifier bit field */
20 #define CLEAN_MOD_NUMLOCK_MASK (GDK_MOD2_MASK)
21 #define CLEAN_MOD_BUTTON_MASK (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
23 /* remove unused bits, numlock symbol and buttons from keymask */
24 #define CLEAN(mask) (mask & (GDK_MODIFIER_MASK) & ~(CLEAN_MOD_NUMLOCK_MASK) & ~(CLEAN_MOD_BUTTON_MASK))
26 #define IS_ESCAPE(event) (IS_ESCAPE_KEY(CLEAN(event->state), event->keyval))
27 #define IS_ESCAPE_KEY(s, k) ((s == 0 && k == GDK_Escape) || \
28 (s == GDK_CONTROL_MASK && k == GDK_bracketleft))
30 /* callbacks here */
31 static void inputbox_activate_cb(GtkEntry *entry, gpointer user_data);
32 static gboolean inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event);
33 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event);
34 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data);
35 static WebKitWebView* inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view);
36 static gboolean notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
37 static gboolean webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data);
38 static gboolean webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data);
39 static void webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data);
40 static gboolean webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event);
41 static void webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
42 static void webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
43 static gboolean webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
44 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data);
45 static gboolean webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
46 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data);
47 static gboolean webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
48 static void webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data);
49 static void webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data);
50 static void window_destroyed_cb(GtkWidget *window, gpointer func_data);
51 static gboolean blank_cb(void);
53 /* functions */
54 static gboolean bookmark(const Arg *arg);
55 static gboolean browser_settings(const Arg *arg);
56 static gboolean commandhistoryfetch(const Arg *arg);
57 static gboolean complete(const Arg *arg);
58 static gboolean descend(const Arg *arg);
59 gboolean echo(const Arg *arg);
60 static gboolean focus_input(const Arg *arg);
61 static gboolean input(const Arg *arg);
62 static gboolean navigate(const Arg *arg);
63 static gboolean number(const Arg *arg);
64 static gboolean open_arg(const Arg *arg);
65 static gboolean open_remembered(const Arg *arg);
66 static gboolean paste(const Arg *arg);
67 static gboolean quickmark(const Arg *arg);
68 static gboolean quit(const Arg *arg);
69 static gboolean revive(const Arg *arg);
70 static gboolean print_frame(const Arg *arg);
71 static gboolean search(const Arg *arg);
72 static gboolean set(const Arg *arg);
73 static gboolean script(const Arg *arg);
74 static gboolean scroll(const Arg *arg);
75 static gboolean search_tag(const Arg *arg);
76 static gboolean yank(const Arg *arg);
77 static gboolean view_source(const Arg * arg);
78 static gboolean zoom(const Arg *arg);
79 static gboolean fake_key_event(const Arg *arg);
81 static void update_url(const char *uri);
82 static void setup_modkeys(void);
83 static void setup_gui(void);
84 static void setup_settings(void);
85 static void setup_signals(void);
86 static void ascii_bar(int total, int state, char *string);
87 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
88 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
89 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
90 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
91 const char *bg_color_str, const char *fg_color_str);
93 static gboolean history(void);
94 static gboolean process_set_line(char *line);
95 void save_command_history(char *line);
96 void toggle_proxy(gboolean onoff);
97 void toggle_scrollbars(gboolean onoff);
99 gboolean process_keypress(GdkEventKey *event);
100 void fill_suggline(char * suggline, const char * command, const char *fill_with);
101 GtkWidget * fill_eventbox(const char * completion_line);
102 static void mop_up(void);
104 #include "main.h"
106 /* variables */
107 static GtkWindow *window;
108 static GtkWidget *viewport;
109 static GtkBox *box;
110 static GtkScrollbar *scroll_h;
111 static GtkScrollbar *scroll_v;
112 static GtkAdjustment *adjust_h;
113 static GtkAdjustment *adjust_v;
114 static GtkWidget *inputbox;
115 static GtkWidget *eventbox;
116 static GtkBox *statusbar;
117 static GtkWidget *status_url;
118 static GtkWidget *status_state;
119 static WebKitWebView *webview;
120 static SoupSession *session;
121 static GtkClipboard *clipboards[2];
122 static GdkKeymap *keymap;
124 static char **args;
125 static unsigned int mode = ModeNormal;
126 static unsigned int count = 0;
127 static float zoomstep;
128 char *modkeys;
129 static char current_modkey;
130 static char *search_handle;
131 static gboolean search_direction;
132 static gboolean echo_active = TRUE;
133 WebKitWebInspector *inspector;
135 static GdkNativeWindow embed = 0;
136 static char *configfile = NULL;
137 static char *winid = NULL;
139 static char rememberedURI[1024] = "";
140 static char followTarget[8] = "";
141 char *error_msg = NULL;
142 char *config_base = NULL;
144 GList *activeDownloads;
146 #include "config.h"
147 #include "keymap.h"
149 char commandhistory[COMMANDHISTSIZE][255];
150 int lastcommand = 0;
151 int maxcommands = 0;
152 int commandpointer = 0;
153 KeyList *keylistroot = NULL;
155 /* Cookie support. */
156 #ifdef ENABLE_COOKIE_SUPPORT
157 static SoupCookieJar *session_cookie_jar = NULL;
158 static SoupCookieJar *file_cookie_jar = NULL;
159 static time_t cookie_timeout = 4800;
160 static char *cookie_store;
161 static void setup_cookies(void);
162 static char *get_cookies(SoupURI *soup_uri);
163 static void load_all_cookies(void);
164 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
165 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
166 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
167 #endif
168 /* callbacks */
169 void
170 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
171 quit(NULL);
174 void
175 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
176 gtk_window_set_title(window, title);
179 void
180 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
181 #ifdef ENABLE_GTK_PROGRESS_BAR
182 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
183 #endif
184 update_state();
187 #ifdef ENABLE_WGET_PROGRESS_BAR
188 void
189 ascii_bar(int total, int state, char *string) {
190 int i;
192 for (i = 0; i < state; i++)
193 string[i] = progressbartickchar;
194 string[i++] = progressbarcurrent;
195 for (; i < total; i++)
196 string[i] = progressbarspacer;
197 string[i] = '\0';
199 #endif
201 void
202 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
203 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
204 const char *uri = webkit_web_view_get_uri(webview);
206 update_url(uri);
207 script(&a);
208 g_free(a.s);
211 void
212 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
213 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_INPUT_FOCUS) };
215 if (HISTORY_MAX_ENTRIES > 0)
216 history();
217 update_state();
218 script(&a);
219 g_free(a.s);
222 static gboolean
223 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
224 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(webview) };
225 if (strlen(rememberedURI) > 0) {
226 a.s = rememberedURI;
228 open_arg(&a);
229 return FALSE;
232 gboolean
233 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
234 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
235 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
236 open_arg(&a);
237 webkit_web_policy_decision_ignore(decision);
238 return TRUE;
241 gboolean
242 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
243 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
244 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
245 webkit_web_policy_decision_download(decision);
246 return TRUE;
247 } else {
248 return FALSE;
252 static WebKitWebView*
253 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
254 gchar* inspector_title;
255 GtkWidget* inspector_window;
256 GtkWidget* inspector_view;
258 /* just enough code to show the inspector - no signal handling etc. */
259 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
260 if (embed) {
261 inspector_window = gtk_plug_new(embed);
262 } else {
263 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
264 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
266 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
267 g_free(inspector_title);
268 inspector_view = webkit_web_view_new();
269 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
270 gtk_widget_show_all(inspector_window);
271 return WEBKIT_WEB_VIEW(inspector_view);
274 gboolean
275 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
276 const gchar *filename;
277 gchar *uri, *path;
278 uint32_t size;
279 Arg a;
280 WebKitDownloadStatus status;
282 filename = webkit_download_get_suggested_filename(download);
283 if (filename == NULL || strlen(filename) == 0) {
284 filename = "vimprobable_download";
286 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
287 uri = g_strconcat("file://", path, NULL);
288 webkit_download_set_destination_uri(download, uri);
289 g_free(uri);
290 size = (uint32_t)webkit_download_get_total_size(download);
291 a.i = Info;
292 if (size > 0)
293 a.s = g_strdup_printf("Download %s started (expected size: %u bytes)...", filename, size);
294 else
295 a.s = g_strdup_printf("Download %s started (unknown size)...", filename);
296 echo(&a);
297 g_free(a.s);
298 activeDownloads = g_list_prepend(activeDownloads, download);
299 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
300 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
301 status = webkit_download_get_status(download);
302 if (status == WEBKIT_DOWNLOAD_STATUS_CREATED)
303 webkit_download_start(download);
304 update_state();
305 return TRUE;
308 gboolean
309 blank_cb(void) {
310 return TRUE;
313 void
314 download_progress(WebKitDownload *d, GParamSpec *pspec) {
315 Arg a;
316 WebKitDownloadStatus status = webkit_download_get_status(d);
318 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
319 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
320 a.i = Error;
321 a.s = g_strdup_printf("Error while downloading %s", webkit_download_get_suggested_filename(d));
322 echo(&a);
323 } else {
324 a.i = Info;
325 a.s = g_strdup_printf("Download %s finished", webkit_download_get_suggested_filename(d));
326 echo(&a);
328 g_free(a.s);
329 activeDownloads = g_list_remove(activeDownloads, d);
331 update_state();
335 gboolean
336 process_keypress(GdkEventKey *event) {
337 KeyList *current;
338 guint keyval;
339 GdkModifierType irrelevant;
341 /* Get a mask of modifiers that shouldn't be considered for this event.
342 * E.g.: It shouldn't matter whether ';' is shifted or not. */
343 gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
344 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
346 current = keylistroot;
348 while (current != NULL) {
349 if (current->Element.mask == (CLEAN(event->state) & ~irrelevant)
350 && (current->Element.modkey == current_modkey
351 || (!current->Element.modkey && !current_modkey)
352 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
353 && current->Element.key == keyval
354 && current->Element.func)
355 if (current->Element.func(&current->Element.arg)) {
356 current_modkey = count = 0;
357 update_state();
358 return TRUE;
360 current = current->next;
362 return FALSE;
365 gboolean
366 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
367 Arg a = { .i = ModeNormal, .s = NULL };
368 guint keyval;
369 GdkModifierType irrelevant;
371 /* Get a mask of modifiers that shouldn't be considered for this event.
372 * E.g.: It shouldn't matter whether ';' is shifted or not. */
373 gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
374 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
376 switch (mode) {
377 case ModeNormal:
378 if ((CLEAN(event->state) & ~irrelevant) == 0) {
379 if (IS_ESCAPE(event)) {
380 a.i = Info;
381 a.s = g_strdup("");
382 echo(&a);
383 g_free(a.s);
384 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
385 || (event->keyval == GDK_0 && count))) {
386 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
387 update_state();
388 return TRUE;
389 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
390 current_modkey = event->keyval;
391 update_state();
392 return TRUE;
395 /* keybindings */
396 if (process_keypress(event) == TRUE) return TRUE;
398 break;
399 case ModeInsert:
400 if (IS_ESCAPE(event)) {
401 a.i = Silent;
402 a.s = g_strdup("hints.clearFocus();");
403 script(&a);
404 g_free(a.s);
405 a.i = ModeNormal;
406 return set(&a);
408 case ModePassThrough:
409 if (IS_ESCAPE(event)) {
410 echo(&a);
411 set(&a);
412 return TRUE;
414 break;
415 case ModeSendKey:
416 echo(&a);
417 set(&a);
418 break;
420 return FALSE;
423 void
424 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
425 const char *fg_color_str) {
426 GdkColor fg_color;
427 GdkColor bg_color;
428 PangoFontDescription *font;
430 font = pango_font_description_from_string(font_str);
431 gtk_widget_modify_font(widget, font);
432 pango_font_description_free(font);
434 if (fg_color_str)
435 gdk_color_parse(fg_color_str, &fg_color);
436 if (bg_color_str)
437 gdk_color_parse(bg_color_str, &bg_color);
439 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
440 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
442 return;
445 void
446 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
447 const char *uri = webkit_web_view_get_uri(webview);
448 char *markup;
450 memset(rememberedURI, 0, 1024);
451 if (link) {
452 markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link);
453 gtk_label_set_markup(GTK_LABEL(status_url), markup);
454 strncpy(rememberedURI, link, 1024);
455 g_free(markup);
456 } else
457 update_url(uri);
460 gboolean
461 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
462 Arg a;
464 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
465 if (gtk_window_has_toplevel_focus(window)) {
466 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
467 a.i = ModeNormal;
468 return set(&a);
469 } else if (!strcmp(message, "insertmode_on")) {
470 a.i = ModeInsert;
471 return set(&a);
474 return FALSE;
477 void
478 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
479 char *text;
480 guint16 length = gtk_entry_get_text_length(entry);
481 Arg a;
482 gboolean success = FALSE, forward = FALSE;
484 a.i = HideCompletion;
485 complete(&a);
486 if (length == 0)
487 return;
488 text = (char*)gtk_entry_get_text(entry);
489 if (length > 1 && text[0] == ':') {
490 success = process_line((text + 1));
491 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
492 webkit_web_view_unmark_text_matches(webview);
493 #ifdef ENABLE_MATCH_HIGHLITING
494 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
495 webkit_web_view_set_highlight_text_matches(webview, TRUE);
496 #endif
497 count = 0;
498 #ifndef ENABLE_INCREMENTAL_SEARCH
499 a.s =& text[1];
500 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
501 search(&a);
502 #else
503 search_direction = forward;
504 search_handle = g_strdup(&text[1]);
505 #endif
506 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
507 a.i = Silent;
508 a.s = g_strdup_printf("hints.fire();");
509 script(&a);
510 g_free(a.s);
511 update_state();
512 } else
513 return;
514 if (!echo_active)
515 gtk_entry_set_text(entry, "");
516 gtk_widget_grab_focus(GTK_WIDGET(webview));
519 gboolean
520 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
521 Arg a;
522 int numval;
524 if (mode == ModeHints) {
525 if (event->keyval == GDK_Tab) {
526 a.i = Silent;
527 a.s = g_strdup_printf("hints.focusNextHint();");
528 script(&a);
529 g_free(a.s);
530 update_state();
531 return TRUE;
533 if (event->keyval == GDK_ISO_Left_Tab) {
534 a.i = Silent;
535 a.s = g_strdup_printf("hints.focusPreviousHint();");
536 script(&a);
537 g_free(a.s);
538 update_state();
539 return TRUE;
541 if (event->keyval == GDK_Return) {
542 a.i = Silent;
543 a.s = g_strdup_printf("hints.fire();");
544 script(&a);
545 g_free(a.s);
546 update_state();
547 return TRUE;
550 switch (event->keyval) {
551 case GDK_bracketleft:
552 case GDK_Escape:
553 if (!IS_ESCAPE(event)) break;
554 a.i = HideCompletion;
555 complete(&a);
556 a.i = ModeNormal;
557 return set(&a);
558 break;
559 case GDK_Tab:
560 a.i = DirectionNext;
561 return complete(&a);
562 break;
563 case GDK_Up:
564 a.i = DirectionPrev;
565 return commandhistoryfetch(&a);
566 break;
567 case GDK_Down:
568 a.i = DirectionNext;
569 return commandhistoryfetch(&a);
570 break;
571 case GDK_ISO_Left_Tab:
572 a.i = DirectionPrev;
573 return complete(&a);
574 break;
577 if (mode == ModeHints) {
578 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
579 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
580 (event->keyval == GDK_BackSpace)) {
581 count /= 10;
582 a.i = Silent;
583 a.s = g_strdup_printf("hints.updateHints(%d);", count);
584 script(&a);
585 g_free(a.s);
586 update_state();
587 return TRUE;
590 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
591 if ((numval >= 1 && numval <= 9) || (numval == 0 && count)) {
592 /* allow a zero as non-first number */
593 count = (count ? count * 10 : 0) + numval;
594 a.i = Silent;
595 a.s = g_strdup_printf("hints.updateHints(%d);", count);
596 script(&a);
597 g_free(a.s);
598 update_state();
599 return TRUE;
603 return FALSE;
606 gboolean
607 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
608 int i;
609 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
610 /* handle mouse click events */
611 for (i = 0; i < LENGTH(mouse); i++) {
612 if (mouse[i].mask == CLEAN(event->button.state)
613 && (mouse[i].modkey == current_modkey
614 || (!mouse[i].modkey && !current_modkey)
615 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
616 && mouse[i].button == event->button.button
617 && mouse[i].func) {
618 if (mouse[i].func(&mouse[i].arg)) {
619 current_modkey = count = 0;
620 update_state();
621 return TRUE;
626 return FALSE;
629 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
630 Arg a;
631 guint16 length = gtk_entry_get_text_length(entry);
633 if (!length) {
634 a.i = HideCompletion;
635 complete(&a);
636 a.i = ModeNormal;
637 return set(&a);
639 return FALSE;
642 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
643 Arg a;
644 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
645 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
646 gboolean forward = FALSE;
648 /* Update incremental search if the user changes the search text.
650 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
651 * from the user. But if the entry is focused and the text is set
652 * through gtk_entry_set_text() in some asyncrounous operation,
653 * I would consider that a bug.
656 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
657 webkit_web_view_unmark_text_matches(webview);
658 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
659 return TRUE;
660 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
661 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
662 a.i = Silent;
663 switch (text[0]) {
664 case '.':
665 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
666 break;
668 case ',':
669 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
670 break;
672 case ';':
673 a.s = NULL;
674 switch (text[1]) {
675 case 's':
676 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
677 break;
678 case 'y':
679 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
680 break;
681 case 'o':
682 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
683 break;
684 case 't': case 'w':
685 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
686 break;
687 case 'O': case 'T': case 'W':
688 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
689 break;
691 break;
693 count = 0;
694 if (a.s) {
695 script(&a);
696 g_free(a.s);
699 return TRUE;
700 } else if (length == 0 && followTarget[0]) {
701 mode = ModeNormal;
702 a.i = Silent;
703 a.s = g_strdup("hints.clearHints();");
704 script(&a);
705 g_free(a.s);
706 count = 0;
707 update_state();
710 return FALSE;
713 /* funcs here */
715 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
716 memset(suggline, 0, 512);
717 strncpy(suggline, command, 512);
718 strncat(suggline, " ", 1);
719 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
722 GtkWidget * fill_eventbox(const char * completion_line) {
723 GtkBox * row;
724 GtkWidget *row_eventbox, *el;
725 GdkColor color;
726 char *markup, *markup_tmp;
728 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
729 row_eventbox = gtk_event_box_new();
730 gdk_color_parse(completionbgcolor[0], &color);
731 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
732 el = gtk_label_new(NULL);
733 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
734 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
735 markup_tmp, "</span>", NULL);
736 gtk_label_set_markup(GTK_LABEL(el), markup);
737 g_free(markup_tmp);
738 g_free(markup);
739 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
740 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
741 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
742 return row_eventbox;
745 gboolean
746 complete(const Arg *arg) {
747 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
748 size_t listlen, len, cmdlen;
749 int i, spacepos;
750 Listelement *elementlist = NULL, *elementpointer;
751 gboolean highlight = FALSE;
752 GtkBox *row;
753 GtkWidget *row_eventbox, *el;
754 GtkBox *_table;
755 GdkColor color;
756 static GtkWidget *table, *top_border;
757 static char *prefix;
758 static char **suggestions;
759 static GtkWidget **widgets;
760 static int n = 0, m, current = -1;
762 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
763 len = strlen(str);
765 /* Get the length of the list of commands for completion. We need this to
766 * malloc/realloc correctly.
768 listlen = LENGTH(commands);
770 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
771 return TRUE;
772 if (prefix) {
773 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
774 gdk_color_parse(completionbgcolor[0], &color);
775 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
776 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
777 if ((arg->i == DirectionNext && current == 0)
778 || (arg->i == DirectionPrev && current == n - 1))
779 current = -1;
780 } else {
781 free(widgets);
782 free(suggestions);
783 free(prefix);
784 gtk_widget_destroy(GTK_WIDGET(table));
785 gtk_widget_destroy(GTK_WIDGET(top_border));
786 table = NULL;
787 widgets = NULL;
788 suggestions = NULL;
789 prefix = NULL;
790 n = 0;
791 current = -1;
792 if (arg->i == HideCompletion)
793 return TRUE;
795 } else if (arg->i == HideCompletion)
796 return TRUE;
797 if (!widgets) {
798 prefix = g_strdup(str);
799 widgets = malloc(sizeof(GtkWidget*) * listlen);
800 suggestions = malloc(sizeof(char*) * listlen);
801 top_border = gtk_event_box_new();
802 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
803 gdk_color_parse(completioncolor[2], &color);
804 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
805 table = gtk_event_box_new();
806 gdk_color_parse(completionbgcolor[0], &color);
807 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
808 highlight = len > 1;
809 if (strchr(str, ' ') == NULL) {
810 /* command completion */
811 listlen = LENGTH(commands);
812 for (i = 0; i < listlen; i++) {
813 if (commands[i].cmd == NULL)
814 break;
815 cmdlen = strlen(commands[i].cmd);
816 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
817 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
818 if (highlight) {
819 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
820 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
821 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
822 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
824 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
825 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
826 row_eventbox = gtk_event_box_new();
827 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
828 el = gtk_label_new(NULL);
829 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
830 free(s);
831 gtk_label_set_markup(GTK_LABEL(el), markup);
832 g_free(markup);
833 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
834 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
835 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
836 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
837 suggestions[n] = commands[i].cmd;
838 widgets[n++] = row_eventbox;
841 } else {
842 entry = (char *)malloc(512 * sizeof(char));
843 if (entry == NULL) {
844 return FALSE;
846 memset(entry, 0, 512);
847 suggurls = malloc(sizeof(char*) * listlen);
848 if (suggurls == NULL) {
849 return FALSE;
851 spacepos = strcspn(str, " ");
852 searchfor = (str + spacepos + 1);
853 strncpy(command, (str + 1), spacepos - 1);
854 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
855 /* browser settings */
856 listlen = LENGTH(browsersettings);
857 for (i = 0; i < listlen; i++) {
858 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
859 /* match */
860 fill_suggline(suggline, command, browsersettings[i].name);
861 /* FIXME(HP): This memory is never freed */
862 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
863 strncpy(suggurls[n], suggline, 512);
864 suggestions[n] = suggurls[n];
865 row_eventbox = fill_eventbox(suggline);
866 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
867 widgets[n++] = row_eventbox;
871 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
872 /* completion on tags */
873 spacepos = strcspn(str, " ");
874 searchfor = (str + spacepos + 1);
875 elementlist = complete_list(searchfor, 1, elementlist);
876 } else {
877 /* URL completion: bookmarks */
878 elementlist = complete_list(searchfor, 0, elementlist);
879 m = count_list(elementlist);
880 if (m < MAX_LIST_SIZE) {
881 /* URL completion: history */
882 elementlist = complete_list(searchfor, 2, elementlist);
885 elementpointer = elementlist;
886 while (elementpointer != NULL) {
887 fill_suggline(suggline, command, elementpointer->element);
888 /* FIXME(HP): This memory is never freed */
889 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
890 strncpy(suggurls[n], suggline, 512);
891 suggestions[n] = suggurls[n];
892 row_eventbox = fill_eventbox(suggline);
893 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
894 widgets[n++] = row_eventbox;
895 elementpointer = elementpointer->next;
896 if (n >= MAX_LIST_SIZE)
897 break;
899 free_list(elementlist);
900 if (suggurls != NULL) {
901 free(suggurls);
902 suggurls = NULL;
904 if (entry != NULL) {
905 free(entry);
906 entry = NULL;
909 /* TA: FIXME - this needs rethinking entirely. */
911 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
912 if (widgets_temp == NULL && widgets == NULL) {
913 fprintf(stderr, "Couldn't realloc() widgets\n");
914 exit(1);
916 widgets = widgets_temp;
917 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
918 if (suggestions_temp == NULL && suggestions == NULL) {
919 fprintf(stderr, "Couldn't realloc() suggestions\n");
920 exit(1);
922 suggestions = suggestions_temp;
924 if (!n) {
925 gdk_color_parse(completionbgcolor[1], &color);
926 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
927 el = gtk_label_new(NULL);
928 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
929 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
930 gtk_label_set_markup(GTK_LABEL(el), markup);
931 g_free(markup);
932 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
934 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
935 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
936 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
937 gtk_widget_show_all(GTK_WIDGET(window));
938 if (!n)
939 return TRUE;
940 current = arg->i == DirectionPrev ? n - 1 : 0;
942 if (current != -1) {
943 gdk_color_parse(completionbgcolor[2], &color);
944 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
945 s = g_strconcat(":", suggestions[current], NULL);
946 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
947 g_free(s);
948 } else
949 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
950 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
951 return TRUE;
954 gboolean
955 descend(const Arg *arg) {
956 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
957 int i, len;
958 count = count ? count : 1;
960 if (!source)
961 return TRUE;
962 if (arg->i == Rootdir) {
963 for (i = 0; i < 3; i++) /* get to the third slash */
964 if (!(p = strchr(++p, '/')))
965 return TRUE; /* if we cannot find it quit */
966 } else {
967 len = strlen(source);
968 if (!len) /* if string is empty quit */
969 return TRUE;
970 p = source + len; /* start at the end */
971 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
972 ++count;
973 for (i = 0; i < count; i++)
974 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
975 if (p == source) /* if we reach the first char pointer quit */
976 return TRUE;
977 ++p; /* since we do p-- in the while, we are pointing at
978 the char before the slash, so +1 */
980 len = p - source + 1; /* new length = end - start + 1 */
981 new = malloc(len + 1);
982 memcpy(new, source, len);
983 new[len] = '\0';
984 webkit_web_view_load_uri(webview, new);
985 free(new);
986 return TRUE;
989 gboolean
990 echo(const Arg *arg) {
991 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
993 if (index < Info || index > Error)
994 return TRUE;
996 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
997 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
999 return TRUE;
1002 gboolean
1003 input(const Arg *arg) {
1004 int pos = 0;
1005 count = 0;
1006 gchar* x_clipboard_text;
1007 const char *url;
1008 int index = Info;
1009 Arg a;
1011 /* if inputbox hidden, show it again */
1012 if (!gtk_widget_get_visible(inputbox))
1013 gtk_widget_set_visible(inputbox, TRUE);
1015 update_state();
1017 /* Set the colour and font back to the default, so that we don't still
1018 * maintain a red colour from a warning from an end of search indicator,
1019 * etc.
1021 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1023 /* to avoid things like :open URL :open URL2 or :open :open URL */
1024 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1025 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1026 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
1027 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1029 x_clipboard_text = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
1030 gtk_widget_grab_focus(inputbox);
1031 if (x_clipboard_text != NULL) {
1032 /* reset x clipboard */
1033 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), x_clipboard_text, -1);
1034 g_free(x_clipboard_text);
1036 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1038 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1039 mode = ModeHints;
1040 memset(followTarget, 0, 8);
1041 strncpy(followTarget, "current", 8);
1042 a.i = Silent;
1043 switch (arg->s[0]) {
1044 case '.':
1045 a.s = g_strdup("hints.createHints('', 'f');");
1046 break;
1048 case ',':
1049 a.s = g_strdup("hints.createHints('', 'F');");
1050 break;
1052 case ';':
1053 a.s = NULL;
1054 if (arg->s[1]) {
1055 switch (arg->s[1]) {
1056 case 's':
1057 a.s = g_strdup("hints.createHints('', 's');");
1058 break;
1059 case 'y':
1060 a.s = g_strdup("hints.createHints('', 'y');");
1061 break;
1062 case 'o':
1063 a.s = g_strdup("hints.createHints('', 'f');");
1064 break;
1065 case 't': case 'w':
1066 a.s = g_strdup("hints.createHints('', 'F');");
1067 break;
1068 case 'O': case 'T': case 'W':
1069 a.s = g_strdup("hints.createHints('', 'O');");
1070 break;
1073 break;
1075 count = 0;
1076 if (a.s) {
1077 script(&a);
1078 g_free(a.s);
1082 return TRUE;
1085 gboolean
1086 navigate(const Arg *arg) {
1087 if (arg->i & NavigationForwardBack)
1088 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
1089 else if (arg->i & NavigationReloadActions)
1090 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
1091 else
1092 webkit_web_view_stop_loading(webview);
1093 return TRUE;
1096 gboolean
1097 number(const Arg *arg) {
1098 const char *source = webkit_web_view_get_uri(webview);
1099 char *uri, *p, *new;
1100 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1102 if (!source)
1103 return TRUE;
1104 uri = g_strdup(source); /* copy string */
1105 p =& uri[0];
1106 while(*p != '\0') /* goto the end of the string */
1107 ++p;
1108 --p;
1109 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1110 --p;
1111 if (*(++p) == '\0') { /* if no numbers were found abort */
1112 free(uri);
1113 return TRUE;
1115 number = atoi(p) + diff; /* apply diff on number */
1116 *p = '\0';
1117 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1118 webkit_web_view_load_uri(webview, new);
1119 g_free(new);
1120 free(uri);
1121 return TRUE;
1124 gboolean
1125 open_arg(const Arg *arg) {
1126 char *argv[6];
1127 char *s = arg->s, *p = NULL, *new;
1128 Arg a = { .i = NavigationReload };
1129 int len;
1130 char *search_uri, *search_term;
1132 if (embed) {
1133 argv[0] = *args;
1134 argv[1] = "-e";
1135 argv[2] = winid;
1136 argv[3] = arg->s;
1137 argv[4] = NULL;
1138 } else {
1139 argv[0] = *args;
1140 argv[1] = arg->s;
1141 argv[2] = NULL;
1144 if (!arg->s)
1145 navigate(&a);
1146 else if (arg->i == TargetCurrent) {
1147 while(*s == ' ') /* strip leading whitespace */
1148 ++s;
1149 p = (s + strlen(s) - 1);
1150 while(*p == ' ') /* strip trailing whitespace */
1151 --p;
1152 *(p + 1) = '\0';
1153 len = strlen(s);
1154 new = NULL, p = strchr(s, ' ');
1155 if (p) { /* check for search engines */
1156 *p = '\0';
1157 search_uri = find_uri_for_searchengine(s);
1158 if (search_uri != NULL) {
1159 search_term = soup_uri_encode(p+1, "&");
1160 new = g_strdup_printf(search_uri, search_term);
1161 g_free(search_term);
1163 *p = ' ';
1165 if (!new) {
1166 if (len > 3 && strstr(s, "://")) { /* valid url? */
1167 p = new = g_malloc(len + 1);
1168 while(*s != '\0') { /* strip whitespaces */
1169 if (*s != ' ')
1170 *(p++) = *s;
1171 ++s;
1173 *p = '\0';
1174 } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */
1175 new = g_malloc(sizeof("file://") + len);
1176 strcpy(new, "file://");
1177 memcpy(&new[sizeof("file://") - 1], s, len + 1);
1178 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1179 search_uri = find_uri_for_searchengine(defaultsearch);
1180 if (search_uri != NULL) {
1181 search_term = soup_uri_encode(s, "&");
1182 new = g_strdup_printf(search_uri, search_term);
1183 g_free(search_term);
1185 } else { /* prepend "http://" */
1186 new = g_malloc(sizeof("http://") + len);
1187 strcpy(new, "http://");
1188 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1191 webkit_web_view_load_uri(webview, new);
1192 g_free(new);
1193 } else
1194 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1195 return TRUE;
1198 gboolean
1199 open_remembered(const Arg *arg)
1201 Arg a = {arg->i, rememberedURI};
1203 if (strcmp(rememberedURI, "")) {
1204 open_arg(&a);
1206 return TRUE;
1209 gboolean
1210 yank(const Arg *arg) {
1211 const char *url, *feedback, *content;
1213 if (arg->i & SourceSelection) {
1214 webkit_web_view_copy_clipboard(webview);
1215 if (arg->i & ClipboardPrimary)
1216 content = gtk_clipboard_wait_for_text(clipboards[0]);
1217 if (!content && arg->i & ClipboardGTK)
1218 content = gtk_clipboard_wait_for_text(clipboards[1]);
1219 if (content) {
1220 feedback = g_strconcat("Yanked ", content, NULL);
1221 g_free((gpointer *)content);
1222 give_feedback(feedback);
1223 g_free((gpointer *)feedback);
1225 } else {
1226 if (arg->i & SourceURL) {
1227 url = webkit_web_view_get_uri(webview);
1228 } else {
1229 url = arg->s;
1231 if (!url)
1232 return TRUE;
1233 feedback = g_strconcat("Yanked ", url, NULL);
1234 give_feedback(feedback);
1235 if (arg->i & ClipboardPrimary)
1236 gtk_clipboard_set_text(clipboards[0], url, -1);
1237 if (arg->i & ClipboardGTK)
1238 gtk_clipboard_set_text(clipboards[1], url, -1);
1240 return TRUE;
1243 gboolean
1244 paste(const Arg *arg) {
1245 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1247 /* If we're over a link, open it in a new target. */
1248 if (strlen(rememberedURI) > 0) {
1249 Arg new_target = { .i = TargetNew, .s = arg->s };
1250 open_arg(&new_target);
1251 return TRUE;
1254 if (arg->i & ClipboardPrimary)
1255 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1256 if (!a.s && arg->i & ClipboardGTK)
1257 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1258 if (a.s) {
1259 open_arg(&a);
1260 g_free(a.s);
1262 return TRUE;
1265 gboolean
1266 quit(const Arg *arg) {
1267 FILE *f;
1268 const char *filename;
1269 const char *uri = webkit_web_view_get_uri(webview);
1270 if (uri != NULL) {
1271 /* write last URL into status file for recreation with "u" */
1272 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1273 f = fopen(filename, "w");
1274 g_free((gpointer *)filename);
1275 if (f != NULL) {
1276 fprintf(f, "%s", uri);
1277 fclose(f);
1280 gtk_main_quit();
1281 return TRUE;
1284 gboolean
1285 revive(const Arg *arg) {
1286 FILE *f;
1287 const char *filename;
1288 char buffer[512] = "";
1289 Arg a = { .i = TargetNew, .s = NULL };
1290 /* get the URL of the window which has been closed last */
1291 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1292 f = fopen(filename, "r");
1293 g_free((gpointer *)filename);
1294 if (f != NULL) {
1295 fgets(buffer, 512, f);
1296 fclose(f);
1298 if (strlen(buffer) > 0) {
1299 a.s = buffer;
1300 open_arg(&a);
1301 return TRUE;
1303 return FALSE;
1306 static
1307 gboolean print_frame(const Arg *arg)
1309 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1310 webkit_web_frame_print (frame);
1311 return TRUE;
1314 gboolean
1315 search(const Arg *arg) {
1316 count = count ? count : 1;
1317 gboolean success, direction = arg->i & DirectionPrev;
1318 Arg a;
1320 if (arg->s) {
1321 free(search_handle);
1322 search_handle = g_strdup(arg->s);
1324 if (!search_handle)
1325 return TRUE;
1326 if (arg->i & DirectionAbsolute)
1327 search_direction = direction;
1328 else
1329 direction ^= search_direction;
1330 do {
1331 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1332 if (!success) {
1333 if (arg->i & Wrapping) {
1334 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1335 if (success) {
1336 a.i = Warning;
1337 a.s = g_strdup_printf("search hit %s, continuing at %s",
1338 direction ? "BOTTOM" : "TOP",
1339 direction ? "TOP" : "BOTTOM");
1340 echo(&a);
1341 g_free(a.s);
1342 } else
1343 break;
1344 } else
1345 break;
1347 } while(--count);
1348 if (!success) {
1349 a.i = Error;
1350 a.s = g_strdup_printf("Pattern not found: %s", search_handle);
1351 echo(&a);
1352 g_free(a.s);
1354 return TRUE;
1357 gboolean
1358 set(const Arg *arg) {
1359 Arg a = { .i = Info | NoAutoHide };
1361 switch (arg->i) {
1362 case ModeNormal:
1363 if (search_handle) {
1364 search_handle = NULL;
1365 webkit_web_view_unmark_text_matches(webview);
1367 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1368 gtk_widget_grab_focus(GTK_WIDGET(webview));
1369 break;
1370 case ModePassThrough:
1371 a.s = g_strdup("-- PASS THROUGH --");
1372 echo(&a);
1373 g_free(a.s);
1374 break;
1375 case ModeSendKey:
1376 a.s = g_strdup("-- PASS TROUGH (next) --");
1377 echo(&a);
1378 g_free(a.s);
1379 break;
1380 case ModeInsert: /* should not be called manually but automatically */
1381 a.s = g_strdup("-- INSERT --");
1382 echo(&a);
1383 g_free(a.s);
1384 break;
1385 default:
1386 return TRUE;
1388 mode = arg->i;
1389 return TRUE;
1392 gchar*
1393 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1394 JSStringRef string_ref;
1395 gchar *string;
1396 size_t length;
1398 string_ref = JSValueToStringCopy(context, ref, NULL);
1399 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1400 string = g_new(gchar, length);
1401 JSStringGetUTF8CString(string_ref, string, length);
1402 JSStringRelease(string_ref);
1403 return string;
1406 void
1407 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1408 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1409 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1410 JSStringRef str;
1411 JSValueRef val, exception;
1413 str = JSStringCreateWithUTF8CString(script);
1414 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1415 JSStringRelease(str);
1416 if (!val)
1417 *message = jsapi_ref_to_string(context, exception);
1418 else
1419 *value = jsapi_ref_to_string(context, val);
1422 gboolean
1423 quickmark(const Arg *a) {
1424 int i, b;
1425 b = atoi(a->s);
1426 char *fn = g_strdup_printf(QUICKMARK_FILE);
1427 FILE *fp;
1428 fp = fopen(fn, "r");
1429 g_free(fn);
1430 fn = NULL;
1431 char buf[100];
1433 if (fp != NULL && b < 10) {
1434 for( i=0; i < b; ++i ) {
1435 if (feof(fp)) {
1436 break;
1438 fgets(buf, 100, fp);
1440 char *ptr = strrchr(buf, '\n');
1441 *ptr = '\0';
1442 Arg x = { .s = buf };
1443 if (strlen(buf))
1444 return open_arg(&x);
1445 else {
1446 x.i = Error;
1447 x.s = g_strdup_printf("Quickmark %d not defined", b);
1448 echo(&x);
1449 g_free(x.s);
1450 return false;
1452 } else { return false; }
1455 gboolean
1456 script(const Arg *arg) {
1457 gchar *value = NULL, *message = NULL;
1458 char text[1024] = "";
1459 Arg a;
1460 WebKitNetworkRequest *request;
1461 WebKitDownload *download;
1463 if (!arg->s) {
1464 set_error("Missing argument.");
1465 return FALSE;
1467 jsapi_evaluate_script(arg->s, &value, &message);
1468 if (message) {
1469 set_error(message);
1470 g_free(message);
1471 return FALSE;
1473 g_free(message);
1474 if (arg->i != Silent && value) {
1475 a.i = arg->i;
1476 a.s = g_strdup(value);
1477 echo(&a);
1478 g_free(a.s);
1480 /* switch mode according to scripts return value */
1481 if (value) {
1482 if (strncmp(value, "done;", 5) == 0) {
1483 a.i = ModeNormal;
1484 set(&a);
1485 } else if (strncmp(value, "insert;", 7) == 0) {
1486 a.i = ModeInsert;
1487 set(&a);
1488 } else if (strncmp(value, "save;", 5) == 0) {
1489 /* forced download */
1490 a.i = ModeNormal;
1491 set(&a);
1492 request = webkit_network_request_new((value + 5));
1493 download = webkit_download_new(request);
1494 webview_download_cb(webview, download, (gpointer *)NULL);
1495 } else if (strncmp(value, "yank;", 5) == 0) {
1496 /* yank link URL to clipboard */
1497 a.i = ModeNormal;
1498 set(&a);
1499 a.i = ClipboardPrimary | ClipboardGTK;
1500 a.s = (value + 5);
1501 yank(&a);
1502 } else if (strncmp(value, "colon;", 6) == 0) {
1503 /* use link URL for colon command */
1504 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(inputbox)), 1023);
1505 a.i = ModeNormal;
1506 set(&a);
1507 switch (text[1]) {
1508 case 'O':
1509 a.s = g_strconcat(":open ", (value + 6), NULL);
1510 break;
1511 case 'T': case 'W':
1512 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1513 break;
1515 if (a.s) {
1516 input(&a);
1517 g_free(a.s);
1519 } else if (strncmp(value, "error;", 6) == 0) {
1520 a.i = Error;
1521 set(&a);
1524 g_free(value);
1525 return TRUE;
1528 gboolean
1529 scroll(const Arg *arg) {
1530 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1531 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1532 float val = gtk_adjustment_get_value(adjust) / max * 100;
1533 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1535 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1536 if (arg->i & ScrollMove)
1537 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1538 direction * /* direction */
1539 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1540 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1541 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1542 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1543 else
1544 gtk_adjustment_set_value(adjust,
1545 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1546 update_state();
1548 return TRUE;
1551 gboolean
1552 zoom(const Arg *arg) {
1553 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1554 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1555 webkit_web_view_get_zoom_level(webview) +
1556 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1557 (count ? (float)count / 100.0 : 1.0));
1558 return TRUE;
1561 gboolean
1562 fake_key_event(const Arg *a) {
1563 if(!embed) {
1564 return FALSE;
1566 Arg err;
1567 err.i = Error;
1568 Display *xdpy;
1569 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1570 err.s = g_strdup("Couldn't find the XDisplay.");
1571 echo(&err);
1572 g_free(err.s);
1573 return FALSE;
1576 XKeyEvent xk;
1577 xk.display = xdpy;
1578 xk.subwindow = None;
1579 xk.time = CurrentTime;
1580 xk.same_screen = True;
1581 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1582 xk.window = embed;
1583 xk.state = a->i;
1585 if( ! a->s ) {
1586 err.s = g_strdup("Zero pointer as argument! Check your config.h");
1587 echo(&err);
1588 g_free(err.s);
1589 return FALSE;
1592 KeySym keysym;
1593 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1594 err.s = g_strdup_printf("Couldn't translate %s to keysym", a->s );
1595 echo(&err);
1596 g_free(err.s);
1597 return FALSE;
1600 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1601 err.s = g_strdup("Couldn't translate keysym to keycode");
1602 echo(&err);
1603 g_free(err.s);
1604 return FALSE;
1607 xk.type = KeyPress;
1608 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1609 err.s = g_strdup("XSendEvent failed");
1610 echo(&err);
1611 g_free(err.s);
1612 return FALSE;
1614 XFlush(xdpy);
1616 return TRUE;
1620 gboolean
1621 commandhistoryfetch(const Arg *arg) {
1622 if (arg->i == DirectionPrev) {
1623 commandpointer--;
1624 if (commandpointer < 0)
1625 commandpointer = maxcommands - 1;
1626 } else {
1627 commandpointer++;
1628 if (commandpointer == COMMANDHISTSIZE || commandpointer == maxcommands)
1629 commandpointer = 0;
1632 if (commandpointer < 0)
1633 return FALSE;
1635 gtk_entry_set_text(GTK_ENTRY(inputbox), commandhistory[commandpointer ]);
1636 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1637 return TRUE;
1641 gboolean
1642 bookmark(const Arg *arg) {
1643 FILE *f;
1644 const char *filename;
1645 const char *uri = webkit_web_view_get_uri(webview);
1646 const char *title = webkit_web_view_get_title(webview);
1647 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1648 f = fopen(filename, "a");
1649 g_free((gpointer *)filename);
1650 if (uri == NULL || strlen(uri) == 0) {
1651 set_error("No URI found to bookmark.");
1652 return FALSE;
1654 if (f != NULL) {
1655 fprintf(f, "%s", uri);
1656 if (title != NULL) {
1657 fprintf(f, "%s", " ");
1658 fprintf(f, "%s", title);
1660 if (arg->s && strlen(arg->s)) {
1661 build_taglist(arg, f);
1663 fprintf(f, "%s", "\n");
1664 fclose(f);
1665 give_feedback( "Bookmark saved" );
1666 return TRUE;
1667 } else {
1668 set_error("Bookmarks file not found.");
1669 return FALSE;
1673 gboolean
1674 history() {
1675 FILE *f;
1676 const char *filename;
1677 const char *uri = webkit_web_view_get_uri(webview);
1678 const char *title = webkit_web_view_get_title(webview);
1679 char *entry, buffer[512], *new;
1680 int n, i = 0;
1681 gboolean finished = FALSE;
1682 if (uri != NULL) {
1683 if (title != NULL) {
1684 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1685 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1686 } else {
1687 entry = malloc((strlen(uri) + 1) * sizeof(char));
1688 memset(entry, 0, strlen(uri) + 1);
1690 if (entry != NULL) {
1691 strncpy(entry, uri, strlen(uri));
1692 if (title != NULL) {
1693 strncat(entry, " ", 1);
1694 strncat(entry, title, strlen(title));
1696 n = strlen(entry);
1697 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1698 f = fopen(filename, "r");
1699 if (f != NULL) {
1700 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1701 if (new != NULL) {
1702 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1703 /* newest entries go on top */
1704 strncpy(new, entry, strlen(entry));
1705 strncat(new, "\n", 1);
1706 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1707 while (finished != TRUE) {
1708 if ((char *)NULL == fgets(buffer, 512, f)) {
1709 /* check if end of file was reached / error occured */
1710 if (!feof(f)) {
1711 break;
1713 /* end of file reached */
1714 finished = TRUE;
1715 continue;
1717 /* compare line (-1 because of newline character) */
1718 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1719 /* if the URI is already in history; we put it on top and skip it here */
1720 strncat(new, buffer, 512);
1721 i++;
1723 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1724 break;
1727 fclose(f);
1729 f = fopen(filename, "w");
1730 g_free((gpointer *)filename);
1731 if (f != NULL) {
1732 fprintf(f, "%s", new);
1733 fclose(f);
1735 if (new != NULL) {
1736 free(new);
1737 new = NULL;
1741 if (entry != NULL) {
1742 free(entry);
1743 entry = NULL;
1746 return TRUE;
1749 static gboolean
1750 view_source(const Arg * arg) {
1751 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1752 webkit_web_view_set_view_source_mode(webview, !current_mode);
1753 webkit_web_view_reload(webview);
1754 return TRUE;
1757 static gboolean
1758 focus_input(const Arg *arg) {
1759 static Arg a;
1761 a.s = g_strdup("hints.focusInput();");
1762 a.i = Silent;
1763 script(&a);
1764 g_free(a.s);
1765 update_state();
1766 return TRUE;
1769 static gboolean
1770 browser_settings(const Arg *arg) {
1771 char line[255];
1772 if (!arg->s) {
1773 set_error("Missing argument.");
1774 return FALSE;
1776 strncpy(line, arg->s, 254);
1777 if (process_set_line(line))
1778 return TRUE;
1779 else {
1780 set_error("Invalid setting.");
1781 return FALSE;
1785 char *
1786 search_word(int whichword) {
1787 int k = 0;
1788 static char word[240];
1789 char *c = my_pair.line;
1791 while (isspace(*c) && *c)
1792 c++;
1794 switch (whichword) {
1795 case 0:
1796 while (*c && !isspace (*c) && *c != '=' && k < 240) {
1797 word[k++] = *c;
1798 c++;
1800 word[k] = '\0';
1801 strncpy(my_pair.what, word, 20);
1802 break;
1803 case 1:
1804 while (*c && k < 240) {
1805 word[k++] = *c;
1806 c++;
1808 word[k] = '\0';
1809 strncpy(my_pair.value, word, 240);
1810 break;
1813 return c;
1816 static gboolean
1817 process_set_line(char *line) {
1818 char *c;
1819 int listlen, i;
1820 gboolean boolval;
1821 WebKitWebSettings *settings;
1823 settings = webkit_web_view_get_settings(webview);
1824 my_pair.line = line;
1825 c = search_word(0);
1826 if (!strlen(my_pair.what))
1827 return FALSE;
1829 while (isspace(*c) && *c)
1830 c++;
1832 if (*c == ':' || *c == '=')
1833 c++;
1835 my_pair.line = c;
1836 c = search_word(1);
1838 listlen = LENGTH(browsersettings);
1839 for (i = 0; i < listlen; i++) {
1840 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
1841 /* mandatory argument not provided */
1842 if (strlen(my_pair.value) == 0)
1843 return FALSE;
1844 /* process qmark? */
1845 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
1846 return (process_save_qmark(my_pair.value, webview));
1848 /* interpret boolean values */
1849 if (browsersettings[i].boolval) {
1850 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) {
1851 boolval = TRUE;
1852 } 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) {
1853 boolval = FALSE;
1854 } else {
1855 return FALSE;
1857 } else if (browsersettings[i].colourval) {
1858 /* interpret as hexadecimal colour */
1859 if (!parse_colour(my_pair.value)) {
1860 return FALSE;
1863 if (browsersettings[i].var != NULL) {
1864 /* write value into internal variable */
1865 /*if (browsersettings[i].intval) {
1866 browsersettings[i].var = atoi(my_pair.value);
1867 } else {*/
1868 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
1869 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
1870 /* in this case, \0 will not have been copied */
1871 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
1872 /* in case this string is also used for a webkit setting, make sure it's consistent */
1873 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
1874 give_feedback("String too long; automatically truncated!");
1876 /*}*/
1878 if (strlen(browsersettings[i].webkit) > 0) {
1879 /* activate appropriate webkit setting */
1880 if (browsersettings[i].boolval) {
1881 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
1882 } else if (browsersettings[i].intval) {
1883 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
1884 } else {
1885 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
1887 webkit_web_view_set_settings(webview, settings);
1890 /* process acceptlanguage*/
1891 if (strlen(my_pair.what) == 14 && strncmp("acceptlanguage", my_pair.what, 14) == 0) {
1892 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
1895 /* toggle proxy usage? */
1896 if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
1897 toggle_proxy(boolval);
1900 /* Toggle scrollbars. */
1901 if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0)
1902 toggle_scrollbars(boolval);
1904 /* Toggle widgets */
1905 if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0)
1906 gtk_widget_set_visible(GTK_WIDGET(statusbar), boolval);
1907 if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0)
1908 gtk_widget_set_visible(inputbox, boolval);
1910 /* case sensitivity of completion */
1911 if (strlen(my_pair.what) == 14 && strncmp("completioncase", my_pair.what, 14) == 0)
1912 complete_case_sensitive = boolval;
1914 /* reload page? */
1915 if (browsersettings[i].reload)
1916 webkit_web_view_reload(webview);
1917 return TRUE;
1920 return FALSE;
1923 gboolean
1924 process_line(char *line) {
1925 char *c = line;
1926 int i;
1927 size_t len, length = strlen(line);
1928 gboolean found = FALSE, success = FALSE;
1929 Arg a;
1931 while (isspace(*c))
1932 c++;
1933 /* Ignore blank lines. */
1934 if (c[0] == '\0')
1935 return TRUE;
1936 for (i = 0; i < LENGTH(commands); i++) {
1937 if (commands[i].cmd == NULL)
1938 break;
1939 len = strlen(commands[i].cmd);
1940 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
1941 found = TRUE;
1942 a.i = commands[i].arg.i;
1943 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
1944 success = commands[i].func(&a);
1945 g_free(a.s);
1946 break;
1949 save_command_history(c);
1950 if (!found) {
1951 a.i = Error;
1952 a.s = g_strdup_printf("Not a browser command: %s", c);
1953 echo(&a);
1954 g_free(a.s);
1955 } else if (!success) {
1956 a.i = Error;
1957 if (error_msg != NULL) {
1958 a.s = g_strdup_printf("%s", error_msg);
1959 g_free(error_msg);
1960 error_msg = NULL;
1961 } else {
1962 a.s = g_strdup_printf("Unknown error. Please file a bug report!");
1964 echo(&a);
1965 g_free(a.s);
1967 return success;
1970 static gboolean
1971 search_tag(const Arg * a) {
1972 FILE *f;
1973 const char *filename;
1974 const char *tag = a->s;
1975 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
1976 int t, i, intag, k;
1978 if (!tag) {
1979 /* The user must give us something to load up. */
1980 set_error("Bookmark tag required with this option.");
1981 return FALSE;
1984 if (strlen(tag) > MAXTAGSIZE) {
1985 set_error("Tag too long.");
1986 return FALSE;
1989 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1990 f = fopen(filename, "r");
1991 g_free((gpointer *)filename);
1992 if (f == NULL) {
1993 set_error("Couldn't open bookmarks file.");
1994 return FALSE;
1996 while (fgets(s, BUFFERSIZE-1, f)) {
1997 intag = 0;
1998 t = strlen(s) - 1;
1999 while (isspace(s[t]))
2000 t--;
2001 if (s[t] != ']') continue;
2002 while (t > 0) {
2003 if (s[t] == ']') {
2004 if (!intag)
2005 intag = t;
2006 else
2007 intag = 0;
2008 } else {
2009 if (s[t] == '[') {
2010 if (intag) {
2011 i = 0;
2012 k = t + 1;
2013 while (k < intag)
2014 foundtag[i++] = s[k++];
2015 foundtag[i] = '\0';
2016 /* foundtag now contains the tag */
2017 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2018 i = 0;
2019 while (isspace(s[i])) i++;
2020 k = 0;
2021 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2022 url[k] = '\0';
2023 Arg x = { .i = TargetNew, .s = url };
2024 open_arg(&x);
2027 intag = 0;
2030 t--;
2033 return TRUE;
2036 void
2037 toggle_proxy(gboolean onoff) {
2038 SoupURI *proxy_uri;
2039 char *filename, *new;
2040 int len;
2042 if (onoff == FALSE) {
2043 g_object_set(session, "proxy-uri", NULL, NULL);
2044 give_feedback("Proxy deactivated");
2045 } else {
2046 filename = (char *)g_getenv("http_proxy");
2048 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2049 * defined.
2051 if (filename == NULL)
2052 filename = (char *)g_getenv("HTTP_PROXY");
2054 if (filename != NULL && 0 < (len = strlen(filename))) {
2055 if (strstr(filename, "://") == NULL) {
2056 /* prepend http:// */
2057 new = g_malloc(sizeof("http://") + len);
2058 strcpy(new, "http://");
2059 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
2060 proxy_uri = soup_uri_new(new);
2061 g_free(new);
2062 } else {
2063 proxy_uri = soup_uri_new(filename);
2065 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2066 give_feedback("Proxy activated");
2071 void
2072 toggle_scrollbars(gboolean onoff) {
2073 if (onoff == TRUE) {
2074 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
2075 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
2076 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2077 } else {
2078 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2079 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2080 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2082 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
2084 return;
2087 void
2088 update_url(const char *uri) {
2089 gboolean ssl = g_str_has_prefix(uri, "https://");
2090 GdkColor color;
2091 gchar *markup;
2092 #ifdef ENABLE_HISTORY_INDICATOR
2093 char before[] = " [";
2094 char after[] = "]";
2095 gboolean back = webkit_web_view_can_go_back(webview);
2096 gboolean fwd = webkit_web_view_can_go_forward(webview);
2098 if (!back && !fwd)
2099 before[0] = after[0] = '\0';
2100 #endif
2101 markup = g_markup_printf_escaped(
2102 #ifdef ENABLE_HISTORY_INDICATOR
2103 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2104 before, back ? "+" : "", fwd ? "-" : "", after
2105 #else
2106 "<span font=\"%s\">%s</span>", statusfont, uri
2107 #endif
2109 gtk_label_set_markup(GTK_LABEL(status_url), markup);
2110 g_free(markup);
2111 gdk_color_parse(ssl ? sslbgcolor : statusbgcolor, &color);
2112 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
2113 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2114 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
2115 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
2118 void
2119 update_state() {
2120 char *markup;
2121 int download_count = g_list_length(activeDownloads);
2122 GString *status = g_string_new("");
2124 /* construct the status line */
2126 /* count, modkey and input buffer */
2127 g_string_append_printf(status, "%.0d", count);
2128 if (current_modkey) g_string_append_c(status, current_modkey);
2130 /* the number of active downloads */
2131 if (activeDownloads) {
2132 g_string_append_printf(status, " %d active %s", download_count,
2133 (download_count == 1) ? "download" : "downloads");
2136 #ifdef ENABLE_WGET_PROGRESS_BAR
2137 /* the progressbar */
2139 int progress = -1;
2140 char progressbar[progressbartick + 1];
2142 if (activeDownloads) {
2143 progress = 0;
2144 GList *ptr;
2146 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
2147 progress += 100 * webkit_download_get_progress(ptr->data);
2150 progress /= download_count;
2152 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
2153 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
2155 progress = webkit_web_view_get_progress(webview) * 100;
2158 if (progress >= 0) {
2159 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2160 g_string_append_printf(status, " %c%s%c",
2161 progressborderleft, progressbar, progressborderright);
2164 #endif
2166 /* and the current scroll position */
2168 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
2169 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
2171 if (max == 0)
2172 g_string_append(status, " All");
2173 else if (val == 0)
2174 g_string_append(status, " Top");
2175 else if (val == 100)
2176 g_string_append(status, " Bot");
2177 else
2178 g_string_append_printf(status, " %d%%", val);
2182 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2183 gtk_label_set_markup(GTK_LABEL(status_state), markup);
2184 g_free(markup);
2186 g_string_free(status, TRUE);
2189 void
2190 setup_modkeys() {
2191 unsigned int i;
2192 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2193 char *ptr = modkeys;
2195 for (i = 0; i < LENGTH(keys); i++)
2196 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
2197 *(ptr++) = keys[i].modkey;
2198 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
2201 void
2202 setup_gui() {
2203 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2204 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2205 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2206 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2207 if (embed) {
2208 window = GTK_WINDOW(gtk_plug_new(embed));
2209 } else {
2210 window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2211 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2213 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2214 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2215 inputbox = gtk_entry_new();
2216 webview = (WebKitWebView*)webkit_web_view_new();
2217 statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2218 eventbox = gtk_event_box_new();
2219 status_url = gtk_label_new(NULL);
2220 status_state = gtk_label_new(NULL);
2221 GdkColor bg;
2222 PangoFontDescription *font;
2223 GdkGeometry hints = { 1, 1 };
2224 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2226 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2227 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2228 setup_settings();
2229 gdk_color_parse(statusbgcolor, &bg);
2230 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2231 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2232 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2234 keymap = gdk_keymap_get_default();
2236 #ifdef DISABLE_SCROLLBAR
2237 viewport = gtk_scrolled_window_new(NULL, NULL);
2238 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2239 #else
2240 /* Ensure we still see scrollbars. */
2241 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2242 #endif
2244 setup_signals();
2245 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2247 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2248 * titlebars, we can still scroll.
2250 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2252 font = pango_font_description_from_string(urlboxfont[0]);
2253 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2254 pango_font_description_free(font);
2255 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2256 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2257 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2258 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2259 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2260 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2261 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2262 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2263 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2264 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2265 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2266 gtk_widget_grab_focus(GTK_WIDGET(webview));
2267 gtk_widget_show_all(GTK_WIDGET(window));
2270 void
2271 setup_settings() {
2272 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2273 SoupURI *proxy_uri;
2274 char *filename, *file_url, *new;
2275 int len;
2277 session = webkit_get_default_session();
2278 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2279 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2280 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2281 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2282 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2283 filename = g_strdup_printf(USER_STYLESHEET);
2284 file_url = g_strdup_printf("file://%s", filename);
2285 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2286 g_free(file_url);
2287 g_free(filename);
2288 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2289 g_object_get(G_OBJECT(settings), "zoom-step", &zoomstep, NULL);
2290 webkit_web_view_set_settings(webview, settings);
2292 /* proxy */
2293 if (use_proxy == TRUE) {
2294 filename = (char *)g_getenv("http_proxy");
2295 if (filename != NULL && 0 < (len = strlen(filename))) {
2296 if (strstr(filename, "://") == NULL) {
2297 /* prepend http:// */
2298 new = g_malloc(sizeof("http://") + len);
2299 strcpy(new, "http://");
2300 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
2301 proxy_uri = soup_uri_new(new);
2302 g_free(new);
2303 } else {
2304 proxy_uri = soup_uri_new(filename);
2306 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2307 soup_uri_free(proxy_uri);
2312 void
2313 setup_signals() {
2314 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
2315 #ifdef ENABLE_COOKIE_SUPPORT
2316 /* Headers. */
2317 g_signal_connect_after(G_OBJECT(session), "request-started", G_CALLBACK(new_generic_request), NULL);
2318 #endif
2319 /* Accept-language header */
2320 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2321 /* window */
2322 g_object_connect(G_OBJECT(window),
2323 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2324 NULL);
2325 /* frame */
2326 g_signal_connect(G_OBJECT(frame),
2327 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2328 /* webview */
2329 g_object_connect(G_OBJECT(webview),
2330 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2331 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2332 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2333 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2334 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2335 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2336 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2337 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2338 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2339 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2340 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2341 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2342 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2343 NULL);
2344 /* webview adjustment */
2345 g_object_connect(G_OBJECT(adjust_v),
2346 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2347 NULL);
2348 /* inputbox */
2349 g_object_connect(G_OBJECT(inputbox),
2350 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2351 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2352 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2353 #ifdef ENABLE_INCREMENTAL_SEARCH
2354 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2355 #endif
2356 NULL);
2357 /* inspector */
2358 g_signal_connect(G_OBJECT(inspector),
2359 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2362 #ifdef ENABLE_COOKIE_SUPPORT
2363 void
2364 setup_cookies()
2366 if (file_cookie_jar)
2367 g_object_unref(file_cookie_jar);
2369 if (session_cookie_jar)
2370 g_object_unref(session_cookie_jar);
2372 session_cookie_jar = soup_cookie_jar_new();
2374 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2376 load_all_cookies();
2378 g_signal_connect(G_OBJECT(file_cookie_jar), "changed",
2379 G_CALLBACK(update_cookie_jar), NULL);
2381 return;
2384 /* TA: XXX - we should be using this callback for any header-requests we
2385 * receive (hence the name "new_generic_request" -- but for now, its use
2386 * is limited to handling cookies.
2388 void
2389 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused) {
2390 SoupMessageHeaders *soup_msg_h;
2391 SoupURI *uri;
2392 char *cookie_str;
2394 soup_msg_h = soup_msg->request_headers;
2395 soup_message_headers_remove(soup_msg_h, "Cookie");
2396 uri = soup_message_get_uri(soup_msg);
2397 if( (cookie_str = get_cookies(uri)) ) {
2398 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2399 g_free(cookie_str);
2402 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2404 return;
2407 char *
2408 get_cookies(SoupURI *soup_uri) {
2409 char *cookie_str;
2411 cookie_str = soup_cookie_jar_get_cookies(file_cookie_jar, soup_uri, TRUE);
2413 return cookie_str;
2416 void
2417 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2419 GSList *resp_cookie = NULL, *cookie_list;
2420 SoupCookie *cookie;
2422 cookie_list = soup_cookies_from_response(soup_msg);
2423 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
2425 SoupDate *soup_date;
2426 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2428 if (cookie_timeout && cookie->expires == NULL) {
2429 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2430 soup_cookie_set_expires(cookie, soup_date);
2431 soup_date_free(soup_date);
2433 soup_cookie_jar_add_cookie(file_cookie_jar, cookie);
2436 soup_cookies_free(cookie_list);
2438 return;
2441 void
2442 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2444 if (!new) {
2445 /* Nothing to do. */
2446 return;
2449 SoupCookie *copy;
2450 copy = soup_cookie_copy(new);
2452 soup_cookie_jar_add_cookie(session_cookie_jar, copy);
2454 return;
2457 void
2458 load_all_cookies(void)
2460 GSList *cookie_list;
2461 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2463 /* Put them back in the session store. */
2464 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2465 cookie_list = cookies_from_file;
2467 for (; cookies_from_file;
2468 cookies_from_file = cookies_from_file->next)
2470 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2473 soup_cookies_free(cookies_from_file);
2474 g_slist_free(cookie_list);
2476 return;
2479 #endif
2481 void
2482 mop_up(void) {
2483 /* Free up any nasty globals before exiting. */
2484 #ifdef ENABLE_COOKIE_SUPPORT
2485 if (cookie_store)
2486 g_free(cookie_store);
2487 #endif
2488 return;
2492 main(int argc, char *argv[]) {
2493 static Arg a;
2494 static char url[256] = "";
2495 static gboolean ver = false;
2496 static gboolean configfile_exists = FALSE;
2497 static const char *cfile = NULL;
2498 char *searchengines_file;
2499 static GOptionEntry opts[] = {
2500 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2501 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2502 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2503 { NULL }
2505 static GError *err;
2506 args = argv;
2508 /* command line argument: version */
2509 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2510 g_printerr("can't init gtk: %s\n", err->message);
2511 g_error_free(err);
2512 return EXIT_FAILURE;
2515 if (ver) {
2516 printf("%s\n", INTERNAL_VERSION);
2517 return EXIT_SUCCESS;
2520 if( getenv("XDG_CONFIG_HOME") )
2521 config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
2522 else
2523 config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
2525 if (cfile)
2526 configfile = g_strdup(cfile);
2527 else
2528 configfile = g_strdup_printf(RCFILE);
2530 if (!g_thread_supported())
2531 g_thread_init(NULL);
2533 if (winid)
2534 embed = atoi(winid);
2536 setup_modkeys();
2537 make_keyslist();
2538 setup_gui();
2539 #ifdef ENABLE_COOKIE_SUPPORT
2540 setup_cookies();
2541 #endif
2543 /* Check if the specified file exists. */
2544 /* And only warn the user, if they explicitly asked for a config on the
2545 * command line.
2547 if (!(access(configfile, F_OK) == 0) && cfile) {
2548 char *feedback_str;
2550 feedback_str = g_strdup_printf("Config file '%s' doesn't exist", cfile);
2551 give_feedback(feedback_str);
2552 g_free(feedback_str);
2553 } else if ((access(configfile, F_OK) == 0))
2554 configfile_exists = true;
2556 /* read config file */
2557 /* But only report errors if we failed, and the file existed. */
2558 if (!read_rcfile(configfile) && configfile_exists) {
2559 a.i = Error;
2560 a.s = g_strdup_printf("Error in config file '%s'", configfile);
2561 echo(&a);
2562 g_free(a.s);
2563 g_free(configfile);
2566 make_searchengines_list(searchengines, LENGTH(searchengines));
2568 /* read searchengines. */
2569 searchengines_file = g_strdup_printf(SEARCHENGINES_STORAGE_FILENAME);
2570 switch (read_searchengines(searchengines_file)) {
2571 case SYNTAX_ERROR:
2572 a.i = Error;
2573 a.s = g_strdup_printf("Syntax error in searchengines file '%s'", searchengines_file);
2574 echo(&a);
2575 g_free(a.s);
2576 break;
2577 case READING_FAILED:
2578 a.i = Error;
2579 a.s = g_strdup_printf("Could not read searchengines file '%s'", searchengines_file);
2580 echo(&a);
2581 g_free(a.s);
2582 break;
2583 default:
2584 break;
2586 g_free(searchengines_file);
2588 /* command line argument: URL */
2589 if (argc > 1) {
2590 strncpy(url, argv[argc - 1], 255);
2591 } else {
2592 strncpy(url, startpage, 255);
2595 a.i = TargetCurrent;
2596 a.s = url;
2597 open_arg(&a);
2598 gtk_main();
2600 mop_up();
2602 return EXIT_SUCCESS;